2010-09-20 2 views
4

J'ai un problème avec cette fonction qui traverse un hachage. Le hachage peut contenir un tableau de hachages. Je veux que la méthode recherche un identifiant puis retourne juste le hash imbriqué qu'il trouve.Traverser un hachage de manière récursive dans Ruby

Il semble fonctionner pour le traversal, mais il renvoie la valeur d'origine transmise.

require 'rubygems' 
require 'ruby-debug' 

def find_by_id(node, find_this="") 
    if node.is_a?(Hash) 
    node.each do |k,v| 
     if v.is_a?(Array) 
     v.each do |elm| 
      if elm["_id"] == find_this && !find_this.empty? 
      return elm  # THIS IS WHAT I WANT! 
      else 
      find_by_id(elm, find_this) 
      end 
     end 
     end 
    end 
    end 
end 

x = {"name" => "first", "_id"=>'4c96a9a56f831b0eb9000005', "items"=>["name" => "second", "_id"=>'4c96a9af6f831b0eb9000009', "others"=>[{"name" => "third", "_id"=>'4c96a9af6f831b0eb9000007'}, {"name" => "fourth", "_id"=>'4c96a9af6f831b0eb9000008'}] ] } 

find_by_id(x, '4c96a9af6f831b0eb9000008') 

Répondre

11

Lorsque vous appelez find_by_id récursive, vous ne faites rien à la valeur de retour. Vous devez vérifier si elle a trouvé quelque chose et si oui retourner que, à savoir:

result = find_by_id(elm, find_this) 
return result if result 

Vous devez également revenir nil à la fin de la méthode (après la chaque boucle), il retourne nil si rien n'a été trouvé . Si ce n'est pas le cas, la valeur de retour de each est renvoyée, ce qui correspond au hachage que vous avez répété.

Edit:

Voici le code complet avec les changements que j'Outlined:

def find_by_id(node, find_this="") 
    if node.is_a?(Hash) 
    node.each do |k,v| 
     if v.is_a?(Array) 
     v.each do |elm| 
      if elm["_id"] == find_this && !find_this.empty? 
      return elm  # THIS IS WHAT I WANT! 
      else 
      result = find_by_id(elm, find_this) 
      return result if result 
      end 
     end 
     end 
    end 
    end 
    # Return nil if no match was found 
    nil 
end 

Edit2:

Une autre approche, que je trouve plus propre, est de séparer la logique de itérer la structure de la logique pour trouver l'élément avec le bon ID:

def dfs(hsh, &blk) 
    return enum_for(:dfs, hsh) unless blk 

    yield hsh 
    hsh.each do |k,v| 
    if v.is_a? Array 
     v.each do |elm| 
     dfs(elm, &blk) 
     end 
    end 
    end 
end 

def find_by_id(hsh, search_for) 
    dfs(hsh).find {|node| node["_id"] == search_for } 
end 

En faisant dfs retourner un Enumerable nous pouvons utiliser la méthode Enumerable#find, ce qui rend le code un peu plus simple.

Cela permet également de réutiliser le code si vous avez besoin d'écrire une autre méthode qui doit parcourir récursivement le hachage, car vous pouvez simplement réutiliser la méthode dfs.

+0

J'ai essayé cela, il renvoie la mauvaise valeur. 'return elm' est atteint quand il est supposé le faire. – Dex

+0

@Dex: Droite. Vous devez également retourner zéro si vous ne trouvez rien. Edité pour ajouter cela. – sepp2k

+0

Je tiens à souligner que ce n'est pas la récursion à blâmer, mais plutôt l'opérateur 'each', à l'intérieur duquel le dernier opérateur ne renvoie pas sa valeur de la fonction d'origine. –

Questions connexes