diff options
Diffstat (limited to 'mrbgems/mruby-array-ext/mrblib/array.rb')
| -rw-r--r-- | mrbgems/mruby-array-ext/mrblib/array.rb | 591 |
1 files changed, 403 insertions, 188 deletions
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 |
