2010-04-07 5 views
4

J'ai une classe avec une coutume chaque méthode:Appelez une méthode de bloc sur un itérateur: each.magic.collect {...}

class CurseArray < Array 
    def each_safe 
     each do |element| 
      unless element =~ /bad/ 
       yield element 
      end 
     end 
    end 
end 

et que vous voulez appeler des méthodes de blocs différents, comme « la collecte "ou" injecter "sur ces éléments itérés. Par exemple:

curse_array.each_safe.magic.collect {|element| "#{element} is a nice sentence."} 

Je sais qu'il ya une fonction spécifique (que j'ai appelé « magique » ici) pour ce faire, mais je l'ai oublié. S'il vous plaît aider! :-)

Répondre

6

Si une méthode vous donne devrez passer un bloc. Il n'y a aucun moyen de définir un bloc qui se passe automatiquement.

le plus proche que je peux obtenir à votre spec est la suivante:

def magic(meth) 
    to_enum(meth) 
end 

def test 
    yield 1 
    yield 2 
end 

magic(:test).to_a 
# returns: [1,2] 

La manière la plus propre de la mise en œuvre de votre demande est probablement:

class MyArray < Array 
    def each_safe 
    return to_enum :each_safe unless block_given? 
    each{|item| yield item unless item =~ /bad/} 
    end 
end 

a = MyArray.new 
a << "good"; a << "bad" 
a.each_safe.to_a 
# returns ["good"] 
+0

qui fait ce que je veux, merci! – blinry

2

La façon dont vous avez écrit votre méthode each_safe, le plus simple serait

curse_array.each_safe { |element| do_something_with(element) } 

Edit: Oh, votre méthode de each_safe n'est pas correct non plus. Il doit être « chacun fait », pas « each.do »

Edit 2: Si vous voulez vraiment être en mesure de faire des choses comme « each_safe.map », tout en étant en même temps aussi capable de le faire " each_safe { ... } "vous pouvez écrire votre méthode comme ceci:

require 'enumerator' 

class CurseArray < Array 
    BLACKLIST = /bad/ 
    def each_safe 
    arr = [] 
    each do |element| 
     unless element =~ BLACKLIST 
     if block_given? 
      yield element 
     else 
      arr << element 
     end 
     end 
    end 

    unless block_given? 
     return Enumerator.new(arr) 
    end 
    end 
end 
+0

Merci. Je voulais utiliser = ~ pour la correspondance regexp. La façon dont vous décrivez comment je le fais maintenant, mais ce n'est pas très élégant. – blinry

+0

@blinry votre utilisation de '= ~' et 'Regexp' il est correct puisque vous aurez probablement envie de mettre plus de mots dans la liste noire. L'approche de @ dominikh pour laisser 'each_safe' fonctionner avec ou sans bloc est bonne. Ce –

+1

est une mauvaise idée de construire les réponses, puis retourner un recenseur. Toute l'idée d'énumérateurs est d'avoir une évaluation paresseuse! Que faire si le tableau est modifié après l'appel de 'each_safe' mais avant d'utiliser l'énumérateur? Et si c'est un long texte et que nous avons seulement besoin de 'first (3)'? Vérifiez la réponse de Sam ci-dessous. –

0

La solution choisie utilise l'idiome commun to_enum :method_name unless block_given? qui il est ok, mais il existe des alternatives:

  1. Laissez votre "hostile" yie méthode lder intacte, utilisez enum_for lors de l'appel.

  2. Utilisez un Enumerator paresseux. Utilisez les tableaux paresseux (nécessite Ruby 2.0 ou gem enumerable-lazy).

est ici un code de démonstration:

class CurseArray < Array 
    def each_safe 
    each do |element| 
     unless element =~ /bad/ 
     yield element 
     end 
    end 
    end 

    def each_safe2 
    Enumerator.new do |enum| 
     each do |element| 
     unless element =~ /bad/ 
      enum.yield element 
     end 
     end 
    end 
    end 

    def each_safe3 
    lazy.map do |element| 
     unless element =~ /bad/ 
     element 
     end 
    end.reject(&:nil?) 
    end 
end 

xs = CurseArray.new(["good1", "bad1", "good2"]) 
xs.enum_for(:each_safe).select { |x| x.length > 1 } 
xs.each_safe2.select { |x| x.length > 1 } 
xs.each_safe3.select { |x| x.length > 1 }.to_a 
Questions connexes