diff options
Diffstat (limited to 'lib/set')
| -rw-r--r-- | lib/set/set.gemspec | 23 | ||||
| -rw-r--r-- | lib/set/sorted_set.rb | 6 | ||||
| -rw-r--r-- | lib/set/subclass_compatible.rb | 347 |
3 files changed, 347 insertions, 29 deletions
diff --git a/lib/set/set.gemspec b/lib/set/set.gemspec deleted file mode 100644 index 83100df785..0000000000 --- a/lib/set/set.gemspec +++ /dev/null @@ -1,23 +0,0 @@ -Gem::Specification.new do |spec| - spec.name = "set" - spec.version = "1.0.2" - spec.authors = ["Akinori MUSHA"] - spec.email = ["knu@idaemons.org"] - - spec.summary = %q{Provides a class to deal with collections of unordered, unique values} - spec.description = %q{Provides a class to deal with collections of unordered, unique values} - spec.homepage = "https://github.com/ruby/set" - spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["changelog_uri"] = "https://github.com/ruby/set/blob/v#{spec.version}/CHANGELOG.md" - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.require_paths = ["lib"] -end diff --git a/lib/set/sorted_set.rb b/lib/set/sorted_set.rb deleted file mode 100644 index bc07bc1fb0..0000000000 --- a/lib/set/sorted_set.rb +++ /dev/null @@ -1,6 +0,0 @@ -begin - require 'sorted_set' -rescue ::LoadError - raise "The `SortedSet` class has been extracted from the `set` library. " \ - "You must use the `sorted_set` gem or other alternatives." -end diff --git a/lib/set/subclass_compatible.rb b/lib/set/subclass_compatible.rb new file mode 100644 index 0000000000..f43c34f6a2 --- /dev/null +++ b/lib/set/subclass_compatible.rb @@ -0,0 +1,347 @@ +# frozen_string_literal: true + +# :markup: markdown +# +# set/subclass_compatible.rb - Provides compatibility for set subclasses +# +# Copyright (c) 2002-2024 Akinori MUSHA <knu@iDaemons.org> +# +# Documentation by Akinori MUSHA and Gavin Sinclair. +# +# All rights reserved. You can redistribute and/or modify it under the same +# terms as Ruby. + + +class Set + # This module is automatically included in subclasses of Set, to + # make them backwards compatible with the pure-Ruby set implementation + # used before Ruby 4. Users who want to use Set subclasses without + # this compatibility layer should subclass from Set::CoreSet. + # + # Note that Set subclasses that access `@hash` are not compatible even + # with this support. Such subclasses must be updated to support Ruby 4. + module SubclassCompatible + module ClassMethods + def [](*ary) + new(ary) + end + end + + # Creates a new set containing the elements of the given enumerable + # object. + # + # If a block is given, the elements of enum are preprocessed by the + # given block. + # + # Set.new([1, 2]) #=> #<Set: {1, 2}> + # Set.new([1, 2, 1]) #=> #<Set: {1, 2}> + # Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}> + # Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}> + # Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}> + def initialize(enum = nil, &block) # :yields: o + enum.nil? and return + + if block + do_with_enum(enum) { |o| add(block[o]) } + else + merge(enum) + end + end + + def do_with_enum(enum, &block) # :nodoc: + if enum.respond_to?(:each_entry) + enum.each_entry(&block) if block + elsif enum.respond_to?(:each) + enum.each(&block) if block + else + raise ArgumentError, "value must be enumerable" + end + end + private :do_with_enum + + def replace(enum) + if enum.instance_of?(self.class) + super + else + do_with_enum(enum) # make sure enum is enumerable before calling clear + clear + merge(enum) + end + end + + def to_set(&block) + return self if instance_of?(Set) && block.nil? + Set.new(self, &block) + end + + def flatten_merge(set, seen = {}) # :nodoc: + set.each { |e| + if e.is_a?(Set) + case seen[e_id = e.object_id] + when true + raise ArgumentError, "tried to flatten recursive Set" + when false + next + end + + seen[e_id] = true + flatten_merge(e, seen) + seen[e_id] = false + else + add(e) + end + } + + self + end + protected :flatten_merge + + def flatten + self.class.new.flatten_merge(self) + end + + def flatten! + replace(flatten()) if any?(Set) + end + + def superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size >= set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias >= superset? + + def proper_superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size > set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias > proper_superset? + + def subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size <= set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias <= subset? + + def proper_subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size < set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias < proper_subset? + + def <=>(set) + return unless set.is_a?(Set) + + case size <=> set.size + when -1 then -1 if proper_subset?(set) + when +1 then +1 if proper_superset?(set) + else 0 if self.==(set) + end + end + + def intersect?(set) + case set + when Set + if size < set.size + any?(set) + else + set.any?(self) + end + when Enumerable + set.any?(self) + else + raise ArgumentError, "value must be enumerable" + end + end + + def disjoint?(set) + !intersect?(set) + end + + def add?(o) + add(o) unless include?(o) + end + + def delete?(o) + delete(o) if include?(o) + end + + def delete_if(&block) + block_given? or return enum_for(__method__) { size } + select(&block).each { |o| delete(o) } + self + end + + def keep_if(&block) + block_given? or return enum_for(__method__) { size } + reject(&block).each { |o| delete(o) } + self + end + + def collect! + block_given? or return enum_for(__method__) { size } + set = self.class.new + each { |o| set << yield(o) } + replace(set) + end + alias map! collect! + + def reject!(&block) + block_given? or return enum_for(__method__) { size } + n = size + delete_if(&block) + self if size != n + end + + def select!(&block) + block_given? or return enum_for(__method__) { size } + n = size + keep_if(&block) + self if size != n + end + + alias filter! select! + + def merge(*enums, **nil) + enums.each do |enum| + if enum.instance_of?(self.class) + super(enum) + else + do_with_enum(enum) { |o| add(o) } + end + end + + self + end + + def subtract(enum) + do_with_enum(enum) { |o| delete(o) } + self + end + + def |(enum) + dup.merge(enum) + end + alias + | + alias union | + + def -(enum) + dup.subtract(enum) + end + alias difference - + + def &(enum) + n = self.class.new + if enum.is_a?(Set) + if enum.size > size + each { |o| n.add(o) if enum.include?(o) } + else + enum.each { |o| n.add(o) if include?(o) } + end + else + do_with_enum(enum) { |o| n.add(o) if include?(o) } + end + n + end + alias intersection & + + def ^(enum) + n = self.class.new(enum) + each { |o| n.add(o) unless n.delete?(o) } + n + end + + def ==(other) + if self.equal?(other) + true + elsif other.instance_of?(self.class) + super + elsif other.is_a?(Set) && self.size == other.size + other.all? { |o| include?(o) } + else + false + end + end + + def eql?(o) # :nodoc: + return false unless o.is_a?(Set) + super + end + + def classify + block_given? or return enum_for(__method__) { size } + + h = {} + + each { |i| + (h[yield(i)] ||= self.class.new).add(i) + } + + h + end + + def join(separator=nil) + to_a.join(separator) + end + + InspectKey = :__inspect_key__ # :nodoc: + + # Returns a string containing a human-readable representation of the + # set ("#<Set: {element1, element2, ...}>"). + def inspect + ids = (Thread.current[InspectKey] ||= []) + + if ids.include?(object_id) + return sprintf('#<%s: {...}>', self.class.name) + end + + ids << object_id + begin + return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) + ensure + ids.pop + end + end + + alias to_s inspect + + def pretty_print(pp) # :nodoc: + pp.group(1, sprintf('#<%s:', self.class.name), '>') { + pp.breakable + pp.group(1, '{', '}') { + pp.seplist(self) { |o| + pp.pp o + } + } + } + end + + def pretty_print_cycle(pp) # :nodoc: + pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') + end + end + private_constant :SubclassCompatible +end |
