2010-11-24 19 views
2

Je n'ai rien codé depuis des années, alors s'il vous plaît pardonnez mes questions stupides, mais je voudrais regrouper les éléments s'ils sont proches de chacun par horodatage. Je veux dire que les éléments qui sont par exemple à moins de 5 minutes l'un de l'autre seraient groupés récursivement. Par récursif, je veux dire que le premier et le dernier objet ne doivent pas être à moins de 5 minutes l'un de l'autre, mais ils doivent avoir entre eux des objets qui sont tous à moins de 5 minutes de l'élément précédent et suivant. Donc, ce dont j'ai besoin est une façon de comparer l'élément actuel avec l'élément précédent et s'ils sont à moins de 5 minutes l'un de l'autre, l'élément actuel est ajouté au même groupe que l'élément précédent.ROR Groupe par la proximité de l'autre

La solution ActiveRecord serait bien, car la quantité d'articles est très importante!

Le problème est que, en utilisant group_by, je ne trouve pas l'élément précédent, de sorte que je pourrais comparer les horodatages. J'ai essayé quelques trucs stupides comme ça juste pour comparer ces articles:

a.group_by { |x| x.created_at == a[a.index(x)-1].created_at } 

Mais je reçois:

NoMethodError: undefined method `created_at' for nil:NilClass

Est-il possible de le faire en utilisant group_by, ou dois-je itérer " manuellement "à travers ces articles? Des suggestions pour une solution assez efficace, car le nombre d'articles est assez important?

Merci!

+0

La raison pour laquelle vous obtenez NoMethodError est probablement parce que la première fois que le bloc de group_by est invoquée, la index est 0 et vous soustrayez 1 de cela et finissez par essayer d'obtenir un [-1] .created_at qui échoue bien sûr. Essayez de penser à une meilleure façon de le faire ... – DanneManne

+0

Oh bien sûr. Mais je n'aime toujours pas l'idée de récupérer le même nœud à partir du hash pour trouver son index, etc. – Charles

+0

Les éléments provenant d'une base de données sont-ils via ActiveRecord ou l'un des ORM? Si c'est le cas, avoir le DBM fait le "figuring" pourrait être un meilleur plan d'attaque. Faire le regroupement en mémoire peut être coûteux, surtout si vous avez beaucoup d'enregistrements. Si c'est le cas, vous pouvez ajouter un tag à votre question pour ActiveRecord ou votre ORM. –

Répondre

0

Je ne peux pas penser à un moyen de regrouper par une plage de temps sans invoquer un bloc vraiment imbriqué. Donc, si je devais faire quelque chose de similaire, je ferais probablement ce groupement quand il serait temps de l'afficher en utilisant la méthode each_with_index.

Je ne sais pas comment vous voulez utiliser ou présenté, mais dites que vous voulez un en-tête pour afficher chaque groupe et chaque élément où à afficher sur sa propre ligne, il pourrait ressembler à ceci:

<% a.each_with_index do |item, index| %> 
    <if index == 0 or (item.created_at - a[index-1].created_at) > 300.seconds %> 
    <h1><%= item.created_at %></h1> 
    <% end %> 
    <p><%= item.title %></p> 
<% end %> 

Ce n'est probablement rien comme vous voulez l'utiliser, mais il montre un exemple de comment each_with_index peut être utilisé.

+0

Le tableau doit être pré-filtré dans le contrôleur, puis transmis à la vue pour l'itérer. Mettre des valeurs "magiques", telles que '300.seconds' dans la vue rend plus difficile la maintenance de l'application. –

+0

C'est un peu comme je le fais en ce moment, mais comme Greg l'a souligné, ce n'est pas facile à maintenir. – Charles

+0

Je suis d'accord. Je ne savais pas que vous pouviez le faire comme écrit par Glenn. Regarde bien mieux. – DanneManne

5

L'ensemble a une fonction divide qui fait exactement cela! Vous auriez besoin de quelque chose comme:

Set[*a].divide { |x,y| (x-y).abs <= 5} 
+0

Il devrait être 'Set [* a]' ou 'a.to_set'. Sinon, vous obtiendrez un ensemble contenant un tableau. – htanata

+0

Oui, c'est ce que je voulais. Sauf que l'ensemble est non ordonné et je dois à nouveau passer par chaque groupe pour le trier ... – Charles

+0

Mais c'est encore une meilleure solution. Merci! – Charles

0

Vous dites que vous voulez regrouper par leur proximité les uns aux autres. Ce que vous voulez est de regrouper par un sous-ensemble de la valeur #created_at, ainsi:

require "rubygems" 
require "active_support/core_ext/array" 
require "ostruct" 
require "pp" 

o1 = OpenStruct.new(:created_at => Time.local(2010, 11, 24, 20, 1, 0, 0)) 
o2 = OpenStruct.new(:created_at => Time.local(2010, 11, 24, 20, 2, 0, 0)) 
o3 = OpenStruct.new(:created_at => Time.local(2010, 11, 24, 20, 6, 0, 0)) 
o4 = OpenStruct.new(:created_at => Time.local(2010, 11, 24, 20, 13, 0, 0)) 

a = [o1, o2, o3, o4] 

grouped = a.group_by do |obj| 
    time = obj.created_at 
    Time.local(time.year, time.month, time.day, time.hour, (time.min/5).floor, 0) 
end 

pp grouped.map {|val, arr| [val, arr.map {|obj| obj.created_at.to_s }] } 

qui retourne:

$ ruby a.rb 
[[Wed Nov 24 20:02:00 -0500 2010, ["Wed Nov 24 20:13:00 -0500 2010"]], 
[Wed Nov 24 20:00:00 -0500 2010, 
    ["Wed Nov 24 20:01:00 -0500 2010", "Wed Nov 24 20:02:00 -0500 2010"]], 
[Wed Nov 24 20:01:00 -0500 2010, ["Wed Nov 24 20:06:00 -0500 2010"]]] 

La première valeur de chaque tableau ci-joint est la clé (minute en groupes de 5 minutes), et les valeurs sont les objets ActiveRecord réels. Pour la lisibilité, j'ai mappé sur la version String de Time, mais c'est la même idée.

Rappelez-vous aussi que #group_by génère un tableau ordonné de la même manière que le tableau original, ainsi vos contraintes de commande sont conservées - vous n'avez pas besoin de recourir au tableau.

+0

Je pense que c'est une réponse à une question différente (et plus simple). Je cite moi-même: "Par récursif, je veux dire que le premier et le dernier objet ne doivent pas être à moins de 5 minutes l'un de l'autre, mais ils doivent avoir des objets qui sont tous à moins de 5 minutes du précédent et prochain point." – Charles

0

je recommande de le faire sur le côté de la base de données en utilisant quelque chose comme:

group_by (to_nearest_five_minutes (updated_date))

+0

Pouvez-vous être plus précis? – Charles