Array#index to take block; fix #2968 close #2970
[mruby.git] / mrbgems / mruby-array-ext / mrblib / array.rb
index 18ed5e3..35be793 100644 (file)
@@ -1,7 +1,8 @@
 class Array
   ##
   # call-seq:
-  #    ary.uniq! -> ary or nil
+  #    ary.uniq!                -> ary or nil
+  #    ary.uniq! { |item| ... } -> ary or nil
   #
   # Removes duplicate elements from +self+.
   # Returns <code>nil</code> if no changes are made (that is, no
@@ -11,13 +12,27 @@ class Array
   #    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!
+  def uniq!(&block)
     ary = self.dup
     result = []
-    while ary.size > 0
-      result << ary.shift
-      ary.delete(result.last)
+    if block
+      hash = {}
+      while ary.size > 0
+        val = ary.shift
+        key = block.call(val)
+        hash[key] = val unless hash.has_key?(key)
+      end
+      hash.each_value do |value|
+        result << value
+      end
+    else
+      while ary.size > 0
+        result << ary.shift
+        ary.delete(result.last)
+      end
     end
     if result.size == self.size
       nil
@@ -28,16 +43,24 @@ class Array
 
   ##
   # call-seq:
-  #    ary.uniq   -> new_ary
+  #    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"]
   #
-  def uniq
+  #    b = [["student","sam"], ["student","george"], ["teacher","matz"]]
+  #    b.uniq { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
+  #
+  def uniq(&block)
     ary = self.dup
-    ary.uniq!
+    if block
+      ary.uniq!(&block)
+    else
+      ary.uniq!
+    end
     ary
   end
 
@@ -194,7 +217,7 @@ class Array
   #    [ "a", "b", "c" ].compact!           #=> nil
   #
   def compact!
-    result = self.select { |e| e != nil }
+    result = self.select { |e| !e.nil? }
     if result.size == self.size
       nil
     else
@@ -204,7 +227,7 @@ class Array
 
   # for efficiency
   def reverse_each(&block)
-    return to_enum :sort_by unless block_given?
+    return to_enum :reverse_each unless block_given?
 
     i = self.size - 1
     while i>=0
@@ -239,7 +262,7 @@ class Array
   #
 
   def fetch(n=nil, ifnone=NONE, &block)
-    warn "block supersedes default value argument" if n != nil && ifnone != NONE && block
+    warn "block supersedes default value argument" if !n.nil? && ifnone != NONE && block
 
     idx = n
     if idx < 0
@@ -289,50 +312,54 @@ class Array
   #
 
   def fill(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)" 
+    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.respond_to?(:begin) && arg0.respond_to?(:end)
+      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 - beg + 1
-      elsif arg0 != nil
+        len = arg0.end
+        len += self.size if len < 0
+        len += 1 unless arg0.exclude_end?
+      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.respond_to?(:begin) && arg1.respond_to?(:end)
-        # ary.fill(obj, range )                         -> ary
         len = self.size
+      elsif !arg0.nil? && !arg1.nil? && arg1.kind_of?(Range)
+        # ary.fill(obj, range )                         -> ary
         beg = arg1.begin
-        len = arg1.end - beg + 1
-      elsif arg0 != nil && arg1 != nil
+        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?
         # 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 = arg1 + arg2
+          len = beg + arg2
         end
       end
     end
@@ -343,7 +370,7 @@ class Array
         self[i] = block.call(i)
         i += 1
       end
-    else 
+    else
       while i < len
         self[i] = arg0
         i += 1
@@ -351,4 +378,335 @@ class Array
     end
     self
   end
+
+  ##
+  #  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
+  #
+  #  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_given?
+
+    idx = 0
+    while idx < self.size do
+      if block.call(self[idx])
+        self.delete_at(idx)
+      else
+        idx += 1
+      end
+    end
+    self
+  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_given?
+
+    len = self.size
+    idx = 0
+    while idx < self.size do
+      if block.call(self[idx])
+        self.delete_at(idx)
+      else
+        idx += 1
+      end
+    end
+    if self.size == len
+      nil
+    else
+      self
+    end
+  end
+
+  ##
+  #  call-seq:
+  #     ary.insert(index, obj...)  -> ary
+  #
+  #  Inserts the given values before the element with the given +index+.
+  #
+  #  Negative indices count backwards from the end of the array, where +-1+ is
+  #  the last element.
+  #
+  #     a = %w{ a b c d }
+  #     a.insert(2, 99)         #=> ["a", "b", 99, "c", "d"]
+  #     a.insert(-2, 1, 2, 3)   #=> ["a", "b", 99, "c", 1, 2, 3, "d"]
+
+  def insert(idx, *args)
+    idx += self.size + 1 if idx < 0
+    self[idx, 0] = args
+    self
+  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_given?
+
+    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
+    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.
+  #
+  #  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_given?
+
+    idx = 0
+    while idx < self.size do
+      if block.call(self[idx])
+        self.delete_at(idx)
+      else
+        idx += 1
+      end
+    end
+    self
+  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_given?
+
+    idx = 0
+    len = self.size
+    while idx < self.size do
+      if block.call(self[idx])
+        idx += 1
+      else
+        self.delete_at(idx)
+      end
+    end
+    self
+  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_given?
+
+    result = []
+    self.each do |x|
+      result << x if block.call(x)
+    end
+    return nil if self.size == 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
+      self.each do |*e|
+        return idx if block.call(*e)
+        idx += 1
+      end
+    else
+      return self.__ary_index(val)
+    end
+    nil
+  end
 end