带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]
Posted
技术标签:
【中文标题】带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]【英文标题】:Cool tricks and expressive snippets with ruby collections/enumerables [closed] 【发布时间】:2011-05-18 01:49:07 【问题描述】:你最喜欢的带有 ruby 集合的代码 sn-ps 是什么?最好它们应该是你的发现,富有表现力,可读性,并在你的编码实践中引入一些乐趣。
数组中的模式匹配(用于局部变量和参数):
(a, b), c = [[:a, :b], :c]
[a,b,c]
=> [:a, :b, :c]
(a,), = [[:a]]
a
=> :a
从非数组赋值给多个变量:
abc, a, b =* "abc".match(/(a)(b)./)
=> ["abc", "a", "b"]
nil1, =* "abc".match(/xyz/)
=> []
用相同的表达式初始化数组元素:
5.times.map 1
=> [1,1,1,1]
Array.new(5) 1
=> [1,1,1,1,1]
用相同的值初始化数组:
[2]*5
=>[2,2,2,2,2]
Array.new 5, 2
=>[2,2,2,2,2]
对数组元素求和:
[1,2,3].reduce(0, &:+)
=> 6
查找所有符合条件的索引:
a.each_with_index.find_all |e, i| some_predicate(e) .map(&:last)
备用 CSS 类:
(1..4).zip(%w[cls1 cls2].cycle)
=> [[1, "cls1"], [2, "cls2"], [3, "cls1"], [4, "cls2"]]
解压:
keys, values = a: 1, b: 2.to_a.transpose
keys
=> [:a, :b]
探索字符串的布尔成员方法:
"".methods.sort.grep(/\?/)
探索特定于字符串的方法:
"".methods.sort - [].methods
【问题讨论】:
【参考方案1】:带有记忆的懒惰斐波那契数列,取自Neeraj Singh:
fibs = 0 => 0, 1 => 1 .tap do |fibs|
fibs.default_proc = ->(fibs, n) fibs[n] = fibs[n-1] + fibs[n-2]
end
fibs.take(10).map(&:last).each(&method(:puts))
计数排序的实现:
module Enumerable
def counting_sort(k)
reduce(Array.new(k+1, 0)) |counting, n| counting.tap counting[n] += 1 .
map.with_index |count, n| [n] * count .flatten
end
end
sum
aka 前缀和的实现:
module Enumerable
def scan(initial=nil, sym=nil, &block)
args = if initial then [initial] else [] end
unless block_given?
args, sym, initial = [], initial, first unless sym
block = ->(acc, el) acc.send(sym, el)
end
[initial || first].tap |res|
reduce(*args) |acc, el|
block.(acc, el).tap |e|
res << e
end
end
在这里,我尝试使用 Hash#each
yield KeyValuePair
s 而不是两个元素 Array
s。在做了如此残酷的猴子补丁之后,有多少代码仍然可以工作,这非常令人惊讶。耶,鸭子打字!
class Hash
KeyValuePair = Struct.new(:key, :value) do
def to_ary
return key, value
end
end
old_each = instance_method(:each)
define_method(:each) do |&blk|
old_each.bind(self).() do |k, v|
blk.(KeyValuePair.new(k, v))
end
end
end
我一直在玩的东西是让Enumerable#===
执行递归结构模式匹配。我不知道这是否有用。我什至不知道它是否真的有效。
module Enumerable
def ===(other)
all? |el|
next true if el.nil?
begin
other.any? |other_el| el === other_el
rescue NoMethodError => e
raise unless e.message =~ /any\?/
el === other
end
end
end
我最近玩弄的另一件事是重新实现Enumerable
中的所有方法,但使用reduce
而不是each
作为基础。在这种情况下,我知道它实际上并不能正常工作。
module Enumerable
def all?
return reduce(true) |res, el| break false unless res; res && el unless block_given?
reduce(true) |res, el| break false unless res; res && yield(el)
end
def any?
return reduce(false) |res, el| break true if res || el unless block_given?
reduce(false) |res, el| break true if res || yield(el)
end
def collect
reduce([]) |res, el| res << yield(el)
end
alias_method :map, :collect
def count(item=undefined = Object.new)
return reduce(0) |res, el| res + 1 if el == item unless undefined.equal?(item)
unless block_given?
return size if respond_to? :size
return reduce(0) |res, el| res + 1
end
reduce(0) |res, el| res + 1 if yield el
end
def detect(ifnone=nil)
reduce(ifnone) |res, el| if yield el then el end unless res
end
alias_method :find, :detect
def drop(n=1)
reduce([]) |res, el| res.tap res << el unless n -= 1 >= 0
end
def drop_while
reduce([]) |res, el| res.tap res << el unless yield el
end
def each
tap reduce(nil) |_, el| yield el
end
def each_with_index
tap reduce(-1) |i, el| (i+1).tap |i| yield el, i
end
def find_all
reduce([]) |res, el| res.tap |res| res << el if yield el
end
alias_method :select, :find_all
def find_index(item=undefined = Object.new)
return reduce(-1) |res, el| break res + 1 if el == item unless undefined.equals?(item)
reduce(-1) |res, el| break res + 1 if yield el
end
def grep(pattern)
return reduce([]) |res, el| res.tap |res| res << el if pattern === el unless block_given?
reduce([]) |res, el| res.tap |res| res << yield(el) if pattern === el
end
def group_by
reduce(Hash.new |hsh, key| hsh[key] = [] ) |res, el| res.tap res[yield el] = el
end
def include?(obj)
reduce(false) |res, el| break true if res || el == obj
end
def reject
reduce([]) |res, el| res.tap |res| res << el unless yield el
end
end
【讨论】:
Jörg,您的斐波那契示例不起作用:fibs.take(7) => [[0, 0], [1, 1]] @Alexey:你是对的。这就是我将一个很酷的双衬里变成一个很酷的单衬里并且之后没有测试它的结果;-) 我回家后会调查。【参考方案2】:初始化数组中的多个值:
a = [1,2,3]
b, *c = a
assert_equal [b, c], [1, [2,3]]
d, = a
assert_equal d, a[0]
【讨论】:
【参考方案3】:我自己是:
用相同的表达式初始化数组元素:
5.times.map some_expression
用相同的值初始化数组:
[value]*5
对数组元素求和:
[1,2,3].reduce(0, &:+)
查找所有符合条件的索引:
a.each_with_index.find_all |e, i| some_predicate(e) .map(&:last)
【讨论】:
我猜 reduce 只是一个例子,因为你有 [1,2,3].sum。最后一个 sn-p 可以写成“a.each_with_index.map |e, i| i if some_predicate(e) .compact”,认为它肯定会生成一个更大的中间数组。 哎呀,看起来 Enumerable#sum 实际上是一个扩展,而不是普通的 Ruby。 除非value
是不可变的,否则[value] * 5
会给你带来一大堆悲伤。
@Andrew,所以在可变情况下使用 5.times.map some_expression 或尽量减少代码中的突变【参考方案4】:
不是真正的 sn-ps,但我喜欢这些通用结构(我只展示如何使用它们,实现很容易在网络上找到)。
转换数组 -> Hash(to_hash
或mash
,思路一样,见Facets实现):
>> [1, 2, 3].mash |k| [k, 2*k]
=> 1=>2, 2=>4, 3=>6
地图 + 选择/检测:你想做一个地图并且只得到第一个结果(所以map ... .first
效率低下):
>> [1, 2, 3].map_select |k| 2*k if k > 1
=> [4, 6]
>> [1, 2, 3].map_detect |k| 2*k if k > 1
=> 4
延迟迭代(lazy_map、lazy_select、...)。示例:
>> 1.upto(1e100).lazy_map |x| 2 *x .first(5)
=> [2, 4, 6, 8, 10]
【讨论】:
Facets、Hashery 和 Tom 的其他库是各种优秀 Ruby 代码的宝库,而不仅仅是与集合和迭代器有关。 def mash &body;哈希[map(&body)] 结束 顺便说一句:最后一个真的让我很烦。例如,在 Scala 中,map
& Co. 保证返回调用它们的相同集合类型。在 Ruby 中,它们总是返回一个具体的、严格的、完全实现的Array
。对于Hash
或Tree
,这很烦人,但对于像Enumerator
这样的潜在无限数据结构,这简直是致命的。如果map
只是返回调用它的同一个东西,你就不需要lazy_map
,它就可以工作。
@Jörg,我绝对同意 Ruby 普遍使用数组不是一个明智的决定。 Python 在某种程度上解决了惰性生成器的问题,但我仍然不知道 Ruby 会如何以一种很好的方式做到这一点。
在大多数实际情况下,我更喜欢 Ruby 的方法而不是 Scala 的方法。在 Scala 中,我必须一直将结果序列转换为另一种类型的序列,因为几乎总是我只需要数组 :)【参考方案5】:
计算满足一个条件或另一个条件的项目数:
items.count do |item|
next true unless first_test?(item)
next true unless second_test?(item)
false
end
count
表示您不必执行i = 0
和i += 1
。
next
表示您可以完成该块的迭代并仍然提供答案,而不是一直徘徊到最后。
(如果您愿意,可以将块的最后两行替换为单行 ! second_test?(item)
,但这会让它看起来更混乱)
【讨论】:
为什么不这样做呢? items.count |i| first_test?(i) 还是 second_test?(i) @Alexey:你可以,除非真正的代码太冗长以至于你不能轻易地将它放在一行中。【参考方案6】:探索字符串的布尔成员方法:
"".methods.sort.grep(/\?/)
探索特定于字符串的方法:
"".methods.sort - [].methods
【讨论】:
以上是关于带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]的主要内容,如果未能解决你的问题,请参考以下文章