[Edit:我已经编辑了我的答案,以纳入 @raph 在对该问题的评论中建议的效率改进(该方法anagram?
以下)。这可能没有必要,但我认为这是个好主意,应该得到一些曝光。我还给出了详细的解释,因为 OP 对 Ruby 来说是新的,其他读者也可能是这样。]
您可以考虑按如下方式进行操作。
Code
def anagrams(a, b)
return nil unless a.size == b.size
a.zip(b).map { |aw,bw| anagram?(aw,bw) ? 1 : 0 }
end
def anagram?(aw, bw)
return false unless aw.size == bw.size
counts = aw.downcase.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
bw.downcase.each_char do |c|
return false unless counts[c] > 0
counts[c] -= 1
end
true
end
Example
a = ['hello', 'goodbye', 'pants', 'baa']
b = ['helio', 'godbye', 'Spant', 'aba']
anagrams(a, b)
#=> [0, 0, 1, 1]
解释
anagrams
method
对于上面的例子,
a.size #=> 4
b.size #=> 4
所以我们不会回来nil
在第一行anagrams
.
Next,
c = a.zip(b)
#=> [["hello", "helio"], ["goodbye", "godbye"],
# ["pants", "Spant"], ["baa", "aba"]]
暂时假设anagram?
按预期工作:
c.map { |e| anagram?(e.first, e.last) ? 1 : 0 }
#=> [0, 0, 1, 1]
Enumerable#map http://www.ruby-doc.org/core-2.1.1/Enumerable.html#method-i-map passes each element of c
(a two-element array) into the block.1. It is clearer, however, to decompose (or "disambiguate") those arrays and assign each of the two words they comprise to a block variable2:
c.map { |aw,bw| anagram?(aw,bw) ? 1 : 0 }
#=> [0, 0, 1, 1]
传入的第一个元素是["hello", "helio"]
, so
aw => "hello"
bw #=> "helio"
我们执行
anagram?("hello", "helio") ? 1 : 0
#=> 0
这是简写
if anagram?("hello", "helio")
1
else
0
end
#=> 0
anagram?
method
现在让我们继续anagram?
, with
aw = "hello"
bw = "helio"
Since
aw.size == bw.size #=> true
我们不会回来。
计算第一个单词中字母的频率
现在让我写下接下来的几行anagram?
略有不同:
counts = Hash.new(0)
#=> {}
aw_down = aw.downcase
#=> "hello"
aw_down.each_char { |c| counts[c] += 1 }
#=> "hello"
counts
#=> {"h"=>1, "e"=>1, "l"=>2, "o"=>1}
(最后一行只是为了显示哈希值。)
In the first line we create a hash counts
with a default value of zero. All this means is that if counts
does not contain the key k
, counts[k]
will return the default value. Very important: doing so does not change the hash!3
String#each_char http://www.ruby-doc.org/core-2.1.3/String.html#method-i-each_char4 passes each character of "hello"
into the block and assigns it to the block variable c
. Initially, c='h'
and h={}
. We then execute
counts['h'] += 1
这是简写
counts['h'] = counts['h'] + 1
Since counts
还没有钥匙'h'
, counts['h']
右边返回默认值:
counts['h'] = 0 + 1 #=> 1
counts #=> {"h"=>1}
同样,之后'e'
和第一个'l'
被传递到块,我们有:
counts #=> {"h"=>1, "e"=>1, "l"=>1}
然而,当我们通过第二个'l'
,我们执行
counts['l'] = counts['l'] + 1
#=> 1 + 1
#=> 2
我们结束了
counts #=> {"h"=>1, "e"=>1, "l"=>2, "o"=>1}
方法可枚举#each_with_object http://www.ruby-doc.org/core-2.1.1/Enumerable.html#method-i-each_with_object会成为好朋友
使用该方法只是为了节省一些步骤。它允许我们写:
counts = Hash.new(0)
aw_down.each_char { |c| counts[c] += 1 }
as
counts = aw_down.each_with_object(Hash.new(0)) { |c,h| h[c] += 1 }
我们也可以摆脱这条线
aw_down = aw.downcase
通过写作
counts = aw.downcase.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
这看起来似乎是一个很小的节省,但是还有许多其他情况需要使用each_with_object
和别的Enumerable
类方法允许方法链接,这是非常有用的。
减少第二个单词中字母的字母计数
Recall
counts #=> {"h"=>1, "e"=>1, "l"=>2, "o"=>1}
我们现在执行
bw_down = bw.downcase
#=> "helio"
"helio".each_char do |c|
return false unless counts[c] > 0
counts[c] -= 1
end
First, 'h'
被传递到块中。作为counts['h'] #=> 1
,我们执行counts['h'] -= 1
, so now
counts #=> {"h"=>0, "e"=>1, "l"=>2, "o"=>1}`.
通过后'e'
and 'l'
到街区,
counts #=> {"h"=>0, "e"=>0, "l"=>1, "o"=>1}
但当我们经过时'i'
, 我们发现
counts['i'] #=> 0
(即返回默认值零,并且我们不想设置counts['i']
to -1
)所以我们返回false
,得出的结论是这两个词不是字谜。 (如果第二个词是"heeio"
,我们会回来的false
当第二个'e'
被传递到块。)
我们有字谜吗?
由于两个单词具有相同的长度,如果我们能够处理第二个单词的所有字符而不返回false
, we must以结束
counts #=> {"h"=>0, "e"=>0, "l"=>0, "o"=>0}
(no need to check!), meaning the two words are anagrams, so in this case we would return true
to anagrams
.5 Hence, the last line of anagram?
.
Notes
1 Under the hood, this is what's happening:
enum = c.map
#=> #<Enumerator: [["hello", "helio"], ["goodbye", "godbye"],
# ["pants", "Spant"], ["baa", "aba"]]:map>
在这里我们可以看到枚举器将传递哪些元素到块中,但有时您需要将枚举器转换为数组才能获取该信息:
enum.to_a
#=> [["hello", "helio"], ["goodbye", "godbye"],
# ["pants", "Spant"], ["baa", "aba"]]
其实就是这个方法数组#each http://www.ruby-doc.org/core-2.1.1/Array.html#method-i-each传递的元素enum
进入块:
enum.each { |aw,bw| anagram?(aw,bw) ? 1 : 0 }
#=> [0, 0, 1, 1]
2 If we pass [[1,2],3]
into a block, and the block variables are written |(a,b),c|
, then a=>1
, b=>2
, c=>3
. This is quite handy. Cool, eh?.
3
h = Hash.new('pig')
h['dog'] = 7 #=> 7
h #=> {"dog"=>7}
h[0] #=> "pig"
h['cat'] #=> "pig"
h[{:a=>1}] #=> "pig"
h #=> {"dog"=>7}
注意有一种形式Hash#new http://www.ruby-doc.org/core-2.1.1/Hash.html它采用块,允许在引用不在哈希中的键时添加它们。
4 Instead of aw_down.each_char
we could have written aw_down.chars.each
, but aw_down.chars
creates an unnecessary intermediate array. each_char
, an enumerator, merely passes values as they are required.
5 We could return 0
rather than false
and 1
rather than true
, in which case we could write
a.zip(b).map { |aw,bw| anagram?(aw,bw) }
in anagrams
,但是如果有的话不是更清楚吗anagrams
返回一个数组,其值为true
or false
, 而不是0
or 1
?