2017-05-05 24 views
1

アキュムレータを使用する場合、アキュムレータはreduceブロック内にのみ存在するのでしょうか、それとも関数内に存在しますか?Rubyでreduce/injectを使用する方法未定義変数

私はのように見える方法があります。

def my_useless_function(str) 
    crazy_letters = ['a','s','d','f','g','h'] 
    str.split.reduce([]) do |new_array, letter| 
    for a in 0..crazy_letters.length-1 
     if letter == crazy_letters[a] 
     new_array << letter 
     end 
    end 
    end 

    return true if (new_array == new_array.sort) 
end 

私はこのコードを実行すると、私はエラー

"undefined variable new_array in line 11 (the return statement)" 

を取得し、私はまた、内部else文として別の変数にnew_array値を代入しようとしたが私のreduceブロックしかし、それは私に同じ結果を与えた。

なぜこのようなことが起こっているのか説明できますか?

答えて

2

ブロックローカル変数

new_array

あなた reduceコールのブロックの外側には存在しません。それは "block local variable"です。

reduceはオブジェクトを返しますが、メソッド内で使用する必要があります。

sum = [1, 2, 3].reduce(0){ |acc, elem| acc + elem } 
puts sum 
# 6 
puts acc 
# undefined local variable or method `acc' for main:Object (NameError) 

あなたのコード

ここでの変化の最小量は、あなたの方法のためです:

def my_useless_function(str) 
    crazy_letters = ['a','s','d','f','g','h'] 
    new_array = str.split(//).reduce([]) do |new_array, letter| 
    for a in 0..crazy_letters.length-1 
     if letter == crazy_letters[a] 
     new_array << letter 
     end 
    end 
    new_array 
    end 

    return true if (new_array == new_array.sort) 
end 

注:

  • returnは終わりに必要とされていません。
  • true if ...はどちらか必要とされていない
  • forループは、ブロック内の最後の式の結果を返しますreduceルビー
  • で使用すべきではありません。あなたのコードにはforでした。
  • 同じオブジェクトを常にreduceに返す必要がある場合は、each_with_objectという記号を使用している可能性があります。
  • "test".splitはちょうど["test"]

StringEnumerableあなたを助けることができる方法を持っています。それらを使用すると、@Phrogz answerのように、よりクリーンで効率的な方法を書くことができます。

3

reduceの呼び出し中にnew_arrayが作成され、その後参照が失われるという問題があります。 Rubyのローカル変数は、それらのブロックにスコープされています。あなたのケースでは配列はreduceから返すことができるので、そこで使うことができます。しかし、いくつか修正する必要があります:

  • str.split文字列をRuby 2+の文字に分割しません。 str.charsまたはstr.split('')を使用してください。
  • reduceの新しい反復ごとに保持されているオブジェクトは、毎回ブロックから戻すことで保持する必要があります。これを行う最も簡単な方法は、ブロック内の最後の式としてnew_arrayを入れることです。このよう

:あなたの関数は非常に慣用的に非常に効率的で、かつないではありません

def my_useless_function(str) 
    crazy_letters = ['a','s','d','f','g','h'] 
    crazy_only = str.split('').reduce([]) do |new_array, letter| 
    for a in 0..crazy_letters.length-1 
     if letter == crazy_letters[a] 
     new_array << letter 
     end 
    end 
    new_array 
    end 

    return true if (crazy_only == crazy_only.sort) 
end 

注意。ここでは、より慣用的な関数の短いバージョンだではなく、はるかに効率的:

def my_useless_function(str) 
    crazy_letters = %w[a s d f g h] 
    crazy_only = str.chars.select{ |c| crazy_letters.include?(c) } 
    crazy_only == crazy_only.sort # evaluates to true or false 
end 

そして、ここで、より効率的だバージョンです:

def efficient_useless(str) 
    crazy_only = str.scan(/[asdfgh]/) # use regex to search for the letters you want 
    crazy_only == crazy_only.sort 
end 
+2

ニースが、私はより効率的なバージョンを書くことを約ありましたそれはあなたのものに非常によく似ていました。配列がソートされているかどうかをチェックするには、ソートする必要はありません(例えば、 'each_cons'を代わりに使うことができます)。 –

+0

@Eric、Rubyでは' class Array; def sorted?:enum = self.to_enum;ループ{return false if(enum.next <=> enum.peek)== 1};真;終わり;終わり; [1,2,3]。 #=> true; [1,3,2]。 #=> false'(または、あなたが示唆したように 'each_cons'を使用します)。 –

+2

@CarySwoveland: 'Array#sorted? 'は、基本ループを持つC関数の完璧な候補になります。 –

関連する問題