【问题标题】: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

【问题讨论】:

    标签: ruby collections code-snippets enumerable


    【解决方案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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-23
        相关资源
        最近更新 更多