lib/extensions/set/set.rb in rhodes-3.2.0.beta.4 vs lib/extensions/set/set.rb in rhodes-3.2.0.beta.5

- old
+ new

@@ -7,22 +7,22 @@ # Documentation by Akinori MUSHA and Gavin Sinclair. # # All rights reserved. You can redistribute and/or modify it under the same # terms as Ruby. # -# $Id: set.rb 18571 2008-08-13 08:03:30Z knu $ +# $Id: set.rb 28095 2010-05-30 13:15:17Z marcandre $ # # == Overview # # This library provides the Set class, which deals with a collection # of unordered values with no duplicates. It is a hybrid of Array's # intuitive inter-operation facilities and Hash's fast lookup. If you # need to keep values ordered, use the SortedSet class. # # The method +to_set+ is added to Enumerable for convenience. # -# See the Set class for an example of usage. +# See the Set and SortedSet documentation for examples of usage. # # Set implements a collection of unordered values with no duplicates. # This is a hybrid of Array's intuitive inter-operation facilities and @@ -68,16 +68,27 @@ @hash ||= Hash.new enum.nil? and return if block - enum.each { |o| add(block[o]) } + do_with_enum(enum) { |o| add(block[o]) } else merge(enum) end end + def do_with_enum(enum, &block) + if enum.respond_to?(:each_entry) + enum.each_entry(&block) + elsif enum.respond_to?(:each) + enum.each(&block) + else + raise ArgumentError, "value must be enumerable" + end + end + private :do_with_enum + # Copy internal hash. def initialize_copy(orig) @hash = orig.instance_eval{@hash}.dup end @@ -121,11 +132,11 @@ def replace(enum) if enum.class == self.class @hash.replace(enum.instance_eval { @hash }) else clear - enum.each { |o| add(o) } + merge(enum) end self end @@ -253,10 +264,18 @@ block_given? or return enum_for(__method__) to_a.each { |o| @hash.delete(o) if yield(o) } self end + # Deletes every element of the set for which block evaluates to + # false, and returns self. + def keep_if + block_given? or return enum_for(__method__) + to_a.each { |o| @hash.delete(o) unless yield(o) } + self + end + # Replaces the elements with ones returned by collect(). def collect! block_given? or return enum_for(__method__) set = self.class.new each { |o| set << yield(o) } @@ -271,26 +290,35 @@ n = size delete_if { |o| yield(o) } size == n ? nil : self end + # Equivalent to Set#keep_if, but returns nil if no changes were + # made. + def select! + block_given? or return enum_for(__method__) + n = size + keep_if { |o| yield(o) } + size == n ? nil : self + end + # Merges the elements of the given enumerable object to the set and # returns self. def merge(enum) - if enum.is_a?(Set) - @hash.update(enum.instance_eval { @hash }) + if enum.instance_of?(self.class) + @hash.update(enum.instance_variable_get(:@hash)) else - enum.each { |o| add(o) } + do_with_enum(enum) { |o| add(o) } end self end # Deletes every element that appears in the given enumerable object # and returns self. def subtract(enum) - enum.each { |o| delete(o) } + do_with_enum(enum) { |o| delete(o) } self end # Returns a new set built by merging the set and the elements of the # given enumerable object. @@ -309,11 +337,11 @@ # Returns a new set containing elements common to the set and the # given enumerable object. def &(enum) n = self.class.new - enum.each { |o| n.add(o) if include?(o) } + do_with_enum(enum) { |o| n.add(o) if include?(o) } n end alias intersection & ## # Returns a new set containing elements exclusive between the set @@ -325,17 +353,20 @@ n end # Returns true if two sets are equal. The equality of each couple # of elements is defined according to Object#eql?. - def ==(set) - equal?(set) and return true - - set.is_a?(Set) && size == set.size or return false - - hash = @hash.dup - set.all? { |o| hash.include?(o) } + def ==(other) + if self.equal?(other) + true + elsif other.instance_of?(self.class) + @hash == other.instance_variable_get(:@hash) + elsif other.is_a?(Set) && self.size == other.size + other.all? { |o| @hash.include?(o) } + else + false + end end def hash # :nodoc: @hash.hash end @@ -449,11 +480,39 @@ def pretty_print_cycle(pp) # :nodoc: pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') end end -# SortedSet implements a set which elements are sorted in order. See Set. +# +# SortedSet implements a Set that guarantees that it's element are +# yielded in sorted order (according to the return values of their +# #<=> methods) when iterating over them. +# +# All elements that are added to a SortedSet must respond to the <=> +# method for comparison. +# +# Also, all elements must be <em>mutually comparable</em>: <tt>el1 <=> +# el2</tt> must not return <tt>nil</tt> for any elements <tt>el1</tt> +# and <tt>el2</tt>, else an ArgumentError will be raised when +# iterating over the SortedSet. +# +# == Example +# +# require "set" +# +# set = SortedSet.new([2, 1, 5, 6, 4, 5, 3, 3, 3]) +# ary = [] +# +# set.each do |obj| +# ary << obj +# end +# +# p ary # => [1, 2, 3, 4, 5, 6] +# +# set2 = SortedSet.new([1, 2, "3"]) +# set2.each { |obj| } # => raises ArgumentError: comparison of Fixnum with String failed +# class SortedSet < Set @@setup = false class << self def [](*ary) # :nodoc: @@ -474,10 +533,16 @@ module_eval %{ def initialize(*args, &block) @hash = RBTree.new super end + + def add(o) + o.respond_to?(:<=>) or raise ArgumentError, "value must respond to <=>" + super + end + alias << add } rescue LoadError module_eval %{ def initialize(*args, &block) @keys = nil @@ -493,13 +558,13 @@ @keys = nil super end def add(o) + o.respond_to?(:<=>) or raise ArgumentError, "value must respond to <=>" @keys = nil - @hash[o] = true - self + super end alias << add def delete(o) @keys = nil @@ -513,10 +578,18 @@ super @keys = nil if @hash.size != n self end + def keep_if + block_given? or return enum_for(__method__) + n = @hash.size + super + @keys = nil if @hash.size != n + self + end + def merge(enum) @keys = nil super end @@ -598,19 +671,21 @@ # self # end # end # # def replace(enum) +# enum.respond_to?(:each) or raise ArgumentError, "value must be enumerable" # clear -# enum.each { |o| add(o) } -# +# enum.each_entry { |o| add(o) } +# # self # end # # def merge(enum) -# enum.each { |o| add(o) } -# +# enum.respond_to?(:each) or raise ArgumentError, "value must be enumerable" +# enum.each_entry { |o| add(o) } +# # self # end # } # else # instance_eval %{ @@ -672,13 +747,13 @@ Set.new(nil) Set.new([]) Set.new([1,2]) Set.new('a'..'c') } - assert_raises(NoMethodError) { + assert_raises(ArgumentError) { Set.new(false) } - assert_raises(NoMethodError) { + assert_raises(ArgumentError) { Set.new(1) } assert_raises(ArgumentError) { Set.new(1,2) }