Permalink
5eab61e Jan 17, 2018
2 contributors

Users who have contributed to this file

@brixen @kachick
865 lines (719 sloc) 18.3 KB
# Just to save you 10 seconds, the reason we always use #each to extract
# elements instead of something simpler is because Enumerable can not assume
# any other methods than #each. If needed, class-specific versions of any of
# these methods can be written *in those classes* to override these.
module Enumerable
def chunk
raise ArgumentError, "no block given" unless block_given?
::Enumerator.new do |yielder|
previous = nil
accumulate = []
each do |element|
key = yield(element)
if key.nil? || (key.is_a?(Symbol) && key.to_s[0, 1] == "_")
yielder.yield [previous, accumulate] unless accumulate.empty?
accumulate = []
previous = nil
case key
when nil, :_separator
when :_alone
yielder.yield [key, [element]]
else
raise RuntimeError, "symbols beginning with an underscore are reserved"
end
else
if previous.nil? || previous == key
accumulate << element
else
yielder.yield [previous, accumulate] unless accumulate.empty?
accumulate = [element]
end
previous = key
end
end
yielder.yield [previous, accumulate] unless accumulate.empty?
end
end
def collect
if block_given?
ary = []
each do |*element|
ary << yield(*element)
end
ary
else
to_enum(:collect) { enumerator_size }
end
end
alias_method :map, :collect
def count(item = undefined)
seq = 0
if !undefined.equal?(item)
each do
element = Rubinius.single_block_arg
seq += 1 if item == element
end
elsif block_given?
each { |element| seq += 1 if yield(element) }
else
each { seq += 1 }
end
seq
end
def each_entry(*pass)
return to_enum(:each_entry, *pass) { enumerator_size } unless block_given?
each(*pass) do
element = Rubinius.single_block_arg
yield element
end
self
end
def each_with_object(memo)
return to_enum(:each_with_object, memo) { enumerator_size } unless block_given?
each do
element = Rubinius.single_block_arg
yield element, memo
end
memo
end
alias_method :with_object, :each_with_object
def flat_map
return to_enum(:flat_map) { enumerator_size } unless block_given?
array = []
each do |*element|
result = yield(*element)
value = Rubinius::Type.try_convert(result, Array, :to_ary) || result
if value.kind_of? Array
array.concat value
else
array.push value
end
end
array
end
alias_method :collect_concat, :flat_map
def lazy
Enumerator::Lazy.new(self, enumerator_size) do |yielder, *args|
yielder.<<(*args)
end
end
def enumerator_size
Rubinius::Type.object_respond_to?(self, :size) ? size : nil
end
private :enumerator_size
def group_by
return to_enum(:group_by) { enumerator_size } unless block_given?
h = {}
each do
element = Rubinius.single_block_arg
key = yield(element)
if h.key?(key)
h[key] << element
else
h[key] = [element]
end
end
Rubinius::Type.infect h, self
h
end
def slice_before(pattern = undefined, &block)
pattern_given = !(undefined.equal? pattern)
raise ArgumentError, "cannot pass both pattern and block" if pattern_given && block_given?
raise ArgumentError, "a pattern or a block must be provided" if !pattern_given && !block_given?
block = Proc.new{ |elem| pattern === elem } if pattern_given
Enumerator.new do |yielder|
accumulator = nil
each do
element = Rubinius.single_block_arg
start_new = block.yield(element)
if start_new
yielder.yield accumulator if accumulator
accumulator = [element]
else
accumulator ||= []
accumulator << element
end
end
yielder.yield accumulator if accumulator
end
end
def slice_after(pattern = undefined, &block)
pattern_given = !undefined.equal?(pattern)
raise ArgumentError, "cannot pass both pattern and block" if pattern_given && block_given?
raise ArgumentError, "a pattern or a block must be provided" if !pattern_given && !block_given?
block = Proc.new { |elem| pattern === elem } if pattern_given
Enumerator.new do |yielder|
accumulator = nil
each do
element = Rubinius.single_block_arg
end_chunk = block.yield(element)
accumulator ||= []
if end_chunk
accumulator << element
yielder.yield accumulator
accumulator = nil
else
accumulator << element
end
end
yielder.yield accumulator if accumulator
end
end
def slice_when(&block)
raise ArgumentError, "wrong number of arguments (0 for 1)" unless block_given?
Enumerator.new do |enum|
ary = nil
last_after = nil
each_cons(2) do |before, after|
last_after = after
match = block.call before, after
ary ||= []
if match
ary << before
enum.yield ary
ary = []
else
ary << before
end
end
unless ary.nil?
ary << last_after
enum.yield ary
end
end
end
def chunk_while(&block)
raise ArgumentError, "no block given" unless block_given?
slice_when { |before, after| !(yield before, after) }
end
def to_a(*arg)
ary = []
each(*arg) do
element = Rubinius.single_block_arg
ary << element
nil
end
Rubinius::Type.infect ary, self
ary
end
alias_method :entries, :to_a
def to_h(*arg)
h = {}
each_with_index(*arg) do |element, i|
unless element.respond_to?(:to_ary)
raise TypeError, "wrong element type #{element.class} at #{i} (expected array)"
end
ary = element.to_ary
if ary.size != 2
raise ArgumentError, "wrong array length at #{i} (expected 2, was #{ary.size})"
end
h[ary[0]] = ary[1]
end
h
end
def zip(*args)
args.map! do |a|
if a.respond_to? :to_ary
a.to_ary
else
a.to_enum(:each)
end
end
results = []
i = 0
each do
element = Rubinius.single_block_arg
entry = args.inject([element]) do |ary, a|
ary << case a
when Array
a[i]
else
begin
a.next
rescue StopIteration
nil
end
end
end
yield entry if block_given?
results << entry
i += 1
end
return nil if block_given?
results
end
def each_with_index(*args)
return to_enum(:each_with_index, *args) { enumerator_size } unless block_given?
idx = 0
each(*args) do
element = Rubinius.single_block_arg
yield element, idx
idx += 1
end
self
end
def grep(pattern)
ary = []
each do
element = Rubinius.single_block_arg
if pattern === element
Regexp.set_block_last_match
if block_given?
ary << yield(element)
else
ary << element
end
end
end
ary
end
def grep_v(pattern)
ary = []
each do
element = Rubinius.single_block_arg
unless pattern === element
if block_given?
ary << yield(element)
else
ary << element
end
end
end
ary
end
def sort(&prc)
ary = to_a
ary.frozen? ? ary.sort(&prc) : ary.sort!(&prc)
end
class SortedElement
def initialize(val, sort_id)
@value, @sort_id = val, sort_id
end
private :initialize
attr_reader :value
attr_reader :sort_id
def <=>(other)
@sort_id <=> other.sort_id
end
end
def sort_by
return to_enum(:sort_by) { enumerator_size } unless block_given?
# Transform each value to a tuple with the value and it's sort by value
sort_values = map do
element = Rubinius.single_block_arg
SortedElement.new(element, yield(element))
end.to_a
# Now sort the tuple according to the sort by value
sort_values.sort!
# Now strip of the tuple leaving the original value
sort_values.map! { |ary| ary.value }
end
def inject(initial=undefined, sym=undefined)
if !block_given? or !undefined.equal?(sym)
if undefined.equal?(sym)
sym = initial
initial = undefined
end
# Do the sym version
sym = sym.to_sym
each do
element = Rubinius.single_block_arg
if undefined.equal? initial
initial = element
else
initial = initial.__send__(sym, element)
end
end
# Block version
else
each do
element = Rubinius.single_block_arg
if undefined.equal? initial
initial = element
else
initial = yield(initial, element)
end
end
end
undefined.equal?(initial) ? nil : initial
end
alias_method :reduce, :inject
def all?
if block_given?
each { |*element| return false unless yield(*element) }
else
each { return false unless Rubinius.single_block_arg }
end
true
end
def any?
if block_given?
each { |*element| return true if yield(*element) }
else
each { return true if Rubinius.single_block_arg }
end
false
end
def cycle(many=nil)
unless block_given?
return to_enum(:cycle, many) do
Rubinius::EnumerableHelper.cycle_size(enumerator_size, many)
end
end
if many
many = Rubinius::Type.coerce_to_collection_index many
return nil if many <= 0
else
many = nil
end
cache = []
each do
element = Rubinius.single_block_arg
cache << element
yield element
end
return nil if cache.empty?
if many
i = 0
many -= 1
while i < many
cache.each { |element| yield element }
i += 1
end
else
while true
cache.each { |element| yield element }
end
end
nil
end
def drop(n)
n = Rubinius::Type.coerce_to_collection_index n
raise ArgumentError, "attempt to drop negative size" if n < 0
ary = to_a
return [] if n > ary.size
ary[n...ary.size]
end
def drop_while
return to_enum(:drop_while) unless block_given?
ary = []
dropping = true
each do
element = Rubinius.single_block_arg
ary << element unless dropping &&= yield(element)
end
ary
end
def each_cons(num)
n = Rubinius::Type.coerce_to_collection_index num
raise ArgumentError, "invalid size: #{n}" if n <= 0
unless block_given?
return to_enum(:each_cons, num) do
enum_size = enumerator_size
if enum_size.nil?
nil
elsif enum_size == 0 || enum_size < n
0
else
enum_size - n + 1
end
end
end
array = []
each do
element = Rubinius.single_block_arg
array << element
array.shift if array.size > n
yield array.dup if array.size == n
end
nil
end
def each_slice(slice_size)
n = Rubinius::Type.coerce_to_collection_index slice_size
raise ArgumentError, "invalid slice size: #{n}" if n <= 0
unless block_given?
return to_enum(:each_slice, slice_size) do
enum_size = enumerator_size
enum_size.nil? ? nil : (enum_size.to_f / n).ceil
end
end
a = []
each do
element = Rubinius.single_block_arg
a << element
if a.length == n
yield a
a = []
end
end
yield a unless a.empty?
nil
end
def find(ifnone=nil)
return to_enum(:find, ifnone) unless block_given?
each do
element = Rubinius.single_block_arg
return element if yield(element)
end
ifnone.call if ifnone
end
alias_method :detect, :find
def find_all
return to_enum(:find_all) { enumerator_size } unless block_given?
ary = []
each do
element = Rubinius.single_block_arg
ary << element if yield(element)
end
ary
end
alias_method :select, :find_all
def find_index(value=undefined)
if undefined.equal? value
return to_enum(:find_index) unless block_given?
i = 0
each do |*args|
return i if yield(*args)
i += 1
end
else
i = 0
each do
e = Rubinius.single_block_arg
return i if e == value
i += 1
end
end
nil
end
def first(n=undefined)
return __take__(n) unless undefined.equal?(n)
each do
return Rubinius.single_block_arg
end
nil
end
def min(n=nil)
if n.nil?
min = undefined
each do
element = Rubinius.single_block_arg
if undefined.equal? min
min = element
else
comp = block_given? ? yield(element, min) : element <=> min
unless comp
raise ArgumentError, "comparison of #{element.class} with #{min} failed"
end
if Comparable.compare_int(comp) < 0
min = element
end
end
end
undefined.equal?(min) ? nil : min
else
if block_given?
sort { |a, b| yield a, b }.take n
else
sort.take n
end
end
end
def max(n=nil)
if n.nil?
max = undefined
each do
element = Rubinius.single_block_arg
if undefined.equal? max
max = element
else
comp = block_given? ? yield(element, max) : element <=> max
unless comp
raise ArgumentError, "comparison of #{element.class} with #{max} failed"
end
if Comparable.compare_int(comp) > 0
max = element
end
end
end
undefined.equal?(max) ? nil : max
else
if block_given?
sort { |a, b| yield a, b }.reverse.take n
else
sort.reverse.take n
end
end
end
def max_by(n=nil)
return to_enum(:max_by, n) { enumerator_size } unless block_given?
if n.nil?
max_element = nil
max_result = undefined
each do
element = Rubinius.single_block_arg
result = yield element
if undefined.equal?(max_result) or \
Rubinius::Type.coerce_to_comparison(max_result, result) < 0
max_element = element
max_result = result
end
end
max_element
else
sort_by { |element| yield element }.reverse.take n
end
end
def min_by(n=nil)
return to_enum(:min_by, n) { enumerator_size } unless block_given?
if n.nil?
min_element = nil
min_result = undefined
each do
element = Rubinius.single_block_arg
result = yield element
if undefined.equal?(min_result) or \
Rubinius::Type.coerce_to_comparison(min_result, result) > 0
min_element = element
min_result = result
end
end
min_element
else
sort_by { |element| yield element }.take n
end
end
def self.sort_proc
@sort_proc ||= Proc.new do |a, b|
unless ret = a <=> b
raise ArgumentError, "Improper spaceship value"
end
ret
end
end
def minmax(&block)
block = Enumerable.sort_proc unless block
first_time = true
min, max = nil
each do
element = Rubinius.single_block_arg
if first_time
min = max = element
first_time = false
else
unless min_cmp = block.call(min, element)
raise ArgumentError, "comparison failed"
end
min = element if min_cmp > 0
unless max_cmp = block.call(max, element)
raise ArgumentError, "comparison failed"
end
max = element if max_cmp < 0
end
end
[min, max]
end
def minmax_by(&block)
return to_enum(:minmax_by) { enumerator_size } unless block_given?
min_element = nil
min_result = undefined
max_element = nil
max_result = undefined
each do
element = Rubinius.single_block_arg
result = yield element
if undefined.equal?(min_result) or \
Rubinius::Type.coerce_to_comparison(min_result, result) > 0
min_element = element
min_result = result
end
if undefined.equal?(max_result) or \
Rubinius::Type.coerce_to_comparison(max_result, result) < 0
max_element = element
max_result = result
end
end
[min_element, max_element]
end
def none?
if block_given?
each { |*element| return false if yield(*element) }
else
each { return false if Rubinius.single_block_arg }
end
return true
end
def one?
found_one = false
if block_given?
each do |*element|
if yield(*element)
return false if found_one
found_one = true
end
end
else
each do
if Rubinius.single_block_arg
return false if found_one
found_one = true
end
end
end
found_one
end
def partition
return to_enum(:partition) { enumerator_size } unless block_given?
left = []
right = []
each do
element = Rubinius.single_block_arg
yield(element) ? left.push(element) : right.push(element)
end
return [left, right]
end
def reject
return to_enum(:reject) { enumerator_size } unless block_given?
ary = []
each do
element = Rubinius.single_block_arg
ary << element unless yield(element)
end
ary
end
def reverse_each(&block)
return to_enum(:reverse_each) { enumerator_size } unless block_given?
# There is no other way then to convert to an array first... see 1.9's source.
to_a.reverse_each(&block)
self
end
def take(n)
n = Rubinius::Type.coerce_to_collection_index n
raise ArgumentError, "attempt to take negative size: #{n}" if n < 0
array = []
unless n <= 0
each do
element = Rubinius.single_block_arg
array << element
break if array.size >= n
end
end
array
end
alias_method :__take__, :take
private :__take__
def take_while
return to_enum(:take_while) unless block_given?
array = []
each do |element|
return array unless yield(element)
array << element
end
array
end
def include?(obj)
each { return true if Rubinius.single_block_arg == obj }
false
end
alias_method :member?, :include?
end