Cool tricks and expressive snippets with ruby collections/enumerables [closed]

假如想象 提交于 2020-01-11 17:06:10

问题


What are your favorite code snippets with ruby collections? Preferably they should be discovery for you, be expressive, readable and introduce some fun in your coding practice.


Pattern-matching in arrays (for local variables and parameters):

(a, b), c = [[:a, :b], :c]
[a,b,c]
=> [:a, :b, :c]

(a,), = [[:a]]
a
=> :a

Assigning from non-arrays to multiple variables:

abc, a, b =* "abc".match(/(a)(b)./)
=> ["abc", "a", "b"]

nil1, =* "abc".match(/xyz/)
=> []

Initialize array elements with the same expression:

5.times.map { 1 }    
=> [1,1,1,1]

Array.new(5) { 1 }
=> [1,1,1,1,1]

Initialize array with the same value:

[2]*5
=>[2,2,2,2,2]

Array.new 5, 2
=>[2,2,2,2,2]

Sum elements of an array:

[1,2,3].reduce(0, &:+)

=> 6

Find all indices that match condition:

a.each_with_index.find_all { |e, i| some_predicate(e) }.map(&:last)

Alternate CSS classes:

(1..4).zip(%w[cls1 cls2].cycle)

=> [[1, "cls1"], [2, "cls2"], [3, "cls1"], [4, "cls2"]]

Unzipping:

keys, values = {a: 1, b: 2}.to_a.transpose
keys
=> [:a, :b]

Exploring boolean member methods of a string:

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

Exploring string-specific methods:

"".methods.sort - [].methods

回答1:


Lazy Fibonacci series with memoization, taken from 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))

An implementation of Counting Sort:

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

An implementation of sum aka prefix sum:

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

Here, I experimented with having Hash#each yield KeyValuePairs instead of two-element Arrays. It's quite surprising, how much code still works, after doing such a brutal monkey-patch. Yay, duck typing!

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

Something I have been playing around with is making Enumerable#=== perform recursive structural pattern matching. I have no idea if this is in any way useful. I don't even know if it actually works.

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

Another thing I toyed around with recently was re-implementing all methods in Enumerable, but using reduce instead of each as the basis. In this case, I know it doesn't actually work properly.

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



回答2:


Initialize multiple values from an array:

a = [1,2,3]
b, *c = a

assert_equal [b, c], [1, [2,3]]

d, = a
assert_equal d, a[0]



回答3:


My own are:

Initialize array elements with same expression:

5.times.map { some_expression }

Initialize array with same value:

[value]*5

Sum elements of an array:

[1,2,3].reduce(0, &:+)

Find all indices that match condition:

a.each_with_index.find_all { |e, i| some_predicate(e) }.map(&:last)



回答4:


Not really snippets, but I like these generic constructions (I show only how to use them, the implementation is easily found on the web).

Conversion Array -> Hash (to_hash or mash, the idea is the same, see Facets implementation):

>> [1, 2, 3].mash { |k| [k, 2*k] } 
=> {1=>2, 2=>4, 3=>6}

Map + select/detect: You want to do a map and get only the first result (so a map { ... }.first would inefficient):

>> [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 iterations (lazy_map, lazy_select, ...). Example:

>> 1.upto(1e100).lazy_map { |x| 2 *x }.first(5)
=> [2, 4, 6, 8, 10]



回答5:


Count the number of items that meet either one condition or another:

items.count do |item|
  next true unless first_test?(item)
  next true unless second_test?(item)
  false
end

count means you don't have to do i = 0 and i += 1.

next means that you can finish that iteration of the block and still supply the answer, rather than hanging around until the end.

(If you wanted, you could replace the last two lines of the block with the single line ! second_test?(item), but that'd make it look messier)




回答6:


Exploring boolean member methods of a string:

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

Exploring string-specific methods:

"".methods.sort - [].methods


来源:https://stackoverflow.com/questions/4317439/cool-tricks-and-expressive-snippets-with-ruby-collections-enumerables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!