开发者

explain one expression of Ruby Array uniq method

c = %w(a b c d)
1.8.7 :025 > c.uniq {|x|x[/^a/]}
=> ["a", "b"] 
1.8.7 :026 > c.uniq {|x|x[/^b/]}
=> ["a", "b"] 
1.8.7 :027 > c.uniq {|开发者_StackOverflow社区x|x[/^c/]}
=> ["a", "c"] 
1.8.7 :029 > c.uniq {|x|x =~ [/^c/]}
=> ["a"] 
1.8.7 :030 > c.uniq {|s|s[/[^abc]/]}
=> ["a", "d"]

I understand the regular expression, I don't understand how does the uniq block work.


That's a bit of a tricky one.

c = %w(a b c d)
1.8.7 :025 > c.uniq {|x|x[/^a/]}
=> ["a", "b"] 

The x in this block is every value in the array. You define uniqueness by "does the string start with a?". Value a is the first to evaluate as true and is therefore the first value. b is the first to evaluate as false so is the second value. Both c and d also evaluate as false, but are not unique since a value that evaluated false was already found.

1.8.7 :026 > c.uniq {|x|x[/^b/]}
=> ["a", "b"] 

The same holds here. a is the first (false) and b the second (true).

1.8.7 :027 > c.uniq {|x|x[/^c/]}
=> ["a", "c"] 

Here you see that a is the first false value and c the first value to evaluate as true and therefore the second unique value.

1.8.7 :029 > c.uniq {|x|x =~ [/^c/]}
=> ["a"] 

Here you have defined uniqueness by "does the string match an array of regular expressions that do match strings not starting with c". Weird!

1.8.7 :030 > c.uniq {|s|s[/[^abc]/]}
=> ["a", "d"]

Here you have defined a character class. You defined uniqueness by "strings containing a, b or c". a is the first to satisfy as true. d is the only value to evaluate as false.

Hope that helps.


The determination of uniqueness is based on the result from passing each element to the block.

So, for example, the first one represents asking each element whether they are "a". The first one says yes, so "a" is considered unique. The second one says no, so "b" is considered unique. The others also say no, so they are not considered unique (since the response "no" is one we've already seen).

However, what I can't explain easily is why this appears to be working for you in Ruby 1.8.7; in 1.8.7 and prior a block argument to Array#uniq was ignored. This feature was added in Ruby 1.9. Perhaps you have a patched version?


Look at this way: when you use uniq with a block, you effectively build an array of arrays where each inner array is [result_of_block, array_element] and then look for unique values in the first elements. So, if we start with:

c = %w(a b c d)

and look at

c.uniq {|x|x[/^a/]}

then the array of arrays would look this:

[
    ['a', 'a'],
    [nil, 'b'],
    [nil, 'c'],
    [nil, 'd']
]

Looking for unique values amongst the first elements yields 'a' and nil:

[
    ['a', 'a'],
    [nil, 'b']
]

and unwrapping yields ['a', 'b']. There's no guarantee that you'll get 'b' as the second element though.

You can write your own uniq_by like this:

def uniq_by(a, &block)
    Hash[a.map { |x| [ block.call(x), x] }].values
end

or this:

def uniq_by(a, &block)
    h = { }
    a.each { |x| h[block.call(x)] = x ] }
    h.values
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜