diff options
Diffstat (limited to 'mrbgems/mruby-array-ext')
| -rw-r--r-- | mrbgems/mruby-array-ext/mrbgem.rake | 2 | ||||
| -rw-r--r-- | mrbgems/mruby-array-ext/mrblib/array.rb | 591 | ||||
| -rw-r--r-- | mrbgems/mruby-array-ext/src/array.c | 282 | ||||
| -rw-r--r-- | mrbgems/mruby-array-ext/test/array.rb | 184 |
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 |
