2010-11-08 4 views
1

J'ai quelques tableaux d'objets Ruby de classe UserInfo:fusion des tableaux Ruby

class UserInfo 
    attr_accessor :name, :title, :age 
end 

Comment puis-je fusionner ces tableaux dans un tableau? Un utilisateur est identifié par son nom, donc je ne veux pas de noms en double. Si le nom, le titre, l'âge, etc. sont égaux, j'aimerais avoir 1 entrée dans le nouveau tableau. Si les noms sont identiques, mais que tous les autres détails diffèrent, je souhaite probablement que ces 2 utilisateurs d'un tableau différent corrigent manuellement les erreurs.

Merci à l'avance

+0

Vous utilisez la fusion de verbes, lorsque vous voulez vraiment supprimer des doublons. Vous avez aussi tagué ceci avec 'hash' quand vous posez des questions sur les tableaux. Vous devriez probablement éditer cette question pour la rendre plus claire. – Olly

+0

@Olly: modification de la balise de 'hash' en 'arrays'. –

Répondre

0

Il y a un an I monkey patched une sorte de instance_variables_compare cryptique sur Object. Je suppose que tu pourrais utiliser ça.

class Object 
    def instance_variables_compare(o) 
    Hash[*self.instance_variables.map {|v| 
     self.instance_variable_get(v)!=o.instance_variable_get(v) ? 
     [v,o.instance_variable_get(v)] : []}.flatten] 
    end 
end 

Un exemple ringard

require 'Date' 

class Cheese 
    attr_accessor :name, :weight, :expire_date 
    def initialize(name, weight, expire_date) 
    @name, @weight, @expire_date = name, weight, expire_date 
    end 
end 

stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02")) 
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17")) 

RIR est mon arme de choix

>> stilton.instance_variables_compare(gorgonzola) 
=> {"@name"=>"Gorgonzola", "@expire_date"=>#<Date: 4910305/2,0,2299161>} 
>> gorgonzola.instance_variables_compare(stilton) 
=> {"@name"=>"Stilton", "@expire_date"=>#<Date: 4910275/2,0,2299161>} 
>> stilton.expire_date=gorgonzola.expire_date 
=> #<Date: 4910305/2,0,2299161> 
>> stilton.instance_variables_compare(gorgonzola) 
=> {"@name"=>"Gorgonzola"} 
>> stilton.instance_variables_compare(stilton) 
=> {} 

Comme vous pouvez le voir les instance_variables_compare renvoie un Hash vide si les deux objets a la même contenu.

Un tableau de fromage

stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02")) 
gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17")) 

arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2 

Un hachage sans problème et une avec

h={} 
problems=Hash.new([]) 

arr.each {|c| 
    if h.has_key?(c.name) 
    if problems.has_key?(c.name) 
     problems[c.name]=problems[c.name]<<c 
    elsif h[c.name].instance_variables_compare(c) != {} 
     problems[c.name]=problems[c.name]<<c<<h[c.name] 
     h.delete(c.name) 
    end 
    else 
    h[c.name]=c 
    end 
} 

Maintenant, le Hash h contient les objets sans problème la fusion et le hachage problems contient ceux qui a des variables d'instance qui diffère.

>> h 
=> {"Gorgonzola"=>#<Cheese:0xb375e8 @name="Gorgonzola", @weight=250, @expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>} 

>> problems 
=> {"Stilton"=>[#<Cheese:0xf54c30 @name="Stilton", @weight=210, @expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 @name="Stilton", @weight=250,@expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}  

Pour autant que je peux voir vous ne serez pas avoir à modifier ce code à tout pour soutenir un tableau de UserInfo objets.

Il serait très probablement beaucoup plus rapide de comparer les propriétés directement ou avec une priorité de ==. Voici comment passer outre ==

def ==(other) 
    return self.weight == other.weight && self.expire_date == other.expire_date 
end 

et la boucle change dans ce

arr.each {|c| 
    if h.has_key?(c.name) 
    if problems.has_key?(c.name) 
     problems[c.name]=problems[c.name]<<c 
    elsif h[c.name] != c 
     problems[c.name]=problems[c.name]<<c<<h[c.name] 
     h.delete(c.name) 
    end 
    else 
    h[c.name]=c 
    end 
} 

Enfin, vous pouvez convertir le Hash retour à un Array

result = h.values 
+0

WOW, thx Jonas;) –

+0

Il s'agit principalement de copier et coller à partir d'un ancien billet de blog. Aussi, j'ai juste corrigé un bug. –

+0

Cela at-il résolu votre problème? –

0

Voici une autre façon possible.Si vous avez un moyen d'identifier chaque UserInfo, par exemple une méthode to_str qui imprime les valeurs suivantes:

def to_str() 
    return "#{@name}:#{@title}:#{@age}" 
    end 

Vous pouvez utiliser l'injection et un hachage

all_users = a + b # collection of users to "merge"  
res = all_users.inject({})do |h,v| 
    h[v.to_str] = v #save the value indexed on the string output 
    h # return h for the next iteration 
end 

merged = res.values #the unique users 
1

Redéfinir comparaison d'égalité sur votre objet, et vous peut se débarrasser des doublons réels rapidement avec Array#uniq

class UserInfo 
    attr_accessor :name, :title, :age 

    def == other 
    name==other.name and title==other.title and age==other.age 
    end 
end 

# assuming a and b are arrays of UserInfo objects 
c = a | b 
# c will only contain one of each UserInfo 

Ensuite, vous pouvez trier par nom et rechercher des doublons nom seulement

d = c.sort{ |p,q| p.name <=> q.name } #sort by name 
name = "" 
e = [] 
d.each do |item| 
    if item.name == name 
    e[-1] = [e[-1],item].flatten 
    else 
    e << item 
    end 
end 
Questions connexes