diff options
| -rw-r--r-- | .ruby-version | 2 | ||||
| -rw-r--r-- | debian/changelog | 17 | ||||
| -rw-r--r-- | debian/control | 4 | ||||
| -rw-r--r-- | debian/gbp.conf | 4 | ||||
| -rw-r--r-- | debian/salsa-ci.yml | 3 | ||||
| -rw-r--r-- | debian/watch | 9 | ||||
| -rw-r--r-- | lib/immutable/_core.rb | 3067 | ||||
| -rw-r--r-- | lib/immutable/hash.rb | 945 | ||||
| -rw-r--r-- | lib/immutable/set.rb | 586 | ||||
| -rw-r--r-- | lib/immutable/vector.rb | 1558 | ||||
| -rw-r--r-- | lib/immutable/version.rb | 2 |
11 files changed, 3107 insertions, 3090 deletions
diff --git a/.ruby-version b/.ruby-version index cb2b00e..0aec50e 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.1 +3.1.4 diff --git a/debian/changelog b/debian/changelog index 3d3048d..3c84643 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,20 @@ +ruby-immutable-ruby (0.2.0-1) unstable; urgency=medium + + * Team upload. + + [ Lucas Nussbaum ] + * debian/gbp.conf: Add for DEP-14 + * debian/gbp.conf: remove trailing empty lines + * debian/salsa-ci.yml: use team-specific include + + [ Simon Quigley ] + * Upgrade the watch file to version 5. + * New upstream release. + * Update Standards-Version to 4.7.3. + * Drop Rules-Requires-Root field, it is now redundant. + + -- Simon Quigley <tsimonq2@debian.org> Tue, 03 Mar 2026 11:38:55 -0600 + ruby-immutable-ruby (0.1.0-2) unstable; urgency=medium * Team upload diff --git a/debian/control b/debian/control index ec5f013..6126492 100644 --- a/debian/control +++ b/debian/control @@ -1,6 +1,5 @@ Source: ruby-immutable-ruby Section: ruby -Priority: optional Maintainer: Debian Ruby Team <pkg-ruby-extras-maintainers@lists.alioth.debian.org> Uploaders: Cédric Boutillier <boutil@debian.org> Build-Depends: debhelper-compat (= 13), @@ -8,12 +7,11 @@ Build-Depends: debhelper-compat (= 13), ruby-concurrent (>= 1.1), ruby-rspec, ruby-sorted-set (>= 1.0) -Standards-Version: 4.6.1 +Standards-Version: 4.7.3 Vcs-Git: https://salsa.debian.org/ruby-team/ruby-immutable-ruby.git Vcs-Browser: https://salsa.debian.org/ruby-team/ruby-immutable-ruby Homepage: https://github.com/immutable-ruby/immutable-ruby Testsuite: autopkgtest-pkg-ruby -Rules-Requires-Root: no Package: ruby-immutable-ruby Architecture: all diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..6b65fe0 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,4 @@ +[DEFAULT] +debian-branch = debian/latest +upstream-branch = upstream/latest +pristine-tar = True diff --git a/debian/salsa-ci.yml b/debian/salsa-ci.yml index 33c3a64..7b5d2f7 100644 --- a/debian/salsa-ci.yml +++ b/debian/salsa-ci.yml @@ -1,4 +1,3 @@ --- include: - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + - https://salsa.debian.org/ruby-team/meta/raw/master/salsa-ci.yml diff --git a/debian/watch b/debian/watch index 07b48a3..c5347ee 100644 --- a/debian/watch +++ b/debian/watch @@ -1,4 +1,5 @@ -version=4 -opts=uversionmangle=s/[_.+-]?(rc|pre|dev|beta|alpha|b|a)/~$1/i,\ -filenamemangle=s%(?:.+/)?v?@ANY_VERSION@(@ARCHIVE_EXT@)%@PACKAGE@-$1$2% \ - https://github.com/immutable-ruby/immutable-ruby/tags .*/v?@ANY_VERSION@@ARCHIVE_EXT@ +Version: 5 +Template: GitHub +Owner: immutable-ruby +Project: immutable-ruby +Uversion-Mangle: auto diff --git a/lib/immutable/_core.rb b/lib/immutable/_core.rb new file mode 100644 index 0000000..a7bb58e --- /dev/null +++ b/lib/immutable/_core.rb @@ -0,0 +1,3067 @@ +require 'immutable/undefined' +require 'immutable/enumerable' +require 'immutable/trie' +require 'immutable/sorted_set' +require 'set' + +module Immutable + + # An `Immutable::Hash` maps a set of unique keys to corresponding values, much + # like a dictionary maps from words to definitions. Given a key, it can store + # and retrieve an associated value in constant time. If an existing key is + # stored again, the new value will replace the old. It behaves much like + # Ruby's built-in Hash, which we will call RubyHash for clarity. Like + # RubyHash, two keys that are `#eql?` to each other and have the same + # `#hash` are considered identical in an `Immutable::Hash`. + # + # An `Immutable::Hash` can be created in a couple of ways: + # + # Immutable::Hash.new(font_size: 10, font_family: 'Arial') + # Immutable::Hash[first_name: 'John', last_name: 'Smith'] + # + # Any `Enumerable` object which yields two-element `[key, value]` arrays + # can be used to initialize an `Immutable::Hash`: + # + # Immutable::Hash.new([[:first_name, 'John'], [:last_name, 'Smith']]) + # + # Key/value pairs can be added using {#put}. A new hash is returned and the + # existing one is left unchanged: + # + # hash = Immutable::Hash[a: 100, b: 200] + # hash.put(:c, 500) # => Immutable::Hash[:a => 100, :b => 200, :c => 500] + # hash # => Immutable::Hash[:a => 100, :b => 200] + # + # {#put} can also take a block, which is used to calculate the value to be + # stored. + # + # hash.put(:a) { |current| current + 200 } # => Immutable::Hash[:a => 300, :b => 200] + # + # Since it is immutable, all methods which you might expect to "modify" a + # `Immutable::Hash` actually return a new hash and leave the existing one + # unchanged. This means that the `hash[key] = value` syntax from RubyHash + # *cannot* be used with `Immutable::Hash`. + # + # Nested data structures can easily be updated using {#update_in}: + # + # hash = Immutable::Hash["a" => Immutable::Vector[Immutable::Hash["c" => 42]]] + # hash.update_in("a", 0, "c") { |value| value + 5 } + # # => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]] + # + # While an `Immutable::Hash` can iterate over its keys or values, it does not + # guarantee any specific iteration order (unlike RubyHash). Methods like + # {#flatten} do not guarantee the order of returned key/value pairs. + # + # Like RubyHash, an `Immutable::Hash` can have a default block which is used + # when looking up a key that does not exist. Unlike RubyHash, the default + # block will only be passed the missing key, without the hash itself: + # + # hash = Immutable::Hash.new { |missing_key| missing_key * 10 } + # hash[5] # => 50 + class Hash + include Immutable::Enumerable + + class << self + # Create a new `Hash` populated with the given key/value pairs. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2] # => Immutable::Hash["A" => 1, "B" => 2] + # Immutable::Hash[["A", 1], ["B", 2]] # => Immutable::Hash["A" => 1, "B" => 2] + # + # @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided. + # @return [Hash] + def [](pairs = nil) + (pairs.nil? || pairs.empty?) ? empty : new(pairs) + end + + # Return an empty `Hash`. If used on a subclass, returns an empty instance + # of that class. + # + # @return [Hash] + def empty + @empty ||= new + end + + # "Raw" allocation of a new `Hash`. Used internally to create a new + # instance quickly after obtaining a modified {Trie}. + # + # @return [Hash] + # @private + def alloc(trie = EmptyTrie, block = nil) + obj = allocate + obj.instance_variable_set(:@trie, trie) + obj.instance_variable_set(:@default, block) + obj.freeze + end + end + + # @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided. + # @yield [key] Optional _default block_ to be stored and used to calculate the default value of a missing key. It will not be yielded during this method. It will not be preserved when marshalling. + # @yieldparam key Key that was not present in the hash. + def initialize(pairs = nil, &block) + @trie = pairs ? Trie[pairs] : EmptyTrie + @default = block + freeze + end + + # Return the default block if there is one. Otherwise, return `nil`. + # + # @return [Proc] + def default_proc + @default + end + + # Return the number of key/value pairs in this `Hash`. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].size # => 3 + # + # @return [Integer] + def size + @trie.size + end + alias length size + + # Return `true` if this `Hash` contains no key/value pairs. + # + # @return [Boolean] + def empty? + @trie.empty? + end + + # Return `true` if the given key object is present in this `Hash`. More precisely, + # return `true` if a key with the same `#hash` code, and which is also `#eql?` + # to the given key object is present. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].key?("B") # => true + # + # @param key [Object] The key to check for + # @return [Boolean] + def key?(key) + @trie.key?(key) + end + alias has_key? key? + alias include? key? + alias member? key? + + # Return `true` if this `Hash` has one or more keys which map to the provided value. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].value?(2) # => true + # + # @param value [Object] The value to check for + # @return [Boolean] + def value?(value) + each { |k,v| return true if value == v } + false + end + alias has_value? value? + + # Retrieve the value corresponding to the provided key object. If not found, and + # this `Hash` has a default block, the default block is called to provide the + # value. Otherwise, return `nil`. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h["B"] # => 2 + # h.get("B") # => 2 + # h.get("Elephant") # => nil + # + # # Immutable Hash with a default proc: + # h = Immutable::Hash.new("A" => 1, "B" => 2, "C" => 3) { |key| key.size } + # h.get("B") # => 2 + # h.get("Elephant") # => 8 + # + # @param key [Object] The key to look up + # @return [Object] + def get(key) + entry = @trie.get(key) + if entry + entry[1] + elsif @default + @default.call(key) + end + end + alias [] get + + # Retrieve the value corresponding to the given key object, or use the provided + # default value or block, or otherwise raise a `KeyError`. + # + # @overload fetch(key) + # Retrieve the value corresponding to the given key, or raise a `KeyError` + # if it is not found. + # @param key [Object] The key to look up + # @overload fetch(key) { |key| ... } + # Retrieve the value corresponding to the given key, or call the optional + # code block (with the missing key) and get its return value. + # @yield [key] The key which was not found + # @yieldreturn [Object] Object to return since the key was not found + # @param key [Object] The key to look up + # @overload fetch(key, default) + # Retrieve the value corresponding to the given key, or else return + # the provided `default` value. + # @param key [Object] The key to look up + # @param default [Object] Object to return if the key is not found + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.fetch("B") # => 2 + # h.fetch("Elephant") # => KeyError: key not found: "Elephant" + # + # # with a default value: + # h.fetch("B", 99) # => 2 + # h.fetch("Elephant", 99) # => 99 + # + # # with a block: + # h.fetch("B") { |key| key.size } # => 2 + # h.fetch("Elephant") { |key| key.size } # => 8 + # + # @return [Object] + def fetch(key, default = Undefined) + entry = @trie.get(key) + if entry + entry[1] + elsif block_given? + yield(key) + elsif default != Undefined + default + else + raise KeyError, "key not found: #{key.inspect}" + end + end + + # Return a new `Hash` with the existing key/value associations, plus an association + # between the provided key and value. If an equivalent key is already present, its + # associated value will be replaced with the provided one. + # + # If the `value` argument is missing, but an optional code block is provided, + # it will be passed the existing value (or `nil` if there is none) and what it + # returns will replace the existing value. This is useful for "transforming" + # the value associated with a certain key. + # + # Avoid mutating objects which are used as keys. `String`s are an exception: + # unfrozen `String`s which are used as keys are internally duplicated and + # frozen. This matches RubyHash's behaviour. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2] + # h.put("C", 3) + # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.put("B") { |value| value * 10 } + # # => Immutable::Hash["A" => 1, "B" => 20] + # + # @param key [Object] The key to store + # @param value [Object] The value to associate it with + # @yield [value] The previously stored value, or `nil` if none. + # @yieldreturn [Object] The new value to store + # @return [Hash] + def put(key, value = yield(get(key))) + new_trie = @trie.put(key, value) + if new_trie.equal?(@trie) + self + else + self.class.alloc(new_trie, @default) + end + end + + # @private + # @raise NoMethodError + def []=(*) + raise NoMethodError, "Immutable::Hash doesn't support `[]='; use `put' instead" + end + + # Return a new `Hash` with a deeply nested value modified to the result of + # the given code block. When traversing the nested `Hash`es and `Vector`s, + # non-existing keys are created with empty `Hash` values. + # + # The code block receives the existing value of the deeply nested key (or + # `nil` if it doesn't exist). This is useful for "transforming" the value + # associated with a certain key. + # + # Note that the original `Hash` and sub-`Hash`es and sub-`Vector`s are left + # unmodified; new data structure copies are created along the path wherever + # needed. + # + # @example + # hash = Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 42]]] + # hash.update_in("a", "b", "c") { |value| value + 5 } + # # => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]] + # + # @param key_path [::Array<Object>] List of keys which form the path to the key to be modified + # @yield [value] The previously stored value + # @yieldreturn [Object] The new value to store + # @return [Hash] + def update_in(*key_path, &block) + if key_path.empty? + raise ArgumentError, 'must have at least one key in path' + end + key = key_path[0] + if key_path.size == 1 + new_value = block.call(get(key)) + else + value = fetch(key, EmptyHash) + new_value = value.update_in(*key_path[1..-1], &block) + end + put(key, new_value) + end + + # An alias for {#put} to match RubyHash's API. Does not support {#put}'s + # block form. + # + # @see #put + # @param key [Object] The key to store + # @param value [Object] The value to associate it with + # @return [Hash] + def store(key, value) + put(key, value) + end + + # Return a new `Hash` with `key` removed. If `key` is not present, return + # `self`. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].delete("B") + # # => Immutable::Hash["A" => 1, "C" => 3] + # + # @param key [Object] The key to remove + # @return [Hash] + def delete(key) + derive_new_hash(@trie.delete(key)) + end + + # Call the block once for each key/value pair in this `Hash`, passing the key/value + # pair as parameters. No specific iteration order is guaranteed, though the order will + # be stable for any particular `Hash`. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each { |k, v| puts "k=#{k} v=#{v}" } + # + # k=A v=1 + # k=C v=3 + # k=B v=2 + # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # + # @yield [key, value] Once for each key/value pair. + # @return [self] + def each(&block) + return to_enum if not block_given? + @trie.each(&block) + self + end + alias each_pair each + + # Call the block once for each key/value pair in this `Hash`, passing the key/value + # pair as parameters. Iteration order will be the opposite of {#each}. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].reverse_each { |k, v| puts "k=#{k} v=#{v}" } + # + # k=B v=2 + # k=C v=3 + # k=A v=1 + # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # + # @yield [key, value] Once for each key/value pair. + # @return [self] + def reverse_each(&block) + return enum_for(:reverse_each) if not block_given? + @trie.reverse_each(&block) + self + end + + # Call the block once for each key/value pair in this `Hash`, passing the key as a + # parameter. Ordering guarantees are the same as {#each}. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each_key { |k| puts "k=#{k}" } + # + # k=A + # k=C + # k=B + # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # + # @yield [key] Once for each key/value pair. + # @return [self] + def each_key + return enum_for(:each_key) if not block_given? + @trie.each { |k,v| yield k } + self + end + + # Call the block once for each key/value pair in this `Hash`, passing the value as a + # parameter. Ordering guarantees are the same as {#each}. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each_value { |v| puts "v=#{v}" } + # + # v=1 + # v=3 + # v=2 + # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # + # @yield [value] Once for each key/value pair. + # @return [self] + def each_value + return enum_for(:each_value) if not block_given? + @trie.each { |k,v| yield v } + self + end + + # Call the block once for each key/value pair in this `Hash`, passing the key/value + # pair as parameters. The block should return a `[key, value]` array each time. + # All the returned `[key, value]` arrays will be gathered into a new `Hash`. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.map { |k, v| ["new-#{k}", v * v] } + # # => Hash["new-C" => 9, "new-B" => 4, "new-A" => 1] + # + # @yield [key, value] Once for each key/value pair. + # @return [Hash] + def map + return enum_for(:map) unless block_given? + return self if empty? + self.class.new(super, &@default) + end + alias collect map + + # Return a new `Hash` with all the key/value pairs for which the block returns true. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.select { |k, v| v >= 2 } + # # => Immutable::Hash["B" => 2, "C" => 3] + # + # @yield [key, value] Once for each key/value pair. + # @yieldreturn Truthy if this pair should be present in the new `Hash`. + # @return [Hash] + def select(&block) + return enum_for(:select) unless block_given? + derive_new_hash(@trie.select(&block)) + end + alias find_all select + alias keep_if select + + # Yield `[key, value]` pairs until one is found for which the block returns true. + # Return that `[key, value]` pair. If the block never returns true, return `nil`. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.find { |k, v| v.even? } + # # => ["B", 2] + # + # @return [Array] + # @yield [key, value] At most once for each key/value pair, until the block returns `true`. + # @yieldreturn Truthy to halt iteration and return the yielded key/value pair. + def find + return enum_for(:find) unless block_given? + each { |entry| return entry if yield entry } + nil + end + alias detect find + + # Return a new `Hash` containing all the key/value pairs from this `Hash` and + # `other`. If no block is provided, the value for entries with colliding keys + # will be that from `other`. Otherwise, the value for each duplicate key is + # determined by calling the block. + # + # `other` can be an `Immutable::Hash`, a built-in Ruby `Hash`, or any `Enumerable` + # object which yields `[key, value]` pairs. + # + # @example + # h1 = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h2 = Immutable::Hash["C" => 70, "D" => 80] + # h1.merge(h2) + # # => Immutable::Hash["C" => 70, "A" => 1, "D" => 80, "B" => 2] + # h1.merge(h2) { |key, v1, v2| v1 + v2 } + # # => Immutable::Hash["C" => 73, "A" => 1, "D" => 80, "B" => 2] + # + # @param other [::Enumerable] The collection to merge with + # @yieldparam key [Object] The key which was present in both collections + # @yieldparam my_value [Object] The associated value from this `Hash` + # @yieldparam other_value [Object] The associated value from the other collection + # @yieldreturn [Object] The value to associate this key with in the new `Hash` + # @return [Hash] + def merge(other) + trie = if block_given? + other.reduce(@trie) do |trie, (key, value)| + if (entry = trie.get(key)) + trie.put(key, yield(key, entry[1], value)) + else + trie.put(key, value) + end + end + else + @trie.bulk_put(other) + end + + derive_new_hash(trie) + end + + # Retrieve the value corresponding to the given key object, or use the provided + # default value or block, or otherwise raise a `KeyError`. + # + # @overload fetch(key) + # Retrieve the value corresponding to the given key, or raise a `KeyError` + # if it is not found. + # @param key [Object] The key to look up + # @overload fetch(key) { |key| ... } + + # Return a sorted {Vector} which contains all the `[key, value]` pairs in + # this `Hash` as two-element `Array`s. + # + # @overload sort + # Uses `#<=>` to determine sorted order. + # @overload sort { |(k1, v1), (k2, v2)| ... } + # Uses the block as a comparator to determine sorted order. + # + # @example + # h = Immutable::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3] + # h.sort { |(k1, v1), (k2, v2)| k1.size <=> k2.size } + # # => Immutable::Vector[["Dog", 1], ["Lion", 3], ["Elephant", 2]] + # @yield [(k1, v1), (k2, v2)] Any number of times with different pairs of key/value associations. + # @yieldreturn [Integer] Negative if the first pair should be sorted + # lower, positive if the latter pair, or 0 if equal. + # + # @see ::Enumerable#sort + # + # @return [Vector] + def sort + Vector.new(super) + end + + # Return a {Vector} which contains all the `[key, value]` pairs in this `Hash` + # as two-element Arrays. The order which the pairs will appear in is determined by + # passing each pair to the code block to obtain a sort key object, and comparing + # the sort keys using `#<=>`. + # + # @see ::Enumerable#sort_by + # + # @example + # h = Immutable::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3] + # h.sort_by { |key, value| key.size } + # # => Immutable::Vector[["Dog", 1], ["Lion", 3], ["Elephant", 2]] + # + # @yield [key, value] Once for each key/value pair. + # @yieldreturn a sort key object for the yielded pair. + # @return [Vector] + def sort_by + Vector.new(super) + end + + # Return a new `Hash` with the associations for all of the given `keys` removed. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.except("A", "C") # => Immutable::Hash["B" => 2] + # + # @param keys [Array] The keys to remove + # @return [Hash] + def except(*keys) + keys.reduce(self) { |hash, key| hash.delete(key) } + end + + # Return a new `Hash` with only the associations for the `wanted` keys retained. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.slice("B", "C") # => Immutable::Hash["B" => 2, "C" => 3] + # + # @param wanted [::Enumerable] The keys to retain + # @return [Hash] + def slice(*wanted) + trie = Trie.new(0) + wanted.each { |key| trie.put!(key, get(key)) if key?(key) } + self.class.alloc(trie, @default) + end + + # Return a {Vector} of the values which correspond to the `wanted` keys. + # If any of the `wanted` keys are not present in this `Hash`, `nil` will be + # placed instead, or the result of the default proc (if one is defined), + # similar to the behavior of {#get}. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.values_at("B", "A", "D") # => Immutable::Vector[2, 1, nil] + # + # @param wanted [Array] The keys to retrieve + # @return [Vector] + def values_at(*wanted) + Vector.new(wanted.map { |key| get(key) }.freeze) + end + + # Return a {Vector} of the values which correspond to the `wanted` keys. + # If any of the `wanted` keys are not present in this `Hash`, raise `KeyError` + # exception. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.fetch_values("C", "A") # => Immutable::Vector[3, 1] + # h.fetch_values("C", "Z") # => KeyError: key not found: "Z" + # + # @param wanted [Array] The keys to retrieve + # @return [Vector] + def fetch_values(*wanted) + array = wanted.map { |key| fetch(key) } + Vector.new(array.freeze) + end + + # Return the value of successively indexing into a nested collection. + # If any of the keys is not present, return `nil`. + # + # @example + # h = Immutable::Hash[a: 9, b: Immutable::Hash[c: 'a', d: 4], e: nil] + # h.dig(:b, :c) # => "a" + # h.dig(:b, :f) # => nil + # + # @return [Object] + def dig(key, *rest) + value = self[key] + if rest.empty? || value.nil? + value + else + value.dig(*rest) + end + end + + # Return a new {Set} containing the keys from this `Hash`. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].keys + # # => Immutable::Set["D", "C", "B", "A"] + # + # @return [Set] + def keys + Set.alloc(@trie) + end + + # Return a new {Vector} populated with the values from this `Hash`. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].values + # # => Immutable::Vector[2, 3, 2, 1] + # + # @return [Vector] + def values + Vector.new(each_value.to_a.freeze) + end + + # Return a new `Hash` created by using keys as values and values as keys. + # If there are multiple values which are equivalent (as determined by `#hash` and + # `#eql?`), only one out of each group of equivalent values will be + # retained. Which one specifically is undefined. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].invert + # # => Immutable::Hash[1 => "A", 3 => "C", 2 => "B"] + # + # @return [Hash] + def invert + pairs = [] + each { |k,v| pairs << [v, k] } + self.class.new(pairs, &@default) + end + + # Return a new {Vector} which is a one-dimensional flattening of this `Hash`. + # If `level` is 1, all the `[key, value]` pairs in the hash will be concatenated + # into one {Vector}. If `level` is greater than 1, keys or values which are + # themselves `Array`s or {Vector}s will be recursively flattened into the output + # {Vector}. The depth to which that flattening will be recursively applied is + # determined by `level`. + # + # As a special case, if `level` is 0, each `[key, value]` pair will be a + # separate element in the returned {Vector}. + # + # @example + # h = Immutable::Hash["A" => 1, "B" => [2, 3, 4]] + # h.flatten + # # => Immutable::Vector["A", 1, "B", [2, 3, 4]] + # h.flatten(2) + # # => Immutable::Vector["A", 1, "B", 2, 3, 4] + # + # @param level [Integer] The number of times to recursively flatten the `[key, value]` pairs in this `Hash`. + # @return [Vector] + def flatten(level = 1) + return Vector.new(self) if level == 0 + array = [] + each { |k,v| array << k; array << v } + array.flatten!(level-1) if level > 1 + Vector.new(array.freeze) + end + + # Searches through the `Hash`, comparing `obj` with each key (using `#==`). + # When a matching key is found, return the `[key, value]` pair as an array. + # Return `nil` if no match is found. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].assoc("B") # => ["B", 2] + # + # @param obj [Object] The key to search for (using #==) + # @return [Array] + def assoc(obj) + each { |entry| return entry if obj == entry[0] } + nil + end + + # Searches through the `Hash`, comparing `obj` with each value (using `#==`). + # When a matching value is found, return the `[key, value]` pair as an array. + # Return `nil` if no match is found. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].rassoc(2) # => ["B", 2] + # + # @param obj [Object] The value to search for (using #==) + # @return [Array] + def rassoc(obj) + each { |entry| return entry if obj == entry[1] } + nil + end + + # Searches through the `Hash`, comparing `value` with each value (using `#==`). + # When a matching value is found, return its associated key object. + # Return `nil` if no match is found. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].key(2) # => "B" + # + # @param value [Object] The value to search for (using #==) + # @return [Object] + def key(value) + each { |entry| return entry[0] if value == entry[1] } + nil + end + + # Return a randomly chosen `[key, value]` pair from this `Hash`. If the hash is empty, + # return `nil`. + # + # @example + # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].sample + # # => ["C", 3] + # + # @return [Array] + def sample + @trie.at(rand(size)) + end + + # Return an empty `Hash` instance, of the same class as this one. Useful if you + # have multiple subclasses of `Hash` and want to treat them polymorphically. + # Maintains the default block, if there is one. + # + # @return [Hash] + def clear + if @default + self.class.alloc(EmptyTrie, @default) + else + self.class.empty + end + end + + # Return true if `other` has the same type and contents as this `Hash`. + # + # @param other [Object] The collection to compare with + # @return [Boolean] + def eql?(other) + return true if other.equal?(self) + instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie)) + end + + # Return true if `other` has the same contents as this `Hash`. Will convert + # `other` to a Ruby `Hash` using `#to_hash` if necessary. + # + # @param other [Object] The object to compare with + # @return [Boolean] + def ==(other) + eql?(other) || (other.respond_to?(:to_hash) && to_hash == other.to_hash) + end + + # Return true if this `Hash` is a proper superset of `other`, which means + # all `other`'s keys are contained in this `Hash` with identical + # values, and the two hashes are not identical. + # + # @param other [Immutable::Hash] The object to compare with + # @return [Boolean] + def >(other) + self != other && self >= other + end + + # Return true if this `Hash` is a superset of `other`, which means all + # `other`'s keys are contained in this `Hash` with identical values. + # + # @param other [Immutable::Hash] The object to compare with + # @return [Boolean] + def >=(other) + other.each do |key, value| + if self[key] != value + return false + end + end + true + end + + # Return true if this `Hash` is a proper subset of `other`, which means all + # its keys are contained in `other` with the identical values, and the two + # hashes are not identical. + # + # @param other [Immutable::Hash] The object to compare with + # @return [Boolean] + def <(other) + other > self + end + + # Return true if this `Hash` is a subset of `other`, which means all its + # keys are contained in `other` with the identical values, and the two + # hashes are not identical. + # + # @param other [Immutable::Hash] The object to compare with + # @return [Boolean] + def <=(other) + other >= self + end + + # See `Object#hash`. + # @return [Integer] + def hash + keys.to_a.sort.reduce(0) do |hash, key| + (hash << 32) - hash + key.hash + get(key).hash + end + end + + # Return the contents of this `Hash` as a programmer-readable `String`. If all the + # keys and values are serializable as Ruby literal strings, the returned string can + # be passed to `eval` to reconstitute an equivalent `Hash`. The default + # block (if there is one) will be lost when doing this, however. + # + # @return [String] + def inspect + result = "#{self.class}[" + i = 0 + each do |key, val| + result << ', ' if i > 0 + result << key.inspect << ' => ' << val.inspect + i += 1 + end + result << ']' + end + + # Return `self`. Since this is an immutable object duplicates are + # equivalent. + # @return [Hash] + def dup + self + end + alias clone dup + + # Allows this `Hash` to be printed at the `pry` console, or using `pp` (from the + # Ruby standard library), in a way which takes the amount of horizontal space on + # the screen into account, and which indents nested structures to make them easier + # to read. + # + # @private + def pretty_print(pp) + pp.group(1, "#{self.class}[", ']') do + pp.breakable '' + pp.seplist(self, nil) do |key, val| + pp.group do + key.pretty_print(pp) + pp.text ' => ' + pp.group(1) do + pp.breakable '' + val.pretty_print(pp) + end + end + end + end + end + + # Convert this `Immutable::Hash` to an instance of Ruby's built-in `Hash`. + # + # @return [::Hash] + def to_hash + output = {} + each do |key, value| + output[key] = value + end + output + end + alias to_h to_hash + + # Return a `Proc` which accepts a key as an argument and returns the value. + # The `Proc` behaves like {#get} (when the key is missing, it returns nil or + # the result of the default proc). + # + # @example + # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] + # h.to_proc.call("B") + # # => 2 + # ["A", "C", "X"].map(&h) # The & is short for .to_proc in Ruby + # # => [1, 3, nil] + # + # @return [Proc] + def to_proc + lambda { |key| get(key) } + end + + # @return [::Hash] + # @private + def marshal_dump + to_hash + end + + # @private + def marshal_load(dictionary) + @trie = Trie[dictionary] + end + + private + + # Return a new `Hash` which is derived from this one, using a modified {Trie}. + # The new `Hash` will retain the existing default block, if there is one. + # + def derive_new_hash(trie) + if trie.equal?(@trie) + self + elsif trie.empty? + if @default + self.class.alloc(EmptyTrie, @default) + else + self.class.empty + end + else + self.class.alloc(trie, @default) + end + end + end + + # The canonical empty `Hash`. Returned by `Hash[]` when + # invoked with no arguments; also returned by `Hash.empty`. Prefer using this + # one rather than creating many empty hashes using `Hash.new`. + # + # @private + EmptyHash = Immutable::Hash.empty + + + # A `Vector` is an ordered, integer-indexed collection of objects. Like + # Ruby's `Array`, `Vector` indexing starts at zero and negative indexes count + # back from the end. + # + # `Vector` has a similar interface to `Array`. The main difference is methods + # that would destructively update an `Array` (such as {#insert} or + # {#delete_at}) instead return new `Vectors` and leave the existing one + # unchanged. + # + # ### Creating New Vectors + # + # Immutable::Vector.new([:first, :second, :third]) + # Immutable::Vector[1, 2, 3, 4, 5] + # + # ### Retrieving Items from Vectors + # + # vector = Immutable::Vector[1, 2, 3, 4, 5] + # + # vector[0] # => 1 + # vector[-1] # => 5 + # vector[0,3] # => Immutable::Vector[1, 2, 3] + # vector[1..-1] # => Immutable::Vector[2, 3, 4, 5] + # vector.first # => 1 + # vector.last # => 5 + # + # ### Creating Modified Vectors + # + # vector.add(6) # => Immutable::Vector[1, 2, 3, 4, 5, 6] + # vector.insert(1, :a, :b) # => Immutable::Vector[1, :a, :b, 2, 3, 4, 5] + # vector.delete_at(2) # => Immutable::Vector[1, 2, 4, 5] + # vector + [6, 7] # => Immutable::Vector[1, 2, 3, 4, 5, 6, 7] + # + class Vector + include Immutable::Enumerable + + # @private + BLOCK_SIZE = 32 + # @private + INDEX_MASK = BLOCK_SIZE - 1 + # @private + BITS_PER_LEVEL = 5 + + # Return the number of items in this `Vector` + # @return [Integer] + attr_reader :size + alias length size + + class << self + # Create a new `Vector` populated with the given items. + # @return [Vector] + def [](*items) + new(items.freeze) + end + + # Return an empty `Vector`. If used on a subclass, returns an empty instance + # of that class. + # + # @return [Vector] + def empty + @empty ||= new + end + + # "Raw" allocation of a new `Vector`. Used internally to create a new + # instance quickly after building a modified trie. + # + # @return [Vector] + # @private + def alloc(root, size, levels) + obj = allocate + obj.instance_variable_set(:@root, root) + obj.instance_variable_set(:@size, size) + obj.instance_variable_set(:@levels, levels) + obj.freeze + end + end + + def initialize(items=[].freeze) + items = items.to_a + if items.size <= 32 + items = items.dup.freeze if !items.frozen? + @root, @size, @levels = items, items.size, 0 + else + root, size, levels = items, items.size, 0 + while root.size > 32 + root = root.each_slice(32).to_a + levels += 1 + end + @root, @size, @levels = root.freeze, size, levels + end + freeze + end + + # Return `true` if this `Vector` contains no items. + # + # @return [Boolean] + def empty? + @size == 0 + end + + # Return the first item in the `Vector`. If the vector is empty, return `nil`. + # + # @example + # Immutable::Vector["A", "B", "C"].first # => "A" + # + # @return [Object] + def first + get(0) + end + + # Return the last item in the `Vector`. If the vector is empty, return `nil`. + # + # @example + # Immutable::Vector["A", "B", "C"].last # => "C" + # + # @return [Object] + def last + get(-1) + end + + # Return a new `Vector` with `item` added after the last occupied position. + # + # @example + # Immutable::Vector[1, 2].add(99) # => Immutable::Vector[1, 2, 99] + # + # @param item [Object] The object to insert at the end of the vector + # @return [Vector] + def add(item) + update_root(@size, item) + end + alias << add + alias push add + + # Return a new `Vector` with a new value at the given `index`. If `index` + # is greater than the length of the vector, the returned vector will be + # padded with `nil`s to the correct size. + # + # @overload set(index, item) + # Return a new `Vector` with the item at `index` replaced by `item`. + # + # @param item [Object] The object to insert into that position + # @example + # Immutable::Vector[1, 2, 3, 4].set(2, 99) + # # => Immutable::Vector[1, 2, 99, 4] + # Immutable::Vector[1, 2, 3, 4].set(-1, 99) + # # => Immutable::Vector[1, 2, 3, 99] + # Immutable::Vector[].set(2, 99) + # # => Immutable::Vector[nil, nil, 99] + # + # @overload set(index) + # Return a new `Vector` with the item at `index` replaced by the return + # value of the block. + # + # @yield (existing) Once with the existing value at the given `index`. + # @example + # Immutable::Vector[1, 2, 3, 4].set(2) { |v| v * 10 } + # # => Immutable::Vector[1, 2, 30, 4] + # + # @param index [Integer] The index to update. May be negative. + # @return [Vector] + def set(index, item = yield(get(index))) + raise IndexError, "index #{index} outside of vector bounds" if index < -@size + index += @size if index < 0 + if index > @size + suffix = Array.new(index - @size, nil) + suffix << item + replace_suffix(@size, suffix) + else + update_root(index, item) + end + end + + # Return a new `Vector` with a deeply nested value modified to the result + # of the given code block. When traversing the nested `Vector`s and + # `Hash`es, non-existing keys are created with empty `Hash` values. + # + # The code block receives the existing value of the deeply nested key (or + # `nil` if it doesn't exist). This is useful for "transforming" the value + # associated with a certain key. + # + # Note that the original `Vector` and sub-`Vector`s and sub-`Hash`es are + # left unmodified; new data structure copies are created along the path + # wherever needed. + # + # @example + # v = Immutable::Vector[123, 456, 789, Immutable::Hash["a" => Immutable::Vector[5, 6, 7]]] + # v.update_in(3, "a", 1) { |value| value + 9 } + # # => Immutable::Vector[123, 456, 789, Immutable::Hash["a" => Immutable::Vector[5, 15, 7]]] + # + # @param key_path [Object(s)] List of keys which form the path to the key to be modified + # @yield [value] The previously stored value + # @yieldreturn [Object] The new value to store + # @return [Vector] + def update_in(*key_path, &block) + if key_path.empty? + raise ArgumentError, 'must have at least one key in path' + end + key = key_path[0] + if key_path.size == 1 + new_value = block.call(get(key)) + else + value = fetch(key, Immutable::EmptyHash) + new_value = value.update_in(*key_path[1..-1], &block) + end + set(key, new_value) + end + + # Retrieve the item at `index`. If there is none (either the provided index + # is too high or too low), return `nil`. + # + # @example + # v = Immutable::Vector["A", "B", "C", "D"] + # v.get(2) # => "C" + # v.get(-1) # => "D" + # v.get(4) # => nil + # + # @param index [Integer] The index to retrieve + # @return [Object] + def get(index) + return nil if @size == 0 + index += @size if index < 0 + return nil if index >= @size || index < 0 + leaf_node_for(@root, @levels * BITS_PER_LEVEL, index)[index & INDEX_MASK] + end + alias at get + + # Retrieve the value at `index` with optional default. + # + # @overload fetch(index) + # Retrieve the value at the given index, or raise an `IndexError` if not + # found. + # + # @param index [Integer] The index to look up + # @raise [IndexError] if index does not exist + # @example + # v = Immutable::Vector["A", "B", "C", "D"] + # v.fetch(2) # => "C" + # v.fetch(-1) # => "D" + # v.fetch(4) # => IndexError: index 4 outside of vector bounds + # + # @overload fetch(index) { |index| ... } + # Retrieve the value at the given index, or return the result of yielding + # the block if not found. + # + # @yield Once if the index is not found. + # @yieldparam [Integer] index The index which does not exist + # @yieldreturn [Object] Default value to return + # @param index [Integer] The index to look up + # @example + # v = Immutable::Vector["A", "B", "C", "D"] + # v.fetch(2) { |i| i * i } # => "C" + # v.fetch(4) { |i| i * i } # => 16 + # + # @overload fetch(index, default) + # Retrieve the value at the given index, or return the provided `default` + # value if not found. + # + # @param index [Integer] The index to look up + # @param default [Object] Object to return if the key is not found + # @example + # v = Immutable::Vector["A", "B", "C", "D"] + # v.fetch(2, "Z") # => "C" + # v.fetch(4, "Z") # => "Z" + # + # @return [Object] + def fetch(index, default = (missing_default = true)) + if index >= -@size && index < @size + get(index) + elsif block_given? + yield(index) + elsif !missing_default + default + else + raise IndexError, "index #{index} outside of vector bounds" + end + end + + # Return the value of successively indexing into a nested collection. + # If any of the keys is not present, return `nil`. + # + # @example + # v = Immutable::Vector[9, Immutable::Hash[c: 'a', d: 4]] + # v.dig(1, :c) # => "a" + # v.dig(1, :f) # => nil + # + # @return [Object] + def dig(key, *rest) + value = self[key] + if rest.empty? || value.nil? + value + else + value.dig(*rest) + end + end + + # Return specific objects from the `Vector`. All overloads return `nil` if + # the starting index is out of range. + # + # @overload vector.slice(index) + # Returns a single object at the given `index`. If `index` is negative, + # count backwards from the end. + # + # @param index [Integer] The index to retrieve. May be negative. + # @return [Object] + # @example + # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] + # v[2] # => "C" + # v[-1] # => "F" + # v[6] # => nil + # + # @overload vector.slice(index, length) + # Return a subvector starting at `index` and continuing for `length` + # elements or until the end of the `Vector`, whichever occurs first. + # + # @param start [Integer] The index to start retrieving items from. May be + # negative. + # @param length [Integer] The number of items to retrieve. + # @return [Vector] + # @example + # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] + # v[2, 3] # => Immutable::Vector["C", "D", "E"] + # v[-2, 3] # => Immutable::Vector["E", "F"] + # v[20, 1] # => nil + # + # @overload vector.slice(index..end) + # Return a subvector starting at `index` and continuing to index + # `end` or the end of the `Vector`, whichever occurs first. + # + # @param range [Range] The range of indices to retrieve. + # @return [Vector] + # @example + # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] + # v[2..3] # => Immutable::Vector["C", "D"] + # v[-2..100] # => Immutable::Vector["E", "F"] + # v[20..21] # => nil + def slice(arg, length = (missing_length = true)) + if missing_length + if arg.is_a?(Range) + from, to = arg.begin, arg.end + from += @size if from < 0 + to += @size if to < 0 + to += 1 if !arg.exclude_end? + length = to - from + length = 0 if length < 0 + subsequence(from, length) + else + get(arg) + end + else + arg += @size if arg < 0 + subsequence(arg, length) + end + end + alias [] slice + + # Return a new `Vector` with the given values inserted before the element + # at `index`. If `index` is greater than the current length, `nil` values + # are added to pad the `Vector` to the required size. + # + # @example + # Immutable::Vector["A", "B", "C", "D"].insert(2, "X", "Y", "Z") + # # => Immutable::Vector["A", "B", "X", "Y", "Z", "C", "D"] + # Immutable::Vector[].insert(2, "X", "Y", "Z") + # # => Immutable::Vector[nil, nil, "X", "Y", "Z"] + # + # @param index [Integer] The index where the new items should go + # @param items [Array] The items to add + # @return [Vector] + # @raise [IndexError] if index exceeds negative range. + def insert(index, *items) + raise IndexError if index < -@size + index += @size if index < 0 + + if index < @size + suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, []) + suffix.unshift(*items) + elsif index == @size + suffix = items + else + suffix = Array.new(index - @size, nil).concat(items) + index = @size + end + + replace_suffix(index, suffix) + end + + # Return a new `Vector` with the element at `index` removed. If the given `index` + # does not exist, return `self`. + # + # @example + # Immutable::Vector["A", "B", "C", "D"].delete_at(2) + # # => Immutable::Vector["A", "B", "D"] + # + # @param index [Integer] The index to remove + # @return [Vector] + def delete_at(index) + return self if index >= @size || index < -@size + index += @size if index < 0 + + suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, []) + replace_suffix(index, suffix.tap(&:shift)) + end + + # Return a new `Vector` with the last element removed. Return `self` if + # empty. + # + # @example + # Immutable::Vector["A", "B", "C"].pop # => Immutable::Vector["A", "B"] + # + # @return [Vector] + def pop + return self if @size == 0 + replace_suffix(@size-1, []) + end + + # Return a new `Vector` with `object` inserted before the first element, + # moving the other elements upwards. + # + # @example + # Immutable::Vector["A", "B"].unshift("Z") + # # => Immutable::Vector["Z", "A", "B"] + # + # @param object [Object] The value to prepend + # @return [Vector] + def unshift(object) + insert(0, object) + end + + # Return a new `Vector` with the first element removed. If empty, return + # `self`. + # + # @example + # Immutable::Vector["A", "B", "C"].shift # => Immutable::Vector["B", "C"] + # + # @return [Vector] + def shift + delete_at(0) + end + + # Call the given block once for each item in the vector, passing each + # item from first to last successively to the block. If no block is given, + # an `Enumerator` is returned instead. + # + # @example + # Immutable::Vector["A", "B", "C"].each { |e| puts "Element: #{e}" } + # + # Element: A + # Element: B + # Element: C + # # => Immutable::Vector["A", "B", "C"] + # + # @return [self, Enumerator] + def each(&block) + return to_enum unless block_given? + traverse_depth_first(@root, @levels, &block) + self + end + + # Call the given block once for each item in the vector, from last to + # first. + # + # @example + # Immutable::Vector["A", "B", "C"].reverse_each { |e| puts "Element: #{e}" } + # + # Element: C + # Element: B + # Element: A + # + # @return [self] + def reverse_each(&block) + return enum_for(:reverse_each) unless block_given? + reverse_traverse_depth_first(@root, @levels, &block) + self + end + + # Return a new `Vector` containing all elements for which the given block returns + # true. + # + # @example + # Immutable::Vector["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } + # # => Immutable::Vector["Bird", "Elephant"] + # + # @return [Vector] + # @yield [element] Once for each element. + def select + return enum_for(:select) unless block_given? + reduce(self.class.empty) { |vector, item| yield(item) ? vector.add(item) : vector } + end + alias find_all select + alias keep_if select + + # Return a new `Vector` with all items which are equal to `obj` removed. + # `#==` is used for checking equality. + # + # @example + # Immutable::Vector["C", "B", "A", "B"].delete("B") # => Immutable::Vector["C", "A"] + # + # @param obj [Object] The object to remove (every occurrence) + # @return [Vector] + def delete(obj) + select { |item| item != obj } + end + + # Invoke the given block once for each item in the vector, and return a new + # `Vector` containing the values returned by the block. If no block is + # provided, return an enumerator. + # + # @example + # Immutable::Vector[3, 2, 1].map { |e| e * e } # => Immutable::Vector[9, 4, 1] + # + # @return [Vector, Enumerator] + def map + return enum_for(:map) if not block_given? + return self if empty? + self.class.new(super) + end + alias collect map + + # Return a new `Vector` with the concatenated results of running the block once + # for every element in this `Vector`. + # + # @example + # Immutable::Vector[1, 2, 3].flat_map { |x| [x, -x] } + # # => Immutable::Vector[1, -1, 2, -2, 3, -3] + # + # @return [Vector] + def flat_map + return enum_for(:flat_map) if not block_given? + return self if empty? + self.class.new(super) + end + + # Return a new `Vector` with the same elements as this one, but randomly permuted. + # + # @example + # Immutable::Vector[1, 2, 3, 4].shuffle # => Immutable::Vector[4, 1, 3, 2] + # + # @return [Vector] + def shuffle + self.class.new(((array = to_a).frozen? ? array.shuffle : array.shuffle!).freeze) + end + + # Return a new `Vector` with no duplicate elements, as determined by `#hash` and + # `#eql?`. For each group of equivalent elements, only the first will be retained. + # + # @example + # Immutable::Vector["A", "B", "C", "B"].uniq # => Immutable::Vector["A", "B", "C"] + # Immutable::Vector["a", "A", "b"].uniq(&:upcase) # => Immutable::Vector["a", "b"] + # + # @return [Vector] + def uniq(&block) + array = to_a + if array.frozen? + self.class.new(array.uniq(&block).freeze) + elsif array.uniq!(&block) # returns nil if no changes were made + self.class.new(array.freeze) + else + self + end + end + + # Return a new `Vector` with the same elements as this one, but in reverse order. + # + # @example + # Immutable::Vector["A", "B", "C"].reverse # => Immutable::Vector["C", "B", "A"] + # + # @return [Vector] + def reverse + self.class.new(((array = to_a).frozen? ? array.reverse : array.reverse!).freeze) + end + + # Return a new `Vector` with the same elements, but rotated so that the one at + # index `count` is the first element of the new vector. If `count` is positive, + # the elements will be shifted left, and those shifted past the lowest position + # will be moved to the end. If `count` is negative, the elements will be shifted + # right, and those shifted past the last position will be moved to the beginning. + # + # @example + # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] + # v.rotate(2) # => Immutable::Vector["C", "D", "E", "F", "A", "B"] + # v.rotate(-1) # => Immutable::Vector["F", "A", "B", "C", "D", "E"] + # + # @param count [Integer] The number of positions to shift items by + # @return [Vector] + def rotate(count = 1) + return self if (count % @size) == 0 + self.class.new(((array = to_a).frozen? ? array.rotate(count) : array.rotate!(count)).freeze) + end + + # Return a new `Vector` with all nested vectors and arrays recursively "flattened + # out". That is, their elements inserted into the new `Vector` in the place where + # the nested array/vector originally was. If an optional `level` argument is + # provided, the flattening will only be done recursively that number of times. + # A `level` of 0 means not to flatten at all, 1 means to only flatten nested + # arrays/vectors which are directly contained within this `Vector`. + # + # @example + # v = Immutable::Vector["A", Immutable::Vector["B", "C", Immutable::Vector["D"]]] + # v.flatten(1) + # # => Immutable::Vector["A", "B", "C", Immutable::Vector["D"]] + # v.flatten + # # => Immutable::Vector["A", "B", "C", "D"] + # + # @param level [Integer] The depth to which flattening should be applied + # @return [Vector] + def flatten(level = -1) + return self if level == 0 + array = to_a + if array.frozen? + self.class.new(array.flatten(level).freeze) + elsif array.flatten!(level) # returns nil if no changes were made + self.class.new(array.freeze) + else + self + end + end + + # Return a new `Vector` built by concatenating this one with `other`. `other` + # can be any object which is convertible to an `Array` using `#to_a`. + # + # @example + # Immutable::Vector["A", "B", "C"] + ["D", "E"] + # # => Immutable::Vector["A", "B", "C", "D", "E"] + # + # @param other [Enumerable] The collection to concatenate onto this vector + # @return [Vector] + def +(other) + other = other.to_a + other = other.dup if other.frozen? + replace_suffix(@size, other) + end + alias concat + + + # Combine two vectors by "zipping" them together. `others` should be arrays + # and/or vectors. The corresponding elements from this `Vector` and each of + # `others` (that is, the elements with the same indices) will be gathered + # into arrays. + # + # If `others` contains fewer elements than this vector, `nil` will be used + # for padding. + # + # @overload zip(*others) + # Return a new vector containing the new arrays. + # + # @return [Vector] + # + # @overload zip(*others) + # @yield [pair] once for each array + # @return [nil] + # + # @example + # v1 = Immutable::Vector["A", "B", "C"] + # v2 = Immutable::Vector[1, 2] + # v1.zip(v2) + # # => Immutable::Vector[["A", 1], ["B", 2], ["C", nil]] + # + # @param others [Array] The arrays/vectors to zip together with this one + # @return [Vector] + def zip(*others) + if block_given? + super + else + self.class.new(super) + end + end + + # Return a new `Vector` with the same items, but sorted. + # + # @overload sort + # Compare elements with their natural sort key (`#<=>`). + # + # @example + # Immutable::Vector["Elephant", "Dog", "Lion"].sort + # # => Immutable::Vector["Dog", "Elephant", "Lion"] + # + # @overload sort + # Uses the block as a comparator to determine sorted order. + # + # @yield [a, b] Any number of times with different pairs of elements. + # @yieldreturn [Integer] Negative if the first element should be sorted + # lower, positive if the latter element, or 0 if + # equal. + # @example + # Immutable::Vector["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } + # # => Immutable::Vector["Dog", "Lion", "Elephant"] + # + # @return [Vector] + def sort + self.class.new(super) + end + + # Return a new `Vector` with the same items, but sorted. The sort order is + # determined by mapping the items through the given block to obtain sort + # keys, and then sorting the keys according to their natural sort order + # (`#<=>`). + # + # @yield [element] Once for each element. + # @yieldreturn a sort key object for the yielded element. + # @example + # Immutable::Vector["Elephant", "Dog", "Lion"].sort_by { |e| e.size } + # # => Immutable::Vector["Dog", "Lion", "Elephant"] + # + # @return [Vector] + def sort_by + self.class.new(super) + end + + # Drop the first `n` elements and return the rest in a new `Vector`. + # + # @example + # Immutable::Vector["A", "B", "C", "D", "E", "F"].drop(2) + # # => Immutable::Vector["C", "D", "E", "F"] + # + # @param n [Integer] The number of elements to remove + # @return [Vector] + # @raise ArgumentError if `n` is negative. + def drop(n) + return self if n == 0 + return self.class.empty if n >= @size + raise ArgumentError, 'attempt to drop negative size' if n < 0 + self.class.new(flatten_suffix(@root, @levels * BITS_PER_LEVEL, n, [])) + end + + # Return only the first `n` elements in a new `Vector`. + # + # @example + # Immutable::Vector["A", "B", "C", "D", "E", "F"].take(4) + # # => Immutable::Vector["A", "B", "C", "D"] + # + # @param n [Integer] The number of elements to retain + # @return [Vector] + def take(n) + return self if n >= @size + self.class.new(super) + end + + # Drop elements up to, but not including, the first element for which the + # block returns `nil` or `false`. Gather the remaining elements into a new + # `Vector`. If no block is given, an `Enumerator` is returned instead. + # + # @example + # Immutable::Vector[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 } + # # => Immutable::Vector[5, 7, 6, 4, 2] + # + # @return [Vector, Enumerator] + def drop_while + return enum_for(:drop_while) if not block_given? + self.class.new(super) + end + + # Gather elements up to, but not including, the first element for which the + # block returns `nil` or `false`, and return them in a new `Vector`. If no block + # is given, an `Enumerator` is returned instead. + # + # @example + # Immutable::Vector[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 } + # # => Immutable::Vector[1, 3] + # + # @return [Vector, Enumerator] + def take_while + return enum_for(:take_while) if not block_given? + self.class.new(super) + end + + # Repetition. Return a new `Vector` built by concatenating `times` copies + # of this one together. + # + # @example + # Immutable::Vector["A", "B"] * 3 + # # => Immutable::Vector["A", "B", "A", "B", "A", "B"] + # + # @param times [Integer] The number of times to repeat the elements in this vector + # @return [Vector] + def *(times) + return self.class.empty if times == 0 + return self if times == 1 + result = (to_a * times) + result.is_a?(Array) ? self.class.new(result) : result + end + + # Replace a range of indexes with the given object. + # + # @overload fill(object) + # Return a new `Vector` of the same size, with every index set to + # `object`. + # + # @param [Object] object Fill value. + # @example + # Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z") + # # => Immutable::Vector["Z", "Z", "Z", "Z", "Z", "Z"] + # + # @overload fill(object, index) + # Return a new `Vector` with all indexes from `index` to the end of the + # vector set to `object`. + # + # @param [Object] object Fill value. + # @param [Integer] index Starting index. May be negative. + # @example + # Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3) + # # => Immutable::Vector["A", "B", "C", "Z", "Z", "Z"] + # + # @overload fill(object, index, length) + # Return a new `Vector` with `length` indexes, beginning from `index`, + # set to `object`. Expands the `Vector` if `length` would extend beyond + # the current length. + # + # @param [Object] object Fill value. + # @param [Integer] index Starting index. May be negative. + # @param [Integer] length + # @example + # Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2) + # # => Immutable::Vector["A", "B", "C", "Z", "Z", "F"] + # Immutable::Vector["A", "B"].fill("Z", 1, 5) + # # => Immutable::Vector["A", "Z", "Z", "Z", "Z", "Z"] + # + # @return [Vector] + # @raise [IndexError] if index is out of negative range. + def fill(object, index = 0, length = nil) + raise IndexError if index < -@size + index += @size if index < 0 + length ||= @size - index # to the end of the array, if no length given + + if index < @size + suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, []) + suffix.fill(object, 0, length) + elsif index == @size + suffix = Array.new(length, object) + else + suffix = Array.new(index - @size, nil).concat(Array.new(length, object)) + index = @size + end + + replace_suffix(index, suffix) + end + + # When invoked with a block, yields all combinations of length `n` of items + # from the `Vector`, and then returns `self`. There is no guarantee about + # which order the combinations will be yielded. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @example + # v = Immutable::Vector[5, 6, 7, 8] + # v.combination(3) { |c| puts "Combination: #{c}" } + # + # Combination: [5, 6, 7] + # Combination: [5, 6, 8] + # Combination: [5, 7, 8] + # Combination: [6, 7, 8] + # #=> Immutable::Vector[5, 6, 7, 8] + # + # @return [self, Enumerator] + def combination(n) + return enum_for(:combination, n) if not block_given? + return self if n < 0 || @size < n + if n == 0 + yield [] + elsif n == 1 + each { |item| yield [item] } + elsif n == @size + yield to_a + else + combos = lambda do |result,index,remaining| + while @size - index > remaining + if remaining == 1 + yield result.dup << get(index) + else + combos[result.dup << get(index), index+1, remaining-1] + end + index += 1 + end + index.upto(@size-1) { |i| result << get(i) } + yield result + end + combos[[], 0, n] + end + self + end + + # When invoked with a block, yields all repeated combinations of length `n` of + # items from the `Vector`, and then returns `self`. A "repeated combination" is + # one in which any item from the `Vector` can appear consecutively any number of + # times. + # + # There is no guarantee about which order the combinations will be yielded in. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @example + # v = Immutable::Vector[5, 6, 7, 8] + # v.repeated_combination(2) { |c| puts "Combination: #{c}" } + # + # Combination: [5, 5] + # Combination: [5, 6] + # Combination: [5, 7] + # Combination: [5, 8] + # Combination: [6, 6] + # Combination: [6, 7] + # Combination: [6, 8] + # Combination: [7, 7] + # Combination: [7, 8] + # Combination: [8, 8] + # # => Immutable::Vector[5, 6, 7, 8] + # + # @return [self, Enumerator] + def repeated_combination(n) + return enum_for(:repeated_combination, n) if not block_given? + if n < 0 + # yield nothing + elsif n == 0 + yield [] + elsif n == 1 + each { |item| yield [item] } + elsif @size == 0 + # yield nothing + else + combos = lambda do |result,index,remaining| + while index < @size-1 + if remaining == 1 + yield result.dup << get(index) + else + combos[result.dup << get(index), index, remaining-1] + end + index += 1 + end + item = get(index) + remaining.times { result << item } + yield result + end + combos[[], 0, n] + end + self + end + + # Yields all permutations of length `n` of items from the `Vector`, and then + # returns `self`. If no length `n` is specified, permutations of all elements + # will be yielded. + # + # There is no guarantee about which order the permutations will be yielded in. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @example + # v = Immutable::Vector[5, 6, 7] + # v.permutation(2) { |p| puts "Permutation: #{p}" } + # + # Permutation: [5, 6] + # Permutation: [5, 7] + # Permutation: [6, 5] + # Permutation: [6, 7] + # Permutation: [7, 5] + # Permutation: [7, 6] + # # => Immutable::Vector[5, 6, 7] + # + # @return [self, Enumerator] + def permutation(n = @size) + return enum_for(:permutation, n) if not block_given? + if n < 0 || @size < n + # yield nothing + elsif n == 0 + yield [] + elsif n == 1 + each { |item| yield [item] } + else + used, result = [], [] + perms = lambda do |index| + 0.upto(@size-1) do |i| + next if used[i] + result[index] = get(i) + if index < n-1 + used[i] = true + perms[index+1] + used[i] = false + else + yield result.dup + end + end + end + perms[0] + end + self + end + + # When invoked with a block, yields all repeated permutations of length `n` of + # items from the `Vector`, and then returns `self`. A "repeated permutation" is + # one where any item from the `Vector` can appear any number of times, and in + # any position (not just consecutively) + # + # If no length `n` is specified, permutations of all elements will be yielded. + # There is no guarantee about which order the permutations will be yielded in. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @example + # v = Immutable::Vector[5, 6, 7] + # v.repeated_permutation(2) { |p| puts "Permutation: #{p}" } + # + # Permutation: [5, 5] + # Permutation: [5, 6] + # Permutation: [5, 7] + # Permutation: [6, 5] + # Permutation: [6, 6] + # Permutation: [6, 7] + # Permutation: [7, 5] + # Permutation: [7, 6] + # Permutation: [7, 7] + # # => Immutable::Vector[5, 6, 7] + # + # @return [self, Enumerator] + def repeated_permutation(n = @size) + return enum_for(:repeated_permutation, n) if not block_given? + if n < 0 + # yield nothing + elsif n == 0 + yield [] + elsif n == 1 + each { |item| yield [item] } + else + result = [] + perms = lambda do |index| + 0.upto(@size-1) do |i| + result[index] = get(i) + if index < n-1 + perms[index+1] + else + yield result.dup + end + end + end + perms[0] + end + self + end + + # Cartesian product or multiplication. + # + # @overload product(*vectors) + # Return a `Vector` of all combinations of elements from this `Vector` and each + # of the given vectors or arrays. The length of the returned `Vector` is the product + # of `self.size` and the size of each argument vector or array. + # @example + # v1 = Immutable::Vector[1, 2, 3] + # v2 = Immutable::Vector["A", "B"] + # v1.product(v2) + # # => [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"], [3, "B"]] + # @overload product + # Return the result of multiplying all the items in this `Vector` together. + # + # @example + # Immutable::Vector[1, 2, 3, 4, 5].product # => 120 + # + # @return [Vector] + def product(*vectors) + # if no vectors passed, return "product" as in result of multiplying all items + return super if vectors.empty? + + vectors.unshift(self) + + if vectors.any?(&:empty?) + return block_given? ? self : [] + end + + counters = Array.new(vectors.size, 0) + + bump_counters = lambda do + i = vectors.size-1 + counters[i] += 1 + while counters[i] == vectors[i].size + counters[i] = 0 + i -= 1 + return true if i == -1 # we are done + counters[i] += 1 + end + false # not done yet + end + build_array = lambda do + array = [] + counters.each_with_index { |index,i| array << vectors[i][index] } + array + end + + if block_given? + loop do + yield build_array[] + return self if bump_counters[] + end + else + result = [] + loop do + result << build_array[] + return result if bump_counters[] + end + end + end + + # Assume all elements are vectors or arrays and transpose the rows and columns. + # In other words, take the first element of each nested vector/array and gather + # them together into a new `Vector`. Do likewise for the second, third, and so on + # down to the end of each nested vector/array. Gather all the resulting `Vectors` + # into a new `Vector` and return it. + # + # This operation is closely related to {#zip}. The result is almost the same as + # calling {#zip} on the first nested vector/array with the others supplied as + # arguments. + # + # @example + # Immutable::Vector[["A", 10], ["B", 20], ["C", 30]].transpose + # # => Immutable::Vector[Immutable::Vector["A", "B", "C"], Immutable::Vector[10, 20, 30]] + # + # @return [Vector] + # @raise [IndexError] if elements are not of the same size. + # @raise [TypeError] if an element does not respond to #size and #[] + def transpose + return self.class.empty if empty? + result = Array.new(first.size) { [] } + + 0.upto(@size-1) do |i| + source = get(i) + if source.size != result.size + raise IndexError, "element size differs (#{source.size} should be #{result.size})" + end + + 0.upto(result.size-1) do |j| + result[j].push(source[j]) + end + end + + result.map! { |a| self.class.new(a) } + self.class.new(result) + rescue NoMethodError + if any? { |x| !x.respond_to?(:size) || !x.respond_to?(:[]) } + bad = find { |x| !x.respond_to?(:size) || !x.respond_to?(:[]) } + raise TypeError, "'#{bad.inspect}' must respond to #size and #[] to be transposed" + else + raise + end + end + + # Finds a value from this `Vector` which meets the condition defined by the + # provided block, using a binary search. The vector must already be sorted + # with respect to the block. See Ruby's `Array#bsearch` for details, + # behaviour is equivalent. + # + # @example + # v = Immutable::Vector[1, 3, 5, 7, 9, 11, 13] + # # Block returns true/false for exact element match: + # v.bsearch { |e| e > 4 } # => 5 + # # Block returns number to match an element in 4 <= e <= 7: + # v.bsearch { |e| 1 - e / 4 } # => 7 + # + # @yield Once for at most `log n` elements, where `n` is the size of the + # vector. The exact elements and ordering are undefined. + # @yieldreturn [Boolean] `true` if this element matches the criteria, `false` otherwise. + # @yieldreturn [Integer] See `Array#bsearch` for details. + # @yieldparam [Object] element element to be evaluated + # @return [Object] The matched element, or `nil` if none found. + # @raise TypeError if the block returns a non-numeric, non-boolean, non-nil + # value. + def bsearch + return enum_for(:bsearch) if not block_given? + low, high, result = 0, @size, nil + while low < high + mid = (low + ((high - low) >> 1)) + val = get(mid) + v = yield val + if v.is_a? Numeric + if v == 0 + return val + elsif v > 0 + high = mid + else + low = mid + 1 + end + elsif v == true + result = val + high = mid + elsif !v + low = mid + 1 + else + raise TypeError, "wrong argument type #{v.class} (must be numeric, true, false, or nil)" + end + end + result + end + + # Return an empty `Vector` instance, of the same class as this one. Useful if you + # have multiple subclasses of `Vector` and want to treat them polymorphically. + # + # @return [Vector] + def clear + self.class.empty + end + + # Return a randomly chosen item from this `Vector`. If the vector is empty, return `nil`. + # + # @example + # Immutable::Vector[1, 2, 3, 4, 5].sample # => 2 + # + # @return [Object] + def sample + get(rand(@size)) + end + + # Return a new `Vector` with only the elements at the given `indices`, in the + # order specified by `indices`. If any of the `indices` do not exist, `nil`s will + # appear in their places. + # + # @example + # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] + # v.values_at(2, 4, 5) # => Immutable::Vector["C", "E", "F"] + # + # @param indices [Array] The indices to retrieve and gather into a new `Vector` + # @return [Vector] + def values_at(*indices) + self.class.new(indices.map { |i| get(i) }.freeze) + end + + # Find the index of an element, starting from the end of the vector. + # Returns `nil` if no element is found. + # + # @overload rindex(obj) + # Return the index of the last element which is `#==` to `obj`. + # + # @example + # v = Immutable::Vector[7, 8, 9, 7, 8, 9] + # v.rindex(8) # => 4 + # + # @overload rindex + # Return the index of the last element for which the block returns true. + # + # @yield [element] Once for each element, last to first, until the block + # returns true. + # @example + # v = Immutable::Vector[7, 8, 9, 7, 8, 9] + # v.rindex { |e| e.even? } # => 4 + # + # @return [Integer] + def rindex(obj = (missing_arg = true)) + i = @size - 1 + if missing_arg + if block_given? + reverse_each { |item| return i if yield item; i -= 1 } + nil + else + enum_for(:rindex) + end + else + reverse_each { |item| return i if item == obj; i -= 1 } + nil + end + end + + # Assumes all elements are nested, indexable collections, and searches through them, + # comparing `obj` with the first element of each nested collection. Return the + # first nested collection which matches, or `nil` if none is found. + # Behaviour is undefined when elements do not meet assumptions (i.e. are + # not indexable collections). + # + # @example + # v = Immutable::Vector[["A", 10], ["B", 20], ["C", 30]] + # v.assoc("B") # => ["B", 20] + # + # @param obj [Object] The object to search for + # @return [Object] + def assoc(obj) + each do |array| + next if !array.respond_to?(:[]) + return array if obj == array[0] + end + nil + end + + # Assumes all elements are nested, indexable collections, and searches through them, + # comparing `obj` with the second element of each nested collection. Return + # the first nested collection which matches, or `nil` if none is found. + # Behaviour is undefined when elements do not meet assumptions (i.e. are + # not indexable collections). + # + # @example + # v = Immutable::Vector[["A", 10], ["B", 20], ["C", 30]] + # v.rassoc(20) # => ["B", 20] + # + # @param obj [Object] The object to search for + # @return [Object] + def rassoc(obj) + each do |array| + next if !array.respond_to?(:[]) + return array if obj == array[1] + end + nil + end + + # Return an `Array` with the same elements, in the same order. The returned + # `Array` may or may not be frozen. + # + # @return [Array] + def to_a + if @levels == 0 + # When initializing a Vector with 32 or less items, we always make + # sure @root is frozen, so we can return it directly here + @root + else + flatten_node(@root, @levels * BITS_PER_LEVEL, []) + end + end + alias to_ary to_a + + # Return true if `other` has the same type and contents as this `Vector`. + # + # @param other [Object] The collection to compare with + # @return [Boolean] + def eql?(other) + return true if other.equal?(self) + return false unless instance_of?(other.class) && @size == other.size + @root.eql?(other.instance_variable_get(:@root)) + end + + # See `Object#hash`. + # @return [Integer] + def hash + reduce(0) { |hash, item| (hash << 5) - hash + item.hash } + end + + # Return `self`. Since this is an immutable object duplicates are + # equivalent. + # @return [Vector] + def dup + self + end + alias clone dup + + # @return [::Array] + # @private + def marshal_dump + to_a + end + + # @private + def marshal_load(array) + initialize(array.freeze) + end + + private + + def traverse_depth_first(node, level, &block) + return node.each(&block) if level == 0 + node.each { |child| traverse_depth_first(child, level - 1, &block) } + end + + def reverse_traverse_depth_first(node, level, &block) + return node.reverse_each(&block) if level == 0 + node.reverse_each { |child| reverse_traverse_depth_first(child, level - 1, &block) } + end + + def leaf_node_for(node, bitshift, index) + while bitshift > 0 + node = node[(index >> bitshift) & INDEX_MASK] + bitshift -= BITS_PER_LEVEL + end + node + end + + def update_root(index, item) + root, levels = @root, @levels + while index >= (1 << (BITS_PER_LEVEL * (levels + 1))) + root = [root].freeze + levels += 1 + end + new_root = update_leaf_node(root, levels * BITS_PER_LEVEL, index, item) + if new_root.equal?(root) + self + else + self.class.alloc(new_root, @size > index ? @size : index + 1, levels) + end + end + + def update_leaf_node(node, bitshift, index, item) + slot_index = (index >> bitshift) & INDEX_MASK + if bitshift > 0 + old_child = node[slot_index] || [] + item = update_leaf_node(old_child, bitshift - BITS_PER_LEVEL, index, item) + end + existing_item = node[slot_index] + if existing_item.equal?(item) + node + else + node.dup.tap { |n| n[slot_index] = item }.freeze + end + end + + def flatten_range(node, bitshift, from, to) + from_slot = (from >> bitshift) & INDEX_MASK + to_slot = (to >> bitshift) & INDEX_MASK + + if bitshift == 0 # are we at the bottom? + node.slice(from_slot, to_slot-from_slot+1) + elsif from_slot == to_slot + flatten_range(node[from_slot], bitshift - BITS_PER_LEVEL, from, to) + else + # the following bitmask can be used to pick out the part of the from/to indices + # which will be used to direct path BELOW this node + mask = ((1 << bitshift) - 1) + result = [] + + if from & mask == 0 + flatten_node(node[from_slot], bitshift - BITS_PER_LEVEL, result) + else + result.concat(flatten_range(node[from_slot], bitshift - BITS_PER_LEVEL, from, from | mask)) + end + + (from_slot+1).upto(to_slot-1) do |slot_index| + flatten_node(node[slot_index], bitshift - BITS_PER_LEVEL, result) + end + + if to & mask == mask + flatten_node(node[to_slot], bitshift - BITS_PER_LEVEL, result) + else + result.concat(flatten_range(node[to_slot], bitshift - BITS_PER_LEVEL, to & ~mask, to)) + end + + result + end + end + + def flatten_node(node, bitshift, result) + if bitshift == 0 + result.concat(node) + elsif bitshift == BITS_PER_LEVEL + node.each { |a| result.concat(a) } + else + bitshift -= BITS_PER_LEVEL + node.each { |a| flatten_node(a, bitshift, result) } + end + result + end + + def subsequence(from, length) + return nil if from > @size || from < 0 || length < 0 + length = @size - from if @size < from + length + return self.class.empty if length == 0 + self.class.new(flatten_range(@root, @levels * BITS_PER_LEVEL, from, from + length - 1)) + end + + def flatten_suffix(node, bitshift, from, result) + from_slot = (from >> bitshift) & INDEX_MASK + + if bitshift == 0 + if from_slot == 0 + result.concat(node) + else + result.concat(node.slice(from_slot, 32)) # entire suffix of node. excess length is ignored by #slice + end + else + mask = ((1 << bitshift) - 1) + if from & mask == 0 + from_slot.upto(node.size-1) do |i| + flatten_node(node[i], bitshift - BITS_PER_LEVEL, result) + end + elsif (child = node[from_slot]) + flatten_suffix(child, bitshift - BITS_PER_LEVEL, from, result) + (from_slot+1).upto(node.size-1) do |i| + flatten_node(node[i], bitshift - BITS_PER_LEVEL, result) + end + end + result + end + end + + def replace_suffix(from, suffix) + # new suffix can go directly after existing elements + raise IndexError if from > @size + root, levels = @root, @levels + + if (from >> (BITS_PER_LEVEL * (@levels + 1))) != 0 + # index where new suffix goes doesn't fall within current tree + # we will need to deepen tree + root = [root].freeze + levels += 1 + end + + new_size = from + suffix.size + root = replace_node_suffix(root, levels * BITS_PER_LEVEL, from, suffix) + + if !suffix.empty? + levels.times { suffix = suffix.each_slice(32).to_a } + root.concat(suffix) + while root.size > 32 + root = root.each_slice(32).to_a + levels += 1 + end + else + while root.size == 1 && levels > 0 + root = root[0] + levels -= 1 + end + end + + self.class.alloc(root.freeze, new_size, levels) + end + + def replace_node_suffix(node, bitshift, from, suffix) + from_slot = (from >> bitshift) & INDEX_MASK + + if bitshift == 0 + if from_slot == 0 + suffix.shift(32) + else + node.take(from_slot).concat(suffix.shift(32 - from_slot)) + end + else + mask = ((1 << bitshift) - 1) + if from & mask == 0 + if from_slot == 0 + new_node = suffix.shift(32 * (1 << bitshift)) + while bitshift != 0 + new_node = new_node.each_slice(32).to_a + bitshift -= BITS_PER_LEVEL + end + new_node + else + result = node.take(from_slot) + remainder = suffix.shift((32 - from_slot) * (1 << bitshift)) + while bitshift != 0 + remainder = remainder.each_slice(32).to_a + bitshift -= BITS_PER_LEVEL + end + result.concat(remainder) + end + elsif (child = node[from_slot]) + result = node.take(from_slot) + result.push(replace_node_suffix(child, bitshift - BITS_PER_LEVEL, from, suffix)) + remainder = suffix.shift((31 - from_slot) * (1 << bitshift)) + while bitshift != 0 + remainder = remainder.each_slice(32).to_a + bitshift -= BITS_PER_LEVEL + end + result.concat(remainder) + else + raise "Shouldn't happen" + end + end + end + end + + # The canonical empty `Vector`. Returned by `Vector[]` when + # invoked with no arguments; also returned by `Vector.empty`. Prefer using this + # one rather than creating many empty vectors using `Vector.new`. + # + # @private + EmptyVector = Immutable::Vector.empty + + + # `Immutable::Set` is a collection of unordered values with no duplicates. Testing whether + # an object is present in the `Set` can be done in constant time. `Set` is also `Enumerable`, so you can + # iterate over the members of the set with {#each}, transform them with {#map}, filter + # them with {#select}, and so on. Some of the `Enumerable` methods are overridden to + # return `immutable-ruby` collections. + # + # Like the `Set` class in Ruby's standard library, which we will call RubySet, + # `Immutable::Set` defines equivalency of objects using `#hash` and `#eql?`. No two + # objects with the same `#hash` code, and which are also `#eql?`, can coexist in the + # same `Set`. If one is already in the `Set`, attempts to add another one will have + # no effect. + # + # `Set`s have no natural ordering and cannot be compared using `#<=>`. However, they + # define {#<}, {#>}, {#<=}, and {#>=} as shorthand for {#proper_subset?}, + # {#proper_superset?}, {#subset?}, and {#superset?} respectively. + # + # The basic set-theoretic operations {#union}, {#intersection}, {#difference}, and + # {#exclusion} work with any `Enumerable` object. + # + # A `Set` can be created in either of the following ways: + # + # Immutable::Set.new([1, 2, 3]) # any Enumerable can be used to initialize + # Immutable::Set['A', 'B', 'C', 'D'] + # + # The latter 2 forms of initialization can be used with your own, custom subclasses + # of `Immutable::Set`. + # + # Unlike RubySet, all methods which you might expect to "modify" an `Immutable::Set` + # actually return a new set and leave the existing one unchanged. + # + # @example + # set1 = Immutable::Set[1, 2] # => Immutable::Set[1, 2] + # set2 = Immutable::Set[1, 2] # => Immutable::Set[1, 2] + # set1 == set2 # => true + # set3 = set1.add("foo") # => Immutable::Set[1, 2, "foo"] + # set3 - set2 # => Immutable::Set["foo"] + # set3.subset?(set1) # => false + # set1.subset?(set3) # => true + # + class Set + include Immutable::Enumerable + + class << self + # Create a new `Set` populated with the given items. + # @return [Set] + def [](*items) + items.empty? ? empty : new(items) + end + + # Return an empty `Set`. If used on a subclass, returns an empty instance + # of that class. + # + # @return [Set] + def empty + @empty ||= new + end + + # "Raw" allocation of a new `Set`. Used internally to create a new + # instance quickly after obtaining a modified {Trie}. + # + # @return [Set] + # @private + def alloc(trie = EmptyTrie) + allocate.tap { |s| s.instance_variable_set(:@trie, trie) }.freeze + end + end + + def initialize(items=[]) + @trie = Trie.new(0) + items.each { |item| @trie.put!(item, nil) } + freeze + end + + # Return `true` if this `Set` contains no items. + # @return [Boolean] + def empty? + @trie.empty? + end + + # Return the number of items in this `Set`. + # @return [Integer] + def size + @trie.size + end + alias length size + + # Return a new `Set` with `item` added. If `item` is already in the set, + # return `self`. + # + # @example + # Immutable::Set[1, 2, 3].add(4) # => Immutable::Set[1, 2, 4, 3] + # Immutable::Set[1, 2, 3].add(2) # => Immutable::Set[1, 2, 3] + # + # @param item [Object] The object to add + # @return [Set] + def add(item) + include?(item) ? self : self.class.alloc(@trie.put(item, nil)) + end + alias << add + + # If `item` is not a member of this `Set`, return a new `Set` with `item` added. + # Otherwise, return `false`. + # + # @example + # Immutable::Set[1, 2, 3].add?(4) # => Immutable::Set[1, 2, 4, 3] + # Immutable::Set[1, 2, 3].add?(2) # => false + # + # @param item [Object] The object to add + # @return [Set, false] + def add?(item) + !include?(item) && add(item) + end + + # Return a new `Set` with `item` removed. If `item` is not a member of the set, + # return `self`. + # + # @example + # Immutable::Set[1, 2, 3].delete(1) # => Immutable::Set[2, 3] + # Immutable::Set[1, 2, 3].delete(99) # => Immutable::Set[1, 2, 3] + # + # @param item [Object] The object to remove + # @return [Set] + def delete(item) + trie = @trie.delete(item) + new_trie(trie) + end + + # If `item` is a member of this `Set`, return a new `Set` with `item` removed. + # Otherwise, return `false`. + # + # @example + # Immutable::Set[1, 2, 3].delete?(1) # => Immutable::Set[2, 3] + # Immutable::Set[1, 2, 3].delete?(99) # => false + # + # @param item [Object] The object to remove + # @return [Set, false] + def delete?(item) + include?(item) && delete(item) + end + + # Call the block once for each item in this `Set`. No specific iteration order + # is guaranteed, but the order will be stable for any particular `Set`. If + # no block is given, an `Enumerator` is returned instead. + # + # @example + # Immutable::Set["Dog", "Elephant", "Lion"].each { |e| puts e } + # Elephant + # Dog + # Lion + # # => Immutable::Set["Dog", "Elephant", "Lion"] + # + # @yield [item] Once for each item. + # @return [self, Enumerator] + def each + return to_enum if not block_given? + @trie.each { |key, _| yield(key) } + self + end + + # Call the block once for each item in this `Set`. Iteration order will be + # the opposite of {#each}. If no block is given, an `Enumerator` is + # returned instead. + # + # @example + # Immutable::Set["Dog", "Elephant", "Lion"].reverse_each { |e| puts e } + # Lion + # Dog + # Elephant + # # => Immutable::Set["Dog", "Elephant", "Lion"] + # + # @yield [item] Once for each item. + # @return [self] + def reverse_each + return enum_for(:reverse_each) if not block_given? + @trie.reverse_each { |key, _| yield(key) } + self + end + + # Return a new `Set` with all the items for which the block returns true. + # + # @example + # Immutable::Set["Elephant", "Dog", "Lion"].select { |e| e.size >= 4 } + # # => Immutable::Set["Elephant", "Lion"] + # @yield [item] Once for each item. + # @return [Set] + def select + return enum_for(:select) unless block_given? + trie = @trie.select { |key, _| yield(key) } + new_trie(trie) + end + alias find_all select + alias keep_if select + + # Call the block once for each item in this `Set`. All the values returned + # from the block will be gathered into a new `Set`. If no block is given, + # an `Enumerator` is returned instead. + # + # @example + # Immutable::Set["Cat", "Elephant", "Dog", "Lion"].map { |e| e.size } + # # => Immutable::Set[8, 4, 3] + # + # @yield [item] Once for each item. + # @return [Set] + def map + return enum_for(:map) if not block_given? + return self if empty? + self.class.new(super) + end + alias collect map + + # Return `true` if the given item is present in this `Set`. More precisely, + # return `true` if an object with the same `#hash` code, and which is also `#eql?` + # to the given object is present. + # + # @example + # Immutable::Set["A", "B", "C"].include?("B") # => true + # Immutable::Set["A", "B", "C"].include?("Z") # => false + # + # @param object [Object] The object to check for + # @return [Boolean] + def include?(object) + @trie.key?(object) + end + alias member? include? + + # Return a member of this `Set`. The member chosen will be the first one which + # would be yielded by {#each}. If the set is empty, return `nil`. + # + # @example + # Immutable::Set["A", "B", "C"].first # => "C" + # + # @return [Object] + def first + (entry = @trie.at(0)) && entry[0] + end + + # Return a {SortedSet} which contains the same items as this `Set`, ordered by + # the given comparator block. + # + # @example + # Immutable::Set["Elephant", "Dog", "Lion"].sort + # # => Immutable::SortedSet["Dog", "Elephant", "Lion"] + # Immutable::Set["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } + # # => Immutable::SortedSet["Dog", "Lion", "Elephant"] + # + # @yield [a, b] Any number of times with different pairs of elements. + # @yieldreturn [Integer] Negative if the first element should be sorted + # lower, positive if the latter element, or 0 if + # equal. + # @return [SortedSet] + def sort(&comparator) + SortedSet.new(to_a, &comparator) + end + + # Return a {SortedSet} which contains the same items as this `Set`, ordered + # by mapping each item through the provided block to obtain sort keys, and + # then sorting the keys. + # + # @example + # Immutable::Set["Elephant", "Dog", "Lion"].sort_by { |e| e.size } + # # => Immutable::SortedSet["Dog", "Lion", "Elephant"] + # + # @yield [item] Once for each item to create the set, and then potentially + # again depending on what operations are performed on the + # returned {SortedSet}. As such, it is recommended that the + # block be a pure function. + # @yieldreturn [Object] sort key for the item + # @return [SortedSet] + def sort_by(&mapper) + SortedSet.new(to_a, &mapper) + end + + # Return a new `Set` which contains all the members of both this `Set` and `other`. + # `other` can be any `Enumerable` object. + # + # @example + # Immutable::Set[1, 2] | Immutable::Set[2, 3] # => Immutable::Set[1, 2, 3] + # + # @param other [Enumerable] The collection to merge with + # @return [Set] + def union(other) + if other.is_a?(Immutable::Set) + if other.size > size + small_set_pairs = @trie + large_set_trie = other.instance_variable_get(:@trie) + else + small_set_pairs = other.instance_variable_get(:@trie) + large_set_trie = @trie + end + else + if other.respond_to?(:lazy) + small_set_pairs = other.lazy.map { |e| [e, nil] } + else + small_set_pairs = other.map { |e| [e, nil] } + end + large_set_trie = @trie + end + + trie = large_set_trie.bulk_put(small_set_pairs) + new_trie(trie) + end + alias | union + alias + union + alias merge union + + # Return a new `Set` which contains all the items which are members of both + # this `Set` and `other`. `other` can be any `Enumerable` object. + # + # @example + # Immutable::Set[1, 2] & Immutable::Set[2, 3] # => Immutable::Set[2] + # + # @param other [Enumerable] The collection to intersect with + # @return [Set] + def intersection(other) + if other.size < @trie.size + if other.is_a?(Immutable::Set) + trie = other.instance_variable_get(:@trie).select { |key, _| include?(key) } + else + trie = Trie.new(0) + other.each { |obj| trie.put!(obj, nil) if include?(obj) } + end + else + trie = @trie.select { |key, _| other.include?(key) } + end + new_trie(trie) + end + alias & intersection + + # Return a new `Set` with all the items in `other` removed. `other` can be + # any `Enumerable` object. + # + # @example + # Immutable::Set[1, 2] - Immutable::Set[2, 3] # => Immutable::Set[1] + # + # @param other [Enumerable] The collection to subtract from this set + # @return [Set] + def difference(other) + trie = if (@trie.size <= other.size) && (other.is_a?(Immutable::Set) || (defined?(::Set) && other.is_a?(::Set))) + @trie.select { |key, _| !other.include?(key) } + else + @trie.bulk_delete(other) + end + new_trie(trie) + end + alias subtract difference + alias - difference + + # Return a new `Set` which contains all the items which are members of this + # `Set` or of `other`, but not both. `other` can be any `Enumerable` object. + # + # @example + # Immutable::Set[1, 2] ^ Immutable::Set[2, 3] # => Immutable::Set[1, 3] + # + # @param other [Enumerable] The collection to take the exclusive disjunction of + # @return [Set] + def exclusion(other) + ((self | other) - (self & other)) + end + alias ^ exclusion + + # Return `true` if all items in this `Set` are also in `other`. + # + # @example + # Immutable::Set[2, 3].subset?(Immutable::Set[1, 2, 3]) # => true + # + # @param other [Set] + # @return [Boolean] + def subset?(other) + return false if other.size < size + + # This method has the potential to be very slow if 'other' is a large Array, so to avoid that, + # we convert those Arrays to Sets before checking presence of items + # Time to convert Array -> Set is linear in array.size + # Time to check for presence of all items in an Array is proportional to set.size * array.size + # Note that both sides of that equation have array.size -- hence those terms cancel out, + # and the break-even point is solely dependent on the size of this collection + # After doing some benchmarking to estimate the constants, it appears break-even is at ~190 items + # We also check other.size, to avoid the more expensive #is_a? checks in cases where it doesn't matter + # + if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) + other = ::Set.new(other) + end + all? { |item| other.include?(item) } + end + alias <= subset? + + # Return `true` if all items in `other` are also in this `Set`. + # + # @example + # Immutable::Set[1, 2, 3].superset?(Immutable::Set[2, 3]) # => true + # + # @param other [Set] + # @return [Boolean] + def superset?(other) + other.subset?(self) + end + alias >= superset? + + # Returns `true` if `other` contains all the items in this `Set`, plus at least + # one item which is not in this set. + # + # @example + # Immutable::Set[2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => true + # Immutable::Set[1, 2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => false + # + # @param other [Set] + # @return [Boolean] + def proper_subset?(other) + return false if other.size <= size + # See comments above + if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) + other = ::Set.new(other) + end + all? { |item| other.include?(item) } + end + alias < proper_subset? + + # Returns `true` if this `Set` contains all the items in `other`, plus at least + # one item which is not in `other`. + # + # @example + # Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[2, 3]) # => true + # Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[1, 2, 3]) # => false + # + # @param other [Set] + # @return [Boolean] + def proper_superset?(other) + other.proper_subset?(self) + end + alias > proper_superset? + + # Return `true` if this `Set` and `other` do not share any items. + # + # @example + # Immutable::Set[1, 2].disjoint?(Immutable::Set[8, 9]) # => true + # + # @param other [Set] + # @return [Boolean] + def disjoint?(other) + if other.size <= size + other.each { |item| return false if include?(item) } + else + # See comment on #subset? + if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) + other = ::Set.new(other) + end + each { |item| return false if other.include?(item) } + end + true + end + + # Return `true` if this `Set` and `other` have at least one item in common. + # + # @example + # Immutable::Set[1, 2].intersect?(Immutable::Set[2, 3]) # => true + # + # @param other [Set] + # @return [Boolean] + def intersect?(other) + !disjoint?(other) + end + + # Recursively insert the contents of any nested `Set`s into this `Set`, and + # remove them. + # + # @example + # Immutable::Set[Immutable::Set[1, 2], Immutable::Set[3, 4]].flatten + # # => Immutable::Set[1, 2, 3, 4] + # + # @return [Set] + def flatten + reduce(self.class.empty) do |set, item| + next set.union(item.flatten) if item.is_a?(Set) + set.add(item) + end + end + + alias group group_by + alias classify group_by + + # Return a randomly chosen item from this `Set`. If the set is empty, return `nil`. + # + # @example + # Immutable::Set[1, 2, 3, 4, 5].sample # => 3 + # + # @return [Object] + def sample + empty? ? nil : @trie.at(rand(size))[0] + end + + # Return an empty `Set` instance, of the same class as this one. Useful if you + # have multiple subclasses of `Set` and want to treat them polymorphically. + # + # @return [Set] + def clear + self.class.empty + end + + # Return true if `other` has the same type and contents as this `Set`. + # + # @param other [Object] The object to compare with + # @return [Boolean] + def eql?(other) + return true if other.equal?(self) + return false if not instance_of?(other.class) + other_trie = other.instance_variable_get(:@trie) + return false if @trie.size != other_trie.size + @trie.each do |key, _| + return false if !other_trie.key?(key) + end + true + end + alias == eql? + + # See `Object#hash`. + # @return [Integer] + def hash + reduce(0) { |hash, item| (hash << 5) - hash + item.hash } + end + + # Return `self`. Since this is an immutable object duplicates are + # equivalent. + # @return [Set] + def dup + self + end + alias clone dup + + undef :"<=>" # Sets are not ordered, so Enumerable#<=> will give a meaningless result + undef :each_index # Set members cannot be accessed by 'index', so #each_index is not meaningful + + # Return `self`. + # + # @return [self] + def to_set + self + end + + # @private + def marshal_dump + output = {} + each do |key| + output[key] = nil + end + output + end + + # @private + def marshal_load(dictionary) + @trie = dictionary.reduce(EmptyTrie) do |trie, key_value| + trie.put(key_value.first, nil) + end + end + + private + + def new_trie(trie) + if trie.empty? + self.class.empty + elsif trie.equal?(@trie) + self + else + self.class.alloc(trie) + end + end + end + + # The canonical empty `Set`. Returned by `Set[]` when + # invoked with no arguments; also returned by `Set.empty`. Prefer using this + # one rather than creating many empty sets using `Set.new`. + # + # @private + EmptySet = Immutable::Set.empty +end diff --git a/lib/immutable/hash.rb b/lib/immutable/hash.rb index b3fb23f..963e90d 100644 --- a/lib/immutable/hash.rb +++ b/lib/immutable/hash.rb @@ -1,941 +1,4 @@ -require 'immutable/undefined' -require 'immutable/enumerable' -require 'immutable/trie' -require 'immutable/set' -require 'immutable/vector' - -module Immutable - - # An `Immutable::Hash` maps a set of unique keys to corresponding values, much - # like a dictionary maps from words to definitions. Given a key, it can store - # and retrieve an associated value in constant time. If an existing key is - # stored again, the new value will replace the old. It behaves much like - # Ruby's built-in Hash, which we will call RubyHash for clarity. Like - # RubyHash, two keys that are `#eql?` to each other and have the same - # `#hash` are considered identical in an `Immutable::Hash`. - # - # An `Immutable::Hash` can be created in a couple of ways: - # - # Immutable::Hash.new(font_size: 10, font_family: 'Arial') - # Immutable::Hash[first_name: 'John', last_name: 'Smith'] - # - # Any `Enumerable` object which yields two-element `[key, value]` arrays - # can be used to initialize an `Immutable::Hash`: - # - # Immutable::Hash.new([[:first_name, 'John'], [:last_name, 'Smith']]) - # - # Key/value pairs can be added using {#put}. A new hash is returned and the - # existing one is left unchanged: - # - # hash = Immutable::Hash[a: 100, b: 200] - # hash.put(:c, 500) # => Immutable::Hash[:a => 100, :b => 200, :c => 500] - # hash # => Immutable::Hash[:a => 100, :b => 200] - # - # {#put} can also take a block, which is used to calculate the value to be - # stored. - # - # hash.put(:a) { |current| current + 200 } # => Immutable::Hash[:a => 300, :b => 200] - # - # Since it is immutable, all methods which you might expect to "modify" a - # `Immutable::Hash` actually return a new hash and leave the existing one - # unchanged. This means that the `hash[key] = value` syntax from RubyHash - # *cannot* be used with `Immutable::Hash`. - # - # Nested data structures can easily be updated using {#update_in}: - # - # hash = Immutable::Hash["a" => Immutable::Vector[Immutable::Hash["c" => 42]]] - # hash.update_in("a", 0, "c") { |value| value + 5 } - # # => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]] - # - # While an `Immutable::Hash` can iterate over its keys or values, it does not - # guarantee any specific iteration order (unlike RubyHash). Methods like - # {#flatten} do not guarantee the order of returned key/value pairs. - # - # Like RubyHash, an `Immutable::Hash` can have a default block which is used - # when looking up a key that does not exist. Unlike RubyHash, the default - # block will only be passed the missing key, without the hash itself: - # - # hash = Immutable::Hash.new { |missing_key| missing_key * 10 } - # hash[5] # => 50 - class Hash - include Immutable::Enumerable - - class << self - # Create a new `Hash` populated with the given key/value pairs. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2] # => Immutable::Hash["A" => 1, "B" => 2] - # Immutable::Hash[["A", 1], ["B", 2]] # => Immutable::Hash["A" => 1, "B" => 2] - # - # @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided. - # @return [Hash] - def [](pairs = nil) - (pairs.nil? || pairs.empty?) ? empty : new(pairs) - end - - # Return an empty `Hash`. If used on a subclass, returns an empty instance - # of that class. - # - # @return [Hash] - def empty - @empty ||= new - end - - # "Raw" allocation of a new `Hash`. Used internally to create a new - # instance quickly after obtaining a modified {Trie}. - # - # @return [Hash] - # @private - def alloc(trie = EmptyTrie, block = nil) - obj = allocate - obj.instance_variable_set(:@trie, trie) - obj.instance_variable_set(:@default, block) - obj.freeze - end - end - - # @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided. - # @yield [key] Optional _default block_ to be stored and used to calculate the default value of a missing key. It will not be yielded during this method. It will not be preserved when marshalling. - # @yieldparam key Key that was not present in the hash. - def initialize(pairs = nil, &block) - @trie = pairs ? Trie[pairs] : EmptyTrie - @default = block - freeze - end - - # Return the default block if there is one. Otherwise, return `nil`. - # - # @return [Proc] - def default_proc - @default - end - - # Return the number of key/value pairs in this `Hash`. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].size # => 3 - # - # @return [Integer] - def size - @trie.size - end - alias length size - - # Return `true` if this `Hash` contains no key/value pairs. - # - # @return [Boolean] - def empty? - @trie.empty? - end - - # Return `true` if the given key object is present in this `Hash`. More precisely, - # return `true` if a key with the same `#hash` code, and which is also `#eql?` - # to the given key object is present. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].key?("B") # => true - # - # @param key [Object] The key to check for - # @return [Boolean] - def key?(key) - @trie.key?(key) - end - alias has_key? key? - alias include? key? - alias member? key? - - # Return `true` if this `Hash` has one or more keys which map to the provided value. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].value?(2) # => true - # - # @param value [Object] The value to check for - # @return [Boolean] - def value?(value) - each { |k,v| return true if value == v } - false - end - alias has_value? value? - - # Retrieve the value corresponding to the provided key object. If not found, and - # this `Hash` has a default block, the default block is called to provide the - # value. Otherwise, return `nil`. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h["B"] # => 2 - # h.get("B") # => 2 - # h.get("Elephant") # => nil - # - # # Immutable Hash with a default proc: - # h = Immutable::Hash.new("A" => 1, "B" => 2, "C" => 3) { |key| key.size } - # h.get("B") # => 2 - # h.get("Elephant") # => 8 - # - # @param key [Object] The key to look up - # @return [Object] - def get(key) - entry = @trie.get(key) - if entry - entry[1] - elsif @default - @default.call(key) - end - end - alias [] get - - # Retrieve the value corresponding to the given key object, or use the provided - # default value or block, or otherwise raise a `KeyError`. - # - # @overload fetch(key) - # Retrieve the value corresponding to the given key, or raise a `KeyError` - # if it is not found. - # @param key [Object] The key to look up - # @overload fetch(key) { |key| ... } - # Retrieve the value corresponding to the given key, or call the optional - # code block (with the missing key) and get its return value. - # @yield [key] The key which was not found - # @yieldreturn [Object] Object to return since the key was not found - # @param key [Object] The key to look up - # @overload fetch(key, default) - # Retrieve the value corresponding to the given key, or else return - # the provided `default` value. - # @param key [Object] The key to look up - # @param default [Object] Object to return if the key is not found - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.fetch("B") # => 2 - # h.fetch("Elephant") # => KeyError: key not found: "Elephant" - # - # # with a default value: - # h.fetch("B", 99) # => 2 - # h.fetch("Elephant", 99) # => 99 - # - # # with a block: - # h.fetch("B") { |key| key.size } # => 2 - # h.fetch("Elephant") { |key| key.size } # => 8 - # - # @return [Object] - def fetch(key, default = Undefined) - entry = @trie.get(key) - if entry - entry[1] - elsif block_given? - yield(key) - elsif default != Undefined - default - else - raise KeyError, "key not found: #{key.inspect}" - end - end - - # Return a new `Hash` with the existing key/value associations, plus an association - # between the provided key and value. If an equivalent key is already present, its - # associated value will be replaced with the provided one. - # - # If the `value` argument is missing, but an optional code block is provided, - # it will be passed the existing value (or `nil` if there is none) and what it - # returns will replace the existing value. This is useful for "transforming" - # the value associated with a certain key. - # - # Avoid mutating objects which are used as keys. `String`s are an exception: - # unfrozen `String`s which are used as keys are internally duplicated and - # frozen. This matches RubyHash's behaviour. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2] - # h.put("C", 3) - # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.put("B") { |value| value * 10 } - # # => Immutable::Hash["A" => 1, "B" => 20] - # - # @param key [Object] The key to store - # @param value [Object] The value to associate it with - # @yield [value] The previously stored value, or `nil` if none. - # @yieldreturn [Object] The new value to store - # @return [Hash] - def put(key, value = yield(get(key))) - new_trie = @trie.put(key, value) - if new_trie.equal?(@trie) - self - else - self.class.alloc(new_trie, @default) - end - end - - # @private - # @raise NoMethodError - def []=(*) - raise NoMethodError, "Immutable::Hash doesn't support `[]='; use `put' instead" - end - - # Return a new `Hash` with a deeply nested value modified to the result of - # the given code block. When traversing the nested `Hash`es and `Vector`s, - # non-existing keys are created with empty `Hash` values. - # - # The code block receives the existing value of the deeply nested key (or - # `nil` if it doesn't exist). This is useful for "transforming" the value - # associated with a certain key. - # - # Note that the original `Hash` and sub-`Hash`es and sub-`Vector`s are left - # unmodified; new data structure copies are created along the path wherever - # needed. - # - # @example - # hash = Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 42]]] - # hash.update_in("a", "b", "c") { |value| value + 5 } - # # => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]] - # - # @param key_path [::Array<Object>] List of keys which form the path to the key to be modified - # @yield [value] The previously stored value - # @yieldreturn [Object] The new value to store - # @return [Hash] - def update_in(*key_path, &block) - if key_path.empty? - raise ArgumentError, 'must have at least one key in path' - end - key = key_path[0] - if key_path.size == 1 - new_value = block.call(get(key)) - else - value = fetch(key, EmptyHash) - new_value = value.update_in(*key_path[1..-1], &block) - end - put(key, new_value) - end - - # An alias for {#put} to match RubyHash's API. Does not support {#put}'s - # block form. - # - # @see #put - # @param key [Object] The key to store - # @param value [Object] The value to associate it with - # @return [Hash] - def store(key, value) - put(key, value) - end - - # Return a new `Hash` with `key` removed. If `key` is not present, return - # `self`. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].delete("B") - # # => Immutable::Hash["A" => 1, "C" => 3] - # - # @param key [Object] The key to remove - # @return [Hash] - def delete(key) - derive_new_hash(@trie.delete(key)) - end - - # Call the block once for each key/value pair in this `Hash`, passing the key/value - # pair as parameters. No specific iteration order is guaranteed, though the order will - # be stable for any particular `Hash`. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each { |k, v| puts "k=#{k} v=#{v}" } - # - # k=A v=1 - # k=C v=3 - # k=B v=2 - # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # - # @yield [key, value] Once for each key/value pair. - # @return [self] - def each(&block) - return to_enum if not block_given? - @trie.each(&block) - self - end - alias each_pair each - - # Call the block once for each key/value pair in this `Hash`, passing the key/value - # pair as parameters. Iteration order will be the opposite of {#each}. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].reverse_each { |k, v| puts "k=#{k} v=#{v}" } - # - # k=B v=2 - # k=C v=3 - # k=A v=1 - # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # - # @yield [key, value] Once for each key/value pair. - # @return [self] - def reverse_each(&block) - return enum_for(:reverse_each) if not block_given? - @trie.reverse_each(&block) - self - end - - # Call the block once for each key/value pair in this `Hash`, passing the key as a - # parameter. Ordering guarantees are the same as {#each}. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each_key { |k| puts "k=#{k}" } - # - # k=A - # k=C - # k=B - # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # - # @yield [key] Once for each key/value pair. - # @return [self] - def each_key - return enum_for(:each_key) if not block_given? - @trie.each { |k,v| yield k } - self - end - - # Call the block once for each key/value pair in this `Hash`, passing the value as a - # parameter. Ordering guarantees are the same as {#each}. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each_value { |v| puts "v=#{v}" } - # - # v=1 - # v=3 - # v=2 - # # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # - # @yield [value] Once for each key/value pair. - # @return [self] - def each_value - return enum_for(:each_value) if not block_given? - @trie.each { |k,v| yield v } - self - end - - # Call the block once for each key/value pair in this `Hash`, passing the key/value - # pair as parameters. The block should return a `[key, value]` array each time. - # All the returned `[key, value]` arrays will be gathered into a new `Hash`. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.map { |k, v| ["new-#{k}", v * v] } - # # => Hash["new-C" => 9, "new-B" => 4, "new-A" => 1] - # - # @yield [key, value] Once for each key/value pair. - # @return [Hash] - def map - return enum_for(:map) unless block_given? - return self if empty? - self.class.new(super, &@default) - end - alias collect map - - # Return a new `Hash` with all the key/value pairs for which the block returns true. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.select { |k, v| v >= 2 } - # # => Immutable::Hash["B" => 2, "C" => 3] - # - # @yield [key, value] Once for each key/value pair. - # @yieldreturn Truthy if this pair should be present in the new `Hash`. - # @return [Hash] - def select(&block) - return enum_for(:select) unless block_given? - derive_new_hash(@trie.select(&block)) - end - alias find_all select - alias keep_if select - - # Yield `[key, value]` pairs until one is found for which the block returns true. - # Return that `[key, value]` pair. If the block never returns true, return `nil`. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.find { |k, v| v.even? } - # # => ["B", 2] - # - # @return [Array] - # @yield [key, value] At most once for each key/value pair, until the block returns `true`. - # @yieldreturn Truthy to halt iteration and return the yielded key/value pair. - def find - return enum_for(:find) unless block_given? - each { |entry| return entry if yield entry } - nil - end - alias detect find - - # Return a new `Hash` containing all the key/value pairs from this `Hash` and - # `other`. If no block is provided, the value for entries with colliding keys - # will be that from `other`. Otherwise, the value for each duplicate key is - # determined by calling the block. - # - # `other` can be an `Immutable::Hash`, a built-in Ruby `Hash`, or any `Enumerable` - # object which yields `[key, value]` pairs. - # - # @example - # h1 = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h2 = Immutable::Hash["C" => 70, "D" => 80] - # h1.merge(h2) - # # => Immutable::Hash["C" => 70, "A" => 1, "D" => 80, "B" => 2] - # h1.merge(h2) { |key, v1, v2| v1 + v2 } - # # => Immutable::Hash["C" => 73, "A" => 1, "D" => 80, "B" => 2] - # - # @param other [::Enumerable] The collection to merge with - # @yieldparam key [Object] The key which was present in both collections - # @yieldparam my_value [Object] The associated value from this `Hash` - # @yieldparam other_value [Object] The associated value from the other collection - # @yieldreturn [Object] The value to associate this key with in the new `Hash` - # @return [Hash] - def merge(other) - trie = if block_given? - other.reduce(@trie) do |trie, (key, value)| - if (entry = trie.get(key)) - trie.put(key, yield(key, entry[1], value)) - else - trie.put(key, value) - end - end - else - @trie.bulk_put(other) - end - - derive_new_hash(trie) - end - - # Retrieve the value corresponding to the given key object, or use the provided - # default value or block, or otherwise raise a `KeyError`. - # - # @overload fetch(key) - # Retrieve the value corresponding to the given key, or raise a `KeyError` - # if it is not found. - # @param key [Object] The key to look up - # @overload fetch(key) { |key| ... } - - # Return a sorted {Vector} which contains all the `[key, value]` pairs in - # this `Hash` as two-element `Array`s. - # - # @overload sort - # Uses `#<=>` to determine sorted order. - # @overload sort { |(k1, v1), (k2, v2)| ... } - # Uses the block as a comparator to determine sorted order. - # - # @example - # h = Immutable::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3] - # h.sort { |(k1, v1), (k2, v2)| k1.size <=> k2.size } - # # => Immutable::Vector[["Dog", 1], ["Lion", 3], ["Elephant", 2]] - # @yield [(k1, v1), (k2, v2)] Any number of times with different pairs of key/value associations. - # @yieldreturn [Integer] Negative if the first pair should be sorted - # lower, positive if the latter pair, or 0 if equal. - # - # @see ::Enumerable#sort - # - # @return [Vector] - def sort - Vector.new(super) - end - - # Return a {Vector} which contains all the `[key, value]` pairs in this `Hash` - # as two-element Arrays. The order which the pairs will appear in is determined by - # passing each pair to the code block to obtain a sort key object, and comparing - # the sort keys using `#<=>`. - # - # @see ::Enumerable#sort_by - # - # @example - # h = Immutable::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3] - # h.sort_by { |key, value| key.size } - # # => Immutable::Vector[["Dog", 1], ["Lion", 3], ["Elephant", 2]] - # - # @yield [key, value] Once for each key/value pair. - # @yieldreturn a sort key object for the yielded pair. - # @return [Vector] - def sort_by - Vector.new(super) - end - - # Return a new `Hash` with the associations for all of the given `keys` removed. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.except("A", "C") # => Immutable::Hash["B" => 2] - # - # @param keys [Array] The keys to remove - # @return [Hash] - def except(*keys) - keys.reduce(self) { |hash, key| hash.delete(key) } - end - - # Return a new `Hash` with only the associations for the `wanted` keys retained. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.slice("B", "C") # => Immutable::Hash["B" => 2, "C" => 3] - # - # @param wanted [::Enumerable] The keys to retain - # @return [Hash] - def slice(*wanted) - trie = Trie.new(0) - wanted.each { |key| trie.put!(key, get(key)) if key?(key) } - self.class.alloc(trie, @default) - end - - # Return a {Vector} of the values which correspond to the `wanted` keys. - # If any of the `wanted` keys are not present in this `Hash`, `nil` will be - # placed instead, or the result of the default proc (if one is defined), - # similar to the behavior of {#get}. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.values_at("B", "A", "D") # => Immutable::Vector[2, 1, nil] - # - # @param wanted [Array] The keys to retrieve - # @return [Vector] - def values_at(*wanted) - Vector.new(wanted.map { |key| get(key) }.freeze) - end - - # Return a {Vector} of the values which correspond to the `wanted` keys. - # If any of the `wanted` keys are not present in this `Hash`, raise `KeyError` - # exception. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.fetch_values("C", "A") # => Immutable::Vector[3, 1] - # h.fetch_values("C", "Z") # => KeyError: key not found: "Z" - # - # @param wanted [Array] The keys to retrieve - # @return [Vector] - def fetch_values(*wanted) - array = wanted.map { |key| fetch(key) } - Vector.new(array.freeze) - end - - # Return the value of successively indexing into a nested collection. - # If any of the keys is not present, return `nil`. - # - # @example - # h = Immutable::Hash[a: 9, b: Immutable::Hash[c: 'a', d: 4], e: nil] - # h.dig(:b, :c) # => "a" - # h.dig(:b, :f) # => nil - # - # @return [Object] - def dig(key, *rest) - value = self[key] - if rest.empty? || value.nil? - value - else - value.dig(*rest) - end - end - - # Return a new {Set} containing the keys from this `Hash`. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].keys - # # => Immutable::Set["D", "C", "B", "A"] - # - # @return [Set] - def keys - Set.alloc(@trie) - end - - # Return a new {Vector} populated with the values from this `Hash`. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].values - # # => Immutable::Vector[2, 3, 2, 1] - # - # @return [Vector] - def values - Vector.new(each_value.to_a.freeze) - end - - # Return a new `Hash` created by using keys as values and values as keys. - # If there are multiple values which are equivalent (as determined by `#hash` and - # `#eql?`), only one out of each group of equivalent values will be - # retained. Which one specifically is undefined. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].invert - # # => Immutable::Hash[1 => "A", 3 => "C", 2 => "B"] - # - # @return [Hash] - def invert - pairs = [] - each { |k,v| pairs << [v, k] } - self.class.new(pairs, &@default) - end - - # Return a new {Vector} which is a one-dimensional flattening of this `Hash`. - # If `level` is 1, all the `[key, value]` pairs in the hash will be concatenated - # into one {Vector}. If `level` is greater than 1, keys or values which are - # themselves `Array`s or {Vector}s will be recursively flattened into the output - # {Vector}. The depth to which that flattening will be recursively applied is - # determined by `level`. - # - # As a special case, if `level` is 0, each `[key, value]` pair will be a - # separate element in the returned {Vector}. - # - # @example - # h = Immutable::Hash["A" => 1, "B" => [2, 3, 4]] - # h.flatten - # # => Immutable::Vector["A", 1, "B", [2, 3, 4]] - # h.flatten(2) - # # => Immutable::Vector["A", 1, "B", 2, 3, 4] - # - # @param level [Integer] The number of times to recursively flatten the `[key, value]` pairs in this `Hash`. - # @return [Vector] - def flatten(level = 1) - return Vector.new(self) if level == 0 - array = [] - each { |k,v| array << k; array << v } - array.flatten!(level-1) if level > 1 - Vector.new(array.freeze) - end - - # Searches through the `Hash`, comparing `obj` with each key (using `#==`). - # When a matching key is found, return the `[key, value]` pair as an array. - # Return `nil` if no match is found. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].assoc("B") # => ["B", 2] - # - # @param obj [Object] The key to search for (using #==) - # @return [Array] - def assoc(obj) - each { |entry| return entry if obj == entry[0] } - nil - end - - # Searches through the `Hash`, comparing `obj` with each value (using `#==`). - # When a matching value is found, return the `[key, value]` pair as an array. - # Return `nil` if no match is found. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].rassoc(2) # => ["B", 2] - # - # @param obj [Object] The value to search for (using #==) - # @return [Array] - def rassoc(obj) - each { |entry| return entry if obj == entry[1] } - nil - end - - # Searches through the `Hash`, comparing `value` with each value (using `#==`). - # When a matching value is found, return its associated key object. - # Return `nil` if no match is found. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].key(2) # => "B" - # - # @param value [Object] The value to search for (using #==) - # @return [Object] - def key(value) - each { |entry| return entry[0] if value == entry[1] } - nil - end - - # Return a randomly chosen `[key, value]` pair from this `Hash`. If the hash is empty, - # return `nil`. - # - # @example - # Immutable::Hash["A" => 1, "B" => 2, "C" => 3].sample - # # => ["C", 3] - # - # @return [Array] - def sample - @trie.at(rand(size)) - end - - # Return an empty `Hash` instance, of the same class as this one. Useful if you - # have multiple subclasses of `Hash` and want to treat them polymorphically. - # Maintains the default block, if there is one. - # - # @return [Hash] - def clear - if @default - self.class.alloc(EmptyTrie, @default) - else - self.class.empty - end - end - - # Return true if `other` has the same type and contents as this `Hash`. - # - # @param other [Object] The collection to compare with - # @return [Boolean] - def eql?(other) - return true if other.equal?(self) - instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie)) - end - - # Return true if `other` has the same contents as this `Hash`. Will convert - # `other` to a Ruby `Hash` using `#to_hash` if necessary. - # - # @param other [Object] The object to compare with - # @return [Boolean] - def ==(other) - eql?(other) || (other.respond_to?(:to_hash) && to_hash == other.to_hash) - end - - # Return true if this `Hash` is a proper superset of `other`, which means - # all `other`'s keys are contained in this `Hash` with identical - # values, and the two hashes are not identical. - # - # @param other [Immutable::Hash] The object to compare with - # @return [Boolean] - def >(other) - self != other && self >= other - end - - # Return true if this `Hash` is a superset of `other`, which means all - # `other`'s keys are contained in this `Hash` with identical values. - # - # @param other [Immutable::Hash] The object to compare with - # @return [Boolean] - def >=(other) - other.each do |key, value| - if self[key] != value - return false - end - end - true - end - - # Return true if this `Hash` is a proper subset of `other`, which means all - # its keys are contained in `other` with the identical values, and the two - # hashes are not identical. - # - # @param other [Immutable::Hash] The object to compare with - # @return [Boolean] - def <(other) - other > self - end - - # Return true if this `Hash` is a subset of `other`, which means all its - # keys are contained in `other` with the identical values, and the two - # hashes are not identical. - # - # @param other [Immutable::Hash] The object to compare with - # @return [Boolean] - def <=(other) - other >= self - end - - # See `Object#hash`. - # @return [Integer] - def hash - keys.to_a.sort.reduce(0) do |hash, key| - (hash << 32) - hash + key.hash + get(key).hash - end - end - - # Return the contents of this `Hash` as a programmer-readable `String`. If all the - # keys and values are serializable as Ruby literal strings, the returned string can - # be passed to `eval` to reconstitute an equivalent `Hash`. The default - # block (if there is one) will be lost when doing this, however. - # - # @return [String] - def inspect - result = "#{self.class}[" - i = 0 - each do |key, val| - result << ', ' if i > 0 - result << key.inspect << ' => ' << val.inspect - i += 1 - end - result << ']' - end - - # Return `self`. Since this is an immutable object duplicates are - # equivalent. - # @return [Hash] - def dup - self - end - alias clone dup - - # Allows this `Hash` to be printed at the `pry` console, or using `pp` (from the - # Ruby standard library), in a way which takes the amount of horizontal space on - # the screen into account, and which indents nested structures to make them easier - # to read. - # - # @private - def pretty_print(pp) - pp.group(1, "#{self.class}[", ']') do - pp.breakable '' - pp.seplist(self, nil) do |key, val| - pp.group do - key.pretty_print(pp) - pp.text ' => ' - pp.group(1) do - pp.breakable '' - val.pretty_print(pp) - end - end - end - end - end - - # Convert this `Immutable::Hash` to an instance of Ruby's built-in `Hash`. - # - # @return [::Hash] - def to_hash - output = {} - each do |key, value| - output[key] = value - end - output - end - alias to_h to_hash - - # Return a `Proc` which accepts a key as an argument and returns the value. - # The `Proc` behaves like {#get} (when the key is missing, it returns nil or - # the result of the default proc). - # - # @example - # h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3] - # h.to_proc.call("B") - # # => 2 - # ["A", "C", "X"].map(&h) # The & is short for .to_proc in Ruby - # # => [1, 3, nil] - # - # @return [Proc] - def to_proc - lambda { |key| get(key) } - end - - # @return [::Hash] - # @private - def marshal_dump - to_hash - end - - # @private - def marshal_load(dictionary) - @trie = Trie[dictionary] - end - - private - - # Return a new `Hash` which is derived from this one, using a modified {Trie}. - # The new `Hash` will retain the existing default block, if there is one. - # - def derive_new_hash(trie) - if trie.equal?(@trie) - self - elsif trie.empty? - if @default - self.class.alloc(EmptyTrie, @default) - else - self.class.empty - end - else - self.class.alloc(trie, @default) - end - end - end - - # The canonical empty `Hash`. Returned by `Hash[]` when - # invoked with no arguments; also returned by `Hash.empty`. Prefer using this - # one rather than creating many empty hashes using `Hash.new`. - # - # @private - EmptyHash = Immutable::Hash.empty -end +# Definition of Immutable::Hash is in a separate file to avoid circular +# dependency warnings caused by dependencies between Hash ↔ Vector and +# Hash ↔ Set +require 'immutable/_core' diff --git a/lib/immutable/set.rb b/lib/immutable/set.rb index f39a0d1..6804112 100644 --- a/lib/immutable/set.rb +++ b/lib/immutable/set.rb @@ -1,583 +1,3 @@ -require 'immutable/undefined' -require 'immutable/enumerable' -require 'immutable/hash' -require 'immutable/trie' -require 'immutable/sorted_set' -require 'set' - -module Immutable - - # `Immutable::Set` is a collection of unordered values with no duplicates. Testing whether - # an object is present in the `Set` can be done in constant time. `Set` is also `Enumerable`, so you can - # iterate over the members of the set with {#each}, transform them with {#map}, filter - # them with {#select}, and so on. Some of the `Enumerable` methods are overridden to - # return `immutable-ruby` collections. - # - # Like the `Set` class in Ruby's standard library, which we will call RubySet, - # `Immutable::Set` defines equivalency of objects using `#hash` and `#eql?`. No two - # objects with the same `#hash` code, and which are also `#eql?`, can coexist in the - # same `Set`. If one is already in the `Set`, attempts to add another one will have - # no effect. - # - # `Set`s have no natural ordering and cannot be compared using `#<=>`. However, they - # define {#<}, {#>}, {#<=}, and {#>=} as shorthand for {#proper_subset?}, - # {#proper_superset?}, {#subset?}, and {#superset?} respectively. - # - # The basic set-theoretic operations {#union}, {#intersection}, {#difference}, and - # {#exclusion} work with any `Enumerable` object. - # - # A `Set` can be created in either of the following ways: - # - # Immutable::Set.new([1, 2, 3]) # any Enumerable can be used to initialize - # Immutable::Set['A', 'B', 'C', 'D'] - # - # The latter 2 forms of initialization can be used with your own, custom subclasses - # of `Immutable::Set`. - # - # Unlike RubySet, all methods which you might expect to "modify" an `Immutable::Set` - # actually return a new set and leave the existing one unchanged. - # - # @example - # set1 = Immutable::Set[1, 2] # => Immutable::Set[1, 2] - # set2 = Immutable::Set[1, 2] # => Immutable::Set[1, 2] - # set1 == set2 # => true - # set3 = set1.add("foo") # => Immutable::Set[1, 2, "foo"] - # set3 - set2 # => Immutable::Set["foo"] - # set3.subset?(set1) # => false - # set1.subset?(set3) # => true - # - class Set - include Immutable::Enumerable - - class << self - # Create a new `Set` populated with the given items. - # @return [Set] - def [](*items) - items.empty? ? empty : new(items) - end - - # Return an empty `Set`. If used on a subclass, returns an empty instance - # of that class. - # - # @return [Set] - def empty - @empty ||= new - end - - # "Raw" allocation of a new `Set`. Used internally to create a new - # instance quickly after obtaining a modified {Trie}. - # - # @return [Set] - # @private - def alloc(trie = EmptyTrie) - allocate.tap { |s| s.instance_variable_set(:@trie, trie) }.freeze - end - end - - def initialize(items=[]) - @trie = Trie.new(0) - items.each { |item| @trie.put!(item, nil) } - freeze - end - - # Return `true` if this `Set` contains no items. - # @return [Boolean] - def empty? - @trie.empty? - end - - # Return the number of items in this `Set`. - # @return [Integer] - def size - @trie.size - end - alias length size - - # Return a new `Set` with `item` added. If `item` is already in the set, - # return `self`. - # - # @example - # Immutable::Set[1, 2, 3].add(4) # => Immutable::Set[1, 2, 4, 3] - # Immutable::Set[1, 2, 3].add(2) # => Immutable::Set[1, 2, 3] - # - # @param item [Object] The object to add - # @return [Set] - def add(item) - include?(item) ? self : self.class.alloc(@trie.put(item, nil)) - end - alias << add - - # If `item` is not a member of this `Set`, return a new `Set` with `item` added. - # Otherwise, return `false`. - # - # @example - # Immutable::Set[1, 2, 3].add?(4) # => Immutable::Set[1, 2, 4, 3] - # Immutable::Set[1, 2, 3].add?(2) # => false - # - # @param item [Object] The object to add - # @return [Set, false] - def add?(item) - !include?(item) && add(item) - end - - # Return a new `Set` with `item` removed. If `item` is not a member of the set, - # return `self`. - # - # @example - # Immutable::Set[1, 2, 3].delete(1) # => Immutable::Set[2, 3] - # Immutable::Set[1, 2, 3].delete(99) # => Immutable::Set[1, 2, 3] - # - # @param item [Object] The object to remove - # @return [Set] - def delete(item) - trie = @trie.delete(item) - new_trie(trie) - end - - # If `item` is a member of this `Set`, return a new `Set` with `item` removed. - # Otherwise, return `false`. - # - # @example - # Immutable::Set[1, 2, 3].delete?(1) # => Immutable::Set[2, 3] - # Immutable::Set[1, 2, 3].delete?(99) # => false - # - # @param item [Object] The object to remove - # @return [Set, false] - def delete?(item) - include?(item) && delete(item) - end - - # Call the block once for each item in this `Set`. No specific iteration order - # is guaranteed, but the order will be stable for any particular `Set`. If - # no block is given, an `Enumerator` is returned instead. - # - # @example - # Immutable::Set["Dog", "Elephant", "Lion"].each { |e| puts e } - # Elephant - # Dog - # Lion - # # => Immutable::Set["Dog", "Elephant", "Lion"] - # - # @yield [item] Once for each item. - # @return [self, Enumerator] - def each - return to_enum if not block_given? - @trie.each { |key, _| yield(key) } - self - end - - # Call the block once for each item in this `Set`. Iteration order will be - # the opposite of {#each}. If no block is given, an `Enumerator` is - # returned instead. - # - # @example - # Immutable::Set["Dog", "Elephant", "Lion"].reverse_each { |e| puts e } - # Lion - # Dog - # Elephant - # # => Immutable::Set["Dog", "Elephant", "Lion"] - # - # @yield [item] Once for each item. - # @return [self] - def reverse_each - return enum_for(:reverse_each) if not block_given? - @trie.reverse_each { |key, _| yield(key) } - self - end - - # Return a new `Set` with all the items for which the block returns true. - # - # @example - # Immutable::Set["Elephant", "Dog", "Lion"].select { |e| e.size >= 4 } - # # => Immutable::Set["Elephant", "Lion"] - # @yield [item] Once for each item. - # @return [Set] - def select - return enum_for(:select) unless block_given? - trie = @trie.select { |key, _| yield(key) } - new_trie(trie) - end - alias find_all select - alias keep_if select - - # Call the block once for each item in this `Set`. All the values returned - # from the block will be gathered into a new `Set`. If no block is given, - # an `Enumerator` is returned instead. - # - # @example - # Immutable::Set["Cat", "Elephant", "Dog", "Lion"].map { |e| e.size } - # # => Immutable::Set[8, 4, 3] - # - # @yield [item] Once for each item. - # @return [Set] - def map - return enum_for(:map) if not block_given? - return self if empty? - self.class.new(super) - end - alias collect map - - # Return `true` if the given item is present in this `Set`. More precisely, - # return `true` if an object with the same `#hash` code, and which is also `#eql?` - # to the given object is present. - # - # @example - # Immutable::Set["A", "B", "C"].include?("B") # => true - # Immutable::Set["A", "B", "C"].include?("Z") # => false - # - # @param object [Object] The object to check for - # @return [Boolean] - def include?(object) - @trie.key?(object) - end - alias member? include? - - # Return a member of this `Set`. The member chosen will be the first one which - # would be yielded by {#each}. If the set is empty, return `nil`. - # - # @example - # Immutable::Set["A", "B", "C"].first # => "C" - # - # @return [Object] - def first - (entry = @trie.at(0)) && entry[0] - end - - # Return a {SortedSet} which contains the same items as this `Set`, ordered by - # the given comparator block. - # - # @example - # Immutable::Set["Elephant", "Dog", "Lion"].sort - # # => Immutable::SortedSet["Dog", "Elephant", "Lion"] - # Immutable::Set["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } - # # => Immutable::SortedSet["Dog", "Lion", "Elephant"] - # - # @yield [a, b] Any number of times with different pairs of elements. - # @yieldreturn [Integer] Negative if the first element should be sorted - # lower, positive if the latter element, or 0 if - # equal. - # @return [SortedSet] - def sort(&comparator) - SortedSet.new(to_a, &comparator) - end - - # Return a {SortedSet} which contains the same items as this `Set`, ordered - # by mapping each item through the provided block to obtain sort keys, and - # then sorting the keys. - # - # @example - # Immutable::Set["Elephant", "Dog", "Lion"].sort_by { |e| e.size } - # # => Immutable::SortedSet["Dog", "Lion", "Elephant"] - # - # @yield [item] Once for each item to create the set, and then potentially - # again depending on what operations are performed on the - # returned {SortedSet}. As such, it is recommended that the - # block be a pure function. - # @yieldreturn [Object] sort key for the item - # @return [SortedSet] - def sort_by(&mapper) - SortedSet.new(to_a, &mapper) - end - - # Return a new `Set` which contains all the members of both this `Set` and `other`. - # `other` can be any `Enumerable` object. - # - # @example - # Immutable::Set[1, 2] | Immutable::Set[2, 3] # => Immutable::Set[1, 2, 3] - # - # @param other [Enumerable] The collection to merge with - # @return [Set] - def union(other) - if other.is_a?(Immutable::Set) - if other.size > size - small_set_pairs = @trie - large_set_trie = other.instance_variable_get(:@trie) - else - small_set_pairs = other.instance_variable_get(:@trie) - large_set_trie = @trie - end - else - if other.respond_to?(:lazy) - small_set_pairs = other.lazy.map { |e| [e, nil] } - else - small_set_pairs = other.map { |e| [e, nil] } - end - large_set_trie = @trie - end - - trie = large_set_trie.bulk_put(small_set_pairs) - new_trie(trie) - end - alias | union - alias + union - alias merge union - - # Return a new `Set` which contains all the items which are members of both - # this `Set` and `other`. `other` can be any `Enumerable` object. - # - # @example - # Immutable::Set[1, 2] & Immutable::Set[2, 3] # => Immutable::Set[2] - # - # @param other [Enumerable] The collection to intersect with - # @return [Set] - def intersection(other) - if other.size < @trie.size - if other.is_a?(Immutable::Set) - trie = other.instance_variable_get(:@trie).select { |key, _| include?(key) } - else - trie = Trie.new(0) - other.each { |obj| trie.put!(obj, nil) if include?(obj) } - end - else - trie = @trie.select { |key, _| other.include?(key) } - end - new_trie(trie) - end - alias & intersection - - # Return a new `Set` with all the items in `other` removed. `other` can be - # any `Enumerable` object. - # - # @example - # Immutable::Set[1, 2] - Immutable::Set[2, 3] # => Immutable::Set[1] - # - # @param other [Enumerable] The collection to subtract from this set - # @return [Set] - def difference(other) - trie = if (@trie.size <= other.size) && (other.is_a?(Immutable::Set) || (defined?(::Set) && other.is_a?(::Set))) - @trie.select { |key, _| !other.include?(key) } - else - @trie.bulk_delete(other) - end - new_trie(trie) - end - alias subtract difference - alias - difference - - # Return a new `Set` which contains all the items which are members of this - # `Set` or of `other`, but not both. `other` can be any `Enumerable` object. - # - # @example - # Immutable::Set[1, 2] ^ Immutable::Set[2, 3] # => Immutable::Set[1, 3] - # - # @param other [Enumerable] The collection to take the exclusive disjunction of - # @return [Set] - def exclusion(other) - ((self | other) - (self & other)) - end - alias ^ exclusion - - # Return `true` if all items in this `Set` are also in `other`. - # - # @example - # Immutable::Set[2, 3].subset?(Immutable::Set[1, 2, 3]) # => true - # - # @param other [Set] - # @return [Boolean] - def subset?(other) - return false if other.size < size - - # This method has the potential to be very slow if 'other' is a large Array, so to avoid that, - # we convert those Arrays to Sets before checking presence of items - # Time to convert Array -> Set is linear in array.size - # Time to check for presence of all items in an Array is proportional to set.size * array.size - # Note that both sides of that equation have array.size -- hence those terms cancel out, - # and the break-even point is solely dependent on the size of this collection - # After doing some benchmarking to estimate the constants, it appears break-even is at ~190 items - # We also check other.size, to avoid the more expensive #is_a? checks in cases where it doesn't matter - # - if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) - other = ::Set.new(other) - end - all? { |item| other.include?(item) } - end - alias <= subset? - - # Return `true` if all items in `other` are also in this `Set`. - # - # @example - # Immutable::Set[1, 2, 3].superset?(Immutable::Set[2, 3]) # => true - # - # @param other [Set] - # @return [Boolean] - def superset?(other) - other.subset?(self) - end - alias >= superset? - - # Returns `true` if `other` contains all the items in this `Set`, plus at least - # one item which is not in this set. - # - # @example - # Immutable::Set[2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => true - # Immutable::Set[1, 2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => false - # - # @param other [Set] - # @return [Boolean] - def proper_subset?(other) - return false if other.size <= size - # See comments above - if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) - other = ::Set.new(other) - end - all? { |item| other.include?(item) } - end - alias < proper_subset? - - # Returns `true` if this `Set` contains all the items in `other`, plus at least - # one item which is not in `other`. - # - # @example - # Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[2, 3]) # => true - # Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[1, 2, 3]) # => false - # - # @param other [Set] - # @return [Boolean] - def proper_superset?(other) - other.proper_subset?(self) - end - alias > proper_superset? - - # Return `true` if this `Set` and `other` do not share any items. - # - # @example - # Immutable::Set[1, 2].disjoint?(Immutable::Set[8, 9]) # => true - # - # @param other [Set] - # @return [Boolean] - def disjoint?(other) - if other.size <= size - other.each { |item| return false if include?(item) } - else - # See comment on #subset? - if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set)) - other = ::Set.new(other) - end - each { |item| return false if other.include?(item) } - end - true - end - - # Return `true` if this `Set` and `other` have at least one item in common. - # - # @example - # Immutable::Set[1, 2].intersect?(Immutable::Set[2, 3]) # => true - # - # @param other [Set] - # @return [Boolean] - def intersect?(other) - !disjoint?(other) - end - - # Recursively insert the contents of any nested `Set`s into this `Set`, and - # remove them. - # - # @example - # Immutable::Set[Immutable::Set[1, 2], Immutable::Set[3, 4]].flatten - # # => Immutable::Set[1, 2, 3, 4] - # - # @return [Set] - def flatten - reduce(self.class.empty) do |set, item| - next set.union(item.flatten) if item.is_a?(Set) - set.add(item) - end - end - - alias group group_by - alias classify group_by - - # Return a randomly chosen item from this `Set`. If the set is empty, return `nil`. - # - # @example - # Immutable::Set[1, 2, 3, 4, 5].sample # => 3 - # - # @return [Object] - def sample - empty? ? nil : @trie.at(rand(size))[0] - end - - # Return an empty `Set` instance, of the same class as this one. Useful if you - # have multiple subclasses of `Set` and want to treat them polymorphically. - # - # @return [Set] - def clear - self.class.empty - end - - # Return true if `other` has the same type and contents as this `Set`. - # - # @param other [Object] The object to compare with - # @return [Boolean] - def eql?(other) - return true if other.equal?(self) - return false if not instance_of?(other.class) - other_trie = other.instance_variable_get(:@trie) - return false if @trie.size != other_trie.size - @trie.each do |key, _| - return false if !other_trie.key?(key) - end - true - end - alias == eql? - - # See `Object#hash`. - # @return [Integer] - def hash - reduce(0) { |hash, item| (hash << 5) - hash + item.hash } - end - - # Return `self`. Since this is an immutable object duplicates are - # equivalent. - # @return [Set] - def dup - self - end - alias clone dup - - undef :"<=>" # Sets are not ordered, so Enumerable#<=> will give a meaningless result - undef :each_index # Set members cannot be accessed by 'index', so #each_index is not meaningful - - # Return `self`. - # - # @return [self] - def to_set - self - end - - # @private - def marshal_dump - output = {} - each do |key| - output[key] = nil - end - output - end - - # @private - def marshal_load(dictionary) - @trie = dictionary.reduce(EmptyTrie) do |trie, key_value| - trie.put(key_value.first, nil) - end - end - - private - - def new_trie(trie) - if trie.empty? - self.class.empty - elsif trie.equal?(@trie) - self - else - self.class.alloc(trie) - end - end - end - - # The canonical empty `Set`. Returned by `Set[]` when - # invoked with no arguments; also returned by `Set.empty`. Prefer using this - # one rather than creating many empty sets using `Set.new`. - # - # @private - EmptySet = Immutable::Set.empty -end +# Definition of Immutable::Vector is in a separate file to avoid +# circular dependency warnings +require 'immutable/_core' diff --git a/lib/immutable/vector.rb b/lib/immutable/vector.rb index 9b9ac80..6804112 100644 --- a/lib/immutable/vector.rb +++ b/lib/immutable/vector.rb @@ -1,1555 +1,3 @@ -require 'immutable/enumerable' -require 'immutable/hash' - -module Immutable - - # A `Vector` is an ordered, integer-indexed collection of objects. Like - # Ruby's `Array`, `Vector` indexing starts at zero and negative indexes count - # back from the end. - # - # `Vector` has a similar interface to `Array`. The main difference is methods - # that would destructively update an `Array` (such as {#insert} or - # {#delete_at}) instead return new `Vectors` and leave the existing one - # unchanged. - # - # ### Creating New Vectors - # - # Immutable::Vector.new([:first, :second, :third]) - # Immutable::Vector[1, 2, 3, 4, 5] - # - # ### Retrieving Items from Vectors - # - # vector = Immutable::Vector[1, 2, 3, 4, 5] - # - # vector[0] # => 1 - # vector[-1] # => 5 - # vector[0,3] # => Immutable::Vector[1, 2, 3] - # vector[1..-1] # => Immutable::Vector[2, 3, 4, 5] - # vector.first # => 1 - # vector.last # => 5 - # - # ### Creating Modified Vectors - # - # vector.add(6) # => Immutable::Vector[1, 2, 3, 4, 5, 6] - # vector.insert(1, :a, :b) # => Immutable::Vector[1, :a, :b, 2, 3, 4, 5] - # vector.delete_at(2) # => Immutable::Vector[1, 2, 4, 5] - # vector + [6, 7] # => Immutable::Vector[1, 2, 3, 4, 5, 6, 7] - # - class Vector - include Immutable::Enumerable - - # @private - BLOCK_SIZE = 32 - # @private - INDEX_MASK = BLOCK_SIZE - 1 - # @private - BITS_PER_LEVEL = 5 - - # Return the number of items in this `Vector` - # @return [Integer] - attr_reader :size - alias length size - - class << self - # Create a new `Vector` populated with the given items. - # @return [Vector] - def [](*items) - new(items.freeze) - end - - # Return an empty `Vector`. If used on a subclass, returns an empty instance - # of that class. - # - # @return [Vector] - def empty - @empty ||= new - end - - # "Raw" allocation of a new `Vector`. Used internally to create a new - # instance quickly after building a modified trie. - # - # @return [Vector] - # @private - def alloc(root, size, levels) - obj = allocate - obj.instance_variable_set(:@root, root) - obj.instance_variable_set(:@size, size) - obj.instance_variable_set(:@levels, levels) - obj.freeze - end - end - - def initialize(items=[].freeze) - items = items.to_a - if items.size <= 32 - items = items.dup.freeze if !items.frozen? - @root, @size, @levels = items, items.size, 0 - else - root, size, levels = items, items.size, 0 - while root.size > 32 - root = root.each_slice(32).to_a - levels += 1 - end - @root, @size, @levels = root.freeze, size, levels - end - freeze - end - - # Return `true` if this `Vector` contains no items. - # - # @return [Boolean] - def empty? - @size == 0 - end - - # Return the first item in the `Vector`. If the vector is empty, return `nil`. - # - # @example - # Immutable::Vector["A", "B", "C"].first # => "A" - # - # @return [Object] - def first - get(0) - end - - # Return the last item in the `Vector`. If the vector is empty, return `nil`. - # - # @example - # Immutable::Vector["A", "B", "C"].last # => "C" - # - # @return [Object] - def last - get(-1) - end - - # Return a new `Vector` with `item` added after the last occupied position. - # - # @example - # Immutable::Vector[1, 2].add(99) # => Immutable::Vector[1, 2, 99] - # - # @param item [Object] The object to insert at the end of the vector - # @return [Vector] - def add(item) - update_root(@size, item) - end - alias << add - alias push add - - # Return a new `Vector` with a new value at the given `index`. If `index` - # is greater than the length of the vector, the returned vector will be - # padded with `nil`s to the correct size. - # - # @overload set(index, item) - # Return a new `Vector` with the item at `index` replaced by `item`. - # - # @param item [Object] The object to insert into that position - # @example - # Immutable::Vector[1, 2, 3, 4].set(2, 99) - # # => Immutable::Vector[1, 2, 99, 4] - # Immutable::Vector[1, 2, 3, 4].set(-1, 99) - # # => Immutable::Vector[1, 2, 3, 99] - # Immutable::Vector[].set(2, 99) - # # => Immutable::Vector[nil, nil, 99] - # - # @overload set(index) - # Return a new `Vector` with the item at `index` replaced by the return - # value of the block. - # - # @yield (existing) Once with the existing value at the given `index`. - # @example - # Immutable::Vector[1, 2, 3, 4].set(2) { |v| v * 10 } - # # => Immutable::Vector[1, 2, 30, 4] - # - # @param index [Integer] The index to update. May be negative. - # @return [Vector] - def set(index, item = yield(get(index))) - raise IndexError, "index #{index} outside of vector bounds" if index < -@size - index += @size if index < 0 - if index > @size - suffix = Array.new(index - @size, nil) - suffix << item - replace_suffix(@size, suffix) - else - update_root(index, item) - end - end - - # Return a new `Vector` with a deeply nested value modified to the result - # of the given code block. When traversing the nested `Vector`s and - # `Hash`es, non-existing keys are created with empty `Hash` values. - # - # The code block receives the existing value of the deeply nested key (or - # `nil` if it doesn't exist). This is useful for "transforming" the value - # associated with a certain key. - # - # Note that the original `Vector` and sub-`Vector`s and sub-`Hash`es are - # left unmodified; new data structure copies are created along the path - # wherever needed. - # - # @example - # v = Immutable::Vector[123, 456, 789, Immutable::Hash["a" => Immutable::Vector[5, 6, 7]]] - # v.update_in(3, "a", 1) { |value| value + 9 } - # # => Immutable::Vector[123, 456, 789, Immutable::Hash["a" => Immutable::Vector[5, 15, 7]]] - # - # @param key_path [Object(s)] List of keys which form the path to the key to be modified - # @yield [value] The previously stored value - # @yieldreturn [Object] The new value to store - # @return [Vector] - def update_in(*key_path, &block) - if key_path.empty? - raise ArgumentError, 'must have at least one key in path' - end - key = key_path[0] - if key_path.size == 1 - new_value = block.call(get(key)) - else - value = fetch(key, Immutable::EmptyHash) - new_value = value.update_in(*key_path[1..-1], &block) - end - set(key, new_value) - end - - # Retrieve the item at `index`. If there is none (either the provided index - # is too high or too low), return `nil`. - # - # @example - # v = Immutable::Vector["A", "B", "C", "D"] - # v.get(2) # => "C" - # v.get(-1) # => "D" - # v.get(4) # => nil - # - # @param index [Integer] The index to retrieve - # @return [Object] - def get(index) - return nil if @size == 0 - index += @size if index < 0 - return nil if index >= @size || index < 0 - leaf_node_for(@root, @levels * BITS_PER_LEVEL, index)[index & INDEX_MASK] - end - alias at get - - # Retrieve the value at `index` with optional default. - # - # @overload fetch(index) - # Retrieve the value at the given index, or raise an `IndexError` if not - # found. - # - # @param index [Integer] The index to look up - # @raise [IndexError] if index does not exist - # @example - # v = Immutable::Vector["A", "B", "C", "D"] - # v.fetch(2) # => "C" - # v.fetch(-1) # => "D" - # v.fetch(4) # => IndexError: index 4 outside of vector bounds - # - # @overload fetch(index) { |index| ... } - # Retrieve the value at the given index, or return the result of yielding - # the block if not found. - # - # @yield Once if the index is not found. - # @yieldparam [Integer] index The index which does not exist - # @yieldreturn [Object] Default value to return - # @param index [Integer] The index to look up - # @example - # v = Immutable::Vector["A", "B", "C", "D"] - # v.fetch(2) { |i| i * i } # => "C" - # v.fetch(4) { |i| i * i } # => 16 - # - # @overload fetch(index, default) - # Retrieve the value at the given index, or return the provided `default` - # value if not found. - # - # @param index [Integer] The index to look up - # @param default [Object] Object to return if the key is not found - # @example - # v = Immutable::Vector["A", "B", "C", "D"] - # v.fetch(2, "Z") # => "C" - # v.fetch(4, "Z") # => "Z" - # - # @return [Object] - def fetch(index, default = (missing_default = true)) - if index >= -@size && index < @size - get(index) - elsif block_given? - yield(index) - elsif !missing_default - default - else - raise IndexError, "index #{index} outside of vector bounds" - end - end - - # Return the value of successively indexing into a nested collection. - # If any of the keys is not present, return `nil`. - # - # @example - # v = Immutable::Vector[9, Immutable::Hash[c: 'a', d: 4]] - # v.dig(1, :c) # => "a" - # v.dig(1, :f) # => nil - # - # @return [Object] - def dig(key, *rest) - value = self[key] - if rest.empty? || value.nil? - value - else - value.dig(*rest) - end - end - - # Return specific objects from the `Vector`. All overloads return `nil` if - # the starting index is out of range. - # - # @overload vector.slice(index) - # Returns a single object at the given `index`. If `index` is negative, - # count backwards from the end. - # - # @param index [Integer] The index to retrieve. May be negative. - # @return [Object] - # @example - # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] - # v[2] # => "C" - # v[-1] # => "F" - # v[6] # => nil - # - # @overload vector.slice(index, length) - # Return a subvector starting at `index` and continuing for `length` - # elements or until the end of the `Vector`, whichever occurs first. - # - # @param start [Integer] The index to start retrieving items from. May be - # negative. - # @param length [Integer] The number of items to retrieve. - # @return [Vector] - # @example - # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] - # v[2, 3] # => Immutable::Vector["C", "D", "E"] - # v[-2, 3] # => Immutable::Vector["E", "F"] - # v[20, 1] # => nil - # - # @overload vector.slice(index..end) - # Return a subvector starting at `index` and continuing to index - # `end` or the end of the `Vector`, whichever occurs first. - # - # @param range [Range] The range of indices to retrieve. - # @return [Vector] - # @example - # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] - # v[2..3] # => Immutable::Vector["C", "D"] - # v[-2..100] # => Immutable::Vector["E", "F"] - # v[20..21] # => nil - def slice(arg, length = (missing_length = true)) - if missing_length - if arg.is_a?(Range) - from, to = arg.begin, arg.end - from += @size if from < 0 - to += @size if to < 0 - to += 1 if !arg.exclude_end? - length = to - from - length = 0 if length < 0 - subsequence(from, length) - else - get(arg) - end - else - arg += @size if arg < 0 - subsequence(arg, length) - end - end - alias [] slice - - # Return a new `Vector` with the given values inserted before the element - # at `index`. If `index` is greater than the current length, `nil` values - # are added to pad the `Vector` to the required size. - # - # @example - # Immutable::Vector["A", "B", "C", "D"].insert(2, "X", "Y", "Z") - # # => Immutable::Vector["A", "B", "X", "Y", "Z", "C", "D"] - # Immutable::Vector[].insert(2, "X", "Y", "Z") - # # => Immutable::Vector[nil, nil, "X", "Y", "Z"] - # - # @param index [Integer] The index where the new items should go - # @param items [Array] The items to add - # @return [Vector] - # @raise [IndexError] if index exceeds negative range. - def insert(index, *items) - raise IndexError if index < -@size - index += @size if index < 0 - - if index < @size - suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, []) - suffix.unshift(*items) - elsif index == @size - suffix = items - else - suffix = Array.new(index - @size, nil).concat(items) - index = @size - end - - replace_suffix(index, suffix) - end - - # Return a new `Vector` with the element at `index` removed. If the given `index` - # does not exist, return `self`. - # - # @example - # Immutable::Vector["A", "B", "C", "D"].delete_at(2) - # # => Immutable::Vector["A", "B", "D"] - # - # @param index [Integer] The index to remove - # @return [Vector] - def delete_at(index) - return self if index >= @size || index < -@size - index += @size if index < 0 - - suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, []) - replace_suffix(index, suffix.tap(&:shift)) - end - - # Return a new `Vector` with the last element removed. Return `self` if - # empty. - # - # @example - # Immutable::Vector["A", "B", "C"].pop # => Immutable::Vector["A", "B"] - # - # @return [Vector] - def pop - return self if @size == 0 - replace_suffix(@size-1, []) - end - - # Return a new `Vector` with `object` inserted before the first element, - # moving the other elements upwards. - # - # @example - # Immutable::Vector["A", "B"].unshift("Z") - # # => Immutable::Vector["Z", "A", "B"] - # - # @param object [Object] The value to prepend - # @return [Vector] - def unshift(object) - insert(0, object) - end - - # Return a new `Vector` with the first element removed. If empty, return - # `self`. - # - # @example - # Immutable::Vector["A", "B", "C"].shift # => Immutable::Vector["B", "C"] - # - # @return [Vector] - def shift - delete_at(0) - end - - # Call the given block once for each item in the vector, passing each - # item from first to last successively to the block. If no block is given, - # an `Enumerator` is returned instead. - # - # @example - # Immutable::Vector["A", "B", "C"].each { |e| puts "Element: #{e}" } - # - # Element: A - # Element: B - # Element: C - # # => Immutable::Vector["A", "B", "C"] - # - # @return [self, Enumerator] - def each(&block) - return to_enum unless block_given? - traverse_depth_first(@root, @levels, &block) - self - end - - # Call the given block once for each item in the vector, from last to - # first. - # - # @example - # Immutable::Vector["A", "B", "C"].reverse_each { |e| puts "Element: #{e}" } - # - # Element: C - # Element: B - # Element: A - # - # @return [self] - def reverse_each(&block) - return enum_for(:reverse_each) unless block_given? - reverse_traverse_depth_first(@root, @levels, &block) - self - end - - # Return a new `Vector` containing all elements for which the given block returns - # true. - # - # @example - # Immutable::Vector["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } - # # => Immutable::Vector["Bird", "Elephant"] - # - # @return [Vector] - # @yield [element] Once for each element. - def select - return enum_for(:select) unless block_given? - reduce(self.class.empty) { |vector, item| yield(item) ? vector.add(item) : vector } - end - alias find_all select - alias keep_if select - - # Return a new `Vector` with all items which are equal to `obj` removed. - # `#==` is used for checking equality. - # - # @example - # Immutable::Vector["C", "B", "A", "B"].delete("B") # => Immutable::Vector["C", "A"] - # - # @param obj [Object] The object to remove (every occurrence) - # @return [Vector] - def delete(obj) - select { |item| item != obj } - end - - # Invoke the given block once for each item in the vector, and return a new - # `Vector` containing the values returned by the block. If no block is - # provided, return an enumerator. - # - # @example - # Immutable::Vector[3, 2, 1].map { |e| e * e } # => Immutable::Vector[9, 4, 1] - # - # @return [Vector, Enumerator] - def map - return enum_for(:map) if not block_given? - return self if empty? - self.class.new(super) - end - alias collect map - - # Return a new `Vector` with the concatenated results of running the block once - # for every element in this `Vector`. - # - # @example - # Immutable::Vector[1, 2, 3].flat_map { |x| [x, -x] } - # # => Immutable::Vector[1, -1, 2, -2, 3, -3] - # - # @return [Vector] - def flat_map - return enum_for(:flat_map) if not block_given? - return self if empty? - self.class.new(super) - end - - # Return a new `Vector` with the same elements as this one, but randomly permuted. - # - # @example - # Immutable::Vector[1, 2, 3, 4].shuffle # => Immutable::Vector[4, 1, 3, 2] - # - # @return [Vector] - def shuffle - self.class.new(((array = to_a).frozen? ? array.shuffle : array.shuffle!).freeze) - end - - # Return a new `Vector` with no duplicate elements, as determined by `#hash` and - # `#eql?`. For each group of equivalent elements, only the first will be retained. - # - # @example - # Immutable::Vector["A", "B", "C", "B"].uniq # => Immutable::Vector["A", "B", "C"] - # Immutable::Vector["a", "A", "b"].uniq(&:upcase) # => Immutable::Vector["a", "b"] - # - # @return [Vector] - def uniq(&block) - array = to_a - if array.frozen? - self.class.new(array.uniq(&block).freeze) - elsif array.uniq!(&block) # returns nil if no changes were made - self.class.new(array.freeze) - else - self - end - end - - # Return a new `Vector` with the same elements as this one, but in reverse order. - # - # @example - # Immutable::Vector["A", "B", "C"].reverse # => Immutable::Vector["C", "B", "A"] - # - # @return [Vector] - def reverse - self.class.new(((array = to_a).frozen? ? array.reverse : array.reverse!).freeze) - end - - # Return a new `Vector` with the same elements, but rotated so that the one at - # index `count` is the first element of the new vector. If `count` is positive, - # the elements will be shifted left, and those shifted past the lowest position - # will be moved to the end. If `count` is negative, the elements will be shifted - # right, and those shifted past the last position will be moved to the beginning. - # - # @example - # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] - # v.rotate(2) # => Immutable::Vector["C", "D", "E", "F", "A", "B"] - # v.rotate(-1) # => Immutable::Vector["F", "A", "B", "C", "D", "E"] - # - # @param count [Integer] The number of positions to shift items by - # @return [Vector] - def rotate(count = 1) - return self if (count % @size) == 0 - self.class.new(((array = to_a).frozen? ? array.rotate(count) : array.rotate!(count)).freeze) - end - - # Return a new `Vector` with all nested vectors and arrays recursively "flattened - # out". That is, their elements inserted into the new `Vector` in the place where - # the nested array/vector originally was. If an optional `level` argument is - # provided, the flattening will only be done recursively that number of times. - # A `level` of 0 means not to flatten at all, 1 means to only flatten nested - # arrays/vectors which are directly contained within this `Vector`. - # - # @example - # v = Immutable::Vector["A", Immutable::Vector["B", "C", Immutable::Vector["D"]]] - # v.flatten(1) - # # => Immutable::Vector["A", "B", "C", Immutable::Vector["D"]] - # v.flatten - # # => Immutable::Vector["A", "B", "C", "D"] - # - # @param level [Integer] The depth to which flattening should be applied - # @return [Vector] - def flatten(level = -1) - return self if level == 0 - array = to_a - if array.frozen? - self.class.new(array.flatten(level).freeze) - elsif array.flatten!(level) # returns nil if no changes were made - self.class.new(array.freeze) - else - self - end - end - - # Return a new `Vector` built by concatenating this one with `other`. `other` - # can be any object which is convertible to an `Array` using `#to_a`. - # - # @example - # Immutable::Vector["A", "B", "C"] + ["D", "E"] - # # => Immutable::Vector["A", "B", "C", "D", "E"] - # - # @param other [Enumerable] The collection to concatenate onto this vector - # @return [Vector] - def +(other) - other = other.to_a - other = other.dup if other.frozen? - replace_suffix(@size, other) - end - alias concat + - - # Combine two vectors by "zipping" them together. `others` should be arrays - # and/or vectors. The corresponding elements from this `Vector` and each of - # `others` (that is, the elements with the same indices) will be gathered - # into arrays. - # - # If `others` contains fewer elements than this vector, `nil` will be used - # for padding. - # - # @overload zip(*others) - # Return a new vector containing the new arrays. - # - # @return [Vector] - # - # @overload zip(*others) - # @yield [pair] once for each array - # @return [nil] - # - # @example - # v1 = Immutable::Vector["A", "B", "C"] - # v2 = Immutable::Vector[1, 2] - # v1.zip(v2) - # # => Immutable::Vector[["A", 1], ["B", 2], ["C", nil]] - # - # @param others [Array] The arrays/vectors to zip together with this one - # @return [Vector] - def zip(*others) - if block_given? - super - else - self.class.new(super) - end - end - - # Return a new `Vector` with the same items, but sorted. - # - # @overload sort - # Compare elements with their natural sort key (`#<=>`). - # - # @example - # Immutable::Vector["Elephant", "Dog", "Lion"].sort - # # => Immutable::Vector["Dog", "Elephant", "Lion"] - # - # @overload sort - # Uses the block as a comparator to determine sorted order. - # - # @yield [a, b] Any number of times with different pairs of elements. - # @yieldreturn [Integer] Negative if the first element should be sorted - # lower, positive if the latter element, or 0 if - # equal. - # @example - # Immutable::Vector["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } - # # => Immutable::Vector["Dog", "Lion", "Elephant"] - # - # @return [Vector] - def sort - self.class.new(super) - end - - # Return a new `Vector` with the same items, but sorted. The sort order is - # determined by mapping the items through the given block to obtain sort - # keys, and then sorting the keys according to their natural sort order - # (`#<=>`). - # - # @yield [element] Once for each element. - # @yieldreturn a sort key object for the yielded element. - # @example - # Immutable::Vector["Elephant", "Dog", "Lion"].sort_by { |e| e.size } - # # => Immutable::Vector["Dog", "Lion", "Elephant"] - # - # @return [Vector] - def sort_by - self.class.new(super) - end - - # Drop the first `n` elements and return the rest in a new `Vector`. - # - # @example - # Immutable::Vector["A", "B", "C", "D", "E", "F"].drop(2) - # # => Immutable::Vector["C", "D", "E", "F"] - # - # @param n [Integer] The number of elements to remove - # @return [Vector] - # @raise ArgumentError if `n` is negative. - def drop(n) - return self if n == 0 - return self.class.empty if n >= @size - raise ArgumentError, 'attempt to drop negative size' if n < 0 - self.class.new(flatten_suffix(@root, @levels * BITS_PER_LEVEL, n, [])) - end - - # Return only the first `n` elements in a new `Vector`. - # - # @example - # Immutable::Vector["A", "B", "C", "D", "E", "F"].take(4) - # # => Immutable::Vector["A", "B", "C", "D"] - # - # @param n [Integer] The number of elements to retain - # @return [Vector] - def take(n) - return self if n >= @size - self.class.new(super) - end - - # Drop elements up to, but not including, the first element for which the - # block returns `nil` or `false`. Gather the remaining elements into a new - # `Vector`. If no block is given, an `Enumerator` is returned instead. - # - # @example - # Immutable::Vector[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 } - # # => Immutable::Vector[5, 7, 6, 4, 2] - # - # @return [Vector, Enumerator] - def drop_while - return enum_for(:drop_while) if not block_given? - self.class.new(super) - end - - # Gather elements up to, but not including, the first element for which the - # block returns `nil` or `false`, and return them in a new `Vector`. If no block - # is given, an `Enumerator` is returned instead. - # - # @example - # Immutable::Vector[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 } - # # => Immutable::Vector[1, 3] - # - # @return [Vector, Enumerator] - def take_while - return enum_for(:take_while) if not block_given? - self.class.new(super) - end - - # Repetition. Return a new `Vector` built by concatenating `times` copies - # of this one together. - # - # @example - # Immutable::Vector["A", "B"] * 3 - # # => Immutable::Vector["A", "B", "A", "B", "A", "B"] - # - # @param times [Integer] The number of times to repeat the elements in this vector - # @return [Vector] - def *(times) - return self.class.empty if times == 0 - return self if times == 1 - result = (to_a * times) - result.is_a?(Array) ? self.class.new(result) : result - end - - # Replace a range of indexes with the given object. - # - # @overload fill(object) - # Return a new `Vector` of the same size, with every index set to - # `object`. - # - # @param [Object] object Fill value. - # @example - # Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z") - # # => Immutable::Vector["Z", "Z", "Z", "Z", "Z", "Z"] - # - # @overload fill(object, index) - # Return a new `Vector` with all indexes from `index` to the end of the - # vector set to `object`. - # - # @param [Object] object Fill value. - # @param [Integer] index Starting index. May be negative. - # @example - # Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3) - # # => Immutable::Vector["A", "B", "C", "Z", "Z", "Z"] - # - # @overload fill(object, index, length) - # Return a new `Vector` with `length` indexes, beginning from `index`, - # set to `object`. Expands the `Vector` if `length` would extend beyond - # the current length. - # - # @param [Object] object Fill value. - # @param [Integer] index Starting index. May be negative. - # @param [Integer] length - # @example - # Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2) - # # => Immutable::Vector["A", "B", "C", "Z", "Z", "F"] - # Immutable::Vector["A", "B"].fill("Z", 1, 5) - # # => Immutable::Vector["A", "Z", "Z", "Z", "Z", "Z"] - # - # @return [Vector] - # @raise [IndexError] if index is out of negative range. - def fill(object, index = 0, length = nil) - raise IndexError if index < -@size - index += @size if index < 0 - length ||= @size - index # to the end of the array, if no length given - - if index < @size - suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, []) - suffix.fill(object, 0, length) - elsif index == @size - suffix = Array.new(length, object) - else - suffix = Array.new(index - @size, nil).concat(Array.new(length, object)) - index = @size - end - - replace_suffix(index, suffix) - end - - # When invoked with a block, yields all combinations of length `n` of items - # from the `Vector`, and then returns `self`. There is no guarantee about - # which order the combinations will be yielded. - # - # If no block is given, an `Enumerator` is returned instead. - # - # @example - # v = Immutable::Vector[5, 6, 7, 8] - # v.combination(3) { |c| puts "Combination: #{c}" } - # - # Combination: [5, 6, 7] - # Combination: [5, 6, 8] - # Combination: [5, 7, 8] - # Combination: [6, 7, 8] - # #=> Immutable::Vector[5, 6, 7, 8] - # - # @return [self, Enumerator] - def combination(n) - return enum_for(:combination, n) if not block_given? - return self if n < 0 || @size < n - if n == 0 - yield [] - elsif n == 1 - each { |item| yield [item] } - elsif n == @size - yield to_a - else - combos = lambda do |result,index,remaining| - while @size - index > remaining - if remaining == 1 - yield result.dup << get(index) - else - combos[result.dup << get(index), index+1, remaining-1] - end - index += 1 - end - index.upto(@size-1) { |i| result << get(i) } - yield result - end - combos[[], 0, n] - end - self - end - - # When invoked with a block, yields all repeated combinations of length `n` of - # items from the `Vector`, and then returns `self`. A "repeated combination" is - # one in which any item from the `Vector` can appear consecutively any number of - # times. - # - # There is no guarantee about which order the combinations will be yielded in. - # - # If no block is given, an `Enumerator` is returned instead. - # - # @example - # v = Immutable::Vector[5, 6, 7, 8] - # v.repeated_combination(2) { |c| puts "Combination: #{c}" } - # - # Combination: [5, 5] - # Combination: [5, 6] - # Combination: [5, 7] - # Combination: [5, 8] - # Combination: [6, 6] - # Combination: [6, 7] - # Combination: [6, 8] - # Combination: [7, 7] - # Combination: [7, 8] - # Combination: [8, 8] - # # => Immutable::Vector[5, 6, 7, 8] - # - # @return [self, Enumerator] - def repeated_combination(n) - return enum_for(:repeated_combination, n) if not block_given? - if n < 0 - # yield nothing - elsif n == 0 - yield [] - elsif n == 1 - each { |item| yield [item] } - elsif @size == 0 - # yield nothing - else - combos = lambda do |result,index,remaining| - while index < @size-1 - if remaining == 1 - yield result.dup << get(index) - else - combos[result.dup << get(index), index, remaining-1] - end - index += 1 - end - item = get(index) - remaining.times { result << item } - yield result - end - combos[[], 0, n] - end - self - end - - # Yields all permutations of length `n` of items from the `Vector`, and then - # returns `self`. If no length `n` is specified, permutations of all elements - # will be yielded. - # - # There is no guarantee about which order the permutations will be yielded in. - # - # If no block is given, an `Enumerator` is returned instead. - # - # @example - # v = Immutable::Vector[5, 6, 7] - # v.permutation(2) { |p| puts "Permutation: #{p}" } - # - # Permutation: [5, 6] - # Permutation: [5, 7] - # Permutation: [6, 5] - # Permutation: [6, 7] - # Permutation: [7, 5] - # Permutation: [7, 6] - # # => Immutable::Vector[5, 6, 7] - # - # @return [self, Enumerator] - def permutation(n = @size) - return enum_for(:permutation, n) if not block_given? - if n < 0 || @size < n - # yield nothing - elsif n == 0 - yield [] - elsif n == 1 - each { |item| yield [item] } - else - used, result = [], [] - perms = lambda do |index| - 0.upto(@size-1) do |i| - next if used[i] - result[index] = get(i) - if index < n-1 - used[i] = true - perms[index+1] - used[i] = false - else - yield result.dup - end - end - end - perms[0] - end - self - end - - # When invoked with a block, yields all repeated permutations of length `n` of - # items from the `Vector`, and then returns `self`. A "repeated permutation" is - # one where any item from the `Vector` can appear any number of times, and in - # any position (not just consecutively) - # - # If no length `n` is specified, permutations of all elements will be yielded. - # There is no guarantee about which order the permutations will be yielded in. - # - # If no block is given, an `Enumerator` is returned instead. - # - # @example - # v = Immutable::Vector[5, 6, 7] - # v.repeated_permutation(2) { |p| puts "Permutation: #{p}" } - # - # Permutation: [5, 5] - # Permutation: [5, 6] - # Permutation: [5, 7] - # Permutation: [6, 5] - # Permutation: [6, 6] - # Permutation: [6, 7] - # Permutation: [7, 5] - # Permutation: [7, 6] - # Permutation: [7, 7] - # # => Immutable::Vector[5, 6, 7] - # - # @return [self, Enumerator] - def repeated_permutation(n = @size) - return enum_for(:repeated_permutation, n) if not block_given? - if n < 0 - # yield nothing - elsif n == 0 - yield [] - elsif n == 1 - each { |item| yield [item] } - else - result = [] - perms = lambda do |index| - 0.upto(@size-1) do |i| - result[index] = get(i) - if index < n-1 - perms[index+1] - else - yield result.dup - end - end - end - perms[0] - end - self - end - - # Cartesian product or multiplication. - # - # @overload product(*vectors) - # Return a `Vector` of all combinations of elements from this `Vector` and each - # of the given vectors or arrays. The length of the returned `Vector` is the product - # of `self.size` and the size of each argument vector or array. - # @example - # v1 = Immutable::Vector[1, 2, 3] - # v2 = Immutable::Vector["A", "B"] - # v1.product(v2) - # # => [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"], [3, "B"]] - # @overload product - # Return the result of multiplying all the items in this `Vector` together. - # - # @example - # Immutable::Vector[1, 2, 3, 4, 5].product # => 120 - # - # @return [Vector] - def product(*vectors) - # if no vectors passed, return "product" as in result of multiplying all items - return super if vectors.empty? - - vectors.unshift(self) - - if vectors.any?(&:empty?) - return block_given? ? self : [] - end - - counters = Array.new(vectors.size, 0) - - bump_counters = lambda do - i = vectors.size-1 - counters[i] += 1 - while counters[i] == vectors[i].size - counters[i] = 0 - i -= 1 - return true if i == -1 # we are done - counters[i] += 1 - end - false # not done yet - end - build_array = lambda do - array = [] - counters.each_with_index { |index,i| array << vectors[i][index] } - array - end - - if block_given? - loop do - yield build_array[] - return self if bump_counters[] - end - else - result = [] - loop do - result << build_array[] - return result if bump_counters[] - end - end - end - - # Assume all elements are vectors or arrays and transpose the rows and columns. - # In other words, take the first element of each nested vector/array and gather - # them together into a new `Vector`. Do likewise for the second, third, and so on - # down to the end of each nested vector/array. Gather all the resulting `Vectors` - # into a new `Vector` and return it. - # - # This operation is closely related to {#zip}. The result is almost the same as - # calling {#zip} on the first nested vector/array with the others supplied as - # arguments. - # - # @example - # Immutable::Vector[["A", 10], ["B", 20], ["C", 30]].transpose - # # => Immutable::Vector[Immutable::Vector["A", "B", "C"], Immutable::Vector[10, 20, 30]] - # - # @return [Vector] - # @raise [IndexError] if elements are not of the same size. - # @raise [TypeError] if an element does not respond to #size and #[] - def transpose - return self.class.empty if empty? - result = Array.new(first.size) { [] } - - 0.upto(@size-1) do |i| - source = get(i) - if source.size != result.size - raise IndexError, "element size differs (#{source.size} should be #{result.size})" - end - - 0.upto(result.size-1) do |j| - result[j].push(source[j]) - end - end - - result.map! { |a| self.class.new(a) } - self.class.new(result) - rescue NoMethodError - if any? { |x| !x.respond_to?(:size) || !x.respond_to?(:[]) } - bad = find { |x| !x.respond_to?(:size) || !x.respond_to?(:[]) } - raise TypeError, "'#{bad.inspect}' must respond to #size and #[] to be transposed" - else - raise - end - end - - # Finds a value from this `Vector` which meets the condition defined by the - # provided block, using a binary search. The vector must already be sorted - # with respect to the block. See Ruby's `Array#bsearch` for details, - # behaviour is equivalent. - # - # @example - # v = Immutable::Vector[1, 3, 5, 7, 9, 11, 13] - # # Block returns true/false for exact element match: - # v.bsearch { |e| e > 4 } # => 5 - # # Block returns number to match an element in 4 <= e <= 7: - # v.bsearch { |e| 1 - e / 4 } # => 7 - # - # @yield Once for at most `log n` elements, where `n` is the size of the - # vector. The exact elements and ordering are undefined. - # @yieldreturn [Boolean] `true` if this element matches the criteria, `false` otherwise. - # @yieldreturn [Integer] See `Array#bsearch` for details. - # @yieldparam [Object] element element to be evaluated - # @return [Object] The matched element, or `nil` if none found. - # @raise TypeError if the block returns a non-numeric, non-boolean, non-nil - # value. - def bsearch - return enum_for(:bsearch) if not block_given? - low, high, result = 0, @size, nil - while low < high - mid = (low + ((high - low) >> 1)) - val = get(mid) - v = yield val - if v.is_a? Numeric - if v == 0 - return val - elsif v > 0 - high = mid - else - low = mid + 1 - end - elsif v == true - result = val - high = mid - elsif !v - low = mid + 1 - else - raise TypeError, "wrong argument type #{v.class} (must be numeric, true, false, or nil)" - end - end - result - end - - # Return an empty `Vector` instance, of the same class as this one. Useful if you - # have multiple subclasses of `Vector` and want to treat them polymorphically. - # - # @return [Vector] - def clear - self.class.empty - end - - # Return a randomly chosen item from this `Vector`. If the vector is empty, return `nil`. - # - # @example - # Immutable::Vector[1, 2, 3, 4, 5].sample # => 2 - # - # @return [Object] - def sample - get(rand(@size)) - end - - # Return a new `Vector` with only the elements at the given `indices`, in the - # order specified by `indices`. If any of the `indices` do not exist, `nil`s will - # appear in their places. - # - # @example - # v = Immutable::Vector["A", "B", "C", "D", "E", "F"] - # v.values_at(2, 4, 5) # => Immutable::Vector["C", "E", "F"] - # - # @param indices [Array] The indices to retrieve and gather into a new `Vector` - # @return [Vector] - def values_at(*indices) - self.class.new(indices.map { |i| get(i) }.freeze) - end - - # Find the index of an element, starting from the end of the vector. - # Returns `nil` if no element is found. - # - # @overload rindex(obj) - # Return the index of the last element which is `#==` to `obj`. - # - # @example - # v = Immutable::Vector[7, 8, 9, 7, 8, 9] - # v.rindex(8) # => 4 - # - # @overload rindex - # Return the index of the last element for which the block returns true. - # - # @yield [element] Once for each element, last to first, until the block - # returns true. - # @example - # v = Immutable::Vector[7, 8, 9, 7, 8, 9] - # v.rindex { |e| e.even? } # => 4 - # - # @return [Integer] - def rindex(obj = (missing_arg = true)) - i = @size - 1 - if missing_arg - if block_given? - reverse_each { |item| return i if yield item; i -= 1 } - nil - else - enum_for(:rindex) - end - else - reverse_each { |item| return i if item == obj; i -= 1 } - nil - end - end - - # Assumes all elements are nested, indexable collections, and searches through them, - # comparing `obj` with the first element of each nested collection. Return the - # first nested collection which matches, or `nil` if none is found. - # Behaviour is undefined when elements do not meet assumptions (i.e. are - # not indexable collections). - # - # @example - # v = Immutable::Vector[["A", 10], ["B", 20], ["C", 30]] - # v.assoc("B") # => ["B", 20] - # - # @param obj [Object] The object to search for - # @return [Object] - def assoc(obj) - each do |array| - next if !array.respond_to?(:[]) - return array if obj == array[0] - end - nil - end - - # Assumes all elements are nested, indexable collections, and searches through them, - # comparing `obj` with the second element of each nested collection. Return - # the first nested collection which matches, or `nil` if none is found. - # Behaviour is undefined when elements do not meet assumptions (i.e. are - # not indexable collections). - # - # @example - # v = Immutable::Vector[["A", 10], ["B", 20], ["C", 30]] - # v.rassoc(20) # => ["B", 20] - # - # @param obj [Object] The object to search for - # @return [Object] - def rassoc(obj) - each do |array| - next if !array.respond_to?(:[]) - return array if obj == array[1] - end - nil - end - - # Return an `Array` with the same elements, in the same order. The returned - # `Array` may or may not be frozen. - # - # @return [Array] - def to_a - if @levels == 0 - # When initializing a Vector with 32 or less items, we always make - # sure @root is frozen, so we can return it directly here - @root - else - flatten_node(@root, @levels * BITS_PER_LEVEL, []) - end - end - alias to_ary to_a - - # Return true if `other` has the same type and contents as this `Vector`. - # - # @param other [Object] The collection to compare with - # @return [Boolean] - def eql?(other) - return true if other.equal?(self) - return false unless instance_of?(other.class) && @size == other.size - @root.eql?(other.instance_variable_get(:@root)) - end - - # See `Object#hash`. - # @return [Integer] - def hash - reduce(0) { |hash, item| (hash << 5) - hash + item.hash } - end - - # Return `self`. Since this is an immutable object duplicates are - # equivalent. - # @return [Vector] - def dup - self - end - alias clone dup - - # @return [::Array] - # @private - def marshal_dump - to_a - end - - # @private - def marshal_load(array) - initialize(array.freeze) - end - - private - - def traverse_depth_first(node, level, &block) - return node.each(&block) if level == 0 - node.each { |child| traverse_depth_first(child, level - 1, &block) } - end - - def reverse_traverse_depth_first(node, level, &block) - return node.reverse_each(&block) if level == 0 - node.reverse_each { |child| reverse_traverse_depth_first(child, level - 1, &block) } - end - - def leaf_node_for(node, bitshift, index) - while bitshift > 0 - node = node[(index >> bitshift) & INDEX_MASK] - bitshift -= BITS_PER_LEVEL - end - node - end - - def update_root(index, item) - root, levels = @root, @levels - while index >= (1 << (BITS_PER_LEVEL * (levels + 1))) - root = [root].freeze - levels += 1 - end - new_root = update_leaf_node(root, levels * BITS_PER_LEVEL, index, item) - if new_root.equal?(root) - self - else - self.class.alloc(new_root, @size > index ? @size : index + 1, levels) - end - end - - def update_leaf_node(node, bitshift, index, item) - slot_index = (index >> bitshift) & INDEX_MASK - if bitshift > 0 - old_child = node[slot_index] || [] - item = update_leaf_node(old_child, bitshift - BITS_PER_LEVEL, index, item) - end - existing_item = node[slot_index] - if existing_item.equal?(item) - node - else - node.dup.tap { |n| n[slot_index] = item }.freeze - end - end - - def flatten_range(node, bitshift, from, to) - from_slot = (from >> bitshift) & INDEX_MASK - to_slot = (to >> bitshift) & INDEX_MASK - - if bitshift == 0 # are we at the bottom? - node.slice(from_slot, to_slot-from_slot+1) - elsif from_slot == to_slot - flatten_range(node[from_slot], bitshift - BITS_PER_LEVEL, from, to) - else - # the following bitmask can be used to pick out the part of the from/to indices - # which will be used to direct path BELOW this node - mask = ((1 << bitshift) - 1) - result = [] - - if from & mask == 0 - flatten_node(node[from_slot], bitshift - BITS_PER_LEVEL, result) - else - result.concat(flatten_range(node[from_slot], bitshift - BITS_PER_LEVEL, from, from | mask)) - end - - (from_slot+1).upto(to_slot-1) do |slot_index| - flatten_node(node[slot_index], bitshift - BITS_PER_LEVEL, result) - end - - if to & mask == mask - flatten_node(node[to_slot], bitshift - BITS_PER_LEVEL, result) - else - result.concat(flatten_range(node[to_slot], bitshift - BITS_PER_LEVEL, to & ~mask, to)) - end - - result - end - end - - def flatten_node(node, bitshift, result) - if bitshift == 0 - result.concat(node) - elsif bitshift == BITS_PER_LEVEL - node.each { |a| result.concat(a) } - else - bitshift -= BITS_PER_LEVEL - node.each { |a| flatten_node(a, bitshift, result) } - end - result - end - - def subsequence(from, length) - return nil if from > @size || from < 0 || length < 0 - length = @size - from if @size < from + length - return self.class.empty if length == 0 - self.class.new(flatten_range(@root, @levels * BITS_PER_LEVEL, from, from + length - 1)) - end - - def flatten_suffix(node, bitshift, from, result) - from_slot = (from >> bitshift) & INDEX_MASK - - if bitshift == 0 - if from_slot == 0 - result.concat(node) - else - result.concat(node.slice(from_slot, 32)) # entire suffix of node. excess length is ignored by #slice - end - else - mask = ((1 << bitshift) - 1) - if from & mask == 0 - from_slot.upto(node.size-1) do |i| - flatten_node(node[i], bitshift - BITS_PER_LEVEL, result) - end - elsif (child = node[from_slot]) - flatten_suffix(child, bitshift - BITS_PER_LEVEL, from, result) - (from_slot+1).upto(node.size-1) do |i| - flatten_node(node[i], bitshift - BITS_PER_LEVEL, result) - end - end - result - end - end - - def replace_suffix(from, suffix) - # new suffix can go directly after existing elements - raise IndexError if from > @size - root, levels = @root, @levels - - if (from >> (BITS_PER_LEVEL * (@levels + 1))) != 0 - # index where new suffix goes doesn't fall within current tree - # we will need to deepen tree - root = [root].freeze - levels += 1 - end - - new_size = from + suffix.size - root = replace_node_suffix(root, levels * BITS_PER_LEVEL, from, suffix) - - if !suffix.empty? - levels.times { suffix = suffix.each_slice(32).to_a } - root.concat(suffix) - while root.size > 32 - root = root.each_slice(32).to_a - levels += 1 - end - else - while root.size == 1 && levels > 0 - root = root[0] - levels -= 1 - end - end - - self.class.alloc(root.freeze, new_size, levels) - end - - def replace_node_suffix(node, bitshift, from, suffix) - from_slot = (from >> bitshift) & INDEX_MASK - - if bitshift == 0 - if from_slot == 0 - suffix.shift(32) - else - node.take(from_slot).concat(suffix.shift(32 - from_slot)) - end - else - mask = ((1 << bitshift) - 1) - if from & mask == 0 - if from_slot == 0 - new_node = suffix.shift(32 * (1 << bitshift)) - while bitshift != 0 - new_node = new_node.each_slice(32).to_a - bitshift -= BITS_PER_LEVEL - end - new_node - else - result = node.take(from_slot) - remainder = suffix.shift((32 - from_slot) * (1 << bitshift)) - while bitshift != 0 - remainder = remainder.each_slice(32).to_a - bitshift -= BITS_PER_LEVEL - end - result.concat(remainder) - end - elsif (child = node[from_slot]) - result = node.take(from_slot) - result.push(replace_node_suffix(child, bitshift - BITS_PER_LEVEL, from, suffix)) - remainder = suffix.shift((31 - from_slot) * (1 << bitshift)) - while bitshift != 0 - remainder = remainder.each_slice(32).to_a - bitshift -= BITS_PER_LEVEL - end - result.concat(remainder) - else - raise "Shouldn't happen" - end - end - end - end - - # The canonical empty `Vector`. Returned by `Vector[]` when - # invoked with no arguments; also returned by `Vector.empty`. Prefer using this - # one rather than creating many empty vectors using `Vector.new`. - # - # @private - EmptyVector = Immutable::Vector.empty -end +# Definition of Immutable::Vector is in a separate file to avoid +# circular dependency warnings +require 'immutable/_core' diff --git a/lib/immutable/version.rb b/lib/immutable/version.rb index f2d47e5..dbdbfb9 100644 --- a/lib/immutable/version.rb +++ b/lib/immutable/version.rb @@ -1,5 +1,5 @@ module Immutable # Current released gem version. Note that master will often have the same # value as a release gem but with different code. - VERSION = '0.1.0' + VERSION = '0.2.0' end |
