calog/vendor/mruby/mrbgems/mruby-array-ext/mrblib/array.rb
2026-07-04 20:25:00 -05:00

762 lines
21 KiB
Ruby
Vendored

class Array
##
# call-seq:
# ary.uniq! -> ary or nil
# ary.uniq! { |item| ... } -> ary or nil
#
# Removes duplicate elements from `self`.
# Returns `nil` if no changes are made (that is, no
# duplicates are found).
#
# a = [ "a", "a", "b", "b", "c" ]
# a.uniq! #=> ["a", "b", "c"]
# b = [ "a", "b", "c" ]
# b.uniq! #=> nil
# c = [["student","sam"], ["student","george"], ["teacher","matz"]]
# c.uniq! { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
#
def uniq!(&block)
if block
hash = {}
result = []
self.each do |val|
key = block.call(val)
unless hash.key?(key)
hash[key] = true
result << val
end
end
if result.size == self.size
nil
else
self.replace(result)
end
else
__uniq!
end
end
##
# call-seq:
# ary.uniq -> new_ary
# ary.uniq { |item| ... } -> new_ary
#
# Returns a new array by removing duplicate values in `self`.
#
# a = [ "a", "a", "b", "b", "c" ]
# a.uniq #=> ["a", "b", "c"]
#
# b = [["student","sam"], ["student","george"], ["teacher","matz"]]
# b.uniq { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
#
def uniq(&block)
if block
ary = self.dup
ary.uniq!(&block)
ary
else
__uniq
end
end
# for efficiency
def reverse_each(&block)
return to_enum(:reverse_each) unless block
i = self.size - 1
while i>=0
block.call(self[i])
i -= 1
end
self
end
##
# call-seq:
# ary.fetch(index) -> obj
# ary.fetch(index, default) -> obj
# ary.fetch(index) { |index| block } -> obj
#
# Tries to return the element at position `index`, but throws an IndexError
# exception if the referenced `index` lies outside of the array bounds. This
# error can be prevented by supplying a second argument, which will act as a
# `default` value.
#
# Alternatively, if a block is given it will only be executed when an
# invalid `index` is referenced.
#
# Negative values of `index` count from the end of the array.
#
# a = [ 11, 22, 33, 44 ]
# a.fetch(1) #=> 22
# a.fetch(-1) #=> 44
# a.fetch(4, 'cat') #=> "cat"
# a.fetch(100) { |i| puts "#{i} is out of bounds" }
# #=> "100 is out of bounds"
#
def fetch(n, ifnone=NONE, &block)
#warn "block supersedes default value argument" if !n.nil? && ifnone != NONE && block
if block
# Block case: use shared index helper + Ruby block handling
normalized_index = __normalize_index(n)
if normalized_index
self[normalized_index]
else
block.call(n)
end
else
# Fast C implementation for non-block cases
__fetch(n, ifnone, NONE)
end
end
##
# call-seq:
# ary.fill(obj) -> ary
# ary.fill(obj, start [, length]) -> ary
# ary.fill(obj, range ) -> ary
# ary.fill { |index| block } -> ary
# ary.fill(start [, length] ) { |index| block } -> ary
# ary.fill(range) { |index| block } -> ary
#
# The first three forms set the selected elements of `self` (which
# may be the entire array) to `obj`.
#
# A `start` of `nil` is equivalent to zero.
#
# A `length` of `nil` is equivalent to the length of the array.
#
# The last three forms fill the array with the value of the given block,
# which is passed the absolute index of each element to be filled.
#
# Negative values of `start` count from the end of the array, where +-1+ is
# the last element.
#
# a = [ "a", "b", "c", "d" ]
# a.fill("x") #=> ["x", "x", "x", "x"]
# a.fill("w", -1) #=> ["x", "x", "x", "w"]
# a.fill("z", 2, 2) #=> ["x", "x", "z", "z"]
# a.fill("y", 0..1) #=> ["y", "y", "z", "z"]
# a.fill { |i| i*i } #=> [0, 1, 4, 9]
# a.fill(-2) { |i| i*i*i } #=> [0, 1, 8, 27]
# a.fill(1, 2) { |i| i+1 } #=> [0, 2, 3, 27]
# a.fill(0..1) { |i| i+1 } #=> [1, 2, 3, 27]
#
def fill(arg0=nil, arg1=nil, arg2=nil, &block)
if arg0.nil? && arg1.nil? && arg2.nil? && !block
raise ArgumentError, "wrong number of arguments (given 0, expected 1..3)"
end
# Use shared C argument parser for all cases
start, length = __fill_parse_arg(arg0, arg1, arg2, &block)
if block
# Block-based filling in Ruby
i = start
while i < start + length
self[i] = block.call(i)
i += 1
end
else
# Use fast C implementation for value filling
__fill_exec(start, length, arg0)
end
self
end
##
# call-seq:
# ary.delete_if { |item| block } -> ary
# ary.delete_if -> Enumerator
#
# Deletes every element of `self` for which block evaluates to `true`.
#
# The array is changed instantly every time the block is called, not after
# the iteration is over.
#
# See also Array#reject!
#
# If no block is given, an Enumerator is returned instead.
#
# scores = [ 97, 42, 75 ]
# scores.delete_if {|score| score < 80 } #=> [97]
def delete_if(&block)
return to_enum(:delete_if) unless block
result = []
idx = 0
len = size
while idx < len
elem = self[idx]
result << elem unless block.call(elem)
idx += 1
end
self.replace(result)
end
##
# call-seq:
# ary.reject! { |item| block } -> ary or nil
# ary.reject! -> Enumerator
#
# Equivalent to Array#delete_if, deleting elements from `self` for which the
# block evaluates to `true`, but returns `nil` if no changes were made.
#
# The array is changed instantly every time the block is called, not after
# the iteration is over.
#
# See also Enumerable#reject and Array#delete_if.
#
# If no block is given, an Enumerator is returned instead.
def reject!(&block)
return to_enum(:reject!) unless block
result = []
idx = 0
len = size
while idx < len
elem = self[idx]
result << elem unless block.call(elem)
idx += 1
end
return nil if len == result.size
self.replace(result)
end
##
# call-seq:
# ary.bsearch {|x| block } -> elem
#
# By using binary search, finds a value from this array which meets
# the given condition in O(log n) where n is the size of the array.
#
# You can use this method in two use cases: a find-minimum mode and
# a find-any mode. In either case, the elements of the array must be
# monotone (or sorted) with respect to the block.
#
# In find-minimum mode (this is a good choice for typical use case),
# the block must return true or false, and there must be an index i
# (0 <= i <= ary.size) so that:
#
# - the block returns false for any element whose index is less than
# i, and
# - the block returns true for any element whose index is greater
# than or equal to i.
#
# This method returns the i-th element. If i is equal to ary.size,
# it returns nil.
#
# ary = [0, 4, 7, 10, 12]
# ary.bsearch {|x| x >= 4 } #=> 4
# ary.bsearch {|x| x >= 6 } #=> 7
# ary.bsearch {|x| x >= -1 } #=> 0
# ary.bsearch {|x| x >= 100 } #=> nil
#
# In find-any mode (this behaves like libc's bsearch(3)), the block
# must return a number, and there must be two indices i and j
# (0 <= i <= j <= ary.size) so that:
#
# - the block returns a positive number for ary[k] if 0 <= k < i,
# - the block returns zero for ary[k] if i <= k < j, and
# - the block returns a negative number for ary[k] if
# j <= k < ary.size.
#
# Under this condition, this method returns any element whose index
# is within i...j. If i is equal to j (i.e., there is no element
# that satisfies the block), this method returns nil.
#
# ary = [0, 4, 7, 10, 12]
# # try to find v such that 4 <= v < 8
# ary.bsearch {|x| 1 - (x / 4).truncate } #=> 4 or 7
# # try to find v such that 8 <= v < 10
# ary.bsearch {|x| 4 - (x / 2).truncate } #=> nil
#
# You must not mix the two modes at a time; the block must always
# return either true/false, or always return a number. It is
# undefined which value is actually picked up at each iteration.
def bsearch(&block)
return to_enum(:bsearch) unless block
if idx = bsearch_index(&block)
self[idx]
else
nil
end
end
##
# call-seq:
# ary.bsearch_index {|x| block } -> int or nil
#
# By using binary search, finds an index of a value from this array which
# meets the given condition in O(log n) where n is the size of the array.
#
# It supports two modes, depending on the nature of the block and they are
# exactly the same as in the case of #bsearch method with the only difference
# being that this method returns the index of the element instead of the
# element itself. For more details consult the documentation for #bsearch.
def bsearch_index(&block)
return to_enum(:bsearch_index) unless block
low = 0
high = size
satisfied = false
while low < high
mid = ((low+high)/2).truncate
res = block.call(self[mid])
case res
when 0 # find-any mode: Found!
return mid
when Numeric # find-any mode: Continue...
in_lower_half = res < 0
when true # find-min mode
in_lower_half = true
satisfied = true
when false, nil # find-min mode
in_lower_half = false
else
raise TypeError, 'invalid block result (must be numeric, true, false or nil)'
end
if in_lower_half
high = mid
else
low = mid + 1
end
end
satisfied ? low : nil
end
##
# call-seq:
# ary.keep_if { |item| block } -> ary
# ary.keep_if -> Enumerator
#
# Deletes every element of `self` for which the given block evaluates to
# `false`.
#
# See also Array#select!
#
# If no block is given, an Enumerator is returned instead.
#
# a = [1, 2, 3, 4, 5]
# a.keep_if { |val| val > 3 } #=> [4, 5]
def keep_if(&block)
return to_enum(:keep_if) unless block
result = []
idx = 0
len = size
while idx < len
elem = self[idx]
result << elem if block.call(elem)
idx += 1
end
self.replace(result)
end
##
# call-seq:
# ary.select! {|item| block } -> ary or nil
# ary.select! -> Enumerator
#
# Invokes the given block passing in successive elements from `self`,
# deleting elements for which the block returns a `false` value.
#
# If changes were made, it will return `self`, otherwise it returns `nil`.
#
# See also Array#keep_if
#
# If no block is given, an Enumerator is returned instead.
def select!(&block)
return to_enum(:select!) unless block
result = []
idx = 0
len = size
while idx < len
elem = self[idx]
result << elem if block.call(elem)
idx += 1
end
return nil if len == result.size
self.replace(result)
end
##
# call-seq:
# ary.dig(idx, ...) -> object
#
# Extracts the nested value specified by the sequence of *idx*
# objects by calling `dig` at each step, returning `nil` if any
# intermediate step is `nil`.
#
def dig(idx,*args)
idx = idx.__to_int
n = self[idx]
if args.size > 0
n&.dig(*args)
else
n
end
end
##
# call-seq:
# ary.permutation { |p| block } -> ary
# ary.permutation -> Enumerator
# ary.permutation(n) { |p| block } -> ary
# ary.permutation(n) -> Enumerator
#
# When invoked with a block, yield all permutations of length `n` of the
# elements of the array, then return the array itself.
#
# If `n` is not specified, yield all permutations of all elements.
#
# The implementation makes no guarantees about the order in which the
# permutations are yielded.
#
# If no block is given, an Enumerator is returned instead.
#
# Examples:
#
# a = [1, 2, 3]
# a.permutation.to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
# a.permutation(1).to_a #=> [[1],[2],[3]]
# a.permutation(2).to_a #=> [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
# a.permutation(3).to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
# a.permutation(0).to_a #=> [[]] # one permutation of length 0
# a.permutation(4).to_a #=> [] # no permutations of length 4
def permutation(n=self.size, &block)
n = n.__to_int
return to_enum(:permutation, n) unless block
size = self.size
if n == 0
yield []
elsif 0 < n && n <= size
i = 0
while i<size
result = [self[i]]
if n-1 > 0
ary = self[0...i] + self[i+1..-1]
ary.permutation(n-1) do |c|
yield result + c
end
else
yield result
end
i += 1
end
end
self
end
##
# call-seq:
# ary.combination(n) { |c| block } -> ary
# ary.combination(n) -> Enumerator
#
# When invoked with a block, yields all combinations of length `n` of elements
# from the array and then returns the array itself.
#
# The implementation makes no guarantees about the order in which the
# combinations are yielded.
#
# If no block is given, an Enumerator is returned instead.
#
# Examples:
#
# a = [1, 2, 3, 4]
# a.combination(1).to_a #=> [[1],[2],[3],[4]]
# a.combination(2).to_a #=> [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
# a.combination(3).to_a #=> [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
# a.combination(4).to_a #=> [[1,2,3,4]]
# a.combination(0).to_a #=> [[]] # one combination of length 0
# a.combination(5).to_a #=> [] # no combinations of length 5
def combination(n, &block)
n = n.__to_int
return to_enum(:combination, n) unless block
size = self.size
if n == 0
yield []
elsif n == 1
i = 0
while i<size
yield [self[i]]
i += 1
end
elsif n <= size
i = 0
while i<size
result = [self[i]]
self[i+1..-1].combination(n-1) do |c|
yield result + c
end
i += 1
end
end
self
end
##
# call-seq:
# ary.transpose -> new_ary
#
# Assumes that self is an array of arrays and transposes the rows and columns.
#
# If the length of the subarrays don't match, an IndexError is raised.
#
# Examples:
#
# a = [[1,2], [3,4], [5,6]]
# a.transpose #=> [[1, 3, 5], [2, 4, 6]]
def transpose
return [] if empty?
column_count = nil
self.each do |row|
raise TypeError unless row.is_a?(Array)
column_count ||= row.size
raise IndexError, 'element size differs' unless column_count == row.size
end
Array.new(column_count) do |column_index|
self.map {|row| row[column_index] }
end
end
##
# call-seq:
# ary.to_h -> Hash
# ary.to_h{|item| ... } -> Hash
#
# Returns the result of interpreting *array* as an array of
# `[key, value]` pairs. If a block is given, it should
# return `[key, value]` pairs to construct a hash.
#
# [[:foo, :bar], [1, 2]].to_h
# # => {:foo => :bar, 1 => 2}
# [1, 2].to_h{|x| [x, x*2]}
# # => {1 => 2, 2 => 4}
#
def to_h(&blk)
h = {}
self.each do |v|
v = blk.call(v) if blk
raise TypeError, "wrong element type #{v.class}" unless Array === v
raise ArgumentError, "wrong array length (expected 2, was #{v.length})" unless v.length == 2
h[v[0]] = v[1]
end
h
end
alias append push
alias prepend unshift
alias filter! select!
##
# call-seq:
# ary.fetch_values(idx, ...) -> array
# ary.fetch_values(idx, ...) { |i| block } -> array
#
# Returns an array containing the values associated with the given indexes.
# but also raises `IndexError` when one of indexes can't be found.
# Also see `Array#values_at` and `Array#fetch`.
#
# a = ["cat", "dog", "cow"]
#
# a.fetch_values(2, 0) #=> ["cow", "cat"]
# a.fetch_values(2, 5) # raises KeyError
# a.fetch_values(2, 5) {|i| "BIRD" } #=> ["cow", "BIRD"]
#
def fetch_values(*idx, &block)
if block
idx.map do |i|
self.fetch(i, &block)
end
else
# Fast path: use C implementation for non-block cases
idx.map do |i|
__fetch(i, NONE, NONE)
end
end
end
##
# call-seq:
# ary.product(*arys) -> array
# ary.product(*arys) { |item| ... } -> self
def product(*arys, &block)
gen = __product_generate(arys, &block)
return gen unless block
if gen
while group = __product_next(arys, gen)
yield group
end
end
self
end
##
# call-seq:
# ary.repeated_combination(n) { |combination| ... } -> ary
# ary.repeated_combination(n) -> Enumerator
#
# When invoked with a block, yields all length `n` combinations of elements
# from the array, with replacement, and then returns the array itself.
#
# This means that, unlike `combination`, elements can be chosen more than once.
#
# The implementation makes no guarantees about the order in which the
# combinations are yielded.
#
# If no block is given, an Enumerator is returned instead.
#
# Examples:
#
# a = [1, 2, 3]
# a.repeated_combination(2).to_a #=> [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
def repeated_combination(n, &block)
raise TypeError, "no implicit conversion into Integer" unless 0 <=> n
return to_enum(:repeated_combination, n) unless block
__repeated_combination(n, false, &block)
end
##
# call-seq:
# ary.repeated_permutation(n) { |permutation| ... } -> ary
# ary.repeated_permutation(n) -> Enumerator
#
# When invoked with a block, yields all length `n` permutations of elements
# from the array, with replacement, and then returns the array itself.
#
# This means that, unlike `permutation`, elements can be chosen more than once.
#
# The implementation makes no guarantees about the order in which the
# permutations are yielded.
#
# If no block is given, an Enumerator is returned instead.
#
# Examples:
#
# a = [1, 2]
# a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]]
def repeated_permutation(n, &block)
n = n.__to_int
raise TypeError, "no implicit conversion into Integer" unless 0 <=> n
return to_enum(:repeated_permutation, n) unless block
__repeated_combination(n, true, &block)
end
def __repeated_combination(n, permutation, &block)
n = n.__to_int
case n
when 0
yield []
when 1
# Keep fast Ruby path for n=1
i = 0
while i < self.size
yield [self[i]]
i += 1
end
else
if n > 0
# Use C iterator for complex cases
state = __combination_init(n, permutation)
while (indices = __combination_next(state))
# Convert indices to elements in Ruby
tmp = [nil] * n
i = 0
while i < n
tmp[i] = self[indices[i]]
i += 1
end
yield tmp
end
end
end
self
end
##
# call-seq:
# ary.find(ifnone = nil) { |elem| block } -> obj or nil
# ary.find(ifnone = nil) -> Enumerator
#
# Returns the first element for which the block returns a true value.
# If no element matches and +ifnone+ is given, calls +ifnone+ and
# returns its result. Otherwise returns +nil+.
#
# This is an optimized version of Enumerable#find for arrays.
#
# [1, 2, 3, 4].find { |x| x > 2 } #=> 3
# [1, 2, 3, 4].find { |x| x > 10 } #=> nil
# [1, 2, 3, 4].find(->{0}) { |x| x > 10 } #=> 0
#
def find(ifnone=nil, &block)
return to_enum(:find, ifnone) unless block
idx = 0
len = self.size
while idx < len
elem = self[idx]
return elem if block.call(elem)
idx += 1
end
ifnone&.call
end
##
# call-seq:
# ary.rfind(ifnone = nil) { |elem| block } -> obj or nil
# ary.rfind(ifnone = nil) -> Enumerator
#
# Returns the last element for which the block returns a true value.
# Searches from the end of the array to the beginning.
# If no element matches and +ifnone+ is given, calls +ifnone+ and
# returns its result. Otherwise returns +nil+.
#
# [1, 2, 3, 4, 3].rfind { |x| x == 3 } #=> 3 (the last one)
# [1, 2, 3, 4].rfind { |x| x > 2 } #=> 4
# [1, 2, 3, 4].rfind { |x| x > 10 } #=> nil
# [1, 2, 3, 4].rfind(->{0}) { |x| x > 10 } #=> 0
#
def rfind(ifnone=nil, &block)
return to_enum(:rfind, ifnone) unless block
idx = self.size - 1
while idx >= 0
elem = self[idx]
return elem if block.call(elem)
idx -= 1
end
ifnone&.call
end
end