[ruby/set] Remove SortedSet implementations
It required RBTree to perform decently and the external dependency was not suitable for a standard library. The pure ruby fallback implementation was originally meant to be an example of how to write a subclass of Set, and its poor performance was not suitable for use in production. I decided it should be distributed as an external library instead of bundling it with Set. https://github.com/ruby/set/commit/dfcc8e568b
This commit is contained in:
parent
46fc8d78a5
commit
a3db08d7b6
247
lib/set.rb
247
lib/set.rb
@ -16,12 +16,9 @@
|
|||||||
#
|
#
|
||||||
# This library provides the Set class, which deals with a collection
|
# This library provides the Set class, which deals with a collection
|
||||||
# of unordered values with no duplicates. It is a hybrid of Array's
|
# of unordered values with no duplicates. It is a hybrid of Array's
|
||||||
# intuitive inter-operation facilities and Hash's fast lookup. If you
|
# intuitive inter-operation facilities and Hash's fast lookup.
|
||||||
# need to keep values sorted in some order, use the SortedSet class.
|
|
||||||
#
|
#
|
||||||
# The method +to_set+ is added to Enumerable for convenience.
|
# The method +to_set+ is added to Enumerable for convenience.
|
||||||
#
|
|
||||||
# See the Set and SortedSet documentation for examples of usage.
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -667,154 +664,6 @@ class Set
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# SortedSet implements a Set that guarantees that its elements 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
|
|
||||||
@@mutex = Mutex.new
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def [](*ary) # :nodoc:
|
|
||||||
new(ary)
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup # :nodoc:
|
|
||||||
@@setup and return
|
|
||||||
|
|
||||||
@@mutex.synchronize do
|
|
||||||
# a hack to shut up warning
|
|
||||||
alias_method :old_init, :initialize
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'rbtree'
|
|
||||||
|
|
||||||
module_eval <<-END, __FILE__, __LINE__+1
|
|
||||||
def initialize(*args)
|
|
||||||
@hash = RBTree.new
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def add(o)
|
|
||||||
o.respond_to?(:<=>) or raise ArgumentError, "value must respond to <=>"
|
|
||||||
super
|
|
||||||
end
|
|
||||||
alias << add
|
|
||||||
END
|
|
||||||
rescue LoadError
|
|
||||||
module_eval <<-END, __FILE__, __LINE__+1
|
|
||||||
def initialize(*args)
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def replace(enum)
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def add(o)
|
|
||||||
o.respond_to?(:<=>) or raise ArgumentError, "value must respond to <=>"
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
alias << add
|
|
||||||
|
|
||||||
def delete(o)
|
|
||||||
@keys = nil
|
|
||||||
@hash.delete(o)
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_if
|
|
||||||
block_given? or return enum_for(__method__) { size }
|
|
||||||
n = @hash.size
|
|
||||||
super
|
|
||||||
@keys = nil if @hash.size != n
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def keep_if
|
|
||||||
block_given? or return enum_for(__method__) { size }
|
|
||||||
n = @hash.size
|
|
||||||
super
|
|
||||||
@keys = nil if @hash.size != n
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def merge(enum)
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def each(&block)
|
|
||||||
block or return enum_for(__method__) { size }
|
|
||||||
to_a.each(&block)
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_a
|
|
||||||
(@keys = @hash.keys).sort! unless @keys
|
|
||||||
@keys.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
def freeze
|
|
||||||
to_a
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def rehash
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
END
|
|
||||||
end
|
|
||||||
# a hack to shut up warning
|
|
||||||
remove_method :old_init
|
|
||||||
|
|
||||||
@@setup = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(*args, &block) # :nodoc:
|
|
||||||
SortedSet.setup
|
|
||||||
@keys = nil
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Enumerable
|
module Enumerable
|
||||||
# Makes a set from the enumerable object with given arguments.
|
# Makes a set from the enumerable object with given arguments.
|
||||||
# Needs to +require "set"+ to use this method.
|
# Needs to +require "set"+ to use this method.
|
||||||
@ -822,97 +671,3 @@ module Enumerable
|
|||||||
klass.new(self, *args, &block)
|
klass.new(self, *args, &block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# =begin
|
|
||||||
# == RestricedSet class
|
|
||||||
# RestricedSet implements a set with restrictions defined by a given
|
|
||||||
# block.
|
|
||||||
#
|
|
||||||
# === Super class
|
|
||||||
# Set
|
|
||||||
#
|
|
||||||
# === Class Methods
|
|
||||||
# --- RestricedSet::new(enum = nil) { |o| ... }
|
|
||||||
# --- RestricedSet::new(enum = nil) { |rset, o| ... }
|
|
||||||
# Creates a new restricted set containing the elements of the given
|
|
||||||
# enumerable object. Restrictions are defined by the given block.
|
|
||||||
#
|
|
||||||
# If the block's arity is 2, it is called with the RestrictedSet
|
|
||||||
# itself and an object to see if the object is allowed to be put in
|
|
||||||
# the set.
|
|
||||||
#
|
|
||||||
# Otherwise, the block is called with an object to see if the object
|
|
||||||
# is allowed to be put in the set.
|
|
||||||
#
|
|
||||||
# === Instance Methods
|
|
||||||
# --- restriction_proc
|
|
||||||
# Returns the restriction procedure of the set.
|
|
||||||
#
|
|
||||||
# =end
|
|
||||||
#
|
|
||||||
# class RestricedSet < Set
|
|
||||||
# def initialize(*args, &block)
|
|
||||||
# @proc = block or raise ArgumentError, "missing a block"
|
|
||||||
#
|
|
||||||
# if @proc.arity == 2
|
|
||||||
# instance_eval %{
|
|
||||||
# def add(o)
|
|
||||||
# @hash[o] = true if @proc.call(self, o)
|
|
||||||
# self
|
|
||||||
# end
|
|
||||||
# alias << add
|
|
||||||
#
|
|
||||||
# def add?(o)
|
|
||||||
# if include?(o) || !@proc.call(self, o)
|
|
||||||
# nil
|
|
||||||
# else
|
|
||||||
# @hash[o] = true
|
|
||||||
# self
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def replace(enum)
|
|
||||||
# enum.respond_to?(:each) or raise ArgumentError, "value must be enumerable"
|
|
||||||
# clear
|
|
||||||
# enum.each_entry { |o| add(o) }
|
|
||||||
#
|
|
||||||
# self
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def merge(enum)
|
|
||||||
# enum.respond_to?(:each) or raise ArgumentError, "value must be enumerable"
|
|
||||||
# enum.each_entry { |o| add(o) }
|
|
||||||
#
|
|
||||||
# self
|
|
||||||
# end
|
|
||||||
# }
|
|
||||||
# else
|
|
||||||
# instance_eval %{
|
|
||||||
# def add(o)
|
|
||||||
# if @proc.call(o)
|
|
||||||
# @hash[o] = true
|
|
||||||
# end
|
|
||||||
# self
|
|
||||||
# end
|
|
||||||
# alias << add
|
|
||||||
#
|
|
||||||
# def add?(o)
|
|
||||||
# if include?(o) || !@proc.call(o)
|
|
||||||
# nil
|
|
||||||
# else
|
|
||||||
# @hash[o] = true
|
|
||||||
# self
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# }
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# super(*args)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def restriction_proc
|
|
||||||
# @proc
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Tests have been moved to test/test_set.rb.
|
|
||||||
|
118
test/test_set.rb
118
test/test_set.rb
@ -796,116 +796,6 @@ class TC_Set < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TC_SortedSet < Test::Unit::TestCase
|
|
||||||
def test_sortedset
|
|
||||||
s = SortedSet[4,5,3,1,2]
|
|
||||||
|
|
||||||
a = s.to_a
|
|
||||||
assert_equal([1,2,3,4,5], a)
|
|
||||||
a << -1
|
|
||||||
assert_equal([1,2,3,4,5], s.to_a)
|
|
||||||
|
|
||||||
prev = nil
|
|
||||||
s.each { |o| assert(prev < o) if prev; prev = o }
|
|
||||||
assert_not_nil(prev)
|
|
||||||
|
|
||||||
s.map! { |o| -2 * o }
|
|
||||||
|
|
||||||
assert_equal([-10,-8,-6,-4,-2], s.to_a)
|
|
||||||
|
|
||||||
prev = nil
|
|
||||||
ret = s.each { |o| assert(prev < o) if prev; prev = o }
|
|
||||||
assert_not_nil(prev)
|
|
||||||
assert_same(s, ret)
|
|
||||||
|
|
||||||
s = SortedSet.new([2,1,3]) { |o| o * -2 }
|
|
||||||
assert_equal([-6,-4,-2], s.to_a)
|
|
||||||
|
|
||||||
s = SortedSet.new(['one', 'two', 'three', 'four'])
|
|
||||||
a = []
|
|
||||||
ret = s.delete_if { |o| a << o; o.start_with?('t') }
|
|
||||||
assert_same(s, ret)
|
|
||||||
assert_equal(['four', 'one'], s.to_a)
|
|
||||||
assert_equal(['four', 'one', 'three', 'two'], a)
|
|
||||||
|
|
||||||
s = SortedSet.new(['one', 'two', 'three', 'four'])
|
|
||||||
a = []
|
|
||||||
ret = s.reject! { |o| a << o; o.start_with?('t') }
|
|
||||||
assert_same(s, ret)
|
|
||||||
assert_equal(['four', 'one'], s.to_a)
|
|
||||||
assert_equal(['four', 'one', 'three', 'two'], a)
|
|
||||||
|
|
||||||
s = SortedSet.new(['one', 'two', 'three', 'four'])
|
|
||||||
a = []
|
|
||||||
ret = s.reject! { |o| a << o; false }
|
|
||||||
assert_same(nil, ret)
|
|
||||||
assert_equal(['four', 'one', 'three', 'two'], s.to_a)
|
|
||||||
assert_equal(['four', 'one', 'three', 'two'], a)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_each
|
|
||||||
ary = [1,3,5,7,10,20]
|
|
||||||
set = SortedSet.new(ary)
|
|
||||||
|
|
||||||
ret = set.each { |o| }
|
|
||||||
assert_same(set, ret)
|
|
||||||
|
|
||||||
e = set.each
|
|
||||||
assert_instance_of(Enumerator, e)
|
|
||||||
|
|
||||||
assert_nothing_raised {
|
|
||||||
set.each { |o|
|
|
||||||
ary.delete(o) or raise "unexpected element: #{o}"
|
|
||||||
}
|
|
||||||
|
|
||||||
ary.empty? or raise "forgotten elements: #{ary.join(', ')}"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal(6, e.size)
|
|
||||||
set << 42
|
|
||||||
assert_equal(7, e.size)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_freeze
|
|
||||||
orig = set = SortedSet[3,2,1]
|
|
||||||
assert_equal false, set.frozen?
|
|
||||||
set << 4
|
|
||||||
assert_same orig, set.freeze
|
|
||||||
assert_equal true, set.frozen?
|
|
||||||
assert_raise(FrozenError) {
|
|
||||||
set << 5
|
|
||||||
}
|
|
||||||
assert_equal 4, set.size
|
|
||||||
|
|
||||||
# https://bugs.ruby-lang.org/issues/12091
|
|
||||||
assert_nothing_raised {
|
|
||||||
assert_equal [1,2,3,4], set.to_a
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_freeze_dup
|
|
||||||
set1 = SortedSet[1,2,3]
|
|
||||||
set1.freeze
|
|
||||||
set2 = set1.dup
|
|
||||||
|
|
||||||
assert_not_predicate set2, :frozen?
|
|
||||||
assert_nothing_raised {
|
|
||||||
set2.add 4
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_freeze_clone
|
|
||||||
set1 = SortedSet[1,2,3]
|
|
||||||
set1.freeze
|
|
||||||
set2 = set1.clone
|
|
||||||
|
|
||||||
assert_predicate set2, :frozen?
|
|
||||||
assert_raise(FrozenError) {
|
|
||||||
set2.add 5
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TC_Enumerable < Test::Unit::TestCase
|
class TC_Enumerable < Test::Unit::TestCase
|
||||||
def test_to_set
|
def test_to_set
|
||||||
ary = [2,5,4,3,2,1,3]
|
ary = [2,5,4,3,2,1,3]
|
||||||
@ -920,13 +810,5 @@ class TC_Enumerable < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_same set, set.to_set
|
assert_same set, set.to_set
|
||||||
assert_not_same set, set.to_set { |o| o }
|
assert_not_same set, set.to_set { |o| o }
|
||||||
|
|
||||||
set = ary.to_set(SortedSet)
|
|
||||||
assert_instance_of(SortedSet, set)
|
|
||||||
assert_equal([1,2,3,4,5], set.to_a)
|
|
||||||
|
|
||||||
set = ary.to_set(SortedSet) { |o| o * -2 }
|
|
||||||
assert_instance_of(SortedSet, set)
|
|
||||||
assert_equal([-10,-8,-6,-4,-2], set.sort)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user