带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]

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 KeyValuePairs 而不是两个元素 Arrays。在做了如此残酷的猴子补丁之后,有多少代码仍然可以工作,这非常令人惊讶。耶,鸭子打字!

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_hashmash,思路一样,见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。对于HashTree,这很烦人,但对于像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 = 0i += 1

next 表示您可以完成该块的迭代并仍然提供答案,而不是一直徘徊到最后。

(如果您愿意,可以将块的最后两行替换为单行 ! second_test?(item),但这会让它看起来更混乱)

【讨论】:

为什么不这样做呢? items.count |i| first_test?(i) 还是 second_test?(i) @Alexey:你可以,除非真正的代码太冗长以至于你不能轻易地将它放在一行中。【参考方案6】:

探索字符串的布尔成员方法:

"".methods.sort.grep(/\?/)

探索特定于字符串的方法:

"".methods.sort - [].methods

【讨论】:

以上是关于带有红宝石集合/可枚举的酷技巧和富有表现力的片段[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

typescript 返回对象的酷技巧

寻找富有表现力的音频编程语言或库

总结富有表现力的JavaScript

实现自定义集合的可枚举类型(IEnumerable)和枚举数(IEnumerator )

MySQL数据类型--------枚举与集合类型实战

是否有一个用于文件比较的 .NET 库并返回添加、更改和删除行的可枚举集合?