module Enumerable
Constants
- Arguments
This is a simple reimplementation of the core
Enumerable
module to allow its methods to take and pass-on arbitrary arguments to the underlying each call. This library usesEnumerator
and scansEnumerable
so it can alwasy stay in sync.NOTE: Any
Enumerable
method with a negative arity cannot pass arguments due to ambiguity in the argument count. So the methods inject and zip do NOT work this way, but simply work as they do inEnumerable
. However, The methods find and detect have been made modified to work by removing its rarely used optional parameter and providing instead an optional keyword parameter (:ifnone => …). Please keep these difference in mind.Example
require 'enumargs' class T include Enumerable::Argumentable def initialize(arr) @arr = arr end def each(n) arr.each{ |e| yield(e+n) } end end t = T.new([1,2,3]) t.collect(4) #=> [5,6,7]
Public Instance Methods
Accumulate a set of a set. For example, in an ORM design where ‘Group has_many User` we might have something equivalent to the following.
Group = Struct.new(:users) User = Struct.new(:name, :friends) user1 = User.new('John', []) user2 = User.new('Jane', ['Jill']) user3 = User.new('Joe' , ['Jack', 'Jim']) group1 = Group.new([user1, user2]) group2 = Group.new([user2, user3]) groups = [group1, group2]
Now we can accumulate the users of all groups.
groups.accumulate.users #=> [user1, user2, user3]
You may pass an argument to perform chains, e.g. the following returns the names of users from all groups.
groups.accumulate(2).users.name #=> ['John','Jane','Joe']
Or we can gather all the friends of all users in groups.
groups.accumulate(2).users.friends #=> ['Jill','Jack','Jim']
This is more convenient then the equivalent.
groups.accumulate.users.accumulate.friends #=> ['Jill','Jack','Jim']
CREDIT: George Moshchovitis, Daniel Emirikol, Robert Dober
# File lib/core/facets/enumerable/accumulate.rb, line 42 def accumulate(iterations=1) return self if iterations == 0 Functor.new do |op, *args| result = [] each { |x| result << x.send(op, *args) } result.flatten.uniq.accumulate(iterations - 1) end end
Same as accumulate
, but does not apply uniq to final result.
groups.accumulate_all(2).users.friends #=> ['Jill', 'Jill','Jack','Jim']
# File lib/core/facets/enumerable/accumulate.rb, line 56 def accumulate_all(iterations=1) return self if iterations == 0 Functor.new do |op, *args| result = [] each { |x| result << x.send(op, *args) } result.flatten.accumulate_all(iterations - 1) end end
Returns an elemental object. This allows you to map a method on to every element.
r = [1,2,3].apply.+ r #=> 6
# File lib/core/facets/enumerable/apply.rb, line 12 def apply #Functor.new do |sym, *args, &blk| # inject{ |r, e| r.__send__(sym, e, *args, &blk) } #end per(:inject) end
Clusters together adjacent elements into a list of sub-arrays.
[2,2,2,3,3,4,2,2,1].cluster{ |x| x } => [[2, 2, 2], [3, 3], [4], [2, 2], [1]] ["dog", "duck", "cat", "dude"].cluster{ |x| x[0] } => [["dog", "duck"], ["cat"], ["dude"]]
@author Oleg K
# File lib/core/facets/enumerable/cluster.rb, line 13 def cluster cluster = [] each do |element| if cluster.last && yield(cluster.last.last) == yield(element) cluster.last << element else cluster << [element] end end cluster end
A more versitle compact method. It can be used to collect and filter items out in one single step.
c = [1,2,3].compact_map do |n| n < 2 ? nil : n end c #=> [2,3]
CREDIT: Trans
DEPRECATE: This method should probably be removed b/c purge
does almost the same thing and enum.map{}.compact works too.
# File lib/core/facets/enumerable/compact_map.rb, line 17 def compact_map(&block) y = [] if block_given? each do |*a| r = yield(*a) y << r unless r.nil? end else each do |r| y << r unless r.nil? end end y end
Without a block: wrap the Enumerable
object in such a way that map, select and similar operations are performed “horizontally” across a series of blocks, instead of building an array of results at each step. This reduces memory usage, allows partial results to be provided early, and permits working with infinite series.
a = (1..1_000_000_000).defer.select{ |i| i % 2 == 0 }. map{ |i| i + 100 }. take(10).to_a
With a block: the block acts as an arbitrary filter on the data. Unlike map, it can choose to drop elements from the result, and/or add additional ones. The first object passed to the block is the receiver of the output.
(1..1_000_000_000). defer { |out,i| out << i if i % 2 == 0 }. # like select defer { |out,i| out << i + 100 }. # like map take(10).to_a
Use a method like to_a or to_h at the end of the chain when you want an Array
or Hash
built with the results, or each{…} if you just want to output each result and discard it.
# File lib/core/facets/enumerable/defer.rb, line 29 def defer(&blk) if block_given? Denumerator.new do |output| each do |*input| yield(output, *input) end end else Denumerator.new do |output| each do |*input| output.yield(*input) end end end end
Iterate through slices. If slice steps
is not given, the arity of the block is used.
x = [] [1,2,3,4].each_by{ |a,b| x << [a,b] } x #=> [ [1,2], [3,4] ] x = [] [1,2,3,4,5,6].each_by(3){ |a| x << a } x #=> [ [1,2,3], [4,5,6] ]
This is just like each_slice, except that it will check the arity of the block. If each_slice ever suppots this this method can be deprecated.
CREDIT: Trans
# File lib/core/facets/enumerable/each_by.rb, line 22 def each_by(steps=nil, &block) if steps each_slice(steps, &block) else steps = block.arity.abs each_slice(steps, &block) #each_slice(steps) {|i| block.call(*i)} end end
Long-term for ewise
.
a = [1,2] (a.elementwise + 3) #=> [4,5] (a.elementwise + [4,5]) #=> [5,7]
Returns an elemental object. This allows you to map a method on to every element.
r = [1,2,3].every + 3 r #=> [4,5,6]
# File lib/core/facets/enumerable/every.rb, line 11 def every per(:map) end
In place version of every
.
# File lib/core/facets/enumerable/every.rb, line 17 def every! raise NoMethodError unless respond_to?(:map!) per(:map!) end
Returns an elementwise Functor
designed to make R-like elementwise operations possible. This is very much like the every
method, but it treats array argument specially.
([1,2].ewise + 3) #=> [4,5]
Vector to vector
([1,2].ewise + [4,5]) #=> [5,7]
Special thanks to Martin DeMello for helping to develop this.
# File lib/core/facets/enumerable/ewise.rb, line 19 def ewise(count=1) Functor.new do |op,*args| if args.empty? r = self count.times do r = r.collect{ |a| a.send(op) } end r else r = args.collect do |arg| if Array === arg #arg.kind_of?(Enumerable) x = self count.times do ln = (arg.length > length ? length : arg.length ) x = x.slice(0...ln) x = x.zip(arg[0...ln]) x = x.collect{ |a,b| a.send(op,b) } #x = x.collect{ |a,b| b ? a.send(op,b) : nil } end x else x = self count.times do x = x.collect{ |a| a.send(op,arg) } end x end end r.flatten! if args.length == 1 r end end end
The inverse of include?.
[:a, :b].exclude?(:c) #=> true [:a, :b].exclude?(:a) #=> false
# File lib/core/facets/enumerable/exclude.rb, line 10 def exclude?(object) !include?(object) end
Expand all elements of an Enumerable
object.
[0, 2..3, 5..7].expand #=> [0,[2, 3],[5,6,7]]
CREDIT: Trans
# File lib/core/facets/enumerable/expand.rb, line 8 def expand map do |x| (Enumerable === x ? x.expand : x) end end
The block acts as an arbitrary filter on the data. Unlike map, it can choose to drop elements from the result and/or add additional elements. The first object passed to the block is the receiver of the output.
x = (1..10000) x = x.filter{ |out,i| out << i if i % 2 == 0 } # like select x = x.filter{ |out,i| out << i + 100 } # like map x = x.take(3) x #=> [102, 104, 106]
This is very similar to each_with_object, but filter
handles argument better by reversing their order and using the splat operator. (This was also once known as injecting.)
CREDIT: David Black, Louis J Scoras
# File lib/core/facets/enumerable/filter.rb, line 21 def filter(output=[]) #:yeild: if block_given? each do |*input| yield(output, *input) end output else to_enum(:filter) end end
Yield each element to the block and return the result of the block when that result evaluates as true, terminating early like detect and find.
obj1 = Object.new obj2 = Object.new def obj1.foo?; false; end def obj2.foo?; true ; end def obj1.foo ; "foo1"; end def obj2.foo ; "foo2"; end [obj1, obj2].find_yield{ |obj| obj.foo if obj.foo? } #=> "foo2"
Another example.
[1,2,3,4,5].find_yield{ |i| j = i+1; j if j % 4 == 0 } #=> 4
If the block is never true, return the object given in the first parameter, or nil if none specified.
[1,2,3].find_yield{ |_| false } #=> nil [false].find_yield(1){ |_| false } #=> 1
# File lib/core/facets/enumerable/find_yield.rb, line 28 def find_yield(fallback=nil) #:yield: each do |member| result = yield(member) return result if result end fallback end
Generates a hash mapping each unique symbol in the array to the absolute frequency it appears.
[:a,:a,:b,:c,:c,:c].frequency #=> {:a=>2,:b=>1,:c=>3}
CREDIT: Brian Schröder
# File lib/core/facets/enumerable/frequency.rb, line 18 def frequency p = Hash.new(0); each{ |v| p[v] += 1 }; p end
Like ‘#map`/`#collect`, but generates a Hash
. The block is expected to return two values: the key and the value for the new hash.
numbers = (1..3) squares = numbers.graph{ |n| [n, n*n] } # { 1=>1, 2=>4, 3=>9 } sq_roots = numbers.graph{ |n| [n*n, n] } # { 1=>1, 4=>2, 9=>3 }
CREDIT: Andrew Dudzik (adudzik), Trans
# File lib/core/facets/enumerable/graph.rb, line 12 def graph(&yld) if yld h = {} each do |*kv| r = yld[*kv] case r when Hash nk, nv = *r.to_a[0] when Range nk, nv = r.first, r.last else nk, nv = *r end h[nk] = nv end h else Enumerator.new(self,:graph) end end
The hashify methods is a higher-order message used to convert an enumerable object into a hash. Converting an enumerable object into a hash is not a one-to-one conversion, for this reason hashify
is used to provide variant approches for the conversion most suited to the use case at hand. Here are some (but not a complete set of) examples.
If the enumerable is a collection of perfect pairs, like that which Hash#to_a generates, then assoc can be used.
a = [ [:a,1], [:b,2] ] a.hashify.assoc #=> { :a=>1, :b=>2 }
If it it contains only arrays, but are not perfect pairs, then concat can be used.
a = [ [:a,1,2], [:b,2], [:c], [:d] ] a.hashify.concat #=> { :a=>[1,2], :b=>[2], :c=>[], :d=>[] }
If the array contains objects other then arrays then the splat method might do the trick.
a = [ [:a,1,2], 2, :b, [:c,3], 9 ] a.hashify.splat #=> { [:a,1,2]=>2, :b=>[:c,3], 9=>nil }
Also, the particular dispatch can be left up the Hashify using the auto method. See Hashify#auto for details on this.
TODO: This method takes arguments only for the sake of the old method which has been deprecated. These will be removed eventually.
CREDIT: Robert Klemme, Sandor Szücs, Trans
# File lib/core/facets/enumerable/hashify.rb, line 40 def hashify(val=nil, &block) if val warn "The old Enumerable#hashify method has been be deprecated. Use #value_by instead." value_by{ val } end if block warn "The old Enumerable#hashify method has been be deprecated. Use #value_by instead." value_by(&block) end Hashifier.new(self) end
Apply each element of an enumerable ot a hash by iterating over each element and yielding the hash and element.
[1,2,3].hinge{|h,e| h[e] = e+1 } #=> {1=>2, 2=>3, 3=>4}
TODO: Enumerable#hinge
will get a new name.
# File lib/core/facets/enumerable/hinge.rb, line 12 def hinge(init={}) h = init each{ |v| yield(h,v) } h end
The same as include? but tested using === instead of ==.
[1, 2, "a"].incase?(2) #=> true [1, 2, "a"].incase?(String) #=> true [1, 2, "a"].incase?(3) #=> false
Why the name ‘incase`? Because the method uses case-equality. Along with the alliteration for “in case” and the similarity with “include?”, it seemed like the perfect fit.
@author Lavir the Whiolet
# File lib/core/facets/enumerable/incase.rb, line 15 def incase?(what) any? { |x| what === x } end
Convert enumerable into a Hash
, iterating over each member where the provided block must return the key to by used to map to the value.
Examples:
[:a,:b,:c].key_by{ |v| v.to_s } #=> {'a'=>:a, 'b'=>:b, 'c'=>:c}
TODO: How should this method behave with a Hash
?
Returns: Hash
# File lib/core/facets/enumerable/key_by.rb, line 16 def key_by return to_enum(:key_by) unless block_given? h = {} each do |v| h[yield(v)] = v end return h end
Like group_by, but maps the second value returned from the block.
a = [1,2,3,4,5] a.map_by{ |e| [e % 2, e + 1] } #=> { 0=>[3,5], 1=>[2,4,6] }
Works well with a hash too.
h = {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2} h.map_by{ |k,v| [v, k.downcase] } #=> {1=>["a", "b", "c"], 2=>["d", "e"]}
If a second value is not returned, map_by
acts like group_by.
h = {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2} h.map_by{ |k,v| v } #=> {1=>[["A",1], ["B",1], ["C",1]], 2=>[["D",2], ["E",2]]}
# File lib/core/facets/enumerable/map_by.rb, line 21 def map_by #:yield: res = {} each do |a| k,v = yield(*a) if v (res[k] ||= []) << v else (res[k] ||= []) << a end end res end
Alias for find_yield
.
DEPRECATE: This has been renamed to find_yield
.
Send a message to each element and collect the result.
[1,2,3].map_send(:+, 3) #=> [4,5,6]
CREDIT: Sean O’Halpin
# File lib/core/facets/enumerable/map_send.rb, line 9 def map_send(meth, *args, &block) map{|e| e.send(meth, *args, &block)} end
Map each element into another class via class constructor.
@param [#new] to_class
Generally a class, but any object that repsonds to #new.
# File lib/core/facets/enumerable/map_to.rb, line 8 def map_to(to_class) map{ |e| to_class.new(e) } end
Combines zip and map in a single efficient operation.
h = {} [1,2,3].map_with [:x,:y,:z] do |n,k| h[k] = n end h #=> {:x=>1, :y=>2, :z=>3}
@return [Hash]
@author Michael Kohl
# File lib/core/facets/enumerable/map_with.rb, line 14 def map_with(*arrays, &block) enum_for(:zip, *arrays).map(&block) end
Same as collect but with an iteration counter.
a = [1,2,3].collect_with_index { |e,i| e*i } a #=> [0,2,6]
CREDIT: Gavin Sinclair
# File lib/core/facets/enumerable/map_with_index.rb, line 10 def map_with_index r = [] each_with_index do |e, i| r << yield(e, i) end r end
Like ‘#map`/`#collect`, but generates a Hash
. The block is expected to return two values: the key and the value for the new hash.
numbers = (1..3) squares = numbers.mash{ |n| [n, n*n] } # { 1=>1, 2=>4, 3=>9 } sq_roots = numbers.mash{ |n| [n*n, n] } # { 1=>1, 4=>2, 9=>3 }
CREDIT: Andrew Dudzik (adudzik), Trans
# File lib/core/facets/enumerable/mash.rb, line 12 def mash(&yld) if yld h = {} each do |*kv| r = yld[*kv] case r when Hash nk, nv = *r.to_a[0] when Range nk, nv = r.first, r.last else nk, nv = *r end h[nk] = nv end h else Enumerator.new(self,:graph) end end
Divide an array into groups by modulo of the index.
[2,4,6,8].modulate(2) #=> [[2,6],[4,8]]
CREDIT: Trans
NOTE: Would the better name for this be ‘collate’?
# File lib/core/facets/enumerable/modulate.rb, line 11 def modulate(modulo) return to_a if modulo == 1 raise ArgumentError, 'bad modulo' if size % modulo != 0 r = Array.new(modulo, []) (0...size).each do |i| r[i % modulo] += [self[i]] end r end
Returns an array of elements for the elements that occur n times. Or according to the results of a given block.
a = [1,1,2,3,3,4,5,5] a.occur(1).sort #=> [2,4] a.occur(2).sort #=> [1,3,5] a.occur(3).sort #=> [] a.occur(1..1).sort #=> [2,4] a.occur(2..3).sort #=> [1,3,5] a.occur { |n| n == 1 }.sort #=> [2,4] a.occur { |n| n > 1 }.sort #=> [1,3,5]
# File lib/core/facets/enumerable/occur.rb, line 18 def occur(n=nil) #:yield: result = Hash.new { |hash, key| hash[key] = Array.new } each do |item| key = item result[key] << item end if block_given? result.reject! { |key, values| ! yield(values.size) } else raise ArgumentError unless n if Range === n result.reject! { |key, values| ! n.include?(values.size) } else result.reject! { |key, values| values.size != n } end end return result.values.flatten.uniq end
Returns the only element in the enumerable. Raises an IndexError if the enumreable has more then one element.
[5].only # => 5 expect IndexError do [1,2,3].only end expect IndexError do [].only end
CREDIT: Lavir the Whiolet, Gavin Sinclair, Noah Gibbs
# File lib/core/facets/enumerable/only.rb, line 18 def only first = false first_item = nil each do |item| if first raise IndexError, "not the only element of enumerable" else first = true first_item = item end end if first return first_item else raise IndexError, "not the only element of enumerable" end end
Does this Enumerable
have the only element?
It differs from Enumerable#one? in that it does not check the items themselves. It checks the quantity only.
CREDIT: Lavir the Whiolet
# File lib/core/facets/enumerable/only.rb, line 45 def only? first = false each do |item| return false if first first = true end return first end
Similar to group_by but returns an array of the groups. Returned elements are sorted by block.
%w{this is a test}.organize_by {|x| x[0]} #=> [ ['a'], ['is'], ['this', 'test'] ]
CREDIT: Erik Veenstra
# File lib/core/facets/enumerable/organize_by.rb, line 11 def organize_by(&b) group_by(&b).sort.transpose.pop || [] # group_by(&b).values ? end
Like ‘each_slice(2)` but ensures the last element has a pair if odd sized.
[:a,1,:b,2,:c,3].pair.to_a #=> [[:a,1],[:b,2],[:c,3]]
# File lib/core/facets/enumerable/pair.rb, line 8 def pair(missing=nil) return to_enum(:pair) unless block_given? each_slice(2) do |kv| if kv.size == 1 yield kv.first, missing else yield kv.first, kv.last end end end
Per element meta-functor.
([1,2,3].per(:map) + 3) #=> [4,5,6] ([1,2,3].per(:select) > 1) #=> [2,3]
Using fluid notation.
([1,2,3].per.map + 3) #=> [4,5,6] ([1,2,3].per.select > 1) #=> [2,3]
# File lib/core/facets/enumerable/per.rb, line 21 def per(enum_method=nil, *enum_args) if enum_method Functor.new do |op, *args, &blk| __send__(enum_method || :map, *enum_args){ |x, *y| x.__send__(op, *y, *args, &blk) } end else Functor.new do |enumr_method, *enumr_args| Functor.new do |op, *args, &blk| __send__(enumr_method, *enumr_args){ |x, *y| x.__send__(op, *y, *args, &blk) } end end end end
A versitle compaction method. Like map but used to filter out multiple items in a single step.
Without trash
arguments nil
is assumed.
[1, nil, 2].purge #=> [1,2]
If trash
arguments are given, each argument is compared for a match using ==.
(1..6).purge(3,4) #=> [1,2,5,6]
If a block is given, the yield is used in the matching condition instead of the element itsef.
(1..6).purge(0){ |n| n % 2 } #=> [1,3,5]
NOTE: This could just as well be an override of the core compact method, but to avoid potential issues associated with overriding core methods we use the alternate name purge
.
CREDIT: Trans
# File lib/core/facets/enumerable/purge.rb, line 27 def purge(*trash, &block) trash = [nil] if trash.empty? r = [] if block_given? each do |e| y = yield(e) r << e unless trash.any?{|t| t == y} end else each do |e| r << e unless trash.any?{|t| t == e} end end r end
Returns a recursive functor, that allows enumerable methods to iterate through enumerable sub-elements. By default it only recurses over elements of the same type.
# File lib/core/facets/enumerable/recursively.rb, line 7 def recursively(*types, &block) Recursor.new(self, *types, &block) end
Squeeze out the same elements. This behaves like C++ unique(), removing equivalent elements that are concomitant to each other. To get a similar result with Array#uniq, the array would have to be sorted first.
Calculation order is O(n).
Examples
[1,2,2,3,3,2,1].squeeze #=> [1,2,3,2,1] [1,2,2,3,3,2,1].sort.squeeze #=> [1,2,3] [1,2,2,3,3,2,1].squeeze(*[3]) #=> [1,2,2,3,2,1]
Returns [Array].
CREDIT: T. Yamada
# File lib/core/facets/enumerable/squeeze.rb, line 20 def squeeze(*limited_to) first = true r = [] # result c = nil # current each do |e| if !limited_to.empty? && !limited_to.include?(e) r << e elsif first || c != e r << e first = false c = e end end r end
Uses + to sum the enumerated elements.
[1,2,3].sum #=> 6 [3,3,3].sum #=> 9
Note that Facets’ sum method is completely generic – it can work on any objects that respond to +.
[[1],[2],[3]].sum #=> [1,2,3]
For this reason it is usually a good idea to provide a default value. Consider the difference between the two expressions below.
[].sum #=> nil [].sum(0) #=> 0
This default value also acts as an initial value.
[].sum(5) #=> 5 [1,2,3].sum(10) #=> 16
A block can also be passed to coax the elements before summation.
[1.1,2.2,3.3].sum(10.4, &:to_i) #=> 16.4
Notice the initial value is not effected by the block.
@author Dawid Marcin Grzesiak
# File lib/core/facets/enumerable/sum.rb, line 32 def sum(*identity, &block) if block_given? map(&block).sum(*identity) else reduce(*identity) { |sum, element| sum + element } end end
Like Enumerable#map but each iteration is processed via a separate thread.
CREDIT: Sean O’Halpin
# File lib/standard/facets/thread.rb, line 59 def threaded_map #:yield: map{ |e| Thread.new(e){ |t| yield(t) } }.map{ |t| t.value } end
Like Enumerable#map_send
but each iteration is processed via a separate thread.
CREDIT: Sean O’Halpin
# File lib/standard/facets/thread.rb, line 68 def threaded_map_send(meth, *args, &block) map{ |e| Thread.new(e){ |t| t.send(meth, *args, &block) } }.map{ |t| t.value } end
Take an associative array and unassociate it.
[[:a,1], [:b,2]].unassociate.to_a #=> [:a, [1], :b, [2]] [[:a,1], [:b,2]].unassociate(1).to_a #=> [:a, 1, :b, 2]
# File lib/core/facets/enumerable/unassociate.rb, line 8 def unassociate(index = 1..-1) return to_enum(:unassociate, index) unless block_given? each do |v| case v when Array yield v[0] yield v[index] else yield v yield nil end end end
Like uniq, but determines uniqueness based on a given block.
(-5..5).to_a.uniq_by {|i| i*i } #=> [-5, -4, -3, -2, -1, 0]
# File lib/core/facets/enumerable/uniq_by.rb, line 8 def uniq_by #:yield: h = {}; inject([]) {|a,x| h[yield(x)] ||= a << x} end
Create a hash whose keys are the enumerable’s elements, with specified values.
If no block is given, the given parameter (default true) is used for all values, e.g.:
[1,2,3].value_by{ true } #=> {1=>true, 2=>true, 3=>true} [1,2,3].value_by{ "a" } #=> {1=>"a", 2=>"a", 3=>"a"}
If a block is given, each key’s value is the result of running the block for that key, e.g.:
[1,2,3].value_by{ |n| "a"*n } #=> {1=>"a", 2=>"aa", 3=>"aaa"}
@author Ronen Barzel
# File lib/core/facets/enumerable/value_by.rb, line 18 def value_by return to_enum(:value_by) unless block_given? h = {} each { |item| h[item] = yield(item) } h end
Recursively iterate over all Enumerable
elements, or subset given :type=>[type1, type2, …].
[1, 2, 8..9].visit{ |x| x.succ } # => [2, 3, [9, 10]]
# File lib/core/facets/enumerable/visit.rb, line 9 def visit(opts={}, &block) type = opts[:type] ? [opts[:type]].flatten : [Enumerable] skip = opts[:skip] map do |v| case v when String # b/c of 1.8 block.call(v) when *type v.visit(opts, &block) else if skip && Enumerable === v v else block.call(v) end end end end