summaryrefslogtreecommitdiffhomepage
path: root/mrbgems/mruby-array-ext
diff options
context:
space:
mode:
Diffstat (limited to 'mrbgems/mruby-array-ext')
-rw-r--r--mrbgems/mruby-array-ext/mrbgem.rake2
-rw-r--r--mrbgems/mruby-array-ext/mrblib/array.rb591
-rw-r--r--mrbgems/mruby-array-ext/src/array.c282
-rw-r--r--mrbgems/mruby-array-ext/test/array.rb184
4 files changed, 804 insertions, 255 deletions
diff --git a/mrbgems/mruby-array-ext/mrbgem.rake b/mrbgems/mruby-array-ext/mrbgem.rake
index e4b5938c7..882caf1ab 100644
--- a/mrbgems/mruby-array-ext/mrbgem.rake
+++ b/mrbgems/mruby-array-ext/mrbgem.rake
@@ -1,5 +1,5 @@
MRuby::Gem::Specification.new('mruby-array-ext') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
- spec.summary = 'extensional Array class'
+ spec.summary = 'Array class extension'
end
diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb
index fd80fa0bb..7520b932f 100644
--- a/mrbgems/mruby-array-ext/mrblib/array.rb
+++ b/mrbgems/mruby-array-ext/mrblib/array.rb
@@ -16,23 +16,19 @@ class Array
# c.uniq! { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
#
def uniq!(&block)
- ary = self.dup
- result = []
+ hash = {}
if block
- hash = {}
- while ary.size > 0
- val = ary.shift
+ self.each do |val|
key = block.call(val)
- hash[key] = val unless hash.has_key?(key)
- end
- hash.each_value do |value|
- result << value
+ hash[key] = val unless hash.key?(key)
end
+ result = hash.values
else
- while ary.size > 0
- result << ary.shift
- ary.delete(result.last)
+ hash = {}
+ self.each do |val|
+ hash[val] = val
end
+ result = hash.keys
end
if result.size == self.size
nil
@@ -55,12 +51,8 @@ class Array
# b.uniq { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
#
def uniq(&block)
- ary = self.dup
- if block
- ary.uniq!(&block)
- else
- ary.uniq!
- end
+ ary = self[0..-1]
+ ary.uniq!(&block)
ary
end
@@ -80,13 +72,40 @@ class Array
hash = {}
array = []
- elem.each { |x| hash[x] = true }
- self.each { |x| array << x unless hash[x] }
+ idx = 0
+ len = elem.size
+ while idx < len
+ hash[elem[idx]] = true
+ idx += 1
+ end
+ idx = 0
+ len = size
+ while idx < len
+ v = self[idx]
+ array << v unless hash[v]
+ idx += 1
+ end
array
end
##
# call-seq:
+ # ary.difference(other_ary1, other_ary2, ...) -> new_ary
+ #
+ # Returns a new array that is a copy of the original array, removing all
+ # occurrences of any item that also appear in +other_ary+. The order is
+ # preserved from the original array.
+ #
+ def difference(*args)
+ ary = self
+ args.each do |x|
+ ary = ary - x
+ end
+ ary
+ end
+
+ ##
+ # call-seq:
# ary | other_ary -> new_ary
#
# Set Union---Returns a new array by joining this array with
@@ -104,6 +123,25 @@ class Array
##
# call-seq:
+ # ary.union(other_ary,...) -> new_ary
+ #
+ # Set Union---Returns a new array by joining this array with
+ # <i>other_ary</i>, removing duplicates.
+ #
+ # ["a", "b", "c"].union(["c", "d", "a"], ["a", "c", "e"])
+ # #=> ["a", "b", "c", "d", "e"]
+ #
+ def union(*args)
+ ary = self.dup
+ args.each do |x|
+ ary.concat(x)
+ ary.uniq!
+ end
+ ary
+ end
+
+ ##
+ # call-seq:
# ary & other_ary -> new_ary
#
# Set Intersection---Returns a new array
@@ -112,22 +150,90 @@ class Array
# [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
#
def &(elem)
- raise TypeError, "can't convert #{elem.class} into Array" unless elem.class == Array
+ raise TypeError, "cannot convert #{elem.class} into Array" unless elem.class == Array
hash = {}
array = []
- elem.each{|v| hash[v] = true }
- self.each do |v|
+ idx = 0
+ len = elem.size
+ while idx < len
+ hash[elem[idx]] = true
+ idx += 1
+ end
+ idx = 0
+ len = size
+ while idx < len
+ v = self[idx]
if hash[v]
array << v
hash.delete v
end
+ idx += 1
end
array
end
##
# call-seq:
+ # ary.intersection(other_ary,...) -> new_ary
+ #
+ # Set Intersection---Returns a new array containing elements common to
+ # this array and <i>other_ary</i>s, removing duplicates. The order is
+ # preserved from the original array.
+ #
+ # [1, 2, 3].intersection([3, 4, 1], [1, 3, 5]) #=> [1, 3]
+ #
+ def intersection(*args)
+ ary = self
+ args.each do |x|
+ ary = ary & x
+ end
+ ary
+ end
+
+ ##
+ # call-seq:
+ # ary.intersect?(other_ary) -> true or false
+ #
+ # Returns +true+ if the array and +other_ary+ have at least one element in
+ # common, otherwise returns +false+.
+ #
+ # a = [ 1, 2, 3 ]
+ # b = [ 3, 4, 5 ]
+ # c = [ 5, 6, 7 ]
+ # a.intersect?(b) #=> true
+ # a.intersect?(c) #=> false
+ def intersect?(ary)
+ raise TypeError, "cannot convert #{ary.class} into Array" unless ary.class == Array
+
+ hash = {}
+ if self.length > ary.length
+ shorter = ary
+ longer = self
+ else
+ shorter = self
+ longer = ary
+ end
+ idx = 0
+ len = shorter.size
+ while idx < len
+ hash[shorter[idx]] = true
+ idx += 1
+ end
+ idx = 0
+ len = size
+ while idx < len
+ v = longer[idx]
+ if hash[v]
+ return true
+ end
+ idx += 1
+ end
+ false
+ end
+
+ ##
+ # call-seq:
# ary.flatten -> new_ary
# ary.flatten(level) -> new_ary
#
@@ -144,15 +250,9 @@ class Array
# a.flatten(1) #=> [1, 2, 3, [4, 5]]
#
def flatten(depth=nil)
- ar = []
- self.each do |e|
- if e.is_a?(Array) && (depth.nil? || depth > 0)
- ar += e.flatten(depth.nil? ? nil : depth - 1)
- else
- ar << e
- end
- end
- ar
+ res = Array.new(self)
+ res.flatten! depth
+ res
end
##
@@ -175,13 +275,17 @@ class Array
def flatten!(depth=nil)
modified = false
ar = []
- self.each do |e|
+ idx = 0
+ len = size
+ while idx < len
+ e = self[idx]
if e.is_a?(Array) && (depth.nil? || depth > 0)
ar += e.flatten(depth.nil? ? nil : depth - 1)
modified = true
else
ar << e
end
+ idx += 1
end
if modified
self.replace(ar)
@@ -190,44 +294,9 @@ class Array
end
end
- ##
- # call-seq:
- # ary.compact -> new_ary
- #
- # Returns a copy of +self+ with all +nil+ elements removed.
- #
- # [ "a", nil, "b", nil, "c", nil ].compact
- # #=> [ "a", "b", "c" ]
- #
- def compact
- result = self.dup
- result.compact!
- result
- end
-
- ##
- # call-seq:
- # ary.compact! -> ary or nil
- #
- # Removes +nil+ elements from the array.
- # Returns +nil+ if no changes were made, otherwise returns
- # <i>ary</i>.
- #
- # [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ]
- # [ "a", "b", "c" ].compact! #=> nil
- #
- def compact!
- result = self.select { |e| e != nil }
- if result.size == self.size
- nil
- else
- self.replace(result)
- end
- end
-
# for efficiency
def reverse_each(&block)
- return to_enum :reverse_each unless block_given?
+ return to_enum :reverse_each unless block
i = self.size - 1
while i>=0
@@ -237,7 +306,6 @@ class Array
self
end
- NONE=Object.new
##
# call-seq:
# ary.fetch(index) -> obj
@@ -250,8 +318,9 @@ class Array
# +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.
+ # invalid +index+ is referenced.
+ #
+ # Negative values of +index+ count from the end of the array.
#
# a = [ 11, 22, 33, 44 ]
# a.fetch(1) #=> 22
@@ -261,8 +330,8 @@ class Array
# #=> "100 is out of bounds"
#
- def fetch(n=nil, ifnone=NONE, &block)
- warn "block supersedes default value argument" if n != nil && ifnone != NONE && block
+ def fetch(n, ifnone=NONE, &block)
+ #warn "block supersedes default value argument" if !n.nil? && ifnone != NONE && block
idx = n
if idx < 0
@@ -312,51 +381,50 @@ class Array
#
def fill(arg0=nil, arg1=nil, arg2=nil, &block)
- if arg0 == nil && arg1 == nil && arg2 == nil && !block
+ if arg0.nil? && arg1.nil? && arg2.nil? && !block
raise ArgumentError, "wrong number of arguments (0 for 1..3)"
end
beg = len = 0
- ary = []
if block
- if arg0 == nil && arg1 == nil && arg2 == nil
+ if arg0.nil? && arg1.nil? && arg2.nil?
# ary.fill { |index| block } -> ary
beg = 0
len = self.size
- elsif arg0 != nil && arg0.kind_of?(Range)
+ elsif !arg0.nil? && arg0.kind_of?(Range)
# ary.fill(range) { |index| block } -> ary
beg = arg0.begin
beg += self.size if beg < 0
len = arg0.end
len += self.size if len < 0
len += 1 unless arg0.exclude_end?
- elsif arg0 != nil
+ elsif !arg0.nil?
# ary.fill(start [, length] ) { |index| block } -> ary
beg = arg0
beg += self.size if beg < 0
- if arg1 == nil
+ if arg1.nil?
len = self.size
else
len = arg0 + arg1
end
end
else
- if arg0 != nil && arg1 == nil && arg2 == nil
+ if !arg0.nil? && arg1.nil? && arg2.nil?
# ary.fill(obj) -> ary
beg = 0
len = self.size
- elsif arg0 != nil && arg1 != nil && arg1.kind_of?(Range)
+ elsif !arg0.nil? && !arg1.nil? && arg1.kind_of?(Range)
# ary.fill(obj, range ) -> ary
beg = arg1.begin
beg += self.size if beg < 0
len = arg1.end
len += self.size if len < 0
len += 1 unless arg1.exclude_end?
- elsif arg0 != nil && arg1 != nil
+ elsif !arg0.nil? && !arg1.nil?
# ary.fill(obj, start [, length]) -> ary
beg = arg1
beg += self.size if beg < 0
- if arg2 == nil
+ if arg2.nil?
len = self.size
else
len = beg + arg2
@@ -381,57 +449,6 @@ class Array
##
# call-seq:
- # ary.rotate(count=1) -> new_ary
- #
- # Returns a new array by rotating +self+ so that the element at +count+ is
- # the first element of the new array.
- #
- # If +count+ is negative then it rotates in the opposite direction, starting
- # from the end of +self+ where +-1+ is the last element.
- #
- # a = [ "a", "b", "c", "d" ]
- # a.rotate #=> ["b", "c", "d", "a"]
- # a #=> ["a", "b", "c", "d"]
- # a.rotate(2) #=> ["c", "d", "a", "b"]
- # a.rotate(-3) #=> ["b", "c", "d", "a"]
-
- def rotate(count=1)
- ary = []
- len = self.length
-
- if len > 0
- idx = (count < 0) ? (len - (~count % len) - 1) : (count % len) # rotate count
- len.times do
- ary << self[idx]
- idx += 1
- idx = 0 if idx > len-1
- end
- end
- ary
- end
-
- ##
- # call-seq:
- # ary.rotate!(count=1) -> ary
- #
- # Rotates +self+ in place so that the element at +count+ comes first, and
- # returns +self+.
- #
- # If +count+ is negative then it rotates in the opposite direction, starting
- # from the end of the array where +-1+ is the last element.
- #
- # a = [ "a", "b", "c", "d" ]
- # a.rotate! #=> ["b", "c", "d", "a"]
- # a #=> ["b", "c", "d", "a"]
- # a.rotate!(2) #=> ["d", "a", "b", "c"]
- # a.rotate!(-3) #=> ["a", "b", "c", "d"]
-
- def rotate!(count=1)
- self.replace(self.rotate(count))
- end
-
- ##
- # call-seq:
# ary.delete_if { |item| block } -> ary
# ary.delete_if -> Enumerator
#
@@ -448,7 +465,7 @@ class Array
# scores.delete_if {|score| score < 80 } #=> [97]
def delete_if(&block)
- return to_enum :delete_if unless block_given?
+ return to_enum :delete_if unless block
idx = 0
while idx < self.size do
@@ -477,7 +494,7 @@ class Array
# If no block is given, an Enumerator is returned instead.
def reject!(&block)
- return to_enum :reject! unless block_given?
+ return to_enum :reject! unless block
len = self.size
idx = 0
@@ -567,64 +584,60 @@ class Array
# undefined which value is actually picked up at each iteration.
def bsearch(&block)
- return to_enum :bsearch unless block_given?
+ return to_enum :bsearch unless block
- low = 0
- high = self.size
- satisfied = false
- while low < high
- mid = low + ((high - low) / 2).truncate
- val = self[mid]
- v = block.call(val)
- if v.is_a?(Integer)
- return val if v == 0
- smaller = v < 0
- elsif v == true
- satisfied = true
- smaller = true
- elsif v == false || v == nil
- smaller = false
- end
- if smaller
- high = mid
- else
- low = mid + 1
- end
+ if idx = bsearch_index(&block)
+ self[idx]
+ else
+ nil
end
- return nil if low == self.size
- return nil unless satisfied
- self[low]
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.
+ # ary.bsearch_index {|x| block } -> int or nil
#
- # See also Array#reject!
- #
- # If no block is given, an Enumerator is returned instead.
+ # 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.
#
- # scores = [ 97, 42, 75 ]
- # scores.delete_if {|score| score < 80 } #=> [97]
+ # 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 delete_if(&block)
- return to_enum :delete_if unless block_given?
+ def bsearch_index(&block)
+ return to_enum :bsearch_index unless block
- idx = 0
- while idx < self.size do
- if block.call(self[idx])
- self.delete_at(idx)
+ 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
- idx += 1
+ 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
- self
+
+ satisfied ? low : nil
end
##
@@ -643,10 +656,9 @@ class Array
# a.keep_if { |val| val > 3 } #=> [4, 5]
def keep_if(&block)
- return to_enum :keep_if unless block_given?
+ return to_enum :keep_if unless block
idx = 0
- len = self.size
while idx < self.size do
if block.call(self[idx])
idx += 1
@@ -672,13 +684,216 @@ class Array
# If no block is given, an Enumerator is returned instead.
def select!(&block)
- return to_enum :select! unless block_given?
+ return to_enum :select! unless block
result = []
- self.each do |x|
- result << x if block.call(x)
+ idx = 0
+ len = size
+ while idx < len
+ elem = self[idx]
+ result << elem if block.call(elem)
+ idx += 1
end
- return nil if self.size == result.size
+ return nil if len == result.size
self.replace(result)
end
+
+ ##
+ # call-seq:
+ # ary.index(val) -> int or nil
+ # ary.index {|item| block } -> int or nil
+ #
+ # Returns the _index_ of the first object in +ary+ such that the object is
+ # <code>==</code> to +obj+.
+ #
+ # If a block is given instead of an argument, returns the _index_ of the
+ # first object for which the block returns +true+. Returns +nil+ if no
+ # match is found.
+ #
+ # ISO 15.2.12.5.14
+ def index(val=NONE, &block)
+ return to_enum(:find_index, val) if !block && val == NONE
+
+ if block
+ idx = 0
+ len = size
+ while idx < len
+ return idx if block.call self[idx]
+ idx += 1
+ end
+ else
+ return self.__ary_index(val)
+ end
+ nil
+ end
+
+ ##
+ # call-seq:
+ # ary.dig(idx, ...) -> object
+ #
+ # Extracts the nested value specified by the sequence of <i>idx</i>
+ # objects by calling +dig+ at each step, returning +nil+ if any
+ # intermediate step is +nil+.
+ #
+ def dig(idx,*args)
+ 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)
+ 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)
+ 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 <i>array</i> as an array of
+ # <tt>[key, value]</tt> pairs. If a block is given, it should
+ # return <tt>[key, value]</tt> 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!
end
diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c
index d69f0ac44..d97778642 100644
--- a/mrbgems/mruby-array-ext/src/array.c
+++ b/mrbgems/mruby-array-ext/src/array.c
@@ -1,8 +1,9 @@
-#include "mruby.h"
-#include "mruby/value.h"
-#include "mruby/array.h"
-#include "mruby/range.h"
-#include "mruby/hash.h"
+#include <mruby.h>
+#include <mruby/value.h>
+#include <mruby/array.h>
+#include <mruby/range.h>
+#include <mruby/hash.h>
+#include <mruby/presym.h>
/*
* call-seq:
@@ -28,9 +29,8 @@ static mrb_value
mrb_ary_assoc(mrb_state *mrb, mrb_value ary)
{
mrb_int i;
- mrb_value v, k;
-
- mrb_get_args(mrb, "o", &k);
+ mrb_value v;
+ mrb_value k = mrb_get_arg1(mrb);
for (i = 0; i < RARRAY_LEN(ary); ++i) {
v = mrb_check_array_type(mrb, RARRAY_PTR(ary)[i]);
@@ -59,13 +59,12 @@ static mrb_value
mrb_ary_rassoc(mrb_state *mrb, mrb_value ary)
{
mrb_int i;
- mrb_value v, value;
-
- mrb_get_args(mrb, "o", &value);
+ mrb_value v;
+ mrb_value value = mrb_get_arg1(mrb);
for (i = 0; i < RARRAY_LEN(ary); ++i) {
v = RARRAY_PTR(ary)[i];
- if (mrb_type(v) == MRB_TT_ARRAY &&
+ if (mrb_array_p(v) &&
RARRAY_LEN(v) > 1 &&
mrb_equal(mrb, RARRAY_PTR(v)[1], value))
return v;
@@ -96,56 +95,259 @@ mrb_ary_at(mrb_state *mrb, mrb_value ary)
}
static mrb_value
+ary_ref(mrb_state *mrb, mrb_value ary, mrb_int n)
+{
+ return mrb_ary_entry(ary, n);
+}
+
+static mrb_value
mrb_ary_values_at(mrb_state *mrb, mrb_value self)
{
mrb_int argc;
- mrb_value *argv;
+ const mrb_value *argv;
mrb_get_args(mrb, "*", &argv, &argc);
- return mrb_get_values_at(mrb, self, RARRAY_LEN(self), argc, argv, mrb_ary_ref);
+ return mrb_get_values_at(mrb, self, RARRAY_LEN(self), argc, argv, ary_ref);
}
/*
* call-seq:
- * ary.to_h -> Hash
+ * ary.slice!(index) -> obj or nil
+ * ary.slice!(start, length) -> new_ary or nil
+ * ary.slice!(range) -> new_ary or nil
+ *
+ * Deletes the element(s) given by an +index+ (optionally up to +length+
+ * elements) or by a +range+.
*
- * Returns the result of interpreting <i>aray</i> as an array of
- * <tt>[key, value]</tt> paris.
+ * Returns the deleted object (or objects), or +nil+ if the +index+ is out of
+ * range.
*
- * [[:foo, :bar], [1, 2]].to_h
- * # => {:foo => :bar, 1 => 2}
+ * a = [ "a", "b", "c" ]
+ * a.slice!(1) #=> "b"
+ * a #=> ["a", "c"]
+ * a.slice!(-1) #=> "c"
+ * a #=> ["a"]
+ * a.slice!(100) #=> nil
+ * a #=> ["a"]
*/
static mrb_value
-mrb_ary_to_h(mrb_state *mrb, mrb_value ary)
+mrb_ary_slice_bang(mrb_state *mrb, mrb_value self)
{
- mrb_int i;
- mrb_value v, hash;
+ struct RArray *a = mrb_ary_ptr(self);
+ mrb_int i, j, k, len, alen;
+ mrb_value val;
+ mrb_value *ptr;
+ mrb_value ary;
- hash = mrb_hash_new_capa(mrb, 0);
+ mrb_ary_modify(mrb, a);
- for (i = 0; i < RARRAY_LEN(ary); ++i) {
- v = mrb_check_array_type(mrb, RARRAY_PTR(ary)[i]);
+ if (mrb_get_argc(mrb) == 1) {
+ mrb_value index = mrb_get_arg1(mrb);
- if (mrb_nil_p(v)) {
- mrb_raisef(mrb, E_TYPE_ERROR, "wrong element type %S at %S (expected array)",
- mrb_str_new_cstr(mrb, mrb_obj_classname(mrb, RARRAY_PTR(ary)[i])),
- mrb_fixnum_value(i)
- );
+ switch (mrb_type(index)) {
+ case MRB_TT_RANGE:
+ if (mrb_range_beg_len(mrb, index, &i, &len, ARY_LEN(a), TRUE) == MRB_RANGE_OK) {
+ goto delete_pos_len;
+ }
+ else {
+ return mrb_nil_value();
+ }
+ case MRB_TT_INTEGER:
+ val = mrb_funcall_id(mrb, self, MRB_SYM(delete_at), 1, index);
+ return val;
+ default:
+ val = mrb_funcall_id(mrb, self, MRB_SYM(delete_at), 1, index);
+ return val;
}
+ }
+
+ mrb_get_args(mrb, "ii", &i, &len);
+ delete_pos_len:
+ alen = ARY_LEN(a);
+ if (i < 0) i += alen;
+ if (i < 0 || alen < i) return mrb_nil_value();
+ if (len < 0) return mrb_nil_value();
+ if (alen == i) return mrb_ary_new(mrb);
+ if (len > alen - i) len = alen - i;
+
+ ary = mrb_ary_new_capa(mrb, len);
+ ptr = ARY_PTR(a);
+ for (j = i, k = 0; k < len; ++j, ++k) {
+ mrb_ary_push(mrb, ary, ptr[j]);
+ }
+
+ ptr += i;
+ for (j = i; j < alen - len; ++j) {
+ *ptr = *(ptr+len);
+ ++ptr;
+ }
+
+ mrb_ary_resize(mrb, self, alen - len);
+ return ary;
+}
- if (RARRAY_LEN(v) != 2) {
- mrb_raisef(mrb, E_ARGUMENT_ERROR, "wrong array length at %S (expected 2, was %S)",
- mrb_fixnum_value(i),
- mrb_fixnum_value(RARRAY_LEN(v))
- );
+/*
+ * call-seq:
+ * ary.compact -> new_ary
+ *
+ * Returns a copy of +self+ with all +nil+ elements removed.
+ *
+ * [ "a", nil, "b", nil, "c", nil ].compact
+ * #=> [ "a", "b", "c" ]
+ */
+
+static mrb_value
+mrb_ary_compact(mrb_state *mrb, mrb_value self)
+{
+ mrb_value ary = mrb_ary_new(mrb);
+ mrb_int len = RARRAY_LEN(self);
+ mrb_value *p = RARRAY_PTR(self);
+
+ for (mrb_int i = 0; i < len; ++i) {
+ if (!mrb_nil_p(p[i])) {
+ mrb_ary_push(mrb, ary, p[i]);
}
+ }
+ return ary;
+}
+
+/*
+ * call-seq:
+ * ary.compact! -> ary or nil
+ *
+ * Removes +nil+ elements from the array.
+ * Returns +nil+ if no changes were made, otherwise returns
+ * <i>ary</i>.
+ *
+ * [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ]
+ * [ "a", "b", "c" ].compact! #=> nil
+ */
+static mrb_value
+mrb_ary_compact_bang(mrb_state *mrb, mrb_value self)
+{
+ struct RArray *a = mrb_ary_ptr(self);
+ mrb_int i, j = 0;
+ mrb_int len = ARY_LEN(a);
+ mrb_value *p = ARY_PTR(a);
- mrb_hash_set(mrb, hash, RARRAY_PTR(v)[0], RARRAY_PTR(v)[1]);
+ mrb_ary_modify(mrb, a);
+ for (i = 0; i < len; ++i) {
+ if (!mrb_nil_p(p[i])) {
+ if (i != j) p[j] = p[i];
+ j++;
+ }
+ }
+ if (i == j) return mrb_nil_value();
+ if (j < len) ARY_SET_LEN(RARRAY(self), j);
+ return self;
+}
+
+
+/*
+ * call-seq:
+ * ary.rotate(count=1) -> new_ary
+ *
+ * Returns a new array by rotating +self+ so that the element at +count+ is
+ * the first element of the new array.
+ *
+ * If +count+ is negative then it rotates in the opposite direction, starting
+ * from the end of +self+ where +-1+ is the last element.
+ *
+ * a = [ "a", "b", "c", "d" ]
+ * a.rotate #=> ["b", "c", "d", "a"]
+ * a #=> ["a", "b", "c", "d"]
+ * a.rotate(2) #=> ["c", "d", "a", "b"]
+ * a.rotate(-3) #=> ["b", "c", "d", "a"]
+ */
+static mrb_value
+mrb_ary_rotate(mrb_state *mrb, mrb_value self)
+{
+ mrb_value ary = mrb_ary_new(mrb);
+ mrb_int len = RARRAY_LEN(self);
+ mrb_value *p = RARRAY_PTR(self);
+ mrb_int count=1, idx;
+
+ mrb_get_args(mrb, "|i", &count);
+ if (len <= 0) return ary;
+ if (count < 0) {
+ idx = len - (~count % len) - 1;
+ }
+ else {
+ idx = count % len;
+ }
+ for (mrb_int i = 0; i<len; i++) {
+ mrb_ary_push(mrb, ary, p[idx++]);
+ if (idx == len) idx = 0;
+ }
+ return ary;
+}
+
+static void
+rev(mrb_value *p, mrb_int beg, mrb_int end)
+{
+ for (mrb_int i=beg,j=end-1; i<j; i++,j--) {
+ mrb_value v = p[i];
+ p[i] = p[j];
+ p[j] = v;
}
+}
- return hash;
+/*
+ * call-seq:
+ * ary.rotate!(count=1) -> ary
+ *
+ * Rotates +self+ in place so that the element at +count+ comes first, and
+ * returns +self+.
+ *
+ * If +count+ is negative then it rotates in the opposite direction, starting
+ * from the end of the array where +-1+ is the last element.
+ *
+ * a = [ "a", "b", "c", "d" ]
+ * a.rotate! #=> ["b", "c", "d", "a"]
+ * a #=> ["b", "c", "d", "a"]
+ * a.rotate!(2) #=> ["d", "a", "b", "c"]
+ * a.rotate!(-3) #=> ["a", "b", "c", "d"]
+ */
+static mrb_value
+mrb_ary_rotate_bang(mrb_state *mrb, mrb_value self)
+{
+ struct RArray *a = mrb_ary_ptr(self);
+ mrb_int len = ARY_LEN(a);
+ mrb_value *p = ARY_PTR(a);
+ mrb_int count=1, idx;
+
+ mrb_get_args(mrb, "|i", &count);
+ mrb_ary_modify(mrb, a);
+ if (len == 0 || count == 0) return self;
+ if (count == 1) {
+ mrb_value v = p[0];
+ for (mrb_int i=1; i<len; i++) {
+ p[i-1] = p[i];
+ }
+ p[len-1] = v;
+ return self;
+ }
+ if (count < 0) {
+ idx = len - (~count % len) - 1;
+ }
+ else {
+ idx = count % len;
+ }
+ /* e.g. [1,2,3,4,5].rotate!(2) -> [3,4,5,1,2] */
+ /* first, reverse the whole array */
+ /* [1,2,3,4,5] -> [5,4,3,2,1] */
+ rev(p, 0, len);
+ /* then, re-reverse part before idx */
+ /* [5,4,3,2,1] -> [3,4,5,2,1] */
+ /* ^idx ~~~~~ */
+ rev(p, 0, len-idx);
+ /* finally, re-reverse part after idx */
+ /* [3,4,5,2,1] -> [3,4,5,1,2] */
+ /* ^idx ~~~ */
+ rev(p, len-idx, len);
+ return self;
}
void
@@ -157,7 +359,11 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb)
mrb_define_method(mrb, a, "at", mrb_ary_at, MRB_ARGS_REQ(1));
mrb_define_method(mrb, a, "rassoc", mrb_ary_rassoc, MRB_ARGS_REQ(1));
mrb_define_method(mrb, a, "values_at", mrb_ary_values_at, MRB_ARGS_ANY());
- mrb_define_method(mrb, a, "to_h", mrb_ary_to_h, MRB_ARGS_REQ(0));
+ mrb_define_method(mrb, a, "slice!", mrb_ary_slice_bang, MRB_ARGS_ARG(1,1));
+ mrb_define_method(mrb, a, "compact", mrb_ary_compact, MRB_ARGS_NONE());
+ mrb_define_method(mrb, a, "compact!", mrb_ary_compact_bang, MRB_ARGS_NONE());
+ mrb_define_method(mrb, a, "rotate", mrb_ary_rotate, MRB_ARGS_OPT(1));
+ mrb_define_method(mrb, a, "rotate!", mrb_ary_rotate_bang, MRB_ARGS_OPT(1));
}
void
diff --git a/mrbgems/mruby-array-ext/test/array.rb b/mrbgems/mruby-array-ext/test/array.rb
index 8c919f7e0..3f73ad8b9 100644
--- a/mrbgems/mruby-array-ext/test/array.rb
+++ b/mrbgems/mruby-array-ext/test/array.rb
@@ -1,6 +1,23 @@
##
# Array(Ext) Test
+def assert_permutation_combination(exp, receiver, meth, *args)
+ act = []
+ ret = receiver.__send__(meth, *args) { |v| act << v }
+ assert "assert_#{meth}" do
+ assert_equal(exp, act.sort)
+ assert_same(receiver, ret)
+ end
+end
+
+def assert_permutation(exp, receiver, *args)
+ assert_permutation_combination(exp, receiver, :permutation, *args)
+end
+
+def assert_combination(exp, receiver, *args)
+ assert_permutation_combination(exp, receiver, :combination, *args)
+end
+
assert("Array#assoc") do
s1 = [ "colors", "red", "blue", "green" ]
s2 = [ "letters", "a", "b", "c" ]
@@ -68,6 +85,22 @@ assert("Array#|") do
assert_equal [1, 2, 3, 1], a
end
+assert("Array#union") do
+ a = [1, 2, 3, 1]
+ b = [1, 4]
+ c = [1, 5]
+
+ assert_equal [1, 2, 3, 4, 5], a.union(b,c)
+end
+
+assert("Array#difference") do
+ a = [1, 2, 3, 1, 6, 7]
+ b = [1, 4, 6]
+ c = [1, 5, 7]
+
+ assert_equal [2, 3], a.difference(b,c)
+end
+
assert("Array#&") do
a = [1, 2, 3, 1]
b = [1, 4]
@@ -78,6 +111,22 @@ assert("Array#&") do
assert_equal [1, 2, 3, 1], a
end
+assert("Array#intersection") do
+ a = [1, 2, 3, 1, 8, 6, 7, 8]
+ b = [1, 4, 6, 8]
+ c = [1, 5, 7, 8]
+
+ assert_equal [1, 8], a.intersection(b,c)
+end
+
+assert("Array#intersect?") do
+ a = [ 1, 2, 3 ]
+ b = [ 3, 4, 5 ]
+ c = [ 5, 6, 7 ]
+ assert_true(a.intersect?(b))
+ assert_false(a.intersect?(c))
+end
+
assert("Array#flatten") do
assert_equal [1, 2, "3", {4=>5}, :'6'], [1, 2, "3", {4=>5}, :'6'].flatten
assert_equal [1, 2, 3, 4, 5, 6], [1, 2, [3, 4, 5], 6].flatten
@@ -154,12 +203,6 @@ assert("Array#reverse_each") do
b << i
end
assert_equal [ "d", "c", "b", "a" ], b
-
- if Object.const_defined?(:Enumerator)
- assert_equal [ "d", "c", "b", "a" ], a.reverse_each.to_a
- else
- true
- end
end
assert("Array#rotate") do
@@ -221,32 +264,48 @@ end
assert("Array#bsearch") do
# Find minimum mode
- a = [0, 4, 7, 10, 12]
- assert_include [4, 7], a.bsearch {|x| x >= 4 }
- assert_equal 7, a.bsearch {|x| x >= 6 }
- assert_equal 0, a.bsearch {|x| x >= -1 }
- assert_nil a.bsearch {|x| x >= 100 }
+ a = [0, 2, 4]
+ assert_equal 0, a.bsearch{ |x| x >= -1 }
+ assert_equal 0, a.bsearch{ |x| x >= 0 }
+ assert_equal 2, a.bsearch{ |x| x >= 1 }
+ assert_equal 2, a.bsearch{ |x| x >= 2 }
+ assert_equal 4, a.bsearch{ |x| x >= 3 }
+ assert_equal 4, a.bsearch{ |x| x >= 4 }
+ assert_nil a.bsearch{ |x| x >= 5 }
# Find any mode
- a = [0, 4, 7, 10, 12]
- assert_include [4, 7], a.bsearch {|x| 1 - (x / 4).truncate }
- assert_nil a.bsearch {|x| 4 - (x / 2).truncate }
- assert_equal(nil, a.bsearch {|x| 1 })
- assert_equal(nil, a.bsearch {|x| -1 })
+ a = [0, 4, 8]
+ def between(lo, x, hi)
+ if x < lo
+ 1
+ elsif x > hi
+ -1
+ else
+ 0
+ end
+ end
+ assert_nil a.bsearch{ |x| between(-3, x, -1) }
+ assert_equal 0, a.bsearch{ |x| between(-1, x, 1) }
+ assert_nil a.bsearch{ |x| between( 1, x, 3) }
+ assert_equal 4, a.bsearch{ |x| between( 3, x, 5) }
+ assert_nil a.bsearch{ |x| between( 5, x, 7) }
+ assert_equal 8, a.bsearch{ |x| between( 7, x, 9) }
+ assert_nil a.bsearch{ |x| between( 9, x, 11) }
+
+ assert_equal 0, a.bsearch{ |x| between( 0, x, 3) }
+ assert_equal 4, a.bsearch{ |x| between( 0, x, 4) }
+ assert_equal 4, a.bsearch{ |x| between( 4, x, 8) }
+ assert_equal 8, a.bsearch{ |x| between( 5, x, 8) }
+
+ # Invalid block result
+ assert_raise TypeError, 'invalid block result (must be numeric, true, false or nil)' do
+ a.bsearch{ 'I like to watch the world burn' }
+ end
end
-assert("Array#delete_if") do
- a = [1, 2, 3, 4, 5]
- assert_equal [1, 2, 3, 4, 5], a.delete_if { false }
- assert_equal [1, 2, 3, 4, 5], a
-
- a = [1, 2, 3, 4, 5]
- assert_equal [], a.delete_if { true }
- assert_equal [], a
-
- a = [ 1, 2, 3, 4, 5 ]
- assert_equal [1, 2, 3], a.delete_if { |val| val > 3 }
-end
+# tested through Array#bsearch
+#assert("Array#bsearch_index") do
+#end
assert("Array#keep_if") do
a = [1, 2, 3, 4, 5]
@@ -293,3 +352,72 @@ assert('Array#to_h') do
assert_raise(TypeError) { [1].to_h }
assert_raise(ArgumentError) { [[1]].to_h }
end
+
+assert("Array#index (block)") do
+ assert_nil (1..10).to_a.index { |i| i % 5 == 0 and i % 7 == 0 }
+ assert_equal 34, (1..100).to_a.index { |i| i % 5 == 0 and i % 7 == 0 }
+end
+
+assert("Array#dig") do
+ h = [[[1]], 0]
+ assert_equal(1, h.dig(0, 0, 0))
+ assert_nil(h.dig(2, 0))
+ assert_raise(TypeError) {h.dig(:a)}
+end
+
+assert("Array#slice!") do
+ a = [1, 2, 3]
+ b = a.slice!(0)
+ c = [1, 2, 3, 4, 5]
+ d = c.slice!(0, 2)
+ e = [1, 2, 3, 4, 5]
+ f = e.slice!(1..3)
+ g = [1, 2, 3]
+ h = g.slice!(-1)
+ i = [1, 2, 3]
+ j = i.slice!(0, -1)
+
+ assert_equal(a, [2, 3])
+ assert_equal(b, 1)
+ assert_equal(c, [3, 4, 5])
+ assert_equal(d, [1, 2])
+ assert_equal(e, [1, 5])
+ assert_equal(f, [2, 3, 4])
+ assert_equal(g, [1, 2])
+ assert_equal(h, 3)
+ assert_equal(i, [1, 2, 3])
+ assert_equal(j, nil)
+end
+
+assert("Array#permutation") do
+ a = [1, 2, 3]
+ assert_permutation([[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]], a)
+ assert_permutation([[1],[2],[3]], a, 1)
+ assert_permutation([[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]], a, 2)
+ assert_permutation([[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]], a, 3)
+ assert_permutation([[]], a, 0)
+ assert_permutation([], a, 4)
+ assert_permutation([], a, -1)
+end
+
+assert("Array#combination") do
+ a = [1, 2, 3, 4]
+ assert_combination([[1],[2],[3],[4]], a, 1)
+ assert_combination([[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]], a, 2)
+ assert_combination([[1,2,3],[1,2,4],[1,3,4],[2,3,4]], a, 3)
+ assert_combination([[1,2,3,4]], a, 4)
+ assert_combination([[]], a, 0)
+ assert_combination([], a, 5)
+ assert_combination([], a, -1)
+end
+
+assert('Array#transpose') do
+ assert_equal([].transpose, [])
+ assert_equal([[]].transpose, [])
+ assert_equal([[1]].transpose, [[1]])
+ assert_equal([[1,2,3]].transpose, [[1], [2], [3]])
+ assert_equal([[1], [2], [3]].transpose, [[1,2,3]])
+ assert_equal([[1,2], [3,4], [5,6]].transpose, [[1,3,5], [2,4,6]])
+ assert_raise(TypeError) { [1].transpose }
+ assert_raise(IndexError) { [[1], [2,3,4]].transpose }
+end