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)
}