immutable-ruby-master/0000755000175000017500000000000014201005456014660 5ustar boutilboutilimmutable-ruby-master/README.md0000644000175000017500000003302314201005456016140 0ustar boutilboutilImmutable Ruby ============== [![Downloads](http://img.shields.io/gem/dtv/immutable-ruby.svg)](https://rubygems.org/gems/immutable-ruby) [![Issues](http://img.shields.io/github/issues/immutable-ruby/immutable-ruby.svg)](http://github.com/immutable-ruby/immutable-ruby/issues) [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) [![Version](http://img.shields.io/gem/v/immutable-ruby.svg)](https://rubygems.org/gems/immutable-ruby) Efficient, immutable, and thread-safe collection classes for Ruby. The `immutable-ruby` gem provides 6 [Persistent Data Structures][PDS]: [`Hash`][HASH-DOC], [`Vector`][VECTOR-DOC], [`Set`][SET-DOC], [`SortedSet`][SORTED-SET-DOC], [`List`][LIST-DOC], and [`Deque`][DEQUE-DOC] (which works as an immutable queue or stack). Whenever you "modify" an `Immutable` collection, the original is preserved and a modified copy is returned. This makes them inherently thread-safe and shareable. At the same time, they remain CPU and memory-efficient by sharing between copies. (However, you *can* still mutate objects stored in these collections. We don't recommend that you do this, unless you are sure you know what you are doing.) `Immutable` collections are almost always closed under a given operation. That is, whereas Ruby's collection methods always return arrays, `Immutable` collections will return an instance of the same class wherever possible. Where possible, `Immutable` collections offer an interface compatible with Ruby's built-in `Hash`, `Array`, and `Enumerable`, to ease code migration. Also, `Immutable` methods accept regular Ruby collections as arguments, so code which uses `Immutable` can easily interoperate with your other Ruby code. And lastly, `Immutable` lists are lazy, making it possible to (among other things) process "infinitely large" lists. [PDS]: http://en.wikipedia.org/wiki/Persistent_data_structure [HASH-DOC]: http://rubydoc.info/github/immutable-ruby/immutable-ruby/master/Immutable/Hash [SET-DOC]: http://rubydoc.info/github/immutable-ruby/immutable-ruby/master/Immutable/Set [VECTOR-DOC]: http://rubydoc.info/github/immutable-ruby/immutable-ruby/master/Immutable/Vector [LIST-DOC]: http://rubydoc.info/github/immutable-ruby/immutable-ruby/master/Immutable/List [SORTED-SET-DOC]: http://rubydoc.info/github/immutable-ruby/immutable-ruby/master/Immutable/SortedSet [DEQUE-DOC]: http://rubydoc.info/github/immutable-ruby/immutable-ruby/master/Immutable/Deque History ======= `Immutable` was forked from Simon Harris' `Hamster` library, which is no longer maintained. It features some bug fixes and performance optimizations which are not included in `Hamster`. Aside from the name of the top-level module, the public API is virtually identical. Using ===== To make the collection classes available in your code: ``` ruby require "immutable" ``` Or if you prefer to only pull in certain collection types: ``` ruby require "immutable/hash" require "immutable/vector" require "immutable/set" require "immutable/sorted_set" require "immutable/list" require "immutable/deque" ```

Hash (API Documentation)

Constructing an `Immutable::Hash` is almost as simple as a regular one: ``` ruby person = Immutable::Hash[name: "Simon", gender: :male] # => Immutable::Hash[:name => "Simon", :gender => :male] ``` Accessing the contents will be familiar to you: ``` ruby person[:name] # => "Simon" person.get(:gender) # => :male ``` Updating the contents is a little different than you are used to: ``` ruby friend = person.put(:name, "James") # => Immutable::Hash[:name => "James", :gender => :male] person # => Immutable::Hash[:name => "Simon", :gender => :male] friend[:name] # => "James" person[:name] # => "Simon" ``` As you can see, updating the hash returned a copy leaving the original intact. Similarly, deleting a key returns yet another copy: ``` ruby male = person.delete(:name) # => Immutable::Hash[:gender => :male] person # => Immutable::Hash[:name => "Simon", :gender => :male] male.key?(:name) # => false person.key?(:name) # => true ``` Since it is immutable, `Immutable::Hash` doesn't provide an assignment (`Hash#[]=`) method. However, `Hash#put` can accept a block which transforms the value associated with a given key: ``` ruby counters = Immutable::Hash[evens: 0, odds: 0] counters.put(:odds) { |n| n + 1 } # => Immutable::Hash[:odds => 1, :evens => 0] ``` Or more succinctly: ``` ruby counters.put(:odds, &:next) # => {:odds => 1, :evens => 0} ``` This is just the beginning; see the [API documentation][HASH-DOC] for details on all `Hash` methods.

Vector (API Documentation)

A `Vector` is an integer-indexed collection much like an immutable `Array`. Examples: ``` ruby vector = Immutable::Vector[1, 2, 3, 4] # => Immutable::Vector[1, 2, 3, 4] vector[0] # => 1 vector[-1] # => 4 vector.set(1, :a) # => Immutable::Vector[1, :a, 3, 4] vector.add(:b) # => Immutable::Vector[1, 2, 3, 4, :b] vector.insert(2, :a, :b) # => Immutable::Vector[1, 2, :a, :b, 3, 4] vector.delete_at(0) # => Immutable::Vector[2, 3, 4] ``` Other `Array`-like methods like `#select`, `#map`, `#shuffle`, `#uniq`, `#reverse`, `#rotate`, `#flatten`, `#sort`, `#sort_by`, `#take`, `#drop`, `#take_while`, `#drop_while`, `#fill`, `#product`, and `#transpose` are also supported. See the [API documentation][VECTOR-DOC] for details on all `Vector` methods.

Set (API Documentation)

A `Set` is an unordered collection of values with no duplicates. It is much like the Ruby standard library's `Set`, but immutable. Examples: ``` ruby set = Immutable::Set[:red, :blue, :yellow] # => Immutable::Set[:red, :blue, :yellow] set.include? :red # => true set.add :green # => Immutable::Set[:red, :blue, :yellow, :green] set.delete :blue # => Immutable::Set[:red, :yellow] set.superset? Immutable::Set[:red, :blue] # => true set.union([:red, :blue, :pink]) # => Immutable::Set[:red, :blue, :yellow, :pink] set.intersection([:red, :blue, :pink]) # => Immutable::Set[:red, :blue] ``` Like most `Immutable` methods, the set-theoretic methods `#union`, `#intersection`, `#difference`, and `#exclusion` (aliased as `#|`, `#&`, `#-`, and `#^`) all work with regular Ruby collections, or indeed any `Enumerable` object. So just like all the other `Immutable` collections, `Immutable::Set` can easily be used in combination with "ordinary" Ruby code. See the [API documentation][SET-DOC] for details on all `Set` methods.

SortedSet (API Documentation)

A `SortedSet` is like a `Set`, but ordered. You can do everything with it that you can do with a `Set`. Additionally, you can get the `#first` and `#last` item, or retrieve an item using an integral index: ``` ruby set = Immutable::SortedSet['toast', 'jam', 'bacon'] # => Immutable::SortedSet["bacon", "jam", "toast"] set.first # => "bacon" set.last # => "toast" set[1] # => "jam" ``` You can also specify the sort order using a block: ``` ruby Immutable::SortedSet.new(['toast', 'jam', 'bacon']) { |a,b| b <=> a } Immutable::SortedSet.new(['toast', 'jam', 'bacon']) { |str| str.chars.last } ``` See the [API documentation][SORTED-SET-DOC] for details on all `SortedSet` methods.

List (API Documentation)

`Immutable::List`s have a *head* (the value at the front of the list), and a *tail* (a list of the remaining items): ``` ruby list = Immutable::List[1, 2, 3] list.head # => 1 list.tail # => Immutable::List[2, 3] ``` Add to a list with `List#add`: ``` ruby original = Immutable::List[1, 2, 3] copy = original.add(0) # => Immutable::List[0, 1, 2, 3] ``` Notice how modifying a list actually returns a new list. ### Laziness `Immutable::List` is lazy where possible. It tries to defer processing items until absolutely necessary. For example, the following code will only call `Prime.prime?` as many times as necessary to generate the first 3 prime numbers between 10,000 and 1,000,000: ``` ruby require 'prime' Immutable.interval(10_000, 1_000_000).select do |number| Prime.prime?(number) end.take(3) # => 0.0009s ``` Compare that to the conventional equivalent which needs to calculate all possible values in the range before taking the first three: ``` ruby (10000..1000000).select do |number| Prime.prime?(number) end.take(3) # => 10s ``` ### Construction Besides `Immutable::List[]` there are other ways to construct lists: - `Immutable.interval(from, to)` creates a lazy list equivalent to a list containing all the values between `from` and `to` without actually creating a list that big. - `Immutable.stream { ... }` allows you to creates infinite lists. Each time a new value is required, the supplied block is called. To generate a list of integers you could do: ``` ruby count = 0 Immutable.stream { count += 1 } ``` - `Immutable.repeat(x)` creates an infinite list with `x` as the value for every element. - `Immutable.replicate(n, x)` creates a list of size `n` with `x` as the value for every element. - `Immutable.iterate(x) { |x| ... }` creates an infinite list where the first item is calculated by applying the block on the initial argument, the second item by applying the function on the previous result and so on. For example, a simpler way to generate a list of integers would be: ``` ruby Immutable.iterate(1) { |i| i + 1 } ``` or even more succinctly: ``` ruby Immutable.iterate(1, &:next) ``` - `Immutable::List.empty` returns an empty list, which you can build up using repeated calls to `#add` or other `List` methods. ### Core Extensions `Enumerable#to_list` will convert any existing `Enumerable` to a list, so you can slowly transition from built-in collection classes to `Immutable`. `IO#to_list` enables lazy processing of huge files. For example, imagine the following code to process a 100MB file: ``` ruby require 'immutable/core_ext' File.open("my_100_mb_file.txt") do |file| lines = [] file.each_line do |line| break if lines.size == 10 lines << line.chomp.downcase.reverse end end ``` Compare to the following more functional version: ``` ruby File.open("my_100_mb_file.txt") do |file| file.map(&:chomp).map(&:downcase).map(&:reverse).take(10) end ``` Unfortunately, though the second example reads nicely, it takes many seconds to run (compared with milliseconds for the first) even though we're only interested in the first ten lines. Using `#to_list` we can get the running time back comparable to the imperative version. ``` ruby File.open("my_100_mb_file.txt") do |file| file.to_list.map(&:chomp).map(&:downcase).map(&:reverse).take(10) end ``` This is possible because `IO#to_list` creates a lazy list whereby each line is only ever read and processed as needed, in effect converting it to the first example. See the API documentation for details on all [`List`][LIST-DOC] methods.

Deque (API Documentation)

A `Deque` (or "double-ended queue") is an ordered collection, which allows you to push and pop items from both front and back. This makes it perfect as an immutable stack *or* queue. Examples: ``` ruby deque = Immutable::Deque[1, 2, 3] # => Immutable::Deque[1, 2, 3] deque.first # 1 deque.last # 3 deque.pop # => Immutable::Deque[1, 2] deque.push(:a) # => Immutable::Deque[1, 2, 3, :a] deque.shift # => Immutable::Deque[2, 3] deque.unshift(:a) # => Immutable::Deque[:a, 1, 2, 3] ``` Of course, you can do the same thing with a `Vector`, but a `Deque` is more efficient. See the API documentation for details on all [`Deque`][DEQUE-DOC] methods. Installing ========== Add this line to your application's Gemfile: gem "immutable-ruby" And then execute: $ bundle Or install it yourself as: $ gem install immutable-ruby Other Reading ============= - The structure which is used for `Immutable::Hash` and `Immutable::Set`: [Hash Array Mapped Tries][HAMT] - An interesting perspective on why immutability itself is inherently a good thing: Matthias Felleisen's [Function Objects presentation][FO]. - The `immutable-ruby` [FAQ](FAQ.md) - [Code of Conduct](CONDUCT.md) - [License](LICENSE) [HAMT]: http://lampwww.epfl.ch/papers/idealhashtrees.pdf [FO]: https://web.archive.org/web/20200113172704/http://www.ccs.neu.edu/home/matthias/Presentations/ecoop2004.pdf immutable-ruby-master/.yardopts0000644000175000017500000000012314201005456016522 0ustar boutilboutil--no-private --markup=markdown --readme=YARD-README.md - LICENSE FAQ.md CONDUCT.md immutable-ruby-master/YARD-README.md0000644000175000017500000002750014201005456016700 0ustar boutilboutilImmutable Ruby ============== Efficient, immutable, and thread-safe collection classes for Ruby. The `immutable-ruby` gem provides 6 [Persistent Data Structures][PDS]: {Immutable::Hash Hash}, {Immutable::Vector Vector}, {Immutable::Set Set}, {Immutable::SortedSet SortedSet}, {Immutable::List List}, and {Immutable::Deque Deque} (which works as an immutable queue or stack). Whenever you modify an `Immutable` collection, the original is preserved and a modified copy is returned. This makes them inherently thread-safe and shareable. At the same time, they remain CPU and memory-efficient by sharing between copies. (However, you *can* still mutate objects stored in these collections. We don't recommend that you do this, unless you are sure you know what you are doing.) `Immutable` collections are almost always closed under a given operation. That is, whereas Ruby's collection methods always return arrays, `Immutable` collections will return an instance of the same class wherever possible. Where possible, `Immutable` collections offer an interface compatible with Ruby's built-in `Hash`, `Array`, `Set`, and `Enumerable`, to ease code migration. Also, `Immutable` methods accept regular Ruby collections as arguments, so code which uses `Immutable` can easily interoperate with your other Ruby code. And lastly, `Immutable` lists are lazy, making it possible to (among other things) process "infinitely large" lists. [PDS]: http://en.wikipedia.org/wiki/Persistent_data_structure Using ===== To make the collection classes available in your code: ``` ruby require "immutable" ``` Or if you prefer to only pull in certain collection types: ``` ruby require "immutable/hash" require "immutable/vector" require "immutable/set" require "immutable/sorted_set" require "immutable/list" require "immutable/deque" ```

Hash ({Immutable::Hash API Documentation})

Constructing an `Immutable::Hash` is almost as simple as a regular one: ``` ruby person = Immutable::Hash[name: "Simon", gender: :male] # => Immutable::Hash[:name => "Simon", :gender => :male] ``` Accessing the contents will be familiar to you: ``` ruby person[:name] # => "Simon" person.get(:gender) # => :male ``` Updating the contents is a little different than you are used to: ``` ruby friend = person.put(:name, "James") # => Immutable::Hash[:name => "James", :gender => :male] person # => Immutable::Hash[:name => "Simon", :gender => :male] friend[:name] # => "James" person[:name] # => "Simon" ``` As you can see, updating the hash returned a copy, leaving the original intact. Similarly, deleting a key returns yet another copy: ``` ruby male = person.delete(:name) # => Immutable::Hash[:gender => :male] person # => Immutable::Hash[:name => "Simon", :gender => :male] male.key?(:name) # => false person.key?(:name) # => true ``` Since it is immutable, `Immutable::Hash` doesn't provide an assignment (`Hash#[]=`) method. However, `Hash#put` can accept a block which transforms the value associated with a given key: ``` ruby counters = Immutable::Hash[evens: 0, odds: 0] counters.put(:odds) { |n| n + 1 } # => Immutable::Hash[:odds => 1, :evens => 0] ``` Or more succinctly: ``` ruby counters.put(:odds, &:next) # => {:odds => 1, :evens => 0} ``` This is just the beginning; see the {Immutable::Hash API documentation} for details on all `Hash` methods.

Vector ({Immutable::Vector API Documentation})

A `Vector` is an integer-indexed collection much like an immutable `Array`. Examples: ``` ruby vector = Immutable::Vector[1, 2, 3, 4] # => Immutable::Vector[1, 2, 3, 4] vector[0] # => 1 vector[-1] # => 4 vector.set(1, :a) # => Immutable::Vector[1, :a, 3, 4] vector.add(:b) # => Immutable::Vector[1, 2, 3, 4, :b] vector.insert(2, :a, :b) # => Immutable::Vector[1, 2, :a, :b, 3, 4] vector.delete_at(0) # => Immutable::Vector[2, 3, 4] ``` Other `Array`-like methods like `#select`, `#map`, `#shuffle`, `#uniq`, `#reverse`, `#rotate`, `#flatten`, `#sort`, `#sort_by`, `#take`, `#drop`, `#take_while`, `#drop_while`, `#fill`, `#product`, and `#transpose` are also supported. See the {Immutable::Vector API documentation} for details on all `Vector` methods.

Set ({Immutable::Set API Documentation})

A `Set` is an unordered collection of values with no duplicates. It is much like the Ruby standard library's `Set`, but immutable. Examples: ``` ruby set = Immutable::Set[:red, :blue, :yellow] # => Immutable::Set[:red, :blue, :yellow] set.include? :red # => true set.add :green # => Immutable::Set[:red, :blue, :yellow, :green] set.delete :blue # => Immutable::Set[:red, :yellow] set.superset? Immutable::Set[:red, :blue] # => true set.union([:red, :blue, :pink]) # => Immutable::Set[:red, :blue, :yellow, :pink] set.intersection([:red, :blue, :pink]) # => Immutable::Set[:red, :blue] ``` Like most immutable methods, the set-theoretic methods `#union`, `#intersection`, `#difference`, and `#exclusion` (aliased as `#|`, `#&`, `#-`, and `#^`) all work with regular Ruby collections, or indeed any `Enumerable` object. So just like all the other immutable collections, `Immutable::Set` can easily be used in combination with "ordinary" Ruby code. See the {Immutable::Set API documentation} for details on all `Set` methods.

SortedSet ({Immutable::SortedSet API Documentation})

A `SortedSet` is like a `Set`, but ordered. You can do everything with it that you can do with a `Set`. Additionally, you can get the `#first` and `#last` item, or retrieve an item using an integral index: ``` ruby set = Immutable::SortedSet['toast', 'jam', 'bacon'] # => Immutable::SortedSet["bacon", "jam", "toast"] set.first # => "bacon" set.last # => "toast" set[1] # => "jam" ``` You can also specify the sort order using a block: ``` ruby Immutable::SortedSet.new(['toast', 'jam', 'bacon']) { |a,b| b <=> a } Immutable::SortedSet.new(['toast', 'jam', 'bacon']) { |str| str.chars.last } ``` See the {Immutable::SortedSet API documentation} for details on all `SortedSet` methods.

List ({Immutable::List API Documentation})

`Immutable::List`s have a *head* (the value at the front of the list), and a *tail* (a list of the remaining items): ``` ruby list = Immutable::List[1, 2, 3] list.head # => 1 list.tail # => Immutable::List[2, 3] ``` Add to a list with {Immutable::List#add}: ``` ruby original = Immutable::List[1, 2, 3] copy = original.add(0) # => Immutable::List[0, 1, 2, 3] ``` Notice how modifying a list actually returns a new list. ### Laziness `Immutable::List` is lazy where possible. It tries to defer processing items until absolutely necessary. For example, the following code will only call `Prime.prime?` as many times as necessary to generate the first 3 prime numbers between 10,000 and 1,000,000: ``` ruby require 'prime' Immutable.interval(10_000, 1_000_000).select do |number| Prime.prime?(number) end.take(3) # => 0.0009s ``` Compare that to the conventional equivalent which needs to calculate all possible values in the range before taking the first three: ``` ruby (10000..1000000).select do |number| Prime.prime?(number) end.take(3) # => 10s ``` ### Construction Besides `Immutable::List[]` there are other ways to construct lists: - {Immutable.interval Immutable.interval(from, to)} creates a lazy list equivalent to a list containing all the values between `from` and `to` without actually creating a list that big. - {Immutable.stream Immutable.stream { ... }} allows you to creates infinite lists. Each time a new value is required, the supplied block is called. To generate a list of integers you could do: ``` ruby count = 0 Immutable.stream { count += 1 } ``` - {Immutable.repeat Immutable.repeat(x)} creates an infinite list with `x` as the value for every element. - {Immutable.replicate Immutable.replicate(n, x)} creates a list of size `n` with `x` as the value for every element. - {Immutable.iterate Immutable.iterate(x) { |x| ... }} creates an infinite list where the first item is calculated by applying the block on the initial argument, the second item by applying the function on the previous result and so on. For example, a simpler way to generate a list of integers would be: ``` ruby Immutable.iterate(1) { |i| i + 1 } ``` or even more succinctly: ``` ruby Immutable.iterate(1, &:next) ``` - {Immutable::List.empty} returns an empty list, which you can build up using repeated calls to {Immutable::List#add #add} or other `List` methods. ### Core Extensions {Enumerable#to_list} will convert any existing `Enumerable` to a list, so you can slowly transition from built-in collection classes to immutable. {IO#to_list} enables lazy processing of huge files. For example, imagine the following code to process a 100MB file: ``` ruby require 'immutable/core_ext' File.open("my_100_mb_file.txt") do |file| lines = [] file.each_line do |line| break if lines.size == 10 lines << line.chomp.downcase.reverse end end ``` Compare to the following more functional version: ``` ruby File.open("my_100_mb_file.txt") do |file| file.map(&:chomp).map(&:downcase).map(&:reverse).take(10) end ``` Unfortunately, though the second example reads nicely it takes many seconds to run (compared with milliseconds for the first) even though we're only interested in the first ten lines. Using `#to_list` we can get the running time back comparable to the imperative version. ``` ruby File.open("my_100_mb_file.txt") do |file| file.to_list.map(&:chomp).map(&:downcase).map(&:reverse).take(10) end ``` This is possible because `IO#to_list` creates a lazy list whereby each line is only ever read and processed as needed, in effect converting it to the first example. See the {Immutable::List API documentation} for details on all List methods.

Deque ({Immutable::Deque API Documentation})

A `Deque` (or "double-ended queue") is an ordered collection, which allows you to push and pop items from both front and back. This makes it perfect as an immutable stack *or* queue. Examples: ``` ruby deque = Immutable::Deque[1, 2, 3] # => Immutable::Deque[1, 2, 3] deque.first # 1 deque.last # 3 deque.pop # => Immutable::Deque[1, 2] deque.push(:a) # => Immutable::Deque[1, 2, 3, :a] deque.shift # => Immutable::Deque[2, 3] deque.unshift(:a) # => Immutable::Deque[:a, 1, 2, 3] ``` Of course, you can do the same thing with a `Vector`, but a `Deque` is more efficient. See the {Immutable::Deque API documentation} for details on all Deque methods. Other Reading ============= - The structure which is used for `Immutable::Hash` and `Immutable::Set`: [Hash Array Mapped Tries][HAMT] - An interesting perspective on why immutability itself is inherently a good thing: Matthias Felleisen's [Function Objects presentation][FO]. - The `immutable-ruby` {file:FAQ.md FAQ} - {file:CONDUCT.md Contributor's Code of Conduct} - {file:LICENSE License} [HAMT]: http://lampwww.epfl.ch/papers/idealhashtrees.pdf [FO]: https://web.archive.org/web/20200113172704/http://www.ccs.neu.edu/home/matthias/Presentations/ecoop2004.pdf immutable-ruby-master/.ruby-version0000644000175000017500000000000614201005456017321 0ustar boutilboutil3.0.1 immutable-ruby-master/LICENSE0000644000175000017500000000207214201005456015666 0ustar boutilboutilLicensing ========= Copyright (c) 2009-2014 Simon Harris Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. immutable-ruby-master/.gitignore0000644000175000017500000000112314201005456016645 0ustar boutilboutil# See http://help.github.com/ignore-files/ for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile ~/.gitignore # Ignore all of the generated gem stuff /pkg /*.gem # Ignore bundler config /.bundle /Gemfile.lock # Ignore all bundler caching /vendor/cache /vendor/ruby # Ignore all tempfiles /tmp # Ignores that should be in the global gitignore /coverage /doc .yardoc # Sublime Text *.sublime-project *.sublime-workspaceimmutable-ruby-master/immutable-ruby.gemspec0000644000175000017500000000246514201005456021172 0ustar boutilboutil# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'immutable/version' Gem::Specification.new do |spec| spec.name = 'immutable-ruby' spec.version = Immutable::VERSION spec.authors = ['Alex Dowad', 'Dov Murik', 'Xavier Shay', 'Simon Harris'] spec.email = ['alexinbeijing@gmail.com'] spec.summary = %q{Efficient, immutable, thread-safe collection classes for Ruby} spec.description = spec.summary spec.homepage = 'https://github.com/immutable-ruby/immutable-ruby' spec.license = 'MIT' spec.date = Time.now.strftime('%Y-%m-%d') spec.platform = Gem::Platform::RUBY spec.required_ruby_version = '>= 2.4.0' spec.files = Dir['lib/**/*'] spec.require_paths = ['lib'] spec.add_runtime_dependency 'concurrent-ruby', '~> 1.1' spec.add_runtime_dependency 'sorted_set', '~> 1.0' spec.add_development_dependency 'bundler', '>= 2.2.10' spec.add_development_dependency 'rspec', '~> 3.9' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'yard', '~> 0.9' spec.add_development_dependency 'pry', '~> 0.13' spec.add_development_dependency 'pry-doc', '~> 1.0.0' spec.add_development_dependency 'benchmark-ips', '~> 2.7' end immutable-ruby-master/bench/0000755000175000017500000000000014201005456015737 5ustar boutilboutilimmutable-ruby-master/bench/set/0000755000175000017500000000000014201005456016532 5ustar boutilboutilimmutable-ruby-master/bench/set/union_bench.rb0000644000175000017500000000047414201005456021353 0ustar boutilboutilrequire 'benchmark/ips' require 'immutable/set' Benchmark.ips do |b| small_set = Immutable::Set.new((1..10).to_a) large_set = Immutable::Set.new((1..1000).to_a) b.report 'small.union(large)' do small_set.union(large_set) end b.report 'large.union(small)' do large_set.union(small_set) end end immutable-ruby-master/bench/hash/0000755000175000017500000000000014201005456016662 5ustar boutilboutilimmutable-ruby-master/bench/hash/get_bench.rb0000644000175000017500000000206314201005456021126 0ustar boutilboutilrequire 'benchmark/ips' require 'immutable/hash' Benchmark.ips do |b| sml_hash = Immutable::Hash[1 => 1] med_hash = Immutable::Hash.empty 1_000.times { |i| med_hash = med_hash.put(i, i) } lrg_hash = Immutable::Hash.empty 1_000_000.times { |i| lrg_hash = lrg_hash.put(i, i) } b.report 'get existing small' do |n| a = 0 x = 0 while a < n x = sml_hash.get(a) a += 1 end end b.report 'get existing medium' do |n| a = 0 x = nil while a < n x = med_hash.get(a) a += 1 end end b.report 'get existing large' do |n| a = 0 x = nil while a < n x = lrg_hash.get(a) a += 1 end end b.report 'get missing small' do |n| a = 0 x = 0 while a < n x = sml_hash.get(-1) a += 1 end end b.report 'get missing medium' do |n| a = 0 x = nil while a < n x = med_hash.get(-1) a += 1 end end b.report 'get missing large' do |n| a = 0 x = nil while a < n x = lrg_hash.get(-1) a += 1 end end end immutable-ruby-master/bench/hash/each_bench.rb0000644000175000017500000000124614201005456021251 0ustar boutilboutilrequire 'benchmark/ips' require 'immutable/hash' Benchmark.ips do |b| sml_hash = Immutable::Hash[1 => 1] med_hash = Immutable::Hash.empty 1_000.times { |i| med_hash = med_hash.put(i, i) } lrg_hash = Immutable::Hash.empty 1_000_000.times { |i| lrg_hash = lrg_hash.put(i, i) } b.report 'each small' do |n| a = 0 x = 0 while a < n sml_hash.each { |y| x = y } a += 1 end end b.report 'each medium' do |n| a = 0 x = 0 while a < n med_hash.each { |y| x = y } a += 1 end end b.report 'each large' do |n| a = 0 x = 0 while a < n lrg_hash.each { |y| x = y } a += 1 end end end immutable-ruby-master/bench/hash/put_bench.rb0000644000175000017500000000126214201005456021157 0ustar boutilboutilrequire 'benchmark/ips' require 'immutable/hash' Benchmark.ips do |b| sml_hash = Immutable::Hash[1 => 1] med_hash = Immutable::Hash.empty 1_000.times { |i| med_hash = med_hash.put(i, i) } lrg_hash = Immutable::Hash.empty 1_000_000.times { |i| lrg_hash = lrg_hash.put(i, i) } b.report 'put value' do |n| a = 0 sml = sml_hash while a < n sml = sml.put(a, a) a += 1 end end b.report 'put value medium' do |n| a = 0 med = med_hash while a < n med = med.put(a, a) a += 1 end end b.report 'put value large' do |n| a = 0 lrg = lrg_hash while a < n lrg = lrg.put(a, a) a += 1 end end end immutable-ruby-master/bench/list/0000755000175000017500000000000014201005456016712 5ustar boutilboutilimmutable-ruby-master/bench/list/at_bench.rb0000644000175000017500000000135014201005456021001 0ustar boutilboutilrequire 'benchmark/ips' require 'immutable/list' Benchmark.ips do |b| sml_list = Immutable::List[1] # med_list = Immutable.iterate(1, &:next).take(100) # lrg_list = Immutable.iterate(1, &:next).take(10000) med_list = Immutable::List.empty 100.times { |i| med_list = med_list.cons(i) } lrg_list = Immutable::List.empty 10000.times { |i| lrg_list = lrg_list.cons(i) } b.report 'at small' do |n| a = 0 x = 0 while a < n x = sml_list.at(0) a += 1 end end b.report 'at medium' do |n| a = 0 x = 0 while a < n x = med_list.at(99) a += 1 end end b.report 'at large' do |n| a = 0 x = 0 while a < n x = lrg_list.at(9999) a += 1 end end end immutable-ruby-master/bench/list/cons_bench.rb0000644000175000017500000000122414201005456021337 0ustar boutilboutilrequire 'benchmark/ips' require 'immutable/list' Benchmark.ips do |b| sml_list = Immutable::List[1] med_list = Immutable::List.empty 100.times { |i| med_list = med_list.cons(i) } lrg_list = Immutable::List.empty 10000.times { |i| lrg_list = lrg_list.cons(i) } b.report 'cons small' do |n| a = 0 sml = sml_list while a < n sml = sml.cons(a) a += 1 end end b.report 'cons medium' do |n| a = 0 med = med_list while a < n med = med.cons(a) a += 1 end end b.report 'cons large' do |n| a = 0 lrg = lrg_list while a < n lrg = lrg.cons(a) a += 1 end end end immutable-ruby-master/spec/0000755000175000017500000000000014201005456015612 5ustar boutilboutilimmutable-ruby-master/spec/spec_helper.rb0000644000175000017500000000330614201005456020432 0ustar boutilboutilrequire 'pry' require 'rspec' require 'immutable/hash' require 'immutable/set' require 'immutable/vector' require 'immutable/sorted_set' require 'immutable/list' require 'immutable/deque' require 'immutable/core_ext' require 'immutable/nested' # Suppress warnings from use of old RSpec expectation and mock syntax # If all tests are eventually updated to use the new syntax, this can be removed RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end config.mock_with :rspec do |c| c.syntax = [:should, :expect] end end V = Immutable::Vector L = Immutable::List H = Immutable::Hash S = Immutable::Set SS = Immutable::SortedSet D = Immutable::Deque EmptyList = Immutable::EmptyList Struct.new('Customer', :name, :address) def fixture(name) File.read(fixture_path(name)) end def fixture_path(name) File.join('spec', 'fixtures', name) end if RUBY_ENGINE == 'ruby' def calculate_stack_overflow_depth(n) calculate_stack_overflow_depth(n + 1) rescue SystemStackError n end STACK_OVERFLOW_DEPTH = calculate_stack_overflow_depth(2) else STACK_OVERFLOW_DEPTH = 16_384 end BigList = Immutable.interval(0, STACK_OVERFLOW_DEPTH) class DeterministicHash attr_reader :hash, :value def initialize(value, hash) @value = value @hash = hash end def to_s @value.to_s end def inspect @value.inspect end def ==(other) other.is_a?(DeterministicHash) && value == other.value end alias eql? == def <=>(other) value <=> other.value end end class EqualNotEql def ==(other) true end def eql?(other) false end end class EqlNotEqual def ==(other) false end def eql?(other) true end end immutable-ruby-master/spec/fixtures/0000755000175000017500000000000014201005456017463 5ustar boutilboutilimmutable-ruby-master/spec/fixtures/io_spec.txt0000644000175000017500000000000614201005456021641 0ustar boutilboutilA B C immutable-ruby-master/spec/lib/0000755000175000017500000000000014201005456016360 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/0000755000175000017500000000000014201005456020337 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/sorted_set/0000755000175000017500000000000014201005456022512 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/sorted_set/intersect_spec.rb0000644000175000017500000000122414201005456026050 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#intersect?' do [ [[], [], false], [['A'], [], false], [[], ['A'], false], [['A'], ['A'], true], [%w[A B C], ['B'], true], [['B'], %w[A B C], true], [%w[A B C], %w[D E], false], [%w[F G H I], %w[A B C], false], [%w[A B C], %w[A B C], true], [%w[A B C], %w[A B C D], true], [%w[D E F G], %w[A B C], false], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do SS[*a].intersect?(SS[*b]).should be(expected) end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/new_spec.rb0000644000175000017500000001016614201005456024646 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '.new' do it 'accepts a single enumerable argument and creates a new sorted set' do sorted_set = SS.new([1,2,3]) sorted_set.size.should be(3) sorted_set[0].should be(1) sorted_set[1].should be(2) sorted_set[2].should be(3) end it 'also works with a Range' do sorted_set = SS.new(1..3) sorted_set.size.should be(3) sorted_set[0].should be(1) sorted_set[1].should be(2) sorted_set[2].should be(3) end it "doesn't mutate the initializer" do array = [3,2,1,3,2,1] # this will need to be sorted and duplicates filtered out sorted_set = SS.new(array) expect(array).to eq([3,2,1,3,2,1]) end it "doesn't change if the initializer is later mutated" do array = [3,2,1,3,2,1] sorted_set = SS.new(array) array.clear expect(sorted_set.to_a).to eq([1,2,3]) end it 'is amenable to overriding of #initialize' do class SnazzySortedSet < Immutable::SortedSet def initialize super(['SNAZZY!!!']) end end sorted_set = SnazzySortedSet.new sorted_set.size.should be(1) sorted_set.to_a.should == ['SNAZZY!!!'] end it 'accepts a block with arity 1' do sorted_set = SS.new(1..3, &:-@) sorted_set[0].should be(3) sorted_set[1].should be(2) sorted_set[2].should be(1) end it 'accepts a block with arity 2' do sorted_set = SS.new(1..3) { |a,b| b <=> a } sorted_set[0].should be(3) sorted_set[1].should be(2) sorted_set[2].should be(1) end it 'can use a block produced by Symbol#to_proc' do sorted_set = SS.new([Object, BasicObject], &:name.to_proc) sorted_set[0].should be(BasicObject) sorted_set[1].should be(Object) end it 'filters out duplicates' do sorted_set = SS.new(['a', 'b', 'a', 'c', 'b', 'a', 'c', 'c']) expect(sorted_set.size).to be(3) end context 'when passed a comparator with arity 2' do it 'still filters out duplicates' do sorted_set = SS.new([1,2,7,8,9,10]) { |x,y| (x%7) <=> (y%7) } expect(sorted_set.to_a).to eq([7,1,2,10]) end it "still doesn't mutate the initializer" do array = [3,2,1,3,2,1] # this will need to be sorted and duplicates filtered out sorted_set = SS.new(array) { |x,y| y <=> x } expect(array).to eq([3,2,1,3,2,1]) end it "still doesn't change if the initializer is later mutated" do array = [3,2,1,3,2,1] sorted_set = SS.new(array) { |x,y| y <=> x } array.clear expect(sorted_set.to_a).to eq([3,2,1]) end end context 'when passed a block with arity 1' do it 'still filters out duplicates' do sorted_set = SS.new([1,2,7,8,9,10]) { |x| x % 7 } expect(sorted_set.to_a).to eq([7,1,2,10]) end it "still doesn't mutate the initializer" do array = [3,2,1,3,2,1] # this will need to be sorted and duplicates filtered out sorted_set = SS.new(array) { |x| x % 7 } expect(array).to eq([3,2,1,3,2,1]) end it "still doesn't change if the initializer is later mutated" do array = [3,2,1,3,2,1] sorted_set = SS.new(array) { |x| x % 7 } array.clear expect(sorted_set.to_a).to eq([1,2,3]) end end context 'from a subclass' do it 'returns a frozen instance of the subclass' do subclass = Class.new(Immutable::SortedSet) instance = subclass.new(['some', 'values']) instance.class.should be subclass instance.frozen?.should be true end end end describe '.[]' do it 'accepts a variable number of items and creates a new sorted set' do sorted_set = SS['a', 'b'] sorted_set.size.should be(2) sorted_set[0].should == 'a' sorted_set[1].should == 'b' end it 'filters out duplicate items' do sorted_set = SS['a', 'b', 'a', 'c', 'b', 'a', 'c', 'c'] expect(sorted_set.size).to be(3) sorted_set[0].should == 'a' sorted_set[1].should == 'b' sorted_set[2].should == 'c' end end end immutable-ruby-master/spec/lib/immutable/sorted_set/at_spec.rb0000644000175000017500000000110014201005456024445 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#at' do [ [[], 10, nil], [['A'], 10, nil], [%w[A B C], 0, 'A'], [%w[A B C], 1, 'B'], [%w[A B C], 2, 'C'], [%w[A B C], 3, nil], [%w[A B C], -1, 'C'], [%w[A B C], -2, 'B'], [%w[A B C], -3, 'A'], [%w[A B C], -4, nil] ].each do |values, number, expected| describe "#{values.inspect} with #{number}" do it "returns #{expected.inspect}" do SS[*values].at(number).should == expected end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/delete_at_spec.rb0000644000175000017500000000103414201005456025775 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#delete_at' do let(:sorted_set) { SS[1,2,3,4,5] } it 'removes the element at the specified index' do sorted_set.delete_at(0).should eql(SS[2,3,4,5]) sorted_set.delete_at(2).should eql(SS[1,2,4,5]) sorted_set.delete_at(-1).should eql(SS[1,2,3,4]) end it 'makes no modification if the index is out of range' do sorted_set.delete_at(5).should eql(sorted_set) sorted_set.delete_at(-6).should eql(sorted_set) end end end immutable-ruby-master/spec/lib/immutable/sorted_set/size_spec.rb0000644000175000017500000000056314201005456025027 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:size, :length].each do |method| describe "##{method}" do [ [[], 0], [['A'], 1], [%w[A B C], 3], ].each do |values, result| it "returns #{result} for #{values.inspect}" do SS[*values].send(method).should == result end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/above_spec.rb0000644000175000017500000000311214201005456025142 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#above' do context 'when called without a block' do it 'returns a sorted set of all items higher than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = set.above(threshold) array = items.select { |x| x > threshold }.sort result.class.should be(Immutable::SortedSet) result.size.should == array.size result.to_a.should == array end end end context 'when called with a block' do it 'yields all the items higher than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = [] set.above(threshold) { |x| result << x } array = items.select { |x| x > threshold }.sort result.size.should == array.size result.should == array end end end context 'on an empty set' do it 'returns an empty set' do SS.empty.above(1).should be_empty SS.empty.above('abc').should be_empty SS.empty.above(:symbol).should be_empty end end context 'with an argument higher than all the values in the set' do it 'returns an empty set' do result = SS.new(1..100).above(100) result.class.should be(Immutable::SortedSet) result.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/drop_spec.rb0000644000175000017500000000272714201005456025025 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#drop' do [ [[], 0, []], [[], 10, []], [['A'], 10, []], [%w[A B C], 0, %w[A B C]], [%w[A B C], 1, %w[B C]], [%w[A B C], 2, ['C']], [%w[A B C], 3, []] ].each do |values, number, expected| context "#{number} from #{values.inspect}" do let(:sorted_set) { SS[*values] } it 'preserves the original' do sorted_set.drop(number) sorted_set.should eql(SS[*values]) end it "returns #{expected.inspect}" do sorted_set.drop(number).should eql(SS[*expected]) end end end context 'when argument is zero' do let(:sorted_set) { SS[6, 7, 8, 9] } it 'returns self' do sorted_set.drop(0).should be(sorted_set) end end context 'when the set has a custom order' do let(:sorted_set) { SS.new([1, 2, 3], &:-@)} it 'maintains the custom order' do sorted_set.drop(1).to_a.should == [2, 1] sorted_set.drop(2).to_a.should == [1] end it 'keeps the comparator even when set is cleared' do s = sorted_set.drop(3) s.add(4).add(5).add(6).to_a.should == [6, 5, 4] end end context 'when called on a subclass' do it 'should return an instance of the subclass' do subclass = Class.new(Immutable::SortedSet) subclass.new([1,2,3]).drop(1).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/take_while_spec.rb0000644000175000017500000000160314201005456026165 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#take_while' do [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B]], ].each do |values, expected| context "on #{values.inspect}" do let(:sorted_set) { SS[*values] } context 'with a block' do it "returns #{expected.inspect}" do sorted_set.take_while { |item| item < 'C' }.should eql(SS[*expected]) end it 'preserves the original' do sorted_set.take_while { |item| item < 'C' } sorted_set.should eql(SS[*values]) end end context 'without a block' do it 'returns an Enumerator' do sorted_set.take_while.class.should be(Enumerator) sorted_set.take_while.each { |item| item < 'C' }.should eql(SS[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/sample_spec.rb0000644000175000017500000000060614201005456025334 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#sample' do let(:sorted_set) { Immutable::SortedSet.new(1..10) } it 'returns a randomly chosen item' do chosen = 100.times.map { sorted_set.sample } chosen.each { |item| sorted_set.include?(item).should == true } sorted_set.each { |item| chosen.include?(item).should == true } end end end immutable-ruby-master/spec/lib/immutable/sorted_set/subset_spec.rb0000644000175000017500000000234614201005456025363 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#subset?' do [ [[], [], true], [['A'], [], false], [[], ['A'], true], [['A'], ['A'], true], [%w[A B C], ['B'], false], [['B'], %w[A B C], true], [%w[A B C], %w[A C], false], [%w[A C], %w[A B C], true], [%w[A B C], %w[A B C], true], [%w[A B C], %w[A B C D], true], [%w[A B C D], %w[A B C], false], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do SS[*a].subset?(SS[*b]).should == expected end end end end describe '#proper_subset?' do [ [[], [], false], [['A'], [], false], [[], ['A'], true], [['A'], ['A'], false], [%w[A B C], ['B'], false], [['B'], %w[A B C], true], [%w[A B C], %w[A C], false], [%w[A C], %w[A B C], true], [%w[A B C], %w[A B C], false], [%w[A B C], %w[A B C D], true], [%w[A B C D], %w[A B C], false], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do SS[*a].proper_subset?(SS[*b]).should == expected end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/minimum_spec.rb0000644000175000017500000000064314201005456025527 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#min' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], [[1,2,3,4,5], 1], [[0, -0.0, 2.2, -4, -4.2], -4.2], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do SS[*values].min.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/inspect_spec.rb0000644000175000017500000000204014201005456025512 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#inspect' do [ [[], 'Immutable::SortedSet[]'], [['A'], 'Immutable::SortedSet["A"]'], [['C', 'B', 'A'], 'Immutable::SortedSet["A", "B", "C"]'] ].each do |values, expected| context "on #{values.inspect}" do let(:sorted_set) { SS[*values] } it "returns #{expected.inspect}" do sorted_set.inspect.should == expected end it "returns a string which can be eval'd to get an equivalent set" do eval(sorted_set.inspect).should eql(sorted_set) end end end MySortedSet = Class.new(Immutable::SortedSet) context 'from a subclass' do let(:sorted_set) { MySortedSet[1, 2] } it 'returns a programmer-readable representation of the set contents' do sorted_set.inspect.should == 'MySortedSet[1, 2]' end it "returns a string which can be eval'd to get an equivalent set" do eval(sorted_set.inspect).should eql(sorted_set) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/marshal_spec.rb0000644000175000017500000000212314201005456025476 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#marshal_dump/#marshal_load' do let(:ruby) do File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) end let(:child_cmd) do %Q|#{ruby} -I lib -r immutable -e 'set = Immutable::SortedSet[5, 10, 15]; $stdout.write(Marshal.dump(set))'| end let(:reloaded_set) do IO.popen(child_cmd, 'r+') do |child| reloaded_set = Marshal.load(child) child.close reloaded_set end end it 'can survive dumping and loading into a new process' do expect(reloaded_set).to eql(SS[5, 10, 15]) end it 'is still possible to find items by index after loading' do expect(reloaded_set[0]).to eq(5) expect(reloaded_set[1]).to eq(10) expect(reloaded_set[2]).to eq(15) expect(reloaded_set.size).to eq(3) end it 'raises a TypeError if set has a custom sort order' do # this is because comparator block can't be serialized -> { Marshal.dump(SS.new([1, 2, 3], &:-@)) }.should raise_error(TypeError) end end end immutable-ruby-master/spec/lib/immutable/sorted_set/empty_spec.rb0000644000175000017500000000145714201005456025216 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#empty?' do [ [[], true], [['A'], false], [%w[A B C], false], ].each do |values, expected| context "on #{values.inspect}" do let(:sorted_set) { SS[*values] } it "returns #{expected.inspect}" do sorted_set.empty?.should == expected end end end end describe '.empty' do it 'returns the canonical empty set' do SS.empty.size.should be(0) SS.empty.object_id.should be(SS.empty.object_id) end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::SortedSet) subclass.empty.class.should be(subclass) subclass.empty.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/fetch_spec.rb0000644000175000017500000000413214201005456025142 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#fetch' do let(:sorted_set) { SS['a', 'b', 'c'] } context 'with no default provided' do context 'when the index exists' do it 'returns the value at the index' do sorted_set.fetch(0).should == 'a' sorted_set.fetch(1).should == 'b' sorted_set.fetch(2).should == 'c' end end context 'when the key does not exist' do it 'raises an IndexError' do -> { sorted_set.fetch(3) }.should raise_error(IndexError) -> { sorted_set.fetch(-4) }.should raise_error(IndexError) end end end context 'with a default value' do context 'when the index exists' do it 'returns the value at the index' do sorted_set.fetch(0, 'default').should == 'a' sorted_set.fetch(1, 'default').should == 'b' sorted_set.fetch(2, 'default').should == 'c' end end context 'when the index does not exist' do it 'returns the default value' do sorted_set.fetch(3, 'default').should == 'default' sorted_set.fetch(-4, 'default').should == 'default' end end end context 'with a default block' do context 'when the index exists' do it 'returns the value at the index' do sorted_set.fetch(0) { 'default'.upcase }.should == 'a' sorted_set.fetch(1) { 'default'.upcase }.should == 'b' sorted_set.fetch(2) { 'default'.upcase }.should == 'c' end end context 'when the index does not exist' do it 'invokes the block with the missing index as parameter' do sorted_set.fetch(3) { |index| index.should == 3 } sorted_set.fetch(-4) { |index| index.should == -4 } sorted_set.fetch(3) { 'default'.upcase }.should == 'DEFAULT' sorted_set.fetch(-4) { 'default'.upcase }.should == 'DEFAULT' end end end it 'gives precedence to default block over default argument if passed both' do sorted_set.fetch(3, 'one') { 'two' }.should == 'two' end end end immutable-ruby-master/spec/lib/immutable/sorted_set/each_spec.rb0000644000175000017500000000124514201005456024753 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#each' do context 'with no block' do let(:sorted_set) { SS['A', 'B', 'C'] } it 'returns an Enumerator' do sorted_set.each.class.should be(Enumerator) sorted_set.each.to_a.should eql(sorted_set.to_a) end end context 'with a block' do let(:sorted_set) { SS.new((1..1025).to_a.reverse) } it 'returns self' do sorted_set.each {}.should be(sorted_set) end it 'iterates over the items in order' do items = [] sorted_set.each { |item| items << item } items.should == (1..1025).to_a end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/last_spec.rb0000644000175000017500000000134014201005456025012 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do let(:sorted_set) { SS[*values] } describe '#last' do let(:last) { sorted_set.last } shared_examples 'checking values' do it 'returns the last item' do expect(last).to eq(last_item) end end context 'with an empty set' do let(:last_item) { nil } let(:values) { [] } include_examples 'checking values' end context 'with a single item set' do let(:last_item) { 'A' } let(:values) { %w[A] } include_examples 'checking values' end context 'with a multi-item set' do let(:last_item) { 'B' } let(:values) { %w[B A] } include_examples 'checking values' end end end immutable-ruby-master/spec/lib/immutable/sorted_set/delete_spec.rb0000644000175000017500000000500114201005456025307 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do let(:sorted_set) { SS['A', 'B', 'C'] } describe '#delete' do context 'on an empty set' do it 'returns an empty set' do SS.empty.delete(0).should be(SS.empty) end end context 'with an existing value' do it 'preserves the original' do sorted_set.delete('B') sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns a copy with the remaining of values' do sorted_set.delete('B').should eql(SS['A', 'C']) end end context 'with a non-existing value' do it 'preserves the original values' do sorted_set.delete('D') sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns self' do sorted_set.delete('D').should equal(sorted_set) end end context 'when removing the last value in a sorted set' do it 'maintains the set order' do ss = SS.new(['peanuts', 'jam', 'milk'], &:length) ss = ss.delete('jam').delete('peanuts').delete('milk') ss = ss.add('banana').add('sugar').add('spam') ss.to_a.should == ['spam', 'sugar', 'banana'] end context 'when the set is in natural order' do it 'returns the canonical empty set' do sorted_set.delete('B').delete('C').delete('A').should be(Immutable::EmptySortedSet) end end end 1.upto(10) do |n| values = (1..n).to_a values.combination(3) do |to_delete| expected = to_delete.reduce(values.dup) { |ary,val| ary.delete(val); ary } describe "on #{values.inspect}, when deleting #{to_delete.inspect}" do it "returns #{expected.inspect}" do set = SS.new(values) result = to_delete.reduce(set) { |s,val| s.delete(val) } result.should eql(SS.new(expected)) result.to_a.should eql(expected) end end end end end describe '#delete?' do context 'with an existing value' do it 'preserves the original' do sorted_set.delete?('B') sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns a copy with the remaining values' do sorted_set.delete?('B').should eql(SS['A', 'C']) end end context 'with a non-existing value' do it 'preserves the original values' do sorted_set.delete?('D') sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns false' do sorted_set.delete?('D').should be(false) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/slice_spec.rb0000644000175000017500000003036314201005456025155 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do let(:sorted_set) { SS[1,2,3,4] } let(:big) { SS.new(1..10000) } [:slice, :[]].each do |method| describe "##{method}" do context 'when passed a positive integral index' do it 'returns the element at that index' do sorted_set.send(method, 0).should be(1) sorted_set.send(method, 1).should be(2) sorted_set.send(method, 2).should be(3) sorted_set.send(method, 3).should be(4) sorted_set.send(method, 4).should be(nil) sorted_set.send(method, 10).should be(nil) big.send(method, 0).should be(1) big.send(method, 9999).should be(10000) end it 'leaves the original unchanged' do sorted_set.should eql(SS[1,2,3,4]) end end context 'when passed a negative integral index' do it 'returns the element which is number (index.abs) counting from the end of the sorted_set' do sorted_set.send(method, -1).should be(4) sorted_set.send(method, -2).should be(3) sorted_set.send(method, -3).should be(2) sorted_set.send(method, -4).should be(1) sorted_set.send(method, -5).should be(nil) sorted_set.send(method, -10).should be(nil) big.send(method, -1).should be(10000) big.send(method, -10000).should be(1) end end context 'when passed a positive integral index and count' do it "returns 'count' elements starting from 'index'" do sorted_set.send(method, 0, 0).should eql(SS.empty) sorted_set.send(method, 0, 1).should eql(SS[1]) sorted_set.send(method, 0, 2).should eql(SS[1,2]) sorted_set.send(method, 0, 4).should eql(SS[1,2,3,4]) sorted_set.send(method, 0, 6).should eql(SS[1,2,3,4]) sorted_set.send(method, 0, -1).should be_nil sorted_set.send(method, 0, -2).should be_nil sorted_set.send(method, 0, -4).should be_nil sorted_set.send(method, 2, 0).should eql(SS.empty) sorted_set.send(method, 2, 1).should eql(SS[3]) sorted_set.send(method, 2, 2).should eql(SS[3,4]) sorted_set.send(method, 2, 4).should eql(SS[3,4]) sorted_set.send(method, 2, -1).should be_nil sorted_set.send(method, 4, 0).should eql(SS.empty) sorted_set.send(method, 4, 2).should eql(SS.empty) sorted_set.send(method, 4, -1).should be_nil sorted_set.send(method, 5, 0).should be_nil sorted_set.send(method, 5, 2).should be_nil sorted_set.send(method, 5, -1).should be_nil sorted_set.send(method, 6, 0).should be_nil sorted_set.send(method, 6, 2).should be_nil sorted_set.send(method, 6, -1).should be_nil big.send(method, 0, 3).should eql(SS[1,2,3]) big.send(method, 1023, 4).should eql(SS[1024,1025,1026,1027]) big.send(method, 1024, 4).should eql(SS[1025,1026,1027,1028]) end it 'leaves the original unchanged' do sorted_set.should eql(SS[1,2,3,4]) end end context 'when passed a negative integral index and count' do it "returns 'count' elements, starting from index which is number 'index.abs' counting from the end of the array" do sorted_set.send(method, -1, 0).should eql(SS.empty) sorted_set.send(method, -1, 1).should eql(SS[4]) sorted_set.send(method, -1, 2).should eql(SS[4]) sorted_set.send(method, -1, -1).should be_nil sorted_set.send(method, -2, 0).should eql(SS.empty) sorted_set.send(method, -2, 1).should eql(SS[3]) sorted_set.send(method, -2, 2).should eql(SS[3,4]) sorted_set.send(method, -2, 4).should eql(SS[3,4]) sorted_set.send(method, -2, -1).should be_nil sorted_set.send(method, -4, 0).should eql(SS.empty) sorted_set.send(method, -4, 1).should eql(SS[1]) sorted_set.send(method, -4, 2).should eql(SS[1,2]) sorted_set.send(method, -4, 4).should eql(SS[1,2,3,4]) sorted_set.send(method, -4, 6).should eql(SS[1,2,3,4]) sorted_set.send(method, -4, -1).should be_nil sorted_set.send(method, -5, 0).should be_nil sorted_set.send(method, -5, 1).should be_nil sorted_set.send(method, -5, 10).should be_nil sorted_set.send(method, -5, -1).should be_nil big.send(method, -1, 1).should eql(SS[10000]) big.send(method, -1, 2).should eql(SS[10000]) big.send(method, -6, 2).should eql(SS[9995,9996]) end end context 'when passed a Range' do it 'returns the elements whose indexes are within the given Range' do sorted_set.send(method, 0..-1).should eql(SS[1,2,3,4]) sorted_set.send(method, 0..-10).should eql(SS.empty) sorted_set.send(method, 0..0).should eql(SS[1]) sorted_set.send(method, 0..1).should eql(SS[1,2]) sorted_set.send(method, 0..2).should eql(SS[1,2,3]) sorted_set.send(method, 0..3).should eql(SS[1,2,3,4]) sorted_set.send(method, 0..4).should eql(SS[1,2,3,4]) sorted_set.send(method, 0..10).should eql(SS[1,2,3,4]) sorted_set.send(method, 2..-10).should eql(SS.empty) sorted_set.send(method, 2..0).should eql(SS.empty) sorted_set.send(method, 2..2).should eql(SS[3]) sorted_set.send(method, 2..3).should eql(SS[3,4]) sorted_set.send(method, 2..4).should eql(SS[3,4]) sorted_set.send(method, 3..0).should eql(SS.empty) sorted_set.send(method, 3..3).should eql(SS[4]) sorted_set.send(method, 3..4).should eql(SS[4]) sorted_set.send(method, 4..0).should eql(SS.empty) sorted_set.send(method, 4..4).should eql(SS.empty) sorted_set.send(method, 4..5).should eql(SS.empty) sorted_set.send(method, 5..0).should be_nil sorted_set.send(method, 5..5).should be_nil sorted_set.send(method, 5..6).should be_nil big.send(method, 159..162).should eql(SS[160,161,162,163]) big.send(method, 160..162).should eql(SS[161,162,163]) big.send(method, 161..162).should eql(SS[162,163]) big.send(method, 9999..10100).should eql(SS[10000]) big.send(method, 10000..10100).should eql(SS.empty) big.send(method, 10001..10100).should be_nil sorted_set.send(method, 0...-1).should eql(SS[1,2,3]) sorted_set.send(method, 0...-10).should eql(SS.empty) sorted_set.send(method, 0...0).should eql(SS.empty) sorted_set.send(method, 0...1).should eql(SS[1]) sorted_set.send(method, 0...2).should eql(SS[1,2]) sorted_set.send(method, 0...3).should eql(SS[1,2,3]) sorted_set.send(method, 0...4).should eql(SS[1,2,3,4]) sorted_set.send(method, 0...10).should eql(SS[1,2,3,4]) sorted_set.send(method, 2...-10).should eql(SS.empty) sorted_set.send(method, 2...0).should eql(SS.empty) sorted_set.send(method, 2...2).should eql(SS.empty) sorted_set.send(method, 2...3).should eql(SS[3]) sorted_set.send(method, 2...4).should eql(SS[3,4]) sorted_set.send(method, 3...0).should eql(SS.empty) sorted_set.send(method, 3...3).should eql(SS.empty) sorted_set.send(method, 3...4).should eql(SS[4]) sorted_set.send(method, 4...0).should eql(SS.empty) sorted_set.send(method, 4...4).should eql(SS.empty) sorted_set.send(method, 4...5).should eql(SS.empty) sorted_set.send(method, 5...0).should be_nil sorted_set.send(method, 5...5).should be_nil sorted_set.send(method, 5...6).should be_nil big.send(method, 159...162).should eql(SS[160,161,162]) big.send(method, 160...162).should eql(SS[161,162]) big.send(method, 161...162).should eql(SS[162]) big.send(method, 9999...10100).should eql(SS[10000]) big.send(method, 10000...10100).should eql(SS.empty) big.send(method, 10001...10100).should be_nil sorted_set.send(method, -1..-1).should eql(SS[4]) sorted_set.send(method, -1...-1).should eql(SS.empty) sorted_set.send(method, -1..3).should eql(SS[4]) sorted_set.send(method, -1...3).should eql(SS.empty) sorted_set.send(method, -1..4).should eql(SS[4]) sorted_set.send(method, -1...4).should eql(SS[4]) sorted_set.send(method, -1..10).should eql(SS[4]) sorted_set.send(method, -1...10).should eql(SS[4]) sorted_set.send(method, -1..0).should eql(SS.empty) sorted_set.send(method, -1..-4).should eql(SS.empty) sorted_set.send(method, -1...-4).should eql(SS.empty) sorted_set.send(method, -1..-6).should eql(SS.empty) sorted_set.send(method, -1...-6).should eql(SS.empty) sorted_set.send(method, -2..-2).should eql(SS[3]) sorted_set.send(method, -2...-2).should eql(SS.empty) sorted_set.send(method, -2..-1).should eql(SS[3,4]) sorted_set.send(method, -2...-1).should eql(SS[3]) sorted_set.send(method, -2..10).should eql(SS[3,4]) sorted_set.send(method, -2...10).should eql(SS[3,4]) big.send(method, -1..-1).should eql(SS[10000]) big.send(method, -1..9999).should eql(SS[10000]) big.send(method, -1...9999).should eql(SS.empty) big.send(method, -2...9999).should eql(SS[9999]) big.send(method, -2..-1).should eql(SS[9999,10000]) sorted_set.send(method, -4..-4).should eql(SS[1]) sorted_set.send(method, -4..-2).should eql(SS[1,2,3]) sorted_set.send(method, -4...-2).should eql(SS[1,2]) sorted_set.send(method, -4..-1).should eql(SS[1,2,3,4]) sorted_set.send(method, -4...-1).should eql(SS[1,2,3]) sorted_set.send(method, -4..3).should eql(SS[1,2,3,4]) sorted_set.send(method, -4...3).should eql(SS[1,2,3]) sorted_set.send(method, -4..4).should eql(SS[1,2,3,4]) sorted_set.send(method, -4...4).should eql(SS[1,2,3,4]) sorted_set.send(method, -4..0).should eql(SS[1]) sorted_set.send(method, -4...0).should eql(SS.empty) sorted_set.send(method, -4..1).should eql(SS[1,2]) sorted_set.send(method, -4...1).should eql(SS[1]) sorted_set.send(method, -5..-5).should be_nil sorted_set.send(method, -5...-5).should be_nil sorted_set.send(method, -5..-4).should be_nil sorted_set.send(method, -5..-1).should be_nil sorted_set.send(method, -5..10).should be_nil big.send(method, -10001..-1).should be_nil end it 'leaves the original unchanged' do sorted_set.should eql(SS[1,2,3,4]) end end end context 'when passed an empty Range' do it 'does not lose custom sort order' do ss = SS.new(['yogurt', 'cake', 'pistachios'], &:length) ss = ss.send(method, 1...1).add('tea').add('fruitcake').add('toast') ss.to_a.should == ['tea', 'toast', 'fruitcake'] end end context 'when passed a length of zero' do it 'does not lose custom sort order' do ss = SS.new(['yogurt', 'cake', 'pistachios'], &:length) ss = ss.send(method, 0, 0).add('tea').add('fruitcake').add('toast') ss.to_a.should == ['tea', 'toast', 'fruitcake'] end end context 'when passed a subclass of Range' do it 'works the same as with a Range' do subclass = Class.new(Range) sorted_set.send(method, subclass.new(1,2)).should eql(SS[2,3]) sorted_set.send(method, subclass.new(-3,-1,true)).should eql(SS[2,3]) end end context 'on a subclass of SortedSet' do it 'with index and count or a range, returns an instance of the subclass' do subclass = Class.new(Immutable::SortedSet) instance = subclass.new([1,2,3]) instance.send(method, 0, 0).class.should be(subclass) instance.send(method, 0, 2).class.should be(subclass) instance.send(method, 0..0).class.should be(subclass) instance.send(method, 1..-1).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/add_spec.rb0000644000175000017500000000326014201005456024602 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do let(:sorted_set) { SS['B', 'C', 'D'] } [:add, :<<].each do |method| describe "##{method}" do context 'with a unique value' do it 'preserves the original' do sorted_set.send(method, 'A') sorted_set.should eql(SS['B', 'C', 'D']) end it 'returns a copy with the superset of values (in order)' do sorted_set.send(method, 'A').should eql(SS['A', 'B', 'C', 'D']) end end context 'with a duplicate value' do it 'preserves the original values' do sorted_set.send(method, 'C') sorted_set.should eql(SS['B', 'C', 'D']) end it 'returns self' do sorted_set.send(method, 'C').should equal(sorted_set) end end context 'on a set ordered by a comparator' do it 'inserts the new item in the correct place' do s = SS.new(['tick', 'pig', 'hippopotamus'], &:length) s.add('giraffe').to_a.should == ['pig', 'tick', 'giraffe', 'hippopotamus'] end end end end describe '#add?' do context 'with a unique value' do it 'preserves the original' do sorted_set.add?('A') sorted_set.should eql(SS['B', 'C', 'D']) end it 'returns a copy with the superset of values' do sorted_set.add?('A').should eql(SS['A', 'B', 'C', 'D']) end end context 'with a duplicate value' do it 'preserves the original values' do sorted_set.add?('C') sorted_set.should eql(SS['B', 'C', 'D']) end it 'returns false' do sorted_set.add?('C').should equal(false) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/from_spec.rb0000644000175000017500000000314414201005456025016 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#from' do context 'when called without a block' do it 'returns a sorted set of all items equal to or greater than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = set.from(threshold) array = items.select { |x| x >= threshold }.sort result.class.should be(Immutable::SortedSet) result.size.should == array.size result.to_a.should == array end end end context 'when called with a block' do it 'yields all the items equal to or greater than than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = [] set.from(threshold) { |x| result << x } array = items.select { |x| x >= threshold }.sort result.size.should == array.size result.should == array end end end context 'on an empty set' do it 'returns an empty set' do SS.empty.from(1).should be_empty SS.empty.from('abc').should be_empty SS.empty.from(:symbol).should be_empty end end context 'with an argument higher than all the values in the set' do it 'returns an empty set' do result = SS.new(1..100).from(101) result.class.should be(Immutable::SortedSet) result.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/take_spec.rb0000644000175000017500000000304414201005456024776 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#take' do [ [[], 10, []], [['A'], 10, ['A']], [%w[A B C], 0, []], [%w[A B C], 2, %w[A B]], ].each do |values, number, expected| context "#{number} from #{values.inspect}" do let(:sorted_set) { SS[*values] } it 'preserves the original' do sorted_set.take(number) sorted_set.should eql(SS[*values]) end it "returns #{expected.inspect}" do sorted_set.take(number).should eql(SS[*expected]) end end end context 'when argument is at least size of receiver' do let(:sorted_set) { SS[6, 7, 8, 9] } it 'returns self' do sorted_set.take(sorted_set.size).should be(sorted_set) sorted_set.take(sorted_set.size + 1).should be(sorted_set) end end context 'when the set has a custom order' do let(:sorted_set) { SS.new([1, 2, 3], &:-@)} it 'maintains the custom order' do sorted_set.take(1).to_a.should == [3] sorted_set.take(2).to_a.should == [3, 2] sorted_set.take(3).to_a.should == [3, 2, 1] end it 'keeps the comparator even when set is cleared' do s = sorted_set.take(0) s.add(4).add(5).add(6).to_a.should == [6, 5, 4] end end context 'when called on a subclass' do it 'should return an instance of the subclass' do subclass = Class.new(Immutable::SortedSet) subclass.new([1,2,3]).take(1).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/values_at_spec.rb0000644000175000017500000000171014201005456026033 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#values_at' do let(:sorted_set) { SS['a', 'b', 'c'] } it 'accepts any number of indices, and returns a sorted_set of items at those indices' do sorted_set.values_at(0).should eql(SS['a']) sorted_set.values_at(1,2).should eql(SS['b', 'c']) end context 'when passed invalid indices' do it 'filters them out' do sorted_set.values_at(1,2,3).should eql(SS['b', 'c']) sorted_set.values_at(-10,10).should eql(SS.empty) end end context 'when passed no arguments' do it 'returns an empty sorted_set' do sorted_set.values_at.should eql(SS.empty) end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::SortedSet) instance = subclass.new([1,2,3]) instance.values_at(1,2).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/find_index_spec.rb0000644000175000017500000000217714201005456026167 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:find_index, :index].each do |method| describe "##{method}" do [ [[], 'A', nil], [[], nil, nil], [['A'], 'A', 0], [['A'], 'B', nil], [['A'], nil, nil], [['A', 'B', 'C'], 'A', 0], [['A', 'B', 'C'], 'B', 1], [['A', 'B', 'C'], 'C', 2], [['A', 'B', 'C'], 'D', nil], [0..1, 1, 1], [0..10, 5, 5], [0..10, 10, 10], [[2], 2, 0], [[2], 2.0, 0], [[2.0], 2.0, 0], [[2.0], 2, 0], ].each do |values, item, expected| unless item.nil? # test breaks otherwise context "looking for #{item.inspect} in #{values.inspect} without block" do it "returns #{expected.inspect}" do SS[*values].send(method, item).should == expected end end end context "looking for #{item.inspect} in #{values.inspect} with block" do it "returns #{expected.inspect}" do SS[*values].send(method) { |x| x == item }.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/select_spec.rb0000644000175000017500000000364614201005456025341 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:select, :find_all].each do |method| describe "##{method}" do let(:sorted_set) { SS['A', 'B', 'C'] } context 'when everything matches' do it 'preserves the original' do sorted_set.send(method) { true } sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns self' do sorted_set.send(method) { |item| true }.should equal(sorted_set) end end context 'when only some things match' do context 'with a block' do it 'preserves the original' do sorted_set.send(method) { |item| item == 'A' } sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns a set with the matching values' do sorted_set.send(method) { |item| item == 'A' }.should eql(SS['A']) end end context 'with no block' do it 'returns an Enumerator' do sorted_set.send(method).class.should be(Enumerator) sorted_set.send(method).each { |item| item == 'A' }.should eql(SS['A']) end end end context 'when nothing matches' do it 'preserves the original' do sorted_set.send(method) { |item| false } sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns the canonical empty set' do sorted_set.send(method) { |item| false }.should equal(Immutable::EmptySortedSet) end end context 'from a subclass' do it 'returns an instance of the same class' do subclass = Class.new(Immutable::SortedSet) instance = subclass.new(['A', 'B', 'C']) instance.send(method) { true }.class.should be(subclass) instance.send(method) { false }.class.should be(subclass) instance.send(method) { rand(2) == 0 }.class.should be(subclass) end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/sorting_spec.rb0000644000175000017500000000330514201005456025537 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [ [:sort, ->(left, right) { left.length <=> right.length }], [:sort_by, ->(item) { item.length }], ].each do |method, comparator| describe "##{method}" do [ [[], []], [['A'], ['A']], [%w[Ichi Ni San], %w[Ni San Ichi]], ].each do |values, expected| describe "on #{values.inspect}" do let(:sorted_set) { SS.new(values, &:reverse)} context 'with a block' do it 'preserves the original' do sorted_set.send(method, &comparator) sorted_set.to_a.should == SS.new(values, &:reverse) end it "returns #{expected.inspect}" do sorted_set.send(method, &comparator).class.should be(Immutable::SortedSet) sorted_set.send(method, &comparator).to_a.should == expected end end context 'without a block' do it 'preserves the original' do sorted_set.send(method) sorted_set.to_a.should == SS.new(values, &:reverse) end it "returns #{expected.sort.inspect}" do sorted_set.send(method).class.should be(Immutable::SortedSet) sorted_set.send(method).to_a.should == expected.sort end end end end end end describe :sort do context 'on a SortedSet with custom sort order' do let(:sorted_set) { SS.new([1,2,3,4]) { |x,y| y <=> x }} it 'returns a SortedSet with the natural sort order' do result = sorted_set.sort expect(sorted_set.to_a).to eq([4,3,2,1]) expect(result.to_a).to eq([1,2,3,4]) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/difference_spec.rb0000644000175000017500000000113214201005456026140 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:difference, :subtract, :-].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], ['A']], [['A'], ['A'], []], [%w[A B C], ['B'], %w[A C]], [%w[A B C], %w[A C], ['B']], [%w[A B C D E F], %w[B E F G M X], %w[A C D]] ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected.inspect}" do SS[*a].send(method, SS[*b]).should eql(SS[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/eql_spec.rb0000644000175000017500000000572514201005456024643 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable::SortedSet do let(:set) { SS[*values] } let(:comparison) { SS[*comparison_values] } describe '#eql?' do let(:eql?) { set.eql?(comparison) } shared_examples 'comparing something which is not a sorted set' do let(:values) { %w[A B C] } it 'returns false' do expect(eql?).to eq(false) end end context 'when comparing to a standard set' do let(:comparison) { ::Set.new(%w[A B C]) } include_examples 'comparing something which is not a sorted set' end context 'when comparing to a arbitrary object' do let(:comparison) { Object.new } include_examples 'comparing something which is not a sorted set' end context 'when comparing to an Immutable::Set' do let(:comparison) { Immutable::Set.new(%w[A B C]) } include_examples 'comparing something which is not a sorted set' end context 'when comparing with a subclass of Immutable::SortedSet' do let(:comparison) { Class.new(Immutable::SortedSet).new(%w[A B C]) } include_examples 'comparing something which is not a sorted set' end context 'with an empty set for each comparison' do let(:values) { [] } let(:comparison_values) { [] } it 'returns true' do expect(eql?).to eq(true) end end context 'with an empty set and a set with nil' do let(:values) { [] } let(:comparison_values) { [nil] } it 'returns false' do expect(eql?).to eq(false) end end context 'with a single item array and empty array' do let(:values) { ['A'] } let(:comparison_values) { [] } it 'returns false' do expect(eql?).to eq(false) end end context 'with matching single item array' do let(:values) { ['A'] } let(:comparison_values) { ['A'] } it 'returns true' do expect(eql?).to eq(true) end end context 'with mismatching single item array' do let(:values) { ['A'] } let(:comparison_values) { ['B'] } it 'returns false' do expect(eql?).to eq(false) end end context 'with a multi-item array and single item array' do let(:values) { %w[A B] } let(:comparison_values) { ['A'] } it 'returns false' do expect(eql?).to eq(false) end end context 'with matching multi-item array' do let(:values) { %w[A B] } let(:comparison_values) { %w[A B] } it 'returns true' do expect(eql?).to eq(true) end end context 'with a mismatching multi-item array' do let(:values) { %w[A B] } let(:comparison_values) { %w[B A] } it 'returns true' do expect(eql?).to eq(true) end end context 'with the same values, but a different sort order' do let(:set) { SS[1, 2, 3] } let(:comparison) { SS.new([1, 2, 3], &:-@)} it 'returns false' do expect(eql?).to eq(false) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/include_spec.rb0000644000175000017500000000111314201005456025470 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:include?, :member?].each do |method| describe "##{method}" do let(:sorted_set) { SS[1, 2, 3, 4.0] } [1, 2, 3, 4.0].each do |value| it "returns true for an existing value (#{value.inspect})" do sorted_set.send(method, value).should == true end end it 'returns false for a non-existing value' do sorted_set.send(method, 5).should == false end it 'uses #<=> for equality' do sorted_set.send(method, 4).should == true end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/group_by_spec.rb0000644000175000017500000000325014201005456025677 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:group_by, :group, :classify].each do |method| describe "##{method}" do context 'with a block' do [ [[], []], [[1], [true => SS[1]]], [[1, 2, 3, 4], [true => SS[3, 1], false => SS[4, 2]]], ].each do |values, expected| context "on #{values.inspect}" do let(:sorted_set) { SS[*values] } it 'preserves the original' do sorted_set.send(method, &:odd?) sorted_set.should eql(SS[*values]) end it "returns #{expected.inspect}" do sorted_set.send(method, &:odd?).should eql(H[*expected]) end end end end context 'without a block' do [ [[], []], [[1], [1 => SS[1]]], [[1, 2, 3, 4], [1 => SS[1], 2 => SS[2], 3 => SS[3], 4 => SS[4]]], ].each do |values, expected| context "on #{values.inspect}" do let(:sorted_set) { SS[*values] } it 'preserves the original' do sorted_set.group_by sorted_set.should eql(SS[*values]) end it "returns #{expected.inspect}" do sorted_set.group_by.should eql(H[*expected]) end end end end context 'from a subclass' do it 'returns an Hash whose values are instances of the subclass' do subclass = Class.new(Immutable::SortedSet) instance = subclass.new(['some', 'strings', 'here']) instance.group_by { |x| x }.values.each { |v| v.class.should be(subclass) } end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/clear_spec.rb0000644000175000017500000000212314201005456025135 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#clear' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values}" do let(:sorted_set) { SS[*values] } it 'preserves the original' do sorted_set.clear sorted_set.should eql(SS[*values]) end it 'returns an empty set' do sorted_set.clear.should equal(Immutable::EmptySortedSet) sorted_set.clear.should be_empty end end end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::SortedSet) instance = subclass.new([:a, :b, :c, :d]) instance.clear.class.should be(subclass) instance.clear.should be_empty end end context 'with a comparator' do let(:sorted_set) { SS.new([1, 2, 3], &:-@) } it 'returns an empty instance with same comparator' do e = sorted_set.clear e.should be_empty e.add(4).add(5).add(6).to_a.should == [6, 5, 4] end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/superset_spec.rb0000644000175000017500000000235614201005456025731 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#superset?' do [ [[], [], true], [['A'], [], true], [[], ['A'], false], [['A'], ['A'], true], [%w[A B C], ['B'], true], [['B'], %w[A B C], false], [%w[A B C], %w[A C], true], [%w[A C], %w[A B C], false], [%w[A B C], %w[A B C], true], [%w[A B C], %w[A B C D], false], [%w[A B C D], %w[A B C], true], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do SS[*a].superset?(SS[*b]).should == expected end end end end describe '#proper_superset?' do [ [[], [], false], [['A'], [], true], [[], ['A'], false], [['A'], ['A'], false], [%w[A B C], ['B'], true], [['B'], %w[A B C], false], [%w[A B C], %w[A C], true], [%w[A C], %w[A B C], false], [%w[A B C], %w[A B C], false], [%w[A B C], %w[A B C D], false], [%w[A B C D], %w[A B C], true], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do SS[*a].proper_superset?(SS[*b]).should == expected end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/disjoint_spec.rb0000644000175000017500000000122114201005456025670 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#disjoint?' do [ [[], [], true], [['A'], [], true], [[], ['A'], true], [['A'], ['A'], false], [%w[A B C], ['B'], false], [['B'], %w[A B C], false], [%w[A B C], %w[D E], true], [%w[F G H I], %w[A B C], true], [%w[A B C], %w[A B C], false], [%w[A B C], %w[A B C D], false], [%w[D E F G], %w[A B C], true], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do SS[*a].disjoint?(SS[*b]).should be(expected) end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/intersection_spec.rb0000644000175000017500000000143214201005456026557 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:intersection, :&].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], []], [['A'], ['A'], ['A']], [%w[A B C], ['B'], ['B']], [%w[A B C], %w[A C], %w[A C]], [%w[A M T X], %w[B C D E F G H I M P Q T U], %w[M T]] ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected.inspect}" do SS[*a].send(method, SS[*b]).should eql(SS[*expected]) end end context "for #{b.inspect} and #{a.inspect}" do it "returns #{expected.inspect}" do SS[*b].send(method, SS[*a]).should eql(SS[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/union_spec.rb0000644000175000017500000000347714201005456025214 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:union, :|, :+, :merge].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], ['A']], [['A'], ['A'], ['A']], [%w[A B C], [], %w[A B C]], [%w[A C E G X], %w[B C D E H M], %w[A B C D E G H M X]] ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected.inspect}" do SS[*a].send(method, SS[*b]).should eql(SS[*expected]) end end context "for #{b.inspect} and #{a.inspect}" do it "returns #{expected.inspect}" do SS[*b].send(method, SS[*a]).should eql(SS[*expected]) end end end end end describe :union do it 'filters out duplicates when passed an Array' do sorted_set = SS['A', 'B', 'C', 'D'].union(['A', 'A', 'A', 'C', 'A', 'B', 'E']) expect(sorted_set.to_a).to eq(['A', 'B', 'C', 'D', 'E']) end it "doesn't mutate an Array which is passed in" do array = [3,2,1,3] sorted_set = SS[1,2,5].union(array) expect(array).to eq([3,2,1,3]) end context 'on a set ordered by a comparator' do # Completely different code is executed when #union is called on a SS # with a comparator block, so we should repeat all the same tests it 'still filters out duplicates when passed an Array' do sorted_set = SS.new([1,2,3]) { |x,y| (x%7) <=> (y%7) } sorted_set = sorted_set.union([7,8,9]) expect(sorted_set.to_a).to eq([7,1,2,3]) end it "still doesn't mutate an Array which is passed in" do array = [3,2,1,3] sorted_set = SS.new([1,2,5]) { |x,y| y <=> x } sorted_set = sorted_set.union(array) expect(array).to eq([3,2,1,3]) end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/drop_while_spec.rb0000644000175000017500000000164714201005456026215 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#drop_while' do [ [[], []], [['A'], []], [%w[A B C], ['C']], [%w[A B C D E F G], %w[C D E F G]] ].each do |values, expected| context "on #{values.inspect}" do let(:sorted_set) { SS[*values] } context 'with a block' do it 'preserves the original' do sorted_set.drop_while { |item| item < 'C' } sorted_set.should eql(SS[*values]) end it "returns #{expected.inspect}" do sorted_set.drop_while { |item| item < 'C' }.should eql(SS[*expected]) end end context 'without a block' do it 'returns an Enumerator' do sorted_set.drop_while.class.should be(Enumerator) sorted_set.drop_while.each { |item| item < 'C' }.should eql(SS[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/maximum_spec.rb0000644000175000017500000000153514201005456025532 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#max' do context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { SS[*values] } let(:result) { set.max { |maximum, item| maximum.length <=> item.length }} it "returns #{expected.inspect}" do result.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'San'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do SS[*values].max.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/first_spec.rb0000644000175000017500000000056714201005456025210 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#first' do [ [[], nil], [['A'], 'A'], [%w[A B C], 'A'], [%w[Z Y X], 'X'] ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do SS[*values].first.should eql(expected) end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/util_spec.rb0000644000175000017500000000267414201005456025037 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do # Utility method used for filtering out duplicate objects, with equality # determined by comparator describe '.uniq_by_comparator!' do it 'can handle empty arrays' do array = [] SS.uniq_by_comparator!(array, ->(x,y) { x <=> y }) expect(array).to be_empty end it 'can handle arrays with 1 element' do array = [1] SS.uniq_by_comparator!(array, ->(x,y) { x <=> y }) expect(array).to eq([1]) end it 'can handle arrays with 2 elements and no dupes' do array = [1, 2] SS.uniq_by_comparator!(array, ->(x,y) { x <=> y }) expect(array).to eq([1, 2]) end it 'can handle arrays with 2 elements and dupes' do array = [1, 1] SS.uniq_by_comparator!(array, ->(x,y) { x <=> y }) expect(array).to eq([1]) end it 'can handle arrays with lots of elements' do 100.times do array1 = rand(100).times.collect { rand(100) }.sort array2 = array1.dup.uniq SS.uniq_by_comparator!(array1, ->(x,y) { x <=> y }) expect(array1).to eq(array2) end end it 'works with funny comparators' do # let's work in modulo arithmetic comparator = ->(x,y) { (x % 7) <=> (y % 7) } array = [21, 1, 8, 1, 9, 10, 3, 5, 6, 20] # this is "sorted" (modulo 7) SS.uniq_by_comparator!(array, comparator) expect(array).to eq([21, 1, 9, 10, 5, 6]) end end end immutable-ruby-master/spec/lib/immutable/sorted_set/exclusion_spec.rb0000644000175000017500000000111514201005456026060 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:exclusion, :^].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], ['A']], [['A'], ['A'], []], [%w[A B C], ['B'], %w[A C]], [%w[A B C], %w[B C D], %w[A D]], [%w[A B C], %w[D E F], %w[A B C D E F]], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do it "returns #{expected.inspect}" do SS[*a].send(method, SS[*b]).should eql(SS[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/map_spec.rb0000644000175000017500000000273214201005456024632 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:map, :collect].each do |method| describe "##{method}" do context 'when empty' do it 'returns self' do SS.empty.send(method) {}.should equal(SS.empty) end end context 'when not empty' do let(:sorted_set) { SS['A', 'B', 'C'] } context 'with a block' do it 'preserves the original values' do sorted_set.send(method, &:downcase) sorted_set.should eql(SS['A', 'B', 'C']) end it 'returns a new set with the mapped values' do sorted_set.send(method, &:downcase).should eql(SS['a', 'b', 'c']) end it 'filters out duplicates' do sorted_set.send(method) { 'blah' }.should eq(SS['blah']) end end context 'with no block' do it 'returns an Enumerator' do sorted_set.send(method).class.should be(Enumerator) sorted_set.send(method).each(&:downcase).should == SS['a', 'b', 'c'] end end end context 'on a set ordered by a comparator' do let(:sorted_set) { SS.new(['A', 'B', 'C']) { |a,b| b <=> a }} it 'returns a new set with the mapped values' do sorted_set.send(method, &:downcase).should == ['c', 'b', 'a'] end it 'filters out duplicates' do sorted_set.send(method) { 'blah' }.should eq(SS['blah']) end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/reverse_each_spec.rb0000644000175000017500000000131614201005456026505 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#reverse_each' do context 'with no block' do let(:sorted_set) { SS['A', 'B', 'C'] } it 'returns an Enumerator' do sorted_set.reverse_each.class.should be(Enumerator) sorted_set.reverse_each.to_a.should eql(sorted_set.to_a.reverse) end end context 'with a block' do let(:sorted_set) { SS.new(1..1025) } it 'returns self' do sorted_set.reverse_each {}.should be(sorted_set) end it 'iterates over the items in order' do items = [] sorted_set.reverse_each { |item| items << item } items.should == (1..1025).to_a.reverse end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/between_spec.rb0000644000175000017500000000322414201005456025503 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#between' do context 'when called without a block' do it 'returns a sorted set of all items from the first argument to the second' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) from,to = [rand(1000),rand(1000)].sort result = set.between(from, to) array = items.select { |x| x >= from && x <= to }.sort result.class.should be(Immutable::SortedSet) result.size.should == array.size result.to_a.should == array end end end context 'when called with a block' do it 'yields all the items lower than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) from,to = [rand(1000),rand(1000)].sort result = [] set.between(from, to) { |x| result << x } array = items.select { |x| x >= from && x <= to }.sort result.size.should == array.size result.should == array end end end context 'on an empty set' do it 'returns an empty set' do SS.empty.between(1, 2).should be_empty SS.empty.between('abc', 'def').should be_empty SS.empty.between(:symbol, :another).should be_empty end end context "with a 'to' argument lower than the 'from' argument" do it 'returns an empty set' do result = SS.new(1..100).between(6, 5) result.class.should be(Immutable::SortedSet) result.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/copying_spec.rb0000644000175000017500000000060114201005456025516 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do [:dup, :clone].each do |method| [ [], ['A'], %w[A B C], (1..32), ].each do |values| describe "on #{values.inspect}" do let(:sorted_set) { SS[*values] } it 'returns self' do sorted_set.send(method).should equal(sorted_set) end end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/below_spec.rb0000644000175000017500000000310514201005456025160 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#below' do context 'when called without a block' do it 'returns a sorted set of all items lower than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = set.below(threshold) array = items.select { |x| x < threshold }.sort result.class.should be(Immutable::SortedSet) result.size.should == array.size result.to_a.should == array end end end context 'when called with a block' do it 'yields all the items lower than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = [] set.below(threshold) { |x| result << x } array = items.select { |x| x < threshold }.sort result.size.should == array.size result.should == array end end end context 'on an empty set' do it 'returns an empty set' do SS.empty.below(1).should be_empty SS.empty.below('abc').should be_empty SS.empty.below(:symbol).should be_empty end end context 'with an argument lower than all the values in the set' do it 'returns an empty set' do result = SS.new(1..100).below(1) result.class.should be(Immutable::SortedSet) result.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/up_to_spec.rb0000644000175000017500000000321514201005456025200 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#up_to' do context 'when called without a block' do it 'returns a sorted set of all items equal to or less than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = set.up_to(threshold) array = items.select { |x| x <= threshold }.sort result.class.should be(Immutable::SortedSet) result.size.should == array.size result.to_a.should == array end end end context 'when called with a block' do it 'yields all the items equal to or less than than the argument' do 100.times do items = rand(100).times.collect { rand(1000) }.uniq set = SS.new(items) threshold = rand(1000) result = [] set.up_to(threshold) { |x| result << x } array = items.select { |x| x <= threshold }.sort result.size.should == array.size result.should == array end end end context 'on an empty set' do it 'returns an empty set' do SS.empty.up_to(1).should be_empty SS.empty.up_to('abc').should be_empty SS.empty.up_to(:symbol).should be_empty SS.empty.up_to(nil).should be_empty end end context 'with an argument less than all the values in the set' do it 'returns an empty set' do result = SS.new(1..100).up_to(0) result.class.should be(Immutable::SortedSet) result.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/sorted_set/to_set_spec.rb0000644000175000017500000000051414201005456025346 0ustar boutilboutilrequire 'spec_helper' describe Immutable::SortedSet do describe '#to_set' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do it 'returns a set with the same values' do SS[*values].to_set.should eql(S[*values]) end end end end end immutable-ruby-master/spec/lib/immutable/set/0000755000175000017500000000000014201005456021132 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/set/intersect_spec.rb0000644000175000017500000000121514201005456024470 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#intersect?' do [ [[], [], false], [['A'], [], false], [[], ['A'], false], [['A'], ['A'], true], [%w[A B C], ['B'], true], [['B'], %w[A B C], true], [%w[A B C], %w[D E], false], [%w[F G H I], %w[A B C], false], [%w[A B C], %w[A B C], true], [%w[A B C], %w[A B C D], true], [%w[D E F G], %w[A B C], false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do S[*a].intersect?(S[*b]).should be(expected) end end end end end immutable-ruby-master/spec/lib/immutable/set/new_spec.rb0000644000175000017500000000251114201005456023261 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '.new' do it 'initializes a new set' do set = S.new([1,2,3]) set.size.should be(3) [1,2,3].each { |n| set.include?(n).should == true } end it 'accepts a Range' do set = S.new(1..3) set.size.should be(3) [1,2,3].each { |n| set.include?(n).should == true } end it "returns a Set which doesn't change even if the initializer is mutated" do array = [1,2,3] set = S.new([1,2,3]) array.push('BAD') set.should eql(S[1,2,3]) end context 'from a subclass' do it 'returns a frozen instance of the subclass' do subclass = Class.new(Immutable::Set) instance = subclass.new(['some', 'values']) instance.class.should be subclass instance.should be_frozen end end it 'is amenable to overriding of #initialize' do class SnazzySet < Immutable::Set def initialize super(['SNAZZY!!!']) end end set = SnazzySet.new set.size.should be(1) set.include?('SNAZZY!!!').should == true end end describe '[]' do it 'accepts any number of arguments and initializes a new set' do set = S[1,2,3,4] set.size.should be(4) [1,2,3,4].each { |n| set.include?(n).should == true } end end end immutable-ruby-master/spec/lib/immutable/set/size_spec.rb0000644000175000017500000000055414201005456023447 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:size, :length].each do |method| describe "##{method}" do [ [[], 0], [['A'], 1], [%w[A B C], 3], ].each do |values, result| it "returns #{result} for #{values.inspect}" do S[*values].send(method).should == result end end end end end immutable-ruby-master/spec/lib/immutable/set/eqeq_spec.rb0000644000175000017500000000436314201005456023432 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable::Set do let(:set) { S[*values] } let(:comparison) { S[*comparison_values] } describe '#==' do let(:eqeq) { set == comparison } shared_examples 'comparing non-sets' do let(:values) { %w[A B C] } it 'returns false' do expect(eqeq).to eq(false) end end context 'when comparing to a standard set' do let(:comparison) { ::Set.new(%w[A B C]) } include_examples 'comparing non-sets' end context 'when comparing to a arbitrary object' do let(:comparison) { Object.new } include_examples 'comparing non-sets' end context 'with an empty set for each comparison' do let(:values) { [] } let(:comparison_values) { [] } it 'returns true' do expect(eqeq).to eq(true) end end context 'with an empty set and a set with nil' do let(:values) { [] } let(:comparison_values) { [nil] } it 'returns false' do expect(eqeq).to eq(false) end end context 'with a single item array and empty array' do let(:values) { ['A'] } let(:comparison_values) { [] } it 'returns false' do expect(eqeq).to eq(false) end end context 'with matching single item array' do let(:values) { ['A'] } let(:comparison_values) { ['A'] } it 'returns true' do expect(eqeq).to eq(true) end end context 'with mismatching single item array' do let(:values) { ['A'] } let(:comparison_values) { ['B'] } it 'returns false' do expect(eqeq).to eq(false) end end context 'with a multi-item array and single item array' do let(:values) { %w[A B] } let(:comparison_values) { ['A'] } it 'returns false' do expect(eqeq).to eq(false) end end context 'with matching multi-item array' do let(:values) { %w[A B] } let(:comparison_values) { %w[A B] } it 'returns true' do expect(eqeq).to eq(true) end end context 'with a mismatching multi-item array' do let(:values) { %w[A B] } let(:comparison_values) { %w[B A] } it 'returns true' do expect(eqeq).to eq(true) end end end end immutable-ruby-master/spec/lib/immutable/set/construction_spec.rb0000644000175000017500000000066014201005456025225 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '.set' do context 'with no values' do it 'returns the empty set' do S.empty.should be_empty S.empty.should equal(Immutable::EmptySet) end end context 'with a list of values' do it 'is equivalent to repeatedly using #add' do S['A', 'B', 'C'].should eql(S.empty.add('A').add('B').add('C')) end end end end immutable-ruby-master/spec/lib/immutable/set/reject_spec.rb0000644000175000017500000000273014201005456023747 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:reject, :delete_if].each do |method| describe "##{method}" do let(:set) { S['A', 'B', 'C'] } context 'when nothing matches' do it 'returns self' do set.send(method) { |item| false }.should equal(set) end end context 'when only some things match' do context 'with a block' do let(:result) { set.send(method) { |item| item == 'A' }} it 'preserves the original' do result set.should eql(S['A', 'B', 'C']) end it 'returns a set with the matching values' do result.should eql(S['B', 'C']) end end context 'with no block' do it 'returns self' do set.send(method).class.should be(Enumerator) set.send(method).each { |item| item == 'A' }.should == S['B', 'C'] end end end context 'on a large set, with many combinations of input' do it 'still works' do array = (1..1000).to_a set = S.new(array) [0, 10, 100, 200, 500, 800, 900, 999, 1000].each do |threshold| result = set.send(method) { |item| item > threshold } result.size.should == threshold 1.upto(threshold) { |n| result.include?(n).should == true } (threshold+1).upto(1000) { |n| result.include?(n).should == false } end end end end end end immutable-ruby-master/spec/lib/immutable/set/flatten_spec.rb0000644000175000017500000000220214201005456024122 0ustar boutilboutilrequire 'spec_helper' describe Immutable do describe '#flatten' do [ [['A'], ['A']], [%w[A B C], %w[A B C]], [['A', S['B'], 'C'], %w[A B C]], [[S['A'], S['B'], S['C']], %w[A B C]], ].each do |values, expected| describe "on #{values}" do let(:set) { S[*values] } it 'preserves the original' do set.flatten set.should eql(S[*values]) end it 'returns the inlined values' do set.flatten.should eql(S[*expected]) end end end context 'on an empty set' do it 'returns an empty set' do S.empty.flatten.should equal(S.empty) end end context 'on a set with multiple levels of nesting' do it 'inlines lower levels of nesting' do set = S[S[S[1]], S[S[2]]] set.flatten.should eql(S[1, 2]) end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Set) subclass.new.flatten.class.should be(subclass) subclass.new([S[1], S[2]]).flatten.class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/set/sample_spec.rb0000644000175000017500000000052114201005456023750 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#sample' do let(:set) { S.new(1..10) } it 'returns a randomly chosen item' do chosen = 100.times.map { set.sample } chosen.each { |item| set.include?(item).should == true } set.each { |item| chosen.include?(item).should == true } end end end immutable-ruby-master/spec/lib/immutable/set/any_spec.rb0000644000175000017500000000255514201005456023267 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#any?' do context 'when empty' do it 'with a block returns false' do S.empty.any? {}.should == false end it 'with no block returns false' do S.empty.any?.should == false end end context 'when not empty' do context 'with a block' do let(:set) { S['A', 'B', 'C', nil] } ['A', 'B', 'C', nil].each do |value| it "returns true if the block ever returns true (#{value.inspect})" do set.any? { |item| item == value }.should == true end end it 'returns false if the block always returns false' do set.any? { |item| item == 'D' }.should == false end it 'propagates exceptions raised in the block' do -> { set.any? { |k,v| raise 'help' } }.should raise_error(RuntimeError) end it 'stops iterating as soon as the block returns true' do yielded = [] set.any? { |k,v| yielded << k; true } yielded.size.should == 1 end end context 'with no block' do it 'returns true if any value is truthy' do S[nil, false, true, 'A'].any?.should == true end it 'returns false if all values are falsey' do S[nil, false].any?.should == false end end end end end immutable-ruby-master/spec/lib/immutable/set/subset_spec.rb0000644000175000017500000000261214201005456023777 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:subset?, :<=].each do |method| describe "##{method}" do [ [[], [], true], [['A'], [], false], [[], ['A'], true], [['A'], ['A'], true], [%w[A B C], ['B'], false], [['B'], %w[A B C], true], [%w[A B C], %w[A C], false], [%w[A C], %w[A B C], true], [%w[A B C], %w[A B C], true], [%w[A B C], %w[A B C D], true], [%w[A B C D], %w[A B C], false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do S[*a].send(method, S[*b]).should == expected end end end end end [:proper_subset?, :<].each do |method| describe "##{method}" do [ [[], [], false], [['A'], [], false], [[], ['A'], true], [['A'], ['A'], false], [%w[A B C], ['B'], false], [['B'], %w[A B C], true], [%w[A B C], %w[A C], false], [%w[A C], %w[A B C], true], [%w[A B C], %w[A B C], false], [%w[A B C], %w[A B C D], true], [%w[A B C D], %w[A B C], false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do S[*a].send(method, S[*b]).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/set/minimum_spec.rb0000644000175000017500000000152414201005456024146 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#min' do context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ni'], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } let(:result) { set.min { |minimum, item| minimum.length <=> item.length }} it "returns #{expected.inspect}" do result.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do S[*values].min.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/set/inspect_spec.rb0000644000175000017500000000242014201005456024134 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#inspect' do [ [[], 'Immutable::Set[]'], [['A'], 'Immutable::Set["A"]'], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } it "returns #{expected.inspect}" do set.inspect.should == expected end it "returns a string which can be eval'd to get an equivalent set" do eval(set.inspect).should eql(set) end end end describe 'on ["A", "B", "C"]' do let(:set) { S['A', 'B', 'C'] } it 'returns a programmer-readable representation of the set contents' do set.inspect.should match(/^Immutable::Set\["[A-C]", "[A-C]", "[A-C]"\]$/) end it "returns a string which can be eval'd to get an equivalent set" do eval(set.inspect).should eql(set) end end context 'from a subclass' do MySet = Class.new(Immutable::Set) let(:set) { MySet[1, 2] } it 'returns a programmer-readable representation of the set contents' do set.inspect.should match(/^MySet\[[1-2], [1-2]\]$/) end it "returns a string which can be eval'd to get an equivalent set" do eval(set.inspect).should eql(set) end end end end immutable-ruby-master/spec/lib/immutable/set/product_spec.rb0000644000175000017500000000073514201005456024156 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#product' do [ [[], 1], [[2], 2], [[1, 3, 5, 7, 11], 1155], ].each do |values, expected| context "on #{values.inspect}" do let(:set) { S[*values] } it "returns #{expected.inspect}" do set.product.should == expected end it "doesn't change the original Set" do set.should eql(S.new(values)) end end end end end immutable-ruby-master/spec/lib/immutable/set/marshal_spec.rb0000644000175000017500000000142514201005456024122 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#marshal_dump/#marshal_load' do let(:ruby) { File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) } let(:child_cmd) do %Q|#{ruby} -I lib -r immutable -e 'set = Immutable::Set[:one, :two]; $stdout.write(Marshal.dump(set))'| end let(:reloaded_hash) do IO.popen(child_cmd, 'r+') do |child| reloaded_hash = Marshal.load(child) child.close reloaded_hash end end it 'can survive dumping and loading into a new process' do reloaded_hash.should eql(S[:one, :two]) end it 'is still possible to test items by key after loading' do reloaded_hash.should include :one reloaded_hash.should include :two end end end immutable-ruby-master/spec/lib/immutable/set/find_spec.rb0000644000175000017500000000172714201005456023420 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:find, :detect].each do |method| describe "##{method}" do [ [[], 'A', nil], [[], nil, nil], [['A'], 'A', 'A'], [['A'], 'B', nil], [['A'], nil, nil], [['A', 'B', nil], 'A', 'A'], [['A', 'B', nil], 'B', 'B'], [['A', 'B', nil], nil, nil], [['A', 'B', nil], 'C', nil], ].each do |values, item, expected| describe "on #{values.inspect}" do context 'with a block' do it "returns #{expected.inspect}" do S[*values].send(method) { |x| x == item }.should == expected end end context 'without a block' do it 'returns an Enumerator' do result = S[*values].send(method) result.class.should be(Enumerator) result.each { |x| x == item}.should == expected end end end end end end end immutable-ruby-master/spec/lib/immutable/set/empty_spec.rb0000644000175000017500000000214714201005456023633 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#empty?' do [ [[], true], [['A'], false], [%w[A B C], false], [[nil], false], [[false], false] ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do S[*values].empty?.should == expected end end end end describe '.empty' do it 'returns the canonical empty set' do S.empty.should be_empty S.empty.object_id.should be(S[].object_id) S.empty.should be(Immutable::EmptySet) end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::Set) subclass.empty.class.should be(subclass) subclass.empty.should be_empty end it 'calls overridden #initialize when creating empty Set' do subclass = Class.new(Immutable::Set) do def initialize @variable = 'value' end end subclass.empty.instance_variable_get(:@variable).should == 'value' end end end end immutable-ruby-master/spec/lib/immutable/set/to_a_spec.rb0000644000175000017500000000140114201005456023407 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:to_a, :entries].each do |method| describe "##{method}" do ('a'..'z').each do |letter| let(:values) { ('a'..letter).to_a } let(:set) { S.new(values) } let(:result) { set.send(method) } context "on 'a'..'#{letter}'" do it 'returns an equivalent array' do result.sort.should == values.sort end it "doesn't change the original Set" do result set.should eql(S[*values]) end it 'returns a mutable array' do expect(result.last).to_not eq('The End') result << 'The End' result.last.should == 'The End' end end end end end end immutable-ruby-master/spec/lib/immutable/set/each_spec.rb0000644000175000017500000000202714201005456023372 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable::Set do let(:set) { S['A', 'B', 'C'] } describe '#each' do let(:each) { set.each(&block) } context 'without a block' do let(:block) { nil } it 'returns an Enumerator' do expect(each.class).to be(Enumerator) expect(each.to_a).to eq(set.to_a) end end context 'with an empty block' do let(:block) { ->(item) {} } it 'returns self' do expect(each).to be(set) end end context 'with a block' do let(:items) { ::Set.new } let(:values) { ::Set.new(%w[A B C]) } let(:block) { ->(item) { items << item } } before(:each) { each } it 'yields all values' do expect(items).to eq(values) end end it 'yields both of a pair of colliding keys' do set = S[DeterministicHash.new('a', 1010), DeterministicHash.new('b', 1010)] yielded = [] set.each { |obj| yielded << obj } yielded.map(&:value).sort.should == ['a', 'b'] end end end immutable-ruby-master/spec/lib/immutable/set/all_spec.rb0000644000175000017500000000250414201005456023242 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#all?' do context 'when empty' do it 'with a block returns true' do S.empty.all? {}.should == true end it 'with no block returns true' do S.empty.all?.should == true end end context 'when not empty' do context 'with a block' do let(:set) { S['A', 'B', 'C'] } it 'returns true if the block always returns true' do set.all? { |item| true }.should == true end it 'returns false if the block ever returns false' do set.all? { |item| item == 'D' }.should == false end it 'propagates an exception from the block' do -> { set.all? { |k,v| raise 'help' } }.should raise_error(RuntimeError) end it 'stops iterating as soon as the block returns false' do yielded = [] set.all? { |k,v| yielded << k; false } yielded.size.should == 1 end end describe 'with no block' do it 'returns true if all values are truthy' do S[true, 'A'].all?.should == true end [nil, false].each do |value| it "returns false if any value is #{value.inspect}" do S[value, true, 'A'].all?.should == false end end end end end end immutable-ruby-master/spec/lib/immutable/set/delete_spec.rb0000644000175000017500000000345514201005456023742 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do let(:set) { S['A', 'B', 'C'] } describe '#delete' do context 'with an existing value' do it 'preserves the original' do set.delete('B') set.should eql(S['A', 'B', 'C']) end it 'returns a copy with the remaining values' do set.delete('B').should eql(S['A', 'C']) end end context 'with a non-existing value' do it 'preserves the original values' do set.delete('D') set.should eql(S['A', 'B', 'C']) end it 'returns self' do set.delete('D').should equal(set) end end context 'when removing the last value in a set' do it 'returns the canonical empty set' do set.delete('B').delete('C').delete('A').should be(Immutable::EmptySet) end end it 'works on large sets, with many combinations of input' do array = 1000.times.map { %w[a b c d e f g h i j k l m n].sample(5).join }.uniq set = S.new(array) array.each do |key| result = set.delete(key) result.size.should == set.size - 1 result.include?(key).should == false other = array.sample (result.include?(other).should == true) if other != key end end end describe '#delete?' do context 'with an existing value' do it 'preserves the original' do set.delete?('B') set.should eql(S['A', 'B', 'C']) end it 'returns a copy with the remaining values' do set.delete?('B').should eql(S['A', 'C']) end end context 'with a non-existing value' do it 'preserves the original values' do set.delete?('D') set.should eql(S['A', 'B', 'C']) end it 'returns false' do set.delete?('D').should be(false) end end end end immutable-ruby-master/spec/lib/immutable/set/join_spec.rb0000644000175000017500000000340114201005456023426 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#join' do context 'with a separator' do [ [[], ''], [['A'], 'A'], [[DeterministicHash.new('A', 1), DeterministicHash.new('B', 2), DeterministicHash.new('C', 3)], 'A|B|C'] ].each do |values, expected| context "on #{values.inspect}" do let(:set) { S[*values] } it 'preserves the original' do set.join('|') set.should eql(S[*values]) end it "returns #{expected.inspect}" do set.join('|').should eql(expected) end end end end context 'without a separator' do [ [[], ''], [['A'], 'A'], [[DeterministicHash.new('A', 1), DeterministicHash.new('B', 2), DeterministicHash.new('C', 3)], 'ABC'] ].each do |values, expected| context "on #{values.inspect}" do let(:set) { S[*values] } it 'preserves the original' do set.join set.should eql(S[*values]) end it "returns #{expected.inspect}" do set.join.should eql(expected) end end end end context 'without a separator (with global default separator set)' do before { $, = '**' } let(:set) { S[DeterministicHash.new('A', 1), DeterministicHash.new('B', 2), DeterministicHash.new('C', 3)] } after { $, = nil } context "on ['A', 'B', 'C']" do it 'preserves the original' do set.join set.should eql(S[DeterministicHash.new('A', 1), DeterministicHash.new('B', 2), DeterministicHash.new('C', 3)]) end it "returns #{@expected.inspect}" do set.join.should == 'A**B**C' end end end end end immutable-ruby-master/spec/lib/immutable/set/add_spec.rb0000644000175000017500000000372414201005456023227 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do let(:original) { S['A', 'B', 'C'] } [:add, :<<].each do |method| describe "##{method}" do context 'with a unique value' do let(:result) { original.send(method, 'D') } it 'preserves the original' do result original.should eql(S['A', 'B', 'C']) end it 'returns a copy with the superset of values' do result.should eql(S['A', 'B', 'C', 'D']) end end context 'with a duplicate value' do let(:result) { original.send(method, 'C') } it 'preserves the original values' do result original.should eql(S['A', 'B', 'C']) end it 'returns self' do result.should equal(original) end end it 'can add nil to a set' do original.add(nil).should eql(S['A', 'B', 'C', nil]) end it 'works on large sets, with many combinations of input' do 50.times do # Array#sample is buggy on RBX 2.5.8; that's why #uniq is needed here # See https://github.com/rubinius/rubinius/issues/3506 array = (1..500).to_a.sample(100).uniq set = S.new(array) to_add = 1000 + rand(1000) set.add(to_add).size.should == array.size + 1 set.add(to_add).include?(to_add).should == true end end end end describe '#add?' do context 'with a unique value' do let(:result) { original.add?('D') } it 'preserves the original' do original.should eql(S['A', 'B', 'C']) end it 'returns a copy with the superset of values' do result.should eql(S['A', 'B', 'C', 'D']) end end context 'with a duplicate value' do let(:result) { original.add?('C') } it 'preserves the original values' do original.should eql(S['A', 'B', 'C']) end it 'returns false' do result.should equal(false) end end end end immutable-ruby-master/spec/lib/immutable/set/grep_spec.rb0000644000175000017500000000247714201005456023440 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do let(:set) { S[*values] } describe '#grep' do let(:grep) { set.grep(String, &block) } shared_examples 'check filtered values' do it 'returns the filtered values' do expect(grep).to eq(S[*filtered]) end end shared_examples 'check different types of inputs' do context 'with an empty set' do let(:values) { [] } let(:filtered) { [] } include_examples 'check filtered values' end context 'with a single item set' do let(:values) { ['A'] } let(:filtered) { ['A'] } include_examples 'check filtered values' end context "with a single item set that doesn't contain match" do let(:values) { [1] } let(:filtered) { [] } include_examples 'check filtered values' end context "with a multi-item set where one isn't a match" do let(:values) { ['A', 2, 'C'] } let(:filtered) { %w[A C] } include_examples 'check filtered values' end end context 'without a block' do let(:block) { nil } include_examples 'check different types of inputs' end describe 'with a block' do let(:block) { ->(item) { item }} include_examples 'check different types of inputs' end end end immutable-ruby-master/spec/lib/immutable/set/select_spec.rb0000644000175000017500000000426014201005456023752 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:select, :find_all].each do |method| describe "##{method}" do let(:set) { S['A', 'B', 'C'] } context 'when everything matches' do it 'returns self' do set.send(method) { |item| true }.should equal(set) end end context 'when only some things match' do context 'with a block' do let(:result) { set.send(method) { |item| item == 'A' }} it 'preserves the original' do result set.should eql(S['A', 'B', 'C']) end it 'returns a set with the matching values' do result.should eql(S['A']) end end context 'with no block' do it 'returns an Enumerator' do set.send(method).class.should be(Enumerator) set.send(method).each { |item| item == 'A' }.should eql(S['A']) end end end context 'when nothing matches' do let(:result) { set.send(method) { |item| false }} it 'preserves the original' do result set.should eql(S['A', 'B', 'C']) end it 'returns the canonical empty set' do result.should equal(Immutable::EmptySet) end end context 'from a subclass' do it 'returns an instance of the same class' do subclass = Class.new(Immutable::Set) instance = subclass.new(['A', 'B', 'C']) instance.send(method) { true }.class.should be(subclass) instance.send(method) { false }.class.should be(subclass) instance.send(method) { rand(2) == 0 }.class.should be(subclass) end end it 'works on a large set, with many combinations of input' do items = (1..1000).to_a original = S.new(items) 30.times do threshold = rand(1000) result = original.send(method) { |item| item <= threshold } result.size.should == threshold result.each { |item| item.should <= threshold } (threshold+1).upto(1000) { |item| result.include?(item).should == false } end original.should eql(S.new(items)) end end end end immutable-ruby-master/spec/lib/immutable/set/sorting_spec.rb0000644000175000017500000000371114201005456024160 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [ [:sort, ->(left, right) { left.length <=> right.length }], [:sort_by, ->(item) { item.length }], ].each do |method, comparator| describe "##{method}" do [ [[], []], [['A'], ['A']], [%w[Ichi Ni San], %w[Ni San Ichi]], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } describe 'with a block' do let(:result) { set.send(method, &comparator) } it "returns #{expected.inspect}" do result.should eql(SS.new(expected, &comparator)) result.to_a.should == expected end it "doesn't change the original Set" do result set.should eql(S.new(values)) end end describe 'without a block' do let(:result) { set.send(method) } it "returns #{expected.sort.inspect}" do result.should eql(SS[*expected]) result.to_a.should == expected.sort end it "doesn't change the original Set" do result set.should eql(S.new(values)) end end end end end end describe '#sort_by' do # originally this test checked that #sort_by only called the block once # for each item # however, when initializing a SortedSet, we need to make sure that it # does not include any duplicates, and we use the block when checking that # the real point here is that the block should not be called an excessive # number of times, degrading performance it 'calls the passed block no more than twice for each item' do count = 0 fn = lambda { |x| count += 1; -x } items = 100.times.collect { rand(10000) }.uniq S[*items].sort_by(&fn).to_a.should == items.sort.reverse count.should <= (items.length * 2) end end end immutable-ruby-master/spec/lib/immutable/set/difference_spec.rb0000644000175000017500000000262114201005456024564 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:difference, :subtract, :-].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], ['A']], [['A'], ['A'], []], [%w[A B C], ['B'], %w[A C]], [%w[A B C], %w[A C], ['B']], [%w[A B C D E F G H], [], %w[A B C D E F G H]], [%w[A B C M X Y Z], %w[B C D E F G H I J X], %w[A M Y Z]] ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do let(:set_a) { S[*a] } let(:set_b) { S[*b] } let(:result) { set_a.send(method, set_b) } it "doesn't modify the original Sets" do result set_a.should eql(S.new(a)) set_b.should eql(S.new(b)) end it "returns #{expected.inspect}" do result.should eql(S[*expected]) end end context 'when passed a Ruby Array' do it 'returns the expected Set' do S[*a].difference(b.freeze).should eql(S[*expected]) end end end it 'works on a wide variety of inputs' do items = ('aa'..'zz').to_a 50.times do array1 = items.sample(200) array2 = items.sample(200) result = S.new(array1).send(method, S.new(array2)) result.to_a.sort.should eql((array1 - array2).sort) end end end end end immutable-ruby-master/spec/lib/immutable/set/eql_spec.rb0000644000175000017500000000466414201005456023264 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable::Set do let(:set) { S[*values] } let(:comparison) { S[*comparison_values] } describe '#eql?' do let(:eql?) { set.eql?(comparison) } shared_examples 'comparing non-sets' do let(:values) { %w[A B C] } it 'returns false' do expect(eql?).to eq(false) end end context 'when comparing to a standard set' do let(:comparison) { ::Set.new(%w[A B C]) } include_examples 'comparing non-sets' end context 'when comparing to a arbitrary object' do let(:comparison) { Object.new } include_examples 'comparing non-sets' end context 'when comparing with a subclass of Immutable::Set' do let(:comparison) { Class.new(Immutable::Set).new(%w[A B C]) } include_examples 'comparing non-sets' end context 'with an empty set for each comparison' do let(:values) { [] } let(:comparison_values) { [] } it 'returns true' do expect(eql?).to eq(true) end end context 'with an empty set and a set with nil' do let(:values) { [] } let(:comparison_values) { [nil] } it 'returns false' do expect(eql?).to eq(false) end end context 'with a single item array and empty array' do let(:values) { ['A'] } let(:comparison_values) { [] } it 'returns false' do expect(eql?).to eq(false) end end context 'with matching single item array' do let(:values) { ['A'] } let(:comparison_values) { ['A'] } it 'returns true' do expect(eql?).to eq(true) end end context 'with mismatching single item array' do let(:values) { ['A'] } let(:comparison_values) { ['B'] } it 'returns false' do expect(eql?).to eq(false) end end context 'with a multi-item array and single item array' do let(:values) { %w[A B] } let(:comparison_values) { ['A'] } it 'returns false' do expect(eql?).to eq(false) end end context 'with matching multi-item array' do let(:values) { %w[A B] } let(:comparison_values) { %w[A B] } it 'returns true' do expect(eql?).to eq(true) end end context 'with a mismatching multi-item array' do let(:values) { %w[A B] } let(:comparison_values) { %w[B A] } it 'returns true' do expect(eql?).to eq(true) end end end end immutable-ruby-master/spec/lib/immutable/set/include_spec.rb0000644000175000017500000000330314201005456024113 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable::Set do [:include?, :member?].each do |method| describe "##{method}" do let(:set) { S['A', 'B', 'C', 2.0, nil] } ['A', 'B', 'C', 2.0, nil].each do |value| it "returns true for an existing value (#{value.inspect})" do set.send(method, value).should == true end end it 'returns false for a non-existing value' do set.send(method, 'D').should == false end it 'returns true even if existing value is nil' do S[nil].include?(nil).should == true end it 'returns true even if existing value is false' do S[false].include?(false).should == true end it 'returns false for a mutable item which is mutated after adding' do item = ['mutable'] item = [rand(1000000)] while (item.hash.abs & 31 == [item[0], 'HOSED!'].hash.abs & 31) set = S[item] item.push('HOSED!') set.include?(item).should == false end it 'uses #eql? for equality' do set.send(method, 2).should == false end it 'returns the right answers after a lot of addings and removings' do array, set, rb_set = [], S.new, ::Set.new 1000.times do if rand(2) == 0 array << (item = rand(10000)) rb_set.add(item) set = set.add(item) set.include?(item).should == true else item = array.sample rb_set.delete(item) set = set.delete(item) set.include?(item).should == false end end array.each { |item| set.include?(item).should == rb_set.include?(item) } end end end end immutable-ruby-master/spec/lib/immutable/set/group_by_spec.rb0000644000175000017500000000335714201005456024327 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:group_by, :group, :classify].each do |method| describe "##{method}" do context 'with a block' do [ [[], []], [[1], [true => S[1]]], [[1, 2, 3, 4], [true => S[3, 1], false => S[4, 2]]], ].each do |values, expected| context "on #{values.inspect}" do let(:set) { S[*values] } it "returns #{expected.inspect}" do set.send(method, &:odd?).should eql(H[*expected]) set.should eql(S.new(values)) # make sure it hasn't changed end end end end context 'without a block' do [ [[], []], [[1], [1 => S[1]]], [[1, 2, 3, 4], [1 => S[1], 2 => S[2], 3 => S[3], 4 => S[4]]], ].each do |values, expected| context "on #{values.inspect}" do let(:set) { S[*values] } it "returns #{expected.inspect}" do set.group_by.should eql(H[*expected]) set.should eql(S.new(values)) # make sure it hasn't changed end end end end context 'on an empty set' do it 'returns an empty hash' do S.empty.group_by { |x| x }.should eql(H.empty) end end it 'returns a hash without default proc' do S[1,2,3].group_by { |x| x }.default_proc.should be_nil end context 'from a subclass' do it 'returns an Hash whose values are instances of the subclass' do subclass = Class.new(Immutable::Set) instance = subclass.new([1, 'string', :symbol]) instance.group_by(&:class).values.each { |v| v.class.should be(subclass) } end end end end end immutable-ruby-master/spec/lib/immutable/set/clear_spec.rb0000644000175000017500000000133014201005456023554 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#clear' do [ [], ['A'], %w[A B C], ].each do |values| describe "on #{values}" do let(:set) { S[*values] } it 'preserves the original' do set.clear set.should eql(S[*values]) end it 'returns an empty set' do set.clear.should equal(S.empty) end end end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::Set) instance = subclass.new([:a, :b, :c, :d]) instance.clear.class.should be(subclass) instance.clear.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/set/superset_spec.rb0000644000175000017500000000261614201005456024350 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:superset?, :>=].each do |method| describe "##{method}" do [ [[], [], true], [['A'], [], true], [[], ['A'], false], [['A'], ['A'], true], [%w[A B C], ['B'], true], [['B'], %w[A B C], false], [%w[A B C], %w[A C], true], [%w[A C], %w[A B C], false], [%w[A B C], %w[A B C], true], [%w[A B C], %w[A B C D], false], [%w[A B C D], %w[A B C], true], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do S[*a].send(method, S[*b]).should == expected end end end end end [:proper_superset?, :>].each do |method| describe "##{method}" do [ [[], [], false], [['A'], [], true], [[], ['A'], false], [['A'], ['A'], false], [%w[A B C], ['B'], true], [['B'], %w[A B C], false], [%w[A B C], %w[A C], true], [%w[A C], %w[A B C], false], [%w[A B C], %w[A B C], false], [%w[A B C], %w[A B C D], false], [%w[A B C D], %w[A B C], true], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do S[*a].send(method, S[*b]).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/set/to_list_spec.rb0000644000175000017500000000135314201005456024150 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#to_list' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do let(:set) { S[*values] } let(:list) { set.to_list } it 'returns a list' do list.is_a?(Immutable::List).should == true end it "doesn't change the original Set" do list set.should eql(S.new(values)) end describe 'the returned list' do it 'has the correct length' do list.size.should == values.size end it 'contains all values' do list.to_a.sort.should == values.sort end end end end end end immutable-ruby-master/spec/lib/immutable/set/disjoint_spec.rb0000644000175000017500000000121214201005456024310 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#disjoint?' do [ [[], [], true], [['A'], [], true], [[], ['A'], true], [['A'], ['A'], false], [%w[A B C], ['B'], false], [['B'], %w[A B C], false], [%w[A B C], %w[D E], true], [%w[F G H I], %w[A B C], true], [%w[A B C], %w[A B C], false], [%w[A B C], %w[A B C D], false], [%w[D E F G], %w[A B C], true], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do S[*a].disjoint?(S[*b]).should be(expected) end end end end end immutable-ruby-master/spec/lib/immutable/set/intersection_spec.rb0000644000175000017500000000311614201005456025200 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:intersection, :&].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], []], [['A'], ['A'], ['A']], [%w[A B C], ['B'], ['B']], [%w[A B C], %w[A C], %w[A C]], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do let(:set_a) { S[*a] } let(:set_b) { S[*b] } it "returns #{expected.inspect}, without changing the original Sets" do set_a.send(method, set_b).should eql(S[*expected]) set_a.should eql(S.new(a)) set_b.should eql(S.new(b)) end end context "for #{b.inspect} and #{a.inspect}" do let(:set_a) { S[*a] } let(:set_b) { S[*b] } it "returns #{expected.inspect}, without changing the original Sets" do set_b.send(method, set_a).should eql(S[*expected]) set_a.should eql(S.new(a)) set_b.should eql(S.new(b)) end end context 'when passed a Ruby Array' do it 'returns the expected Set' do S[*a].send(method, b.freeze).should eql(S[*expected]) end end end it 'returns results consistent with Array#&' do 50.times do array1 = rand(100).times.map { rand(1000000).to_s(16) } array2 = rand(100).times.map { rand(1000000).to_s(16) } result = S.new(array1).send(method, S.new(array2)) result.to_a.sort.should eql((array1 & array2).sort) end end end end end immutable-ruby-master/spec/lib/immutable/set/union_spec.rb0000644000175000017500000000365114201005456023626 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:union, :|, :+, :merge].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], ['A']], [['A'], ['A'], ['A']], [[], ['A'], ['A']], [%w[A B C], [], %w[A B C]], [%w[A B C], %w[A B C], %w[A B C]], [%w[A B C], %w[X Y Z], %w[A B C X Y Z]] ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do let(:set_a) { S[*a] } let(:set_b) { S[*b] } it "returns #{expected.inspect}, without changing the original Sets" do set_a.send(method, set_b).should eql(S[*expected]) set_a.should eql(S.new(a)) set_b.should eql(S.new(b)) end end context "for #{b.inspect} and #{a.inspect}" do let(:set_a) { S[*a] } let(:set_b) { S[*b] } it "returns #{expected.inspect}, without changing the original Sets" do set_b.send(method, set_a).should eql(S[*expected]) set_a.should eql(S.new(a)) set_b.should eql(S.new(b)) end end context 'when passed a Ruby Array' do it 'returns the expected Set' do S[*a].send(method, b.freeze).should eql(S[*expected]) S[*b].send(method, a.freeze).should eql(S[*expected]) end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Set) subclass.new(a).send(method, S.new(b)).class.should be(subclass) subclass.new(b).send(method, S.new(a)).class.should be(subclass) end end end context 'when receiving a subset' do let(:set_a) { S.new(1..300) } let(:set_b) { S.new(1..200) } it 'returns self' do set_a.send(method, set_b).should be(set_a) end end end end end immutable-ruby-master/spec/lib/immutable/set/compact_spec.rb0000644000175000017500000000122614201005456024120 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#compact' do [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B C]], [[nil], []], [[nil, 'B'], ['B']], [['A', nil], ['A']], [[nil, nil], []], [['A', nil, 'C'], %w[A C]], [[nil, 'B', nil], ['B']], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } it 'preserves the original' do set.compact set.should eql(S[*values]) end it "returns #{expected.inspect}" do set.compact.should eql(S[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/set/none_spec.rb0000644000175000017500000000233414201005456023432 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#none?' do context 'when empty' do it 'with a block returns true' do S.empty.none? {}.should == true end it 'with no block returns true' do S.empty.none?.should == true end end context 'when not empty' do context 'with a block' do let(:set) { S['A', 'B', 'C', nil] } ['A', 'B', 'C', nil].each do |value| it "returns false if the block ever returns true (#{value.inspect})" do set.none? { |item| item == value }.should == false end end it 'returns true if the block always returns false' do set.none? { |item| item == 'D' }.should == true end it 'stops iterating as soon as the block returns true' do yielded = [] set.none? { |item| yielded << item; true } yielded.size.should == 1 end end context 'with no block' do it 'returns false if any value is truthy' do S[nil, false, true, 'A'].none?.should == false end it 'returns true if all values are falsey' do S[nil, false].none?.should == true end end end end end immutable-ruby-master/spec/lib/immutable/set/maximum_spec.rb0000644000175000017500000000152514201005456024151 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#max' do context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } let(:result) { set.max { |maximum, item| maximum.length <=> item.length }} it "returns #{expected.inspect}" do result.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'San'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do S[*values].max.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/set/first_spec.rb0000644000175000017500000000125014201005456023616 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#first' do context 'on an empty set' do it 'returns nil' do S.empty.first.should be_nil end end context 'on a non-empty set' do it 'returns an arbitrary value from the set' do %w[A B C].include?(S['A', 'B', 'C'].first).should == true end end it 'returns nil if only member of set is nil' do S[nil].first.should be(nil) end it 'returns the first item yielded by #each' do 10.times do set = S.new((rand(10)+1).times.collect { rand(10000 )}) set.each { |item| break item }.should be(set.first) end end end end immutable-ruby-master/spec/lib/immutable/set/one_spec.rb0000644000175000017500000000230214201005456023247 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#one?' do context 'when empty' do it 'with a block returns false' do S.empty.one? {}.should == false end it 'with no block returns false' do S.empty.one?.should == false end end context 'when not empty' do context 'with a block' do let(:set) { S['A', 'B', 'C'] } it 'returns false if the block returns true more than once' do set.one? { |item| true }.should == false end it 'returns false if the block never returns true' do set.one? { |item| false }.should == false end it 'returns true if the block only returns true once' do set.one? { |item| item == 'A' }.should == true end end context 'with no block' do it 'returns false if more than one value is truthy' do S[nil, true, 'A'].one?.should == false end it 'returns true if only one value is truthy' do S[nil, true, false].one?.should == true end it 'returns false if no values are truthy' do S[nil, false].one?.should == false end end end end end immutable-ruby-master/spec/lib/immutable/set/count_spec.rb0000644000175000017500000000142214201005456023620 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#count' do [ [[], 0], [[1], 1], [[1, 2], 1], [[1, 2, 3], 2], [[1, 2, 3, 4], 2], [[1, 2, 3, 4, 5], 3], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } context 'with a block' do it "returns #{expected.inspect}" do set.count(&:odd?).should == expected end end context 'without a block' do it 'returns length' do set.count.should == set.length end end end end it 'works on large sets' do set = Immutable::Set.new(1..2000) set.count.should == 2000 set.count(&:odd?).should == 1000 end end end immutable-ruby-master/spec/lib/immutable/set/grep_v_spec.rb0000644000175000017500000000256314201005456023761 0ustar boutilboutilrequire 'spec_helper' require 'immutable/set' describe Immutable::Set do let(:set) { S[*values] } describe '#grep_v' do let(:grep_v) { set.grep_v(String, &block) } shared_examples 'check filtered values' do it 'returns the filtered values' do expect(grep_v).to eq(S[*filtered]) end end context 'without a block' do let(:block) { nil } context 'with an empty set' do let(:values) { [] } let(:filtered) { [] } include_examples 'check filtered values' end context 'with a single item set' do let(:values) { ['A'] } let(:filtered) { [] } include_examples 'check filtered values' end context "with a single item set that doesn't contain match" do let(:values) { [1] } let(:filtered) { [1] } include_examples 'check filtered values' end context "with a multi-item set where one isn't a match" do let(:values) { [2, 'C', 4] } let(:filtered) { [2, 4] } include_examples 'check filtered values' end end describe 'with a block' do let(:block) { ->(item) { item + 100 }} context 'resulting items are processed with the block' do let(:values) { [2, 'C', 4] } let(:filtered) { [102, 104] } include_examples 'check filtered values' end end end end immutable-ruby-master/spec/lib/immutable/set/exclusion_spec.rb0000644000175000017500000000247014201005456024505 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:exclusion, :^].each do |method| describe "##{method}" do [ [[], [], []], [['A'], [], ['A']], [['A'], ['A'], []], [%w[A B C], ['B'], %w[A C]], [%w[A B C], %w[B C D], %w[A D]], [%w[A B C], %w[D E F], %w[A B C D E F]], ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do let(:set_a) { S[*a] } let(:set_b) { S[*b] } let(:result) { set_a.send(method, set_b) } it "doesn't modify the original Sets" do result set_a.should eql(S.new(a)) set_b.should eql(S.new(b)) end it "returns #{expected.inspect}" do result.should eql(S[*expected]) end end context 'when passed a Ruby Array' do it 'returns the expected Set' do S[*a].exclusion(b.freeze).should eql(S[*expected]) end end end it 'works for a wide variety of inputs' do 50.times do array1 = (1..400).to_a.sample(100) array2 = (1..400).to_a.sample(100) result = S.new(array1) ^ S.new(array2) result.to_a.sort.should eql(((array1 | array2) - (array1 & array2)).sort) end end end end end immutable-ruby-master/spec/lib/immutable/set/reduce_spec.rb0000644000175000017500000000273414201005456023746 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:reduce, :inject].each do |method| describe "##{method}" do [ [[], 10, 10], [[1], 10, 9], [[1, 2, 3], 10, 4], ].each do |values, initial, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } context "with an initial value of #{initial}" do context 'and a block' do it "returns #{expected.inspect}" do set.send(method, initial) { |memo, item| memo - item }.should == expected end end end end end [ [[], nil], [[1], 1], [[1, 2, 3], 6], ].each do |values, expected| describe "on #{values.inspect}" do let(:set) { S[*values] } context 'with no initial value' do context 'and a block' do it "returns #{expected.inspect}" do set.send(method) { |memo, item| memo + item }.should == expected end end end end end describe 'with no block and a symbol argument' do it 'uses the symbol as the name of a method to reduce with' do S[1, 2, 3].reduce(:+).should == 6 end end describe 'with no block and a string argument' do it 'uses the string as the name of a method to reduce with' do S[1, 2, 3].reduce('+').should == 6 end end end end end immutable-ruby-master/spec/lib/immutable/set/sum_spec.rb0000644000175000017500000000072314201005456023277 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#sum' do [ [[], 0], [[2], 2], [[1, 3, 5, 7, 11], 27], ].each do |values, expected| context "on #{values.inspect}" do let(:set) { S[*values] } it "returns #{expected.inspect}" do set.sum.should == expected end it "doesn't change the original Set" do set.should eql(S.new(values)) end end end end end immutable-ruby-master/spec/lib/immutable/set/map_spec.rb0000644000175000017500000000323414201005456023250 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:map, :collect].each do |method| describe "##{method}" do context 'when empty' do it 'returns self' do S.empty.send(method) {}.should equal(S.empty) end end context 'when not empty' do let(:set) { S['A', 'B', 'C'] } context 'with a block' do it 'preserves the original values' do set.send(method, &:downcase) set.should eql(S['A', 'B', 'C']) end it 'returns a new set with the mapped values' do set.send(method, &:downcase).should eql(S['a', 'b', 'c']) end end context 'with no block' do it 'returns an Enumerator' do set.send(method).class.should be(Enumerator) set.send(method).each(&:downcase).should == S['a', 'b', 'c'] end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Set) instance = subclass['a', 'b'] instance.map(&:upcase).class.should be(subclass) end end context 'when multiple items map to the same value' do it 'filters out the duplicates' do set = S.new('aa'..'zz') result = set.map { |s| s[0] } result.should eql(Immutable::Set.new('a'..'z')) result.size.should == 26 end end it 'works on large sets' do set = S.new(1..1000) result = set.map { |x| x * 10 } result.size.should == 1000 1.upto(1000) { |n| result.include?(n * 10).should == true } end end end end immutable-ruby-master/spec/lib/immutable/set/reverse_each_spec.rb0000644000175000017500000000152614201005456025130 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable::Set do let(:set) { S['A', 'B', 'C'] } describe '#reverse_each' do let(:reverse_each) { set.reverse_each(&block) } context 'without a block' do let(:block) { nil } it 'returns an Enumerator' do expect(reverse_each.class).to be(Enumerator) expect(reverse_each.to_a).to eq(set.to_a.reverse) end end context 'with an empty block' do let(:block) { ->(item) {} } it 'returns self' do expect(reverse_each).to be(set) end end context 'with a block' do let(:items) { ::Set.new } let(:values) { ::Set.new(%w[A B C]) } let(:block) { ->(item) { items << item } } before(:each) { reverse_each } it 'yields all values' do expect(items).to eq(values) end end end end immutable-ruby-master/spec/lib/immutable/set/hash_spec.rb0000644000175000017500000000127114201005456023415 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#hash' do context 'on an empty set' do it 'returns 0' do S.empty.hash.should == 0 end end it 'generates the same hash value for a set regardless of the order things were added to it' do item1 = DeterministicHash.new('a', 121) item2 = DeterministicHash.new('b', 474) item3 = DeterministicHash.new('c', 121) S.empty.add(item1).add(item2).add(item3).hash.should == S.empty.add(item3).add(item2).add(item1).hash end it 'values are sufficiently distributed' do (1..4000).each_slice(4).map { |a, b, c, d| S[a, b, c, d].hash }.uniq.size.should == 1000 end end end immutable-ruby-master/spec/lib/immutable/set/copying_spec.rb0000644000175000017500000000036714201005456024147 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do [:dup, :clone].each do |method| let(:set) { S['A', 'B', 'C'] } describe "##{method}" do it 'returns self' do set.send(method).should equal(set) end end end end immutable-ruby-master/spec/lib/immutable/set/partition_spec.rb0000644000175000017500000000265214201005456024507 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#partition' do [ [[], [], []], [[1], [1], []], [[1, 2], [1], [2]], [[1, 2, 3], [1, 3], [2]], [[1, 2, 3, 4], [1, 3], [2, 4]], [[2, 3, 4], [3], [2, 4]], [[3, 4], [3], [4]], [[4], [], [4]], ].each do |values, expected_matches, expected_remainder| context "on #{values.inspect}" do let(:set) { S[*values] } context 'with a block' do let(:result) { set.partition(&:odd?) } let(:matches) { result.first } let(:remainder) { result.last } it 'preserves the original' do result set.should eql(S[*values]) end it 'returns a frozen array with two items' do result.class.should be(Array) result.should be_frozen result.size.should be(2) end it 'correctly identifies the matches' do matches.should eql(S[*expected_matches]) end it 'correctly identifies the remainder' do remainder.should eql(S[*expected_remainder]) end end describe 'without a block' do it 'returns an Enumerator' do set.partition.class.should be(Enumerator) set.partition.each(&:odd?).should eql([S.new(expected_matches), S.new(expected_remainder)]) end end end end end end immutable-ruby-master/spec/lib/immutable/set/to_set_spec.rb0000644000175000017500000000050614201005456023767 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Set do describe '#to_set' do [ [], ['A'], %w[A B C], ].each do |values| describe "on #{values.inspect}" do let(:set) { S[*values] } it 'returns self' do set.to_set.should equal(set) end end end end end immutable-ruby-master/spec/lib/immutable/deque/0000755000175000017500000000000014201005456021442 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/deque/new_spec.rb0000644000175000017500000000223014201005456023567 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '.new' do it 'accepts a single enumerable argument and creates a new deque' do deque = Immutable::Deque.new([1,2,3]) deque.size.should be(3) deque.first.should be(1) deque.dequeue.first.should be(2) deque.dequeue.dequeue.first.should be(3) end it 'is amenable to overriding of #initialize' do class SnazzyDeque < Immutable::Deque def initialize super(['SNAZZY!!!']) end end deque = SnazzyDeque.new deque.size.should be(1) deque.to_a.should == ['SNAZZY!!!'] end context 'from a subclass' do it 'returns a frozen instance of the subclass' do subclass = Class.new(Immutable::Deque) instance = subclass.new(['some', 'values']) instance.class.should be subclass instance.frozen?.should be true end end end describe '.[]' do it 'accepts a variable number of items and creates a new deque' do deque = Immutable::Deque['a', 'b'] deque.size.should be(2) deque.first.should == 'a' deque.dequeue.first.should == 'b' end end end immutable-ruby-master/spec/lib/immutable/deque/to_ary_spec.rb0000644000175000017500000000132614201005456024300 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do let(:deque) { D['A', 'B', 'C', 'D'] } describe '#to_ary' do context 'enables implicit conversion to' do it 'block parameters' do def func(&block) yield(deque) end func do |a, b, *c| expect(a).to eq('A') expect(b).to eq('B') expect(c).to eq(%w[C D]) end end it 'method arguments' do def func(a, b, *c) expect(a).to eq('A') expect(b).to eq('B') expect(c).to eq(%w[C D]) end func(*deque) end it 'works with splat' do array = *deque expect(array).to eq(%w[A B C D]) end end end end immutable-ruby-master/spec/lib/immutable/deque/size_spec.rb0000644000175000017500000000064214201005456023755 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do [:size, :length].each do |method| describe "##{method}" do [ [[], 0], [['A'], 1], [%w[A B C], 3], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do D[*values].send(method).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/deque/construction_spec.rb0000644000175000017500000000130414201005456025531 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '.[]' do context 'with no arguments' do it 'always returns the same instance' do D[].class.should be(Immutable::Deque) D[].should equal(D[]) end it 'returns an empty, frozen deque' do D[].should be_empty D[].should be_frozen end end context 'with a number of items' do let(:deque) { D['A', 'B', 'C'] } it 'always returns a different instance' do deque.should_not equal(D['A', 'B', 'C']) end it 'is the same as repeatedly using #endeque' do deque.should eql(D.empty.enqueue('A').enqueue('B').enqueue('C')) end end end end immutable-ruby-master/spec/lib/immutable/deque/pop_spec.rb0000644000175000017500000000151214201005456023576 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#pop' do [ [[], []], [['A'], []], [%w[A B C], %w[A B]], ].each do |values, expected| context "on #{values.inspect}" do let(:deque) { D[*values] } it 'preserves the original' do deque.pop deque.should eql(D[*values]) end it "returns #{expected.inspect}" do deque.pop.should eql(D[*expected]) end it 'returns a frozen instance' do deque.pop.should be_frozen end end end context 'on empty subclass' do let(:subclass) { Class.new(Immutable::Deque) } let(:empty_instance) { subclass.new } it 'returns an empty object of the same class' do empty_instance.pop.class.should be subclass end end end end immutable-ruby-master/spec/lib/immutable/deque/push_spec.rb0000644000175000017500000000164214201005456023763 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#push' do [ [[], 'A', ['A']], [['A'], 'B', %w[A B]], [%w[A B C], 'D', %w[A B C D]], ].each do |original, item, expected| context "pushing #{item.inspect} into #{original.inspect}" do let(:deque) { D.new(original) } it 'preserves the original' do deque.push(item) deque.should eql(D.new(original)) end it "returns #{expected.inspect}" do deque.push(item).should eql(D.new(expected)) end it 'returns a frozen instance' do deque.push(item).should be_frozen end end end context 'on a subclass' do let(:subclass) { Class.new(Immutable::Deque) } let(:empty_instance) { subclass.new } it 'returns an object of same class' do empty_instance.push(1).class.should be subclass end end end end immutable-ruby-master/spec/lib/immutable/deque/inspect_spec.rb0000644000175000017500000000111414201005456024443 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#inspect' do [ [[], 'Immutable::Deque[]'], [['A'], 'Immutable::Deque["A"]'], [%w[A B C], 'Immutable::Deque["A", "B", "C"]'] ].each do |values, expected| context "on #{values.inspect}" do let(:deque) { D[*values] } it "returns #{expected.inspect}" do deque.inspect.should == expected end it "returns a string which can be eval'd to get an equivalent object" do eval(deque.inspect).should eql(deque) end end end end end immutable-ruby-master/spec/lib/immutable/deque/marshal_spec.rb0000644000175000017500000000203614201005456024431 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#marshal_dump/#marshal_load' do let(:ruby) do File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) end let(:child_cmd) do %Q|#{ruby} -I lib -r immutable -e 'deque = Immutable::Deque[5, 10, 15]; $stdout.write(Marshal.dump(deque))'| end let(:reloaded_deque) do IO.popen(child_cmd, 'r+') do |child| reloaded_deque = Marshal.load(child) child.close reloaded_deque end end it 'can survive dumping and loading into a new process' do expect(reloaded_deque).to eql(D[5, 10, 15]) end it 'is still possible to push and pop items after loading' do expect(reloaded_deque.first).to eq(5) expect(reloaded_deque.last).to eq(15) expect(reloaded_deque.push(20)).to eql(D[5, 10, 15, 20]) expect(reloaded_deque.pop).to eql(D[5, 10]) expect(reloaded_deque.unshift(1)).to eql(D[1, 5, 10, 15]) expect(reloaded_deque.shift).to eql(D[10, 15]) end end end immutable-ruby-master/spec/lib/immutable/deque/empty_spec.rb0000644000175000017500000000174214201005456024143 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#empty?' do [ [[], true], [['A'], false], [%w[A B C], false], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do D[*values].empty?.should == expected end end end context "after dedequeing an item from #{%w[A B C].inspect}" do it 'returns false' do D['A', 'B', 'C'].dequeue.should_not be_empty end end end describe '.empty' do it 'returns the canonical empty deque' do D.empty.size.should be(0) D.empty.class.should be(Immutable::Deque) D.empty.object_id.should be(Immutable::EmptyDeque.object_id) end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::Deque) subclass.empty.class.should be(subclass) subclass.empty.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/deque/enqueue_spec.rb0000644000175000017500000000131014201005456024443 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do [:enqueue, :push].each do |method| describe "##{method}" do [ [[], 'A', ['A']], [['A'], 'B', %w[A B]], [['A'], 'A', %w[A A]], [%w[A B C], 'D', %w[A B C D]], ].each do |values, new_value, expected| describe "on #{values.inspect} with #{new_value.inspect}" do let(:deque) { D[*values] } it 'preserves the original' do deque.send(method, new_value) deque.should eql(D[*values]) end it "returns #{expected.inspect}" do deque.send(method, new_value).should eql(D[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/deque/to_a_spec.rb0000644000175000017500000000115514201005456023725 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do [:to_a, :entries].each do |method| describe "##{method}" do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do it "returns #{values.inspect}" do D[*values].send(method).should == values end it 'returns a mutable array' do result = D[*values].send(method) expect(result.last).to_not eq('The End') result << 'The End' result.last.should == 'The End' end end end end end end immutable-ruby-master/spec/lib/immutable/deque/pretty_print_spec.rb0000644000175000017500000000110614201005456025542 0ustar boutilboutilrequire 'spec_helper' require 'pp' require 'stringio' describe Immutable::Deque do describe '#pretty_print' do let(:deque) { Immutable::Deque['AAAA', 'BBBB', 'CCCC'] } let(:stringio) { StringIO.new } it 'prints the whole Deque on one line if it fits' do PP.pp(deque, stringio, 80) stringio.string.chomp.should == 'Immutable::Deque["AAAA", "BBBB", "CCCC"]' end it 'prints each item on its own line, if not' do PP.pp(deque, stringio, 10) stringio.string.chomp.should == 'Immutable::Deque[ "AAAA", "BBBB", "CCCC"]' end end end immutable-ruby-master/spec/lib/immutable/deque/last_spec.rb0000644000175000017500000000053114201005456023743 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#last' do [ [[], nil], [['A'], 'A'], [%w[A B C], 'C'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do D[*values].last.should eql(expected) end end end end end immutable-ruby-master/spec/lib/immutable/deque/random_modification_spec.rb0000644000175000017500000000157314201005456027014 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe 'modification (using #push, #pop, #shift, and #unshift)' do it 'works when applied in many random combinations' do array = [1,2,3] deque = Immutable::Deque.new(array) 1000.times do case [:push, :pop, :shift, :unshift].sample when :push value = rand(10000) array.push(value) deque = deque.push(value) when :pop array.pop deque = deque.pop when :shift array.shift deque = deque.shift when :unshift value = rand(10000) array.unshift(value) deque = deque.unshift(value) end deque.to_a.should eql(array) deque.size.should == array.size deque.first.should == array.first deque.last.should == array.last end end end end immutable-ruby-master/spec/lib/immutable/deque/rotate_spec.rb0000644000175000017500000000411114201005456024274 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do # Deques can have items distributed differently between the 'front' and 'rear' lists # and still be equivalent # Since the implementation of #rotate depends on how items are distributed between the # two lists, we need to test both the case where most items are on the 'front' and # where most are on the 'rear' big_front = D.alloc(L.from_enum([1, 2, 3]), L.from_enum([5, 4])) big_rear = D.alloc(L.from_enum([1, 2]), L.from_enum([5, 4, 3])) describe '#rotate' do [ [[], 9999, []], [['A'], -1, ['A']], [['A', 'B', 'C'], -1, ['B', 'C', 'A']], [['A', 'B', 'C', 'D'], 0, ['A', 'B', 'C', 'D']], [%w[A B C D], 2, %w[C D A B]], ].each do |values, rotation, expected| context "on #{values.inspect}" do let(:deque) { D[*values] } it 'preserves the original' do deque.rotate(rotation) deque.should eql(D[*values]) end it "returns #{expected.inspect}" do deque.rotate(rotation).should eql(D[*expected]) end it 'returns a frozen instance' do deque.rotate(rotation).should be_frozen end end end context "on a Deque with most items on 'front' list" do it 'works with a small rotation' do big_front.rotate(2).should eql(D[4, 5, 1, 2, 3]) end it 'works with a larger rotation' do big_front.rotate(4).should eql(D[2, 3, 4, 5, 1]) end end context "on a Deque with most items on 'rear' list" do it 'works with a small rotation' do big_rear.rotate(2).should eql(D[4, 5, 1, 2, 3]) end it 'works with a larger rotation' do big_rear.rotate(4).should eql(D[2, 3, 4, 5, 1]) end end context 'on empty subclass' do let(:subclass) { Class.new(Immutable::Deque) } let(:empty_instance) { subclass.new } it 'returns an empty object of the same class' do empty_instance.rotate(1).class.should be subclass empty_instance.rotate(-1).class.should be subclass end end end end immutable-ruby-master/spec/lib/immutable/deque/clear_spec.rb0000644000175000017500000000130414201005456024065 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#clear' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values}" do let(:deque) { D[*values] } it 'preserves the original' do deque.clear deque.should eql(D[*values]) end it 'returns an empty deque' do deque.clear.should equal(D.empty) end end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Deque) instance = subclass.new([1,2]) instance.clear.should be_empty instance.clear.class.should be(subclass) end end end immutable-ruby-master/spec/lib/immutable/deque/to_list_spec.rb0000644000175000017500000000107114201005456024455 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#to_list' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do it "returns a list containing #{values.inspect}" do D[*values].to_list.should eql(L[*values]) end end end context "after dedequeing an item from #{%w[A B C].inspect}" do it "returns a list containing #{%w[B C].inspect}" do list = D['A', 'B', 'C'].dequeue.to_list list.should eql(L['B', 'C']) end end end end immutable-ruby-master/spec/lib/immutable/deque/first_spec.rb0000644000175000017500000000053114201005456024127 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#first' do [ [[], nil], [['A'], 'A'], [%w[A B C], 'A'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do D[*values].first.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/deque/dequeue_spec.rb0000644000175000017500000000153014201005456024435 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do [:dequeue, :shift].each do |method| describe "##{method}" do [ [[], []], [['A'], []], [%w[A B C], %w[B C]], ].each do |values, expected| context "on #{values.inspect}" do let(:deque) { D[*values] } it 'preserves the original' do deque.send(method) deque.should eql(D[*values]) end it "returns #{expected.inspect}" do deque.send(method).should eql(D[*expected]) end end end end context 'on empty subclass' do let(:subclass) { Class.new(Immutable::Deque) } let(:empty_instance) { subclass.new } it 'returns empty object of same class' do empty_instance.send(method).class.should be subclass end end end end immutable-ruby-master/spec/lib/immutable/deque/shift_spec.rb0000644000175000017500000000113114201005456024112 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#shift' do [ [[], []], [['A'], []], [%w[A B C], %w[B C]], ].each do |values, expected| context "on #{values.inspect}" do let(:deque) { D.new(values) } it 'preserves the original' do deque.shift deque.should eql(D.new(values)) end it "returns #{expected.inspect}" do deque.shift.should eql(D.new(expected)) end it 'returns a frozen instance' do deque.shift.should be_frozen end end end end end immutable-ruby-master/spec/lib/immutable/deque/unshift_spec.rb0000644000175000017500000000132614201005456024463 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do describe '#unshift' do [ [[], 'A', ['A']], [['A'], 'B', %w[B A]], [['A'], 'A', %w[A A]], [%w[A B C], 'D', %w[D A B C]], ].each do |values, new_value, expected| context "on #{values.inspect} with #{new_value.inspect}" do let(:deque) { D[*values] } it 'preserves the original' do deque.unshift(new_value) deque.should eql(D[*values]) end it "returns #{expected.inspect}" do deque.unshift(new_value).should eql(D[*expected]) end it 'returns a frozen instance' do deque.unshift(new_value).should be_frozen end end end end end immutable-ruby-master/spec/lib/immutable/deque/copying_spec.rb0000644000175000017500000000053514201005456024454 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Deque do [:dup, :clone].each do |method| [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do let(:deque) { D[*values] } it 'returns self' do deque.send(method).should equal(deque) end end end end end immutable-ruby-master/spec/lib/immutable/hash/0000755000175000017500000000000014201005456021262 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/hash/key_spec.rb0000644000175000017500000000135514201005456023415 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#key' do let(:hash) { H[a: 1, b: 1, c: 2, d: 3] } it 'returns a key associated with the given value, if there is one' do [:a, :b].include?(hash.key(1)).should == true hash.key(2).should be(:c) hash.key(3).should be(:d) end it 'returns nil if there is no key associated with the given value' do hash.key(5).should be_nil hash.key(0).should be_nil end it 'uses #== to compare values for equality' do hash.key(EqualNotEql.new).should_not be_nil hash.key(EqlNotEqual.new).should be_nil end it "doesn't use default block if value is not found" do H.new(a: 1) { fail }.key(2).should be_nil end end end immutable-ruby-master/spec/lib/immutable/hash/new_spec.rb0000644000175000017500000000365714201005456023425 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '.new' do it 'is amenable to overriding of #initialize' do class SnazzyHash < Immutable::Hash def initialize super({'snazzy?' => 'oh yeah'}) end end SnazzyHash.new['snazzy?'].should == 'oh yeah' end context 'from a subclass' do it 'returns a frozen instance of the subclass' do subclass = Class.new(Immutable::Hash) instance = subclass.new('some' => 'values') instance.class.should be(subclass) instance.frozen?.should be true end end it 'accepts an array as initializer' do H.new([['a', 'b'], ['c', 'd']]).should eql(H['a' => 'b', 'c' => 'd']) end it "returns a Hash which doesn't change even if initializer is mutated" do rbhash = {a: 1, b: 2} hash = H.new(rbhash) rbhash[:a] = 'BAD' hash.should eql(H[a: 1, b: 2]) end end describe '.[]' do it 'accepts a Ruby Hash as initializer' do hash = H[a: 1, b: 2] hash.class.should be(Immutable::Hash) hash.size.should == 2 hash.key?(:a).should == true hash.key?(:b).should == true end it 'accepts a Immutable::Hash as initializer' do hash = H[H.new(a: 1, b: 2)] hash.class.should be(Immutable::Hash) hash.size.should == 2 hash.key?(:a).should == true hash.key?(:b).should == true end it 'accepts an array as initializer' do hash = H[[[:a, 1], [:b, 2]]] hash.class.should be(Immutable::Hash) hash.size.should == 2 hash.key?(:a).should == true hash.key?(:b).should == true end it 'can be used with a subclass of Immutable::Hash' do subclass = Class.new(Immutable::Hash) instance = subclass[a: 1, b: 2] instance.class.should be(subclass) instance.size.should == 2 instance.key?(:a).should == true instance.key?(:b).should == true end end end immutable-ruby-master/spec/lib/immutable/hash/size_spec.rb0000644000175000017500000000254614201005456023602 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:size, :length].each do |method| describe "##{method}" do [ [[], 0], [['A' => 'aye'], 1], [['A' => 'bee', 'B' => 'bee', 'C' => 'see'], 3], ].each do |values, result| it "returns #{result} for #{values.inspect}" do H[*values].send(method).should == result end end lots = (1..10_842).to_a srand 89_533_474 random_things = (lots + lots).sort_by { |x|rand } it 'has the correct size after adding lots of things with colliding keys and such' do h = H.empty random_things.each do |thing| h = h.put(thing, thing * 2) end h.size.should == 10_842 end random_actions = (lots.map { |x|[:add, x] } + lots.map { |x|[:add, x] } + lots.map { |x|[:remove, x] }).sort_by { |x|rand } ending_size = random_actions.reduce({}) do |h, (act, ob)| if act == :add h[ob] = 1 else h.delete(ob) end h end.size it 'has the correct size after lots of addings and removings' do h = H.empty random_actions.each do |(act, ob)| if act == :add h = h.put(ob, ob * 3) else h = h.delete(ob) end end h.size.should == ending_size end end end end immutable-ruby-master/spec/lib/immutable/hash/construction_spec.rb0000644000175000017500000000164214201005456025356 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '.hash' do context 'with nothing' do it 'returns the canonical empty hash' do H.empty.should be_empty H.empty.should equal(Immutable::EmptyHash) end end context 'with an implicit hash' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } it 'is equivalent to repeatedly using #put' do hash.should eql(H.empty.put('A', 'aye').put('B', 'bee').put('C', 'see')) hash.size.should == 3 end end context 'with an array of pairs' do let(:hash) { H[[[:a, 1], [:b, 2]]] } it 'initializes a new Hash' do hash.should eql(H[a: 1, b: 2]) end end context 'with an Immutable::Hash' do let(:hash) { H[a: 1, b: 2] } let(:other) { H[hash] } it 'initializes an equivalent Hash' do hash.should eql(other) end end end end immutable-ruby-master/spec/lib/immutable/hash/sort_spec.rb0000644000175000017500000000133014201005456023605 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H[a: 3, b: 2, c: 1] } describe '#sort' do it 'returns a Vector of sorted key/val pairs' do hash.sort.should eql(V[[:a, 3], [:b, 2], [:c, 1]]) end it 'works on large hashes' do array = (1..1000).map { |n| [n,n] } H.new(array.shuffle).sort.should eql(V.new(array)) end it 'uses block as comparator to sort if passed a block' do hash.sort { |a,b| b <=> a }.should eql(V[[:c, 1], [:b, 2], [:a, 3]]) end end describe '#sort_by' do it 'returns a Vector of key/val pairs, sorted using the block as a key function' do hash.sort_by { |k,v| v }.should eql(V[[:c, 1], [:b, 2], [:a, 3]]) end end end immutable-ruby-master/spec/lib/immutable/hash/reject_spec.rb0000644000175000017500000000411614201005456024077 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:reject, :delete_if].each do |method| describe "##{method}" do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'when nothing matches' do it 'returns self' do hash.send(method) { |key, value| false }.should equal(hash) end end context 'when only some things match' do context 'with a block' do let(:result) { hash.send(method) { |key, value| key == 'A' && value == 'aye' }} it 'preserves the original' do result hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a set with the matching values' do result.should eql(H['B' => 'bee', 'C' => 'see']) end it 'yields entries in the same order as #each' do each_pairs = [] remove_pairs = [] hash.each_pair { |k,v| each_pairs << [k,v] } hash.send(method) { |k,v| remove_pairs << [k,v] } each_pairs.should == remove_pairs end end context 'with no block' do it 'returns an Enumerator' do hash.send(method).class.should be(Enumerator) hash.send(method).to_a.sort.should == [['A', 'aye'], ['B', 'bee'], ['C', 'see']] hash.send(method).each { true }.should eql(H.empty) end end context 'on a large hash, with many combinations of input' do it 'still works' do array = 1000.times.collect { |n| [n, n] } hash = H.new(array) [0, 10, 100, 200, 500, 800, 900, 999, 1000].each do |threshold| result = hash.send(method) { |k,v| k >= threshold} result.size.should == threshold 0.upto(threshold-1) { |n| result.key?(n).should == true } threshold.upto(1000) { |n| result.key?(n).should == false } end # shouldn't have changed hash.should eql(H.new(1000.times.collect { |n| [n, n] })) end end end end end end immutable-ruby-master/spec/lib/immutable/hash/flatten_spec.rb0000644000175000017500000000773614201005456024273 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#flatten' do context 'with flatten depth of zero' do it 'returns a vector of keys/value' do hash = H[a: 1, b: 2] hash.flatten(0).sort.should eql(V[[:a, 1], [:b, 2]]) end end context 'without array keys or values' do it 'returns a vector of keys and values' do hash = H[a: 1, b: 2, c: 3] possibilities = [[:a, 1, :b, 2, :c, 3], [:a, 1, :c, 3, :b, 2], [:b, 2, :a, 1, :c, 3], [:b, 2, :c, 3, :a, 1], [:c, 3, :a, 1, :b, 2], [:c, 3, :b, 2, :a, 1]] possibilities.include?(hash.flatten).should == true possibilities.include?(hash.flatten(1)).should == true possibilities.include?(hash.flatten(2)).should == true hash.flatten(2).class.should be(Immutable::Vector) possibilities.include?(hash.flatten(10)).should == true end it "doesn't modify the receiver" do hash = H[a: 1, b: 2, c: 3] hash.flatten(1) hash.flatten(2) hash.should eql(H[a: 1, b: 2, c: 3]) end end context 'on an empty Hash' do it 'returns an empty Vector' do H.empty.flatten.should eql(V.empty) end end context 'with array keys' do it 'flattens array keys into returned vector if flatten depth is sufficient' do hash = H[[1, 2] => 3, [4, 5] => 6] [[[1, 2], 3, [4, 5], 6], [[4, 5], 6, [1, 2], 3]].include?(hash.flatten(1)).should == true [[[1, 2], 3, [4, 5], 6], [[4, 5], 6, [1, 2], 3]].include?(hash.flatten).should == true hash.flatten(1).class.should be(Immutable::Vector) [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(2)).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(3)).should == true end it "doesn't modify the receiver (or its contents)" do hash = H[[1, 2] => 3, [4, 5] => 6] hash.flatten(1) hash.flatten(2) hash.should eql(H[[1, 2] => 3, [4, 5] => 6]) end end context 'with array values' do it 'flattens array values into returned vector if flatten depth is sufficient' do hash = H[1 => [2, 3], 4 => [5, 6]] [[1, [2, 3], 4, [5, 6]], [4, [5, 6], 1, [2, 3]]].include?(hash.flatten(1)).should == true [[1, [2, 3], 4, [5, 6]], [4, [5, 6], 1, [2, 3]]].include?(hash.flatten).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(2)).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(3)).should == true hash.flatten(3).class.should be(Immutable::Vector) end it "doesn't modify the receiver (or its contents)" do hash = H[1 => [2, 3], 4 => [5, 6]] hash.flatten(1) hash.flatten(2) hash.should eql(H[1 => [2, 3], 4 => [5, 6]]) end end context 'with vector keys' do it 'flattens vector keys into returned vector if flatten depth is sufficient' do hash = H[V[1, 2] => 3, V[4, 5] => 6] [[V[1, 2], 3, V[4, 5], 6], [V[4, 5], 6, V[1, 2], 3]].include?(hash.flatten).should == true [[V[1, 2], 3, V[4, 5], 6], [V[4, 5], 6, V[1, 2], 3]].include?(hash.flatten(1)).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(2)).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(3)).should == true end end context 'with vector values' do it 'flattens vector values into returned vector if flatten depth is sufficient' do hash = H[1 => V[2, 3], 4 => V[5, 6]] [[1, V[2, 3], 4, V[5, 6]], [4, V[5, 6], 1, V[2, 3]]].include?(hash.flatten(1)).should == true [[1, V[2, 3], 4, V[5, 6]], [4, V[5, 6], 1, V[2, 3]]].include?(hash.flatten).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(2)).should == true [[1, 2, 3, 4, 5, 6], [4, 5, 6, 1, 2, 3]].include?(hash.flatten(3)).should == true end end end end immutable-ruby-master/spec/lib/immutable/hash/dig_spec.rb0000644000175000017500000000177314201005456023374 0ustar boutilboutilrequire 'spec_helper' require 'immutable/hash' describe Immutable::Hash do describe '#dig' do let(:h) { H[:a => 9, :b => H[:c => 'a', :d => 4], :e => nil] } it 'returns the value with one argument to dig' do expect(h.dig(:a)).to eq(9) end it 'returns the value in nested hashes' do expect(h.dig(:b, :c)).to eq('a') end it 'returns nil if the key is not present' do expect(h.dig(:f, :foo)).to eq(nil) end it 'returns nil if you dig out the end of the hash' do expect(h.dig(:f, :foo, :bar)).to eq(nil) end # This is a bit different from Ruby's Hash; it raises TypeError for # objects which don't respond to #dig it 'raises a NoMethodError if a value does not support #dig' do expect { h.dig(:a, :foo) }.to raise_error(NoMethodError) end it 'returns the correct value when there is a default proc' do default_hash = H.new { |k, v| "#{k}-default" } expect(default_hash.dig(:a)).to eq('a-default') end end end immutable-ruby-master/spec/lib/immutable/hash/values_spec.rb0000644000175000017500000000113514201005456024120 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#values' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } let(:result) { hash.values } it 'returns the keys as a Vector' do result.should be_a Immutable::Vector result.to_a.sort.should == %w(aye bee see) end context 'with duplicates' do let(:hash) { H[:A => 15, :B => 19, :C => 15] } let(:result) { hash.values } it 'returns the keys as a Vector' do result.class.should be(Immutable::Vector) result.to_a.sort.should == [15, 15, 19] end end end end immutable-ruby-master/spec/lib/immutable/hash/merge_spec.rb0000644000175000017500000000466414201005456023732 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#merge' do [ [{}, {}, {}], [{'A' => 'aye'}, {}, {'A' => 'aye'}], [{'A' => 'aye'}, {'A' => 'bee'}, {'A' => 'bee'}], [{'A' => 'aye'}, {'B' => 'bee'}, {'A' => 'aye', 'B' => 'bee'}], [(1..300).zip(1..300), (150..450).zip(150..450), (1..450).zip(1..450)] ].each do |a, b, expected| context "for #{a.inspect} and #{b.inspect}" do let(:hash_a) { H[a] } let(:hash_b) { H[b] } let(:result) { hash_a.merge(hash_b) } it "returns #{expected.inspect} when passed an Immutable::Hash" do result.should eql(H[expected]) end it "returns #{expected.inspect} when passed a Ruby Hash" do H[a].merge(::Hash[b]).should eql(H[expected]) end it "doesn't change the original Hashes" do result hash_a.should eql(H[a]) hash_b.should eql(H[b]) end end end context 'when merging with an empty Hash' do it 'returns self' do hash = H[a: 1, b: 2] hash.merge(H.empty).should be(hash) end end context 'when merging with subset Hash' do it 'returns self' do big_hash = H[(1..300).zip(1..300)] small_hash = H[(1..200).zip(1..200)] big_hash.merge(small_hash).should be(big_hash) end end context 'when called on a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Hash) instance = subclass.new(a: 1, b: 2) instance.merge(c: 3, d: 4).class.should be(subclass) end end it 'sets any duplicate key to the value of block if passed a block' do h1 = H[a: 2, b: 1, d: 5] h2 = H[a: -2, b: 4, c: -3] r = h1.merge(h2) { |k,x,y| nil } r.should eql(H[a: nil, b: nil, c: -3, d: 5]) r = h1.merge(h2) { |k,x,y| "#{k}:#{x+2*y}" } r.should eql(H[a: 'a:-2', b: 'b:9', c: -3, d: 5]) lambda { h1.merge(h2) { |k, x, y| raise(IndexError) } }.should raise_error(IndexError) r = h1.merge(h1) { |k,x,y| :x } r.should eql(H[a: :x, b: :x, d: :x]) end it 'yields key/value pairs in the same order as #each' do hash = H[a: 1, b: 2, c: 3] each_pairs = [] merge_pairs = [] hash.each { |k, v| each_pairs << [k, v] } hash.merge(hash) { |k, v1, v2| merge_pairs << [k, v1] } each_pairs.should == merge_pairs end end end immutable-ruby-master/spec/lib/immutable/hash/sample_spec.rb0000644000175000017500000000057714201005456024113 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#sample' do let(:hash) { Immutable::Hash.new((:a..:z).zip(1..26)) } it 'returns a randomly chosen item' do chosen = 250.times.map { hash.sample }.sort.uniq chosen.each { |item| hash.include?(item[0]).should == true } hash.each { |item| chosen.include?(item).should == true } end end end immutable-ruby-master/spec/lib/immutable/hash/any_spec.rb0000644000175000017500000000260314201005456023411 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#any?' do context 'when empty' do it 'with a block returns false' do H.empty.any? {}.should == false end it 'with no block returns false' do H.empty.any?.should == false end end context 'when not empty' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL'] } context 'with a block' do [ %w[A aye], %w[B bee], %w[C see], [nil, 'NIL'], ].each do |pair| it "returns true if the block ever returns true (#{pair.inspect})" do hash.any? { |key, value| key == pair.first && value == pair.last }.should == true end it 'returns false if the block always returns false' do hash.any? { |key, value| key == 'D' && value == 'dee' }.should == false end end it 'propagates exceptions raised in the block' do -> { hash.any? { |k,v| raise 'help' } }.should raise_error(RuntimeError) end it 'stops iterating as soon as the block returns true' do yielded = [] hash.any? { |k,v| yielded << k; true } yielded.size.should == 1 end end context 'with no block' do it 'returns true' do hash.any?.should == true end end end end end immutable-ruby-master/spec/lib/immutable/hash/subset_spec.rb0000644000175000017500000000227714201005456024136 0ustar boutilboutilrequire 'spec_helper' require 'immutable/hash' describe Immutable::Hash do describe '#<=' do [ [{}, {}, true], [{'A' => 1}, {}, false], [{}, {'A' => 1}, true], [{'A' => 1}, {'A' => 1}, true], [{'A' => 1}, {'A' => 2}, false], [{'B' => 2}, {'A' => 1, 'B' => 2, 'C' => 3}, true], [{'A' => 1, 'B' => 2, 'C' => 3}, {'B' => 2}, false], [{'B' => 0}, {'A' => 1, 'B' => 2, 'C' => 3}, false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do expect(H[a] <= H[b]).to eq(expected) end end end end describe '#<' do [ [{}, {}, false], [{'A' => 1}, {}, false], [{}, {'A' => 1}, true], [{'A' => 1}, {'A' => 1}, false], [{'A' => 1}, {'A' => 2}, false], [{'B' => 2}, {'A' => 1, 'B' => 2, 'C' => 3}, true], [{'A' => 1, 'B' => 2, 'C' => 3}, {'B' => 2}, false], [{'B' => 0}, {'A' => 1, 'B' => 2, 'C' => 3}, false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do expect(H[a] < H[b]).to eq(expected) end end end end end immutable-ruby-master/spec/lib/immutable/hash/get_spec.rb0000644000175000017500000000414414201005456023403 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:get, :[]].each do |method| describe "##{method}" do context 'with a default block' do let(:hash) { H.new('A' => 'aye') { |key| fail }} context 'when the key exists' do it 'returns the value associated with the key' do hash.send(method, 'A').should == 'aye' end it "does not call the default block even if the key is 'nil'" do H.new(nil => 'something') { fail }.send(method, nil) end end context 'when the key does not exist' do let(:hash) do H.new('A' => 'aye') do |key| expect(key).to eq('B') 'bee' end end it 'returns the value from the default block' do hash.send(method, 'B').should == 'bee' end end end context 'with no default block' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL'] } [ %w[A aye], %w[B bee], %w[C see], [nil, 'NIL'] ].each do |key, value| it "returns the value (#{value.inspect}) for an existing key (#{key.inspect})" do hash.send(method, key).should == value end end it 'returns nil for a non-existing key' do hash.send(method, 'D').should be_nil end end it 'uses #hash to look up keys' do x = double('0') x.should_receive(:hash).and_return(0) H[foo: :bar].send(method, x).should be_nil end it 'uses #eql? to compare keys with the same hash code' do x = double('x', hash: 42) x.should_not_receive(:eql?) y = double('y', hash: 42) y.should_receive(:eql?).and_return(true) H[y => 1][x].should == 1 end it 'does not use #eql? to compare keys with different hash codes' do x = double('x', hash: 0) x.should_not_receive(:eql?) y = double('y', hash: 1) y.should_not_receive(:eql?) H[y => 1][x].should be_nil end end end end immutable-ruby-master/spec/lib/immutable/hash/inspect_spec.rb0000644000175000017500000000161414201005456024270 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#inspect' do [ [[], 'Immutable::Hash[]'], [['A' => 'aye'], 'Immutable::Hash["A" => "aye"]'], [[DeterministicHash.new('A', 1) => 'aye', DeterministicHash.new('B', 2) => 'bee', DeterministicHash.new('C', 3) => 'see'], 'Immutable::Hash["A" => "aye", "B" => "bee", "C" => "see"]'] ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do H[*values].inspect.should == expected end end end [ {}, {'A' => 'aye'}, {a: 'aye', b: 'bee', c: 'see'} ].each do |values| describe "on #{values.inspect}" do it "returns a string which can be eval'd to get an equivalent object" do original = H.new(values) eval(original.inspect).should eql(original) end end end end end immutable-ruby-master/spec/lib/immutable/hash/keys_spec.rb0000644000175000017500000000051614201005456023576 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#keys' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } it 'returns the keys as a set' do hash.keys.should eql(S['A', 'B', 'C']) end it 'returns frozen String keys' do hash.keys.each { |s| s.should be_frozen } end end end immutable-ruby-master/spec/lib/immutable/hash/to_hash_spec.rb0000644000175000017500000000107014201005456024244 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:to_hash, :to_h].each do |method| describe "##{method}" do it 'converts an empty Immutable::Hash to an empty Ruby Hash' do H.empty.send(method).should eql({}) end it 'converts a non-empty Immutable::Hash to a Hash with the same keys and values' do H[a: 1, b: 2].send(method).should eql({a: 1, b: 2}) end it "doesn't modify the receiver" do hash = H[a: 1, b: 2] hash.send(method) hash.should eql(H[a: 1, b: 2]) end end end end immutable-ruby-master/spec/lib/immutable/hash/default_proc_spec.rb0000644000175000017500000000400714201005456025271 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#default_proc' do let(:hash) { H.new(1 => 2, 2 => 4) { |k| k * 2 } } it 'returns the default block given when the Hash was created' do hash.default_proc.class.should be(Proc) hash.default_proc.call(3).should == 6 end it 'returns nil if no default block was given' do H.empty.default_proc.should be_nil end context 'after a key/val pair are inserted' do it "doesn't change" do other = hash.put(3, 6) other.default_proc.should be(hash.default_proc) other.default_proc.call(4).should == 8 end end context 'after all key/val pairs are filtered out' do it "doesn't change" do other = hash.reject { true } other.default_proc.should be(hash.default_proc) other.default_proc.call(4).should == 8 end end context 'after Hash is inverted' do it "doesn't change" do other = hash.invert other.default_proc.should be(hash.default_proc) other.default_proc.call(4).should == 8 end end context 'when a slice is taken' do it "doesn't change" do other = hash.slice(1) other.default_proc.should be(hash.default_proc) other.default_proc.call(5).should == 10 end end context 'when keys are removed with #except' do it "doesn't change" do other = hash.except(1, 2) other.default_proc.should be(hash.default_proc) other.default_proc.call(5).should == 10 end end context 'when Hash is mapped' do it "doesn't change" do other = hash.map { |k,v| [k + 10, v] } other.default_proc.should be(hash.default_proc) other.default_proc.call(5).should == 10 end end context 'when another Hash is merged in' do it "doesn't change" do other = hash.merge(3 => 6, 4 => 8) other.default_proc.should be(hash.default_proc) other.default_proc.call(5).should == 10 end end end end immutable-ruby-master/spec/lib/immutable/hash/marshal_spec.rb0000644000175000017500000000150414201005456024250 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#marshal_dump/#marshal_load' do let(:ruby) do File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) end let(:child_cmd) do %Q|#{ruby} -I lib -r immutable -e 'dict = Immutable::Hash[existing_key: 42, other_thing: "data"]; $stdout.write(Marshal.dump(dict))'| end let(:reloaded_hash) do IO.popen(child_cmd, 'r+') do |child| reloaded_hash = Marshal.load(child) child.close reloaded_hash end end it 'can survive dumping and loading into a new process' do expect(reloaded_hash).to eql(H[existing_key: 42, other_thing: 'data']) end it 'is still possible to find items by key after loading' do expect(reloaded_hash[:existing_key]).to eq(42) end end end immutable-ruby-master/spec/lib/immutable/hash/flat_map_spec.rb0000644000175000017500000000220214201005456024400 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } describe '#flat_map' do it 'yields each key/val pair' do passed = [] hash.flat_map { |pair| passed << pair } passed.sort.should == [['A', 'aye'], ['B', 'bee'], ['C', 'see']] end it 'returns the concatenation of block return values' do hash.flat_map { |k,v| [k,v] }.sort.should == ['A', 'B', 'C', 'aye', 'bee', 'see'] hash.flat_map { |k,v| L[k,v] }.sort.should == ['A', 'B', 'C', 'aye', 'bee', 'see'] hash.flat_map { |k,v| V[k,v] }.sort.should == ['A', 'B', 'C', 'aye', 'bee', 'see'] end it "doesn't change the receiver" do hash.flat_map { |k,v| [k,v] } hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end context 'with no block' do it 'returns an Enumerator' do hash.flat_map.class.should be(Enumerator) hash.flat_map.each { |k,v| [k] }.sort.should == ['A', 'B', 'C'] end end it 'returns an empty array if only empty arrays are returned by block' do hash.flat_map { [] }.should eql([]) end end end immutable-ruby-master/spec/lib/immutable/hash/find_spec.rb0000644000175000017500000000252414201005456023544 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:find, :detect].each do |method| describe "##{method}" do [ [[], 'A', nil], [[], nil, nil], [['A' => 'aye'], 'A', ['A', 'aye']], [['A' => 'aye'], 'B', nil], [['A' => 'aye'], nil, nil], [['A' => 'aye', 'B' => 'bee', nil => 'NIL'], 'A', ['A', 'aye']], [['A' => 'aye', 'B' => 'bee', nil => 'NIL'], 'B', ['B', 'bee']], [['A' => 'aye', 'B' => 'bee', nil => 'NIL'], nil, [nil, 'NIL']], [['A' => 'aye', 'B' => 'bee', nil => 'NIL'], 'C', nil], ].each do |values, key, expected| describe "on #{values.inspect}" do let(:hash) { H[*values] } describe 'with a block' do it "returns #{expected.inspect}" do hash.send(method) { |k, v| k == key }.should == expected end end describe 'without a block' do it 'returns an Enumerator' do result = hash.send(method) result.class.should be(Enumerator) result.each { |k,v| k == key }.should == expected end end end end it 'stops iterating when the block returns true' do yielded = [] H[a: 1, b: 2].find { |k,v| yielded << k; true } yielded.size.should == 1 end end end end immutable-ruby-master/spec/lib/immutable/hash/empty_spec.rb0000644000175000017500000000220214201005456023753 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#empty?' do [ [[], true], [['A' => 'aye'], false], [['A' => 'aye', 'B' => 'bee', 'C' => 'see'], false], ].each do |pairs, result| it "returns #{result} for #{pairs.inspect}" do H[*pairs].empty?.should == result end end it 'returns true for empty hashes which have a default block' do H.new { 'default' }.empty?.should == true end end describe '.empty' do it 'returns the canonical empty Hash' do H.empty.should be_empty H.empty.should be(Immutable::EmptyHash) end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::Hash) subclass.empty.class.should be subclass subclass.empty.should be_empty end it 'calls overridden #initialize when creating empty Hash' do subclass = Class.new(Immutable::Hash) do def initialize @variable = 'value' end end subclass.empty.instance_variable_get(:@variable).should == 'value' end end end end immutable-ruby-master/spec/lib/immutable/hash/to_a_spec.rb0000644000175000017500000000054014201005456023542 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#to_a' do it 'returns an Array of [key, value] pairs in same order as #each' do hash = H[:a => 1, 1 => :a, 3 => :b, :b => 5] pairs = [] hash.each_pair { |k,v| pairs << [k,v] } hash.to_a.should be_kind_of(Array) hash.to_a.should == pairs end end end immutable-ruby-master/spec/lib/immutable/hash/fetch_spec.rb0000644000175000017500000000337614201005456023723 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#fetch' do context 'with no default provided' do context 'when the key exists' do it 'returns the value associated with the key' do H['A' => 'aye'].fetch('A').should == 'aye' end end context 'when the key does not exist' do it 'raises a KeyError' do -> { H['A' => 'aye'].fetch('B') }.should raise_error(KeyError) end end end context 'with a default value' do context 'when the key exists' do it 'returns the value associated with the key' do H['A' => 'aye'].fetch('A', 'default').should == 'aye' end end context 'when the key does not exist' do it 'returns the default value' do H['A' => 'aye'].fetch('B', 'default').should == 'default' end end end context 'with a default block' do context 'when the key exists' do it 'returns the value associated with the key' do H['A' => 'aye'].fetch('A') { 'default'.upcase }.should == 'aye' end end context 'when the key does not exist' do it 'invokes the default block with the missing key as paramter' do H['A' => 'aye'].fetch('B') { |key| key.should == 'B' } H['A' => 'aye'].fetch('B') { 'default'.upcase }.should == 'DEFAULT' end end end it 'gives precedence to default block over default argument if passed both' do H['A' => 'aye'].fetch('B', 'one') { 'two' }.should == 'two' end it 'raises an ArgumentError when not passed one or 2 arguments' do -> { H.empty.fetch }.should raise_error(ArgumentError) -> { H.empty.fetch(1, 2, 3) }.should raise_error(ArgumentError) end end end immutable-ruby-master/spec/lib/immutable/hash/update_in_spec.rb0000644000175000017500000000475014201005456024577 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#update_in' do let(:hash) { Immutable::Hash[ 'A' => 'aye', 'B' => Immutable::Hash['C' => 'see', 'D' => Immutable::Hash['E' => 'eee']], 'F' => Immutable::Vector['G', Immutable::Hash['H' => 'eitch'], 'I'] ] } context 'with one level on existing key' do it 'passes the value to the block' do hash.update_in('A') { |value| value.should == 'aye' } end it 'replaces the value with the result of the block' do result = hash.update_in('A') { |value| 'FLIBBLE' } result.get('A').should == 'FLIBBLE' end it 'should preserve the original' do result = hash.update_in('A') { |value| 'FLIBBLE' } hash.get('A').should == 'aye' end end context 'with multi-level on existing keys' do it 'passes the value to the block' do hash.update_in('B', 'D', 'E') { |value| value.should == 'eee' } end it 'replaces the value with the result of the block' do result = hash.update_in('B', 'D', 'E') { |value| 'FLIBBLE' } result['B']['D']['E'].should == 'FLIBBLE' end it 'should preserve the original' do result = hash.update_in('B', 'D', 'E') { |value| 'FLIBBLE' } hash['B']['D']['E'].should == 'eee' end end context "with multi-level creating sub-hashes when keys don't exist" do it 'passes nil to the block' do hash.update_in('B', 'X', 'Y') { |value| value.should be_nil } end it 'creates subhashes on the way to set the value' do result = hash.update_in('B', 'X', 'Y') { |value| 'NEWVALUE' } result['B']['X']['Y'].should == 'NEWVALUE' result['B']['D']['E'].should == 'eee' end end context 'with multi-level including vector with existing keys' do it 'passes the value to the block' do hash.update_in('F', 1, 'H') { |value| value.should == 'eitch' } end it 'replaces the value with the result of the block' do result = hash.update_in('F', 1, 'H') { |value| 'FLIBBLE' } result['F'][1]['H'].should == 'FLIBBLE' end it 'should preserve the original' do result = hash.update_in('F', 1, 'H') { |value| 'FLIBBLE' } hash['F'][1]['H'].should == 'eitch' end end context 'with empty key_path' do it 'raises ArguemntError' do expect { hash.update_in() { |v| 42 } }.to raise_error(ArgumentError) end end end end immutable-ruby-master/spec/lib/immutable/hash/each_spec.rb0000644000175000017500000000426114201005456023524 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } [:each, :each_pair].each do |method| describe "##{method}" do context 'with a block (internal iteration)' do it 'returns self' do hash.send(method) {}.should be(hash) end it 'yields all key/value pairs' do actual_pairs = {} hash.send(method) { |key, value| actual_pairs[key] = value } actual_pairs.should == { 'A' => 'aye', 'B' => 'bee', 'C' => 'see' } end it 'yields key/value pairs in the same order as #each_key and #each_value' do hash.each.to_a.should eql(hash.each_key.zip(hash.each_value)) end it 'yields both of a pair of colliding keys' do yielded = [] hash = H[DeterministicHash.new('a', 1) => 1, DeterministicHash.new('b', 1) => 1] hash.each { |k,v| yielded << k } yielded.size.should == 2 yielded.map(&:value).sort.should == ['a', 'b'] end it 'yields only the key to a block expecting |key,|' do keys = [] hash.each { |key,| keys << key } keys.sort.should == ['A', 'B', 'C'] end end context 'with no block' do it 'returns an Enumerator' do @result = hash.send(method) @result.class.should be(Enumerator) @result.to_a.should == hash.to_a end end end end describe '#each_key' do it 'yields all keys' do keys = [] hash.each_key { |k| keys << k } keys.sort.should == ['A', 'B', 'C'] end context 'with no block' do it 'returns an Enumerator' do hash.each_key.class.should be(Enumerator) hash.each_key.to_a.sort.should == ['A', 'B', 'C'] end end end describe '#each_value' do it 'yields all values' do values = [] hash.each_value { |v| values << v } values.sort.should == ['aye', 'bee', 'see'] end context 'with no block' do it 'returns an Enumerator' do hash.each_value.class.should be(Enumerator) hash.each_value.to_a.sort.should == ['aye', 'bee', 'see'] end end end end immutable-ruby-master/spec/lib/immutable/hash/invert_spec.rb0000644000175000017500000000146314201005456024134 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#invert' do let(:hash) { H[a: 3, b: 2, c: 1] } it 'uses the existing keys as values and values as keys' do hash.invert.should eql(H[3 => :a, 2 => :b, 1 => :c]) end it 'will select one key/value pair among multiple which have same value' do [H[1 => :a], H[1 => :b], H[1 => :c]].include?(H[a: 1, b: 1, c: 1].invert).should == true end it "doesn't change the original Hash" do hash.invert hash.should eql(H[a: 3, b: 2, c: 1]) end context 'from a subclass of Hash' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Hash) instance = subclass.new(a: 1, b: 2) instance.invert.class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/hash/has_value_spec.rb0000644000175000017500000000153514201005456024574 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H[toast: 'buttered', jam: 'strawberry'] } [:value?, :has_value?].each do |method| describe "##{method}" do it 'returns true if any key/val pair in Hash has the same value' do hash.send(method, 'strawberry').should == true end it 'returns false if no key/val pair in Hash has the same value' do hash.send(method, 'marmalade').should == false end it 'uses #== to check equality' do H[a: EqualNotEql.new].send(method, EqualNotEql.new).should == true H[a: EqlNotEqual.new].send(method, EqlNotEqual.new).should == false end it 'works on a large hash' do large = H.new((1..1000).zip(2..1001)) [2, 100, 200, 500, 900, 1000, 1001].each { |n| large.value?(n).should == true } end end end end immutable-ruby-master/spec/lib/immutable/hash/store_spec.rb0000644000175000017500000000433114201005456023756 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#store' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'with a unique key' do let(:result) { hash.store('D', 'dee') } it 'preserves the original' do result hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a copy with the superset of key/value pairs' do result.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see', 'D' => 'dee']) end end context 'with a duplicate key' do let(:result) { hash.store('C', 'sea') } it 'preserves the original' do result hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a copy with the superset of key/value pairs' do result.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'sea']) end end context 'with duplicate key and identical value' do let(:hash) { H['X' => 1, 'Y' => 2] } let(:result) { hash.store('X', 1) } it 'returns the original hash unmodified' do result.should be(hash) end context 'with big hash (force nested tries)' do let(:keys) { (0..99).map(&:to_s) } let(:values) { (100..199).to_a } let(:hash) { H[keys.zip(values)] } it 'returns the original hash unmodified for all changes' do keys.each_with_index do |key, index| result = hash.store(key, values[index]) result.should be(hash) end end end end context 'with unequal keys which hash to the same value' do let(:hash) { H[DeterministicHash.new('a', 1) => 'aye'] } it 'stores and can retrieve both' do result = hash.store(DeterministicHash.new('b', 1), 'bee') result.get(DeterministicHash.new('a', 1)).should eql('aye') result.get(DeterministicHash.new('b', 1)).should eql('bee') end end context 'when a String is inserted as key and then mutated' do it 'is not affected' do string = 'a string!' hash = H.empty.store(string, 'a value!') string.upcase! hash['a string!'].should == 'a value!' hash['A STRING!'].should be_nil end end end end immutable-ruby-master/spec/lib/immutable/hash/all_spec.rb0000644000175000017500000000237414201005456023377 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H[values] } describe '#all?' do context 'when empty' do let(:values) { H.new } context 'without a block' do it 'returns true' do hash.all?.should == true end end context 'with a block' do it 'returns true' do hash.all? { false }.should == true end end end context 'when not empty' do let(:values) { { 'A' => 1, 'B' => 2, 'C' => 3 } } context 'without a block' do it 'returns true' do hash.all?.should == true end end context 'with a block' do it 'returns true if the block always returns true' do hash.all? { true }.should == true end it 'returns false if the block ever returns false' do hash.all? { |k,v| k != 'C' }.should == false end it 'propagates an exception from the block' do -> { hash.all? { |k,v| raise 'help' } }.should raise_error(RuntimeError) end it 'stops iterating as soon as the block returns false' do yielded = [] hash.all? { |k,v| yielded << k; false } yielded.size.should == 1 end end end end end immutable-ruby-master/spec/lib/immutable/hash/pretty_print_spec.rb0000644000175000017500000000200614201005456025362 0ustar boutilboutilrequire 'spec_helper' require 'pp' require 'stringio' describe Immutable::Hash do describe '#pretty_print' do let(:hash) { Immutable::Hash.new(DeterministicHash.new(1,1) => 'tin', DeterministicHash.new(2,2) => 'earwax', DeterministicHash.new(3,3) => 'neanderthal') } let(:stringio) { StringIO.new } it 'prints the whole Hash on one line if it fits' do PP.pp(hash, stringio, 80) stringio.string.chomp.should == 'Immutable::Hash[1 => "tin", 2 => "earwax", 3 => "neanderthal"]' end it 'prints each key/val pair on its own line, if not' do PP.pp(hash, stringio, 20) stringio.string.chomp.should == 'Immutable::Hash[ 1 => "tin", 2 => "earwax", 3 => "neanderthal"]' end it 'prints keys and vals on separate lines, if space is very tight' do PP.pp(hash, stringio, 15) # the trailing space after "3 =>" below is needed, don't remove it stringio.string.chomp.should == 'Immutable::Hash[ 1 => "tin", 2 => "earwax", 3 => "neanderthal"]' end end end immutable-ruby-master/spec/lib/immutable/hash/delete_spec.rb0000644000175000017500000000200614201005456024061 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#delete' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'with an existing key' do let(:result) { hash.delete('B') } it 'preserves the original' do hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a copy with the remaining key/value pairs' do result.should eql(H['A' => 'aye', 'C' => 'see']) end end context 'with a non-existing key' do let(:result) { hash.delete('D') } it 'preserves the original values' do hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns self' do result.should equal(hash) end end context 'when removing the last key' do context 'from a Hash with no default block' do it 'returns the canonical empty Hash' do hash.delete('A').delete('B').delete('C').should be(Immutable::EmptyHash) end end end end end immutable-ruby-master/spec/lib/immutable/hash/slice_spec.rb0000644000175000017500000000234114201005456023720 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H.new('A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL') } describe '#slice' do let(:slice) { hash.slice(*values) } context 'with all keys present in the Hash' do let(:values) { ['B', nil] } it 'returns the sliced values' do expect(slice).to eq(described_class.new('B' => 'bee', nil => 'NIL')) end it "doesn't modify the original Hash" do slice hash.should eql(H.new('A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL')) end end context "with keys aren't present in the Hash" do let(:values) { ['B', 'A', 3] } it 'returns the sliced values of the matching keys' do expect(slice).to eq(described_class.new('A' => 'aye', 'B' => 'bee')) end it "doesn't modify the original Hash" do slice hash.should eql(H.new('A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL')) end end context 'on a Hash with a default block' do let(:hash) { H.new('A' => 'aye', 'B' => 'bee') { 'nothing' }} let(:values) { ['B', nil] } it 'maintains the default block' do expect(slice['C']).to eq('nothing') end end end end immutable-ruby-master/spec/lib/immutable/hash/to_proc_spec.rb0000644000175000017500000000210314201005456024262 0ustar boutilboutilrequire 'spec_helper' require 'immutable/hash' describe Immutable::Hash do describe '#to_proc' do context 'on Hash without default proc' do let(:hash) { H.new('A' => 'aye') } it 'returns a Proc instance' do hash.to_proc.should be_kind_of(Proc) end it 'returns a Proc that returns the value of an existing key' do hash.to_proc.call('A').should == 'aye' end it 'returns a Proc that returns nil for a missing key' do hash.to_proc.call('B').should be_nil end end context 'on Hash with a default proc' do let(:hash) { H.new('A' => 'aye') { |key| "#{key}-VAL" } } it 'returns a Proc instance' do hash.to_proc.should be_kind_of(Proc) end it 'returns a Proc that returns the value of an existing key' do hash.to_proc.call('A').should == 'aye' end it "returns a Proc that returns the result of the hash's default proc for a missing key" do hash.to_proc.call('B').should == 'B-VAL' hash.should == H.new('A' => 'aye') end end end end immutable-ruby-master/spec/lib/immutable/hash/each_with_index_spec.rb0000644000175000017500000000167614201005456025755 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#each_with_index' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } describe 'with a block (internal iteration)' do it 'returns self' do hash.each_with_index {}.should be(hash) end it 'yields all key/value pairs with numeric indexes' do actual_pairs = {} indexes = [] hash.each_with_index { |(key, value), index| actual_pairs[key] = value; indexes << index } actual_pairs.should == { 'A' => 'aye', 'B' => 'bee', 'C' => 'see' } indexes.sort.should == [0, 1, 2] end end describe 'with no block' do it 'returns an Enumerator' do hash.each_with_index.should be_kind_of(Enumerator) hash.each_with_index.to_a.map(&:first).sort.should eql([['A', 'aye'], ['B', 'bee'], ['C', 'see']]) hash.each_with_index.to_a.map(&:last).should eql([0,1,2]) end end end end immutable-ruby-master/spec/lib/immutable/hash/take_spec.rb0000644000175000017500000000250214201005456023544 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } describe '#take' do it 'returns the first N key/val pairs from hash' do hash.take(0).should == [] [[['A', 'aye']], [['B', 'bee']], [['C', 'see']]].include?(hash.take(1)).should == true [['A', 'aye'], ['B', 'bee'], ['C', 'see']].combination(2).include?(hash.take(2).sort).should == true hash.take(3).sort.should == [['A', 'aye'], ['B', 'bee'], ['C', 'see']] hash.take(4).sort.should == [['A', 'aye'], ['B', 'bee'], ['C', 'see']] end end describe '#take_while' do it 'passes elements to the block until the block returns nil/false' do passed = nil hash.take_while { |k,v| passed = k; false } ['A', 'B', 'C'].include?(passed).should == true end it 'returns an array of all elements before the one which returned nil/false' do count = 0 result = hash.take_while { count += 1; count < 3 } [['A', 'aye'], ['B', 'bee'], ['C', 'see']].combination(2).include?(result.sort).should == true end it 'passes all elements if the block never returns nil/false' do passed = [] hash.take_while { |k,v| passed << [k, v]; true }.should == hash.to_a passed.sort.should == [['A', 'aye'], ['B', 'bee'], ['C', 'see']] end end end immutable-ruby-master/spec/lib/immutable/hash/values_at_spec.rb0000644000175000017500000000216614201005456024611 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#values_at' do context 'on Hash without default proc' do let(:hash) { H[:a => 9, :b => 'a', :c => -10, :d => nil] } it 'returns an empty vector when no keys are given' do hash.values_at.should be_kind_of(Immutable::Vector) hash.values_at.should eql(V.empty) end it 'returns a vector of values for the given keys' do hash.values_at(:a, :d, :b).should be_kind_of(Immutable::Vector) hash.values_at(:a, :d, :b).should eql(V[9, nil, 'a']) end it 'fills nil when keys are missing' do hash.values_at(:x, :a, :y, :b).should be_kind_of(Immutable::Vector) hash.values_at(:x, :a, :y, :b).should eql(V[nil, 9, nil, 'a']) end end context 'on Hash with default proc' do let(:hash) { Immutable::Hash.new(:a => 9) { |key| "#{key}-VAL" } } it 'fills the result of the default proc when keys are missing' do hash.values_at(:x, :a, :y).should be_kind_of(Immutable::Vector) hash.values_at(:x, :a, :y).should eql(V['x-VAL', 9, 'y-VAL']) end end end end immutable-ruby-master/spec/lib/immutable/hash/select_spec.rb0000644000175000017500000000357714201005456024114 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:select, :find_all, :keep_if].each do |method| describe "##{method}" do let(:original) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'when everything matches' do it 'returns self' do original.send(method) { |key, value| true }.should equal(original) end end context 'when only some things match' do context 'with a block' do let(:result) { original.send(method) { |key, value| key == 'A' && value == 'aye' }} it 'preserves the original' do original.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a set with the matching values' do result.should eql(H['A' => 'aye']) end end it 'yields entries as [key, value] pairs' do original.send(method) do |e| e.should be_kind_of(Array) ['A', 'B', 'C'].include?(e[0]).should == true ['aye', 'bee', 'see'].include?(e[1]).should == true end end context 'with no block' do it 'returns an Enumerator' do original.send(method).class.should be(Enumerator) original.send(method).to_a.sort.should == [['A', 'aye'], ['B', 'bee'], ['C', 'see']] end end end it 'works on a large hash, with many combinations of input' do keys = (1..1000).to_a original = H.new(keys.zip(2..1001)) 25.times do threshold = rand(1000) result = original.send(method) { |k,v| k <= threshold } result.size.should == threshold result.each_key { |k| k.should <= threshold } (threshold+1).upto(1000) { |k| result.key?(k).should == false } end original.should eql(H.new(keys.zip(2..1001))) # shouldn't have changed end end end end immutable-ruby-master/spec/lib/immutable/hash/eql_spec.rb0000644000175000017500000000510214201005456023400 0ustar boutilboutilrequire 'spec_helper' require 'bigdecimal' describe Immutable::Hash do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } describe '#eql?' do it 'returns false when comparing with a standard hash' do hash.eql?('A' => 'aye', 'B' => 'bee', 'C' => 'see').should == false end it 'returns false when comparing with an arbitrary object' do hash.eql?(Object.new).should == false end it 'returns false when comparing with a subclass of Immutable::Hash' do subclass = Class.new(Immutable::Hash) instance = subclass.new('A' => 'aye', 'B' => 'bee', 'C' => 'see') hash.eql?(instance).should == false end end describe '#==' do it 'returns true when comparing with a standard hash' do (hash == {'A' => 'aye', 'B' => 'bee', 'C' => 'see'}).should == true end it 'returns false when comparing with an arbitrary object' do (hash == Object.new).should == false end it 'returns true when comparing with a subclass of Immutable::Hash' do subclass = Class.new(Immutable::Hash) instance = subclass.new('A' => 'aye', 'B' => 'bee', 'C' => 'see') (hash == instance).should == true end it 'performs numeric conversions between floats and BigDecimals' do expect(H[a: 0.0] == H[a: BigDecimal('0.0')]).to be true expect(H[a: BigDecimal('0.0')] == H[a: 0.0]).to be true end end [:eql?, :==].each do |method| describe "##{method}" do [ [{}, {}, true], [{ 'A' => 'aye' }, {}, false], [{}, { 'A' => 'aye' }, false], [{ 'A' => 'aye' }, { 'A' => 'aye' }, true], [{ 'A' => 'aye' }, { 'B' => 'bee' }, false], [{ 'A' => 'aye', 'B' => 'bee' }, { 'A' => 'aye' }, false], [{ 'A' => 'aye' }, { 'A' => 'aye', 'B' => 'bee' }, false], [{ 'A' => 'aye', 'B' => 'bee', 'C' => 'see' }, { 'A' => 'aye', 'B' => 'bee', 'C' => 'see' }, true], [{ 'C' => 'see', 'A' => 'aye', 'B' => 'bee' }, { 'A' => 'aye', 'B' => 'bee', 'C' => 'see' }, true], ].each do |a, b, expected| describe "returns #{expected.inspect}" do it "for #{a.inspect} and #{b.inspect}" do H[a].send(method, H[b]).should == expected end it "for #{b.inspect} and #{a.inspect}" do H[b].send(method, H[a]).should == expected end end end end end it 'returns true on a large hash which is modified and then modified back again' do hash = H.new((1..1000).zip(2..1001)) hash.put('a', 1).delete('a').should == hash hash.put('b', 2).delete('b').should eql(hash) end end immutable-ruby-master/spec/lib/immutable/hash/clear_spec.rb0000644000175000017500000000203514201005456023707 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#clear' do [ [], ['A' => 'aye'], ['A' => 'aye', 'B' => 'bee', 'C' => 'see'], ].each do |values| context "on #{values}" do let(:original) { H[*values] } let(:result) { original.clear } it 'preserves the original' do result original.should eql(H[*values]) end it 'returns an empty hash' do result.should equal(H.empty) result.should be_empty end end end it 'maintains the default Proc, if there is one' do hash = H.new(a: 1) { 1 } hash.clear[:b].should == 1 hash.clear[:c].should == 1 hash.clear.default_proc.should_not be_nil end context 'on a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::Hash) instance = subclass.new(a: 1, b: 2) instance.clear.class.should be(subclass) instance.clear.should be_empty end end end end immutable-ruby-master/spec/lib/immutable/hash/except_spec.rb0000644000175000017500000000272514201005456024117 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#except' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL'] } context 'with only keys that the Hash has' do it 'returns a Hash without those values' do hash.except('B', nil).should eql(H['A' => 'aye', 'C' => 'see']) end it "doesn't change the original Hash" do hash.except('B', nil) hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL']) end end context "with keys that the Hash doesn't have" do it 'returns a Hash without the values that it had keys for' do hash.except('B', 'A', 3).should eql(H['C' => 'see', nil => 'NIL']) end it "doesn't change the original Hash" do hash.except('B', 'A', 3) hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL']) end end it 'works on a large Hash, with many combinations of input' do keys = (1..1000).to_a original = H.new(keys.zip(2..1001)) 100.times do to_remove = rand(100).times.collect { keys.sample } result = original.except(*to_remove) result.size.should == original.size - to_remove.uniq.size to_remove.each { |key| result.key?(key).should == false } (keys.sample(100) - to_remove).each { |key| result.key?(key).should == true } end original.should eql(H.new(keys.zip(2..1001))) # shouldn't have changed end end end immutable-ruby-master/spec/lib/immutable/hash/superset_spec.rb0000644000175000017500000000227714201005456024503 0ustar boutilboutilrequire 'spec_helper' require 'immutable/hash' describe Immutable::Hash do describe '#>=' do [ [{}, {}, true], [{'A' => 1}, {}, true], [{}, {'A' => 1}, false], [{'A' => 1}, {'A' => 1}, true], [{'A' => 1}, {'A' => 2}, false], [{'A' => 1, 'B' => 2, 'C' => 3}, {'B' => 2}, true], [{'B' => 2}, {'A' => 1, 'B' => 2, 'C' => 3}, false], [{'A' => 1, 'B' => 2, 'C' => 3}, {'B' => 0}, false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do expect(H[a] >= H[b]).to eq(expected) end end end end describe '#>' do [ [{}, {}, false], [{'A' => 1}, {}, true], [{}, {'A' => 1}, false], [{'A' => 1}, {'A' => 1}, false], [{'A' => 1}, {'A' => 2}, false], [{'A' => 1, 'B' => 2, 'C' => 3}, {'B' => 2}, true], [{'B' => 2}, {'A' => 1, 'B' => 2, 'C' => 3}, false], [{'A' => 1, 'B' => 2, 'C' => 3}, {'B' => 0}, false], ].each do |a, b, expected| describe "for #{a.inspect} and #{b.inspect}" do it "returns #{expected}" do expect(H[a] > H[b]).to eq(expected) end end end end end immutable-ruby-master/spec/lib/immutable/hash/none_spec.rb0000644000175000017500000000236714201005456023570 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#none?' do context 'when empty' do it 'with a block returns true' do H.empty.none? {}.should == true end it 'with no block returns true' do H.empty.none?.should == true end end context 'when not empty' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL'] } context 'with a block' do [ %w[A aye], %w[B bee], %w[C see], [nil, 'NIL'], ].each do |pair| it "returns false if the block ever returns true (#{pair.inspect})" do hash.none? { |key, value| key == pair.first && value == pair.last }.should == false end it 'returns true if the block always returns false' do hash.none? { |key, value| key == 'D' && value == 'dee' }.should == true end it 'stops iterating as soon as the block returns true' do yielded = [] hash.none? { |k,v| yielded << k; true } yielded.size.should == 1 end end end context 'with no block' do it 'returns false' do hash.none?.should == false end end end end end immutable-ruby-master/spec/lib/immutable/hash/min_max_spec.rb0000644000175000017500000000221014201005456024244 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['a' => 3, 'b' => 2, 'c' => 1] } describe '#min' do it 'returns the smallest key/val pair' do hash.min.should == ['a', 3] end end describe '#max' do it 'returns the largest key/val pair' do hash.max.should == ['c', 1] end end describe '#min_by' do it 'returns the smallest key/val pair (after passing it through a key function)' do hash.min_by { |k,v| v }.should == ['c', 1] end it 'returns the first key/val pair yielded by #each in case of a tie' do hash.min_by { 0 }.should == hash.each.first end it 'returns nil if the hash is empty' do H.empty.min_by { |k,v| v }.should be_nil end end describe '#max_by' do it 'returns the largest key/val pair (after passing it through a key function)' do hash.max_by { |k,v| v }.should == ['a', 3] end it 'returns the first key/val pair yielded by #each in case of a tie' do hash.max_by { 0 }.should == hash.each.first end it 'returns nil if the hash is empty' do H.empty.max_by { |k,v| v }.should be_nil end end end immutable-ruby-master/spec/lib/immutable/hash/fetch_values_spec.rb0000644000175000017500000000133714201005456025275 0ustar boutilboutilrequire 'spec_helper' require 'immutable/hash' describe Immutable::Hash do describe '#fetch_values' do context 'when the all the requested keys exist' do it 'returns a vector of values for the given keys' do h = H[:a => 9, :b => 'a', :c => -10, :d => nil] h.fetch_values.should be_kind_of(Immutable::Vector) h.fetch_values.should eql(V.empty) h.fetch_values(:a, :d, :b).should be_kind_of(Immutable::Vector) h.fetch_values(:a, :d, :b).should eql(V[9, nil, 'a']) end end context 'when the key does not exist' do it 'raises a KeyError' do -> { H['A' => 'aye', 'C' => 'Cee'].fetch_values('A', 'B') }.should raise_error(KeyError) end end end end immutable-ruby-master/spec/lib/immutable/hash/reduce_spec.rb0000644000175000017500000000200314201005456024063 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:reduce, :inject].each do |method| describe "##{method}" do context 'when empty' do it 'returns the memo' do H.empty.send(method, 'ABC') {}.should == 'ABC' end end context 'when not empty' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'with a block' do it 'returns the final memo' do hash.send(method, 0) { |memo, key, value| memo + 1 }.should == 3 end end context 'with no block' do let(:hash) { H[a: 1, b: 2] } it 'uses a passed string as the name of a method to use instead' do [[:a, 1, :b, 2], [:b, 2, :a, 1]].include?(hash.send(method, '+')).should == true end it 'uses a passed symbol as the name of a method to use instead' do [[:a, 1, :b, 2], [:b, 2, :a, 1]].include?(hash.send(method, :+)).should == true end end end end end end immutable-ruby-master/spec/lib/immutable/hash/put_spec.rb0000644000175000017500000000633614201005456023441 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#[]=' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } it 'raises error pointing to #put' do expect { hash[:A] = 'aye' } .to raise_error(NoMethodError, /Immutable::Hash.*`put'/) end end describe '#put' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'with a block' do it 'passes the value to the block' do hash.put('A') { |value| value.should == 'aye' } end it 'replaces the value with the result of the block' do result = hash.put('A') { |value| 'FLIBBLE' } result.get('A').should == 'FLIBBLE' end it 'supports to_proc methods' do result = hash.put('A', &:upcase) result.get('A').should == 'AYE' end context 'if there is no existing association' do it 'passes nil to the block' do hash.put('D') { |value| value.should be_nil } end it 'stores the result of the block as the new value' do result = hash.put('D') { |value| 'FLIBBLE' } result.get('D').should == 'FLIBBLE' end end end context 'with a unique key' do let(:result) { hash.put('D', 'dee') } it 'preserves the original' do result hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a copy with the superset of key/value pairs' do result.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see', 'D' => 'dee']) end end context 'with a duplicate key' do let(:result) { hash.put('C', 'sea') } it 'preserves the original' do result hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a copy with the superset of key/value pairs' do result.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'sea']) end end context 'with duplicate key and identical value' do let(:hash) { H['X' => 1, 'Y' => 2] } let(:result) { hash.put('X', 1) } it 'returns the original hash unmodified' do result.should be(hash) end context 'with big hash (force nested tries)' do let(:keys) { (0..99).map(&:to_s) } let(:values) { (100..199).to_a } let(:hash) { H[keys.zip(values)] } it 'returns the original hash unmodified for all changes' do keys.each_with_index do |key, index| result = hash.put(key, values[index]) result.should be(hash) end end end end context 'with unequal keys which hash to the same value' do let(:hash) { H[DeterministicHash.new('a', 1) => 'aye'] } it 'stores and can retrieve both' do result = hash.put(DeterministicHash.new('b', 1), 'bee') result.get(DeterministicHash.new('a', 1)).should eql('aye') result.get(DeterministicHash.new('b', 1)).should eql('bee') end end context 'when a String is inserted as key and then mutated' do it 'is not affected' do string = 'a string!' hash = H.empty.put(string, 'a value!') string.upcase! hash['a string!'].should == 'a value!' hash['A STRING!'].should be_nil end end end end immutable-ruby-master/spec/lib/immutable/hash/map_spec.rb0000644000175000017500000000255314201005456023403 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:map, :collect].each do |method| describe "##{method}" do context 'when empty' do it 'returns self' do H.empty.send(method) {}.should equal(H.empty) end end context 'when not empty' do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } context 'with a block' do let(:mapped) { hash.send(method) { |key, value| [key.downcase, value.upcase] }} it 'preserves the original values' do mapped hash.should eql(H['A' => 'aye', 'B' => 'bee', 'C' => 'see']) end it 'returns a new hash with the mapped values' do mapped.should eql(H['a' => 'AYE', 'b' => 'BEE', 'c' => 'SEE']) end end context 'with no block' do it 'returns an Enumerator' do hash.send(method).class.should be(Enumerator) hash.send(method).each { |k,v| [k.downcase, v] }.should == hash.map { |k,v| [k.downcase, v] } end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Hash) instance = subclass.new('a' => 'aye', 'b' => 'bee') instance.map { |k,v| [k, v.upcase] }.class.should be(subclass) end end end end end immutable-ruby-master/spec/lib/immutable/hash/reverse_each_spec.rb0000644000175000017500000000126114201005456025254 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } describe '#reverse_each' do context 'with a block' do it 'returns self' do hash.reverse_each {}.should be(hash) end it 'yields all key/value pairs in the opposite order as #each' do result = [] hash.reverse_each { |entry| result << entry } result.should eql(hash.to_a.reverse) end end context 'with no block' do it 'returns an Enumerator' do result = hash.reverse_each result.class.should be(Enumerator) result.to_a.should eql(hash.to_a.reverse) end end end end immutable-ruby-master/spec/lib/immutable/hash/assoc_spec.rb0000644000175000017500000000263114201005456023733 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H[a: 3, b: 2, c: 1] } describe '#assoc' do it 'searches for a key/val pair with a given key' do hash.assoc(:a).should == [:a, 3] hash.assoc(:b).should == [:b, 2] hash.assoc(:c).should == [:c, 1] end it 'returns nil if a matching key is not found' do hash.assoc(:d).should be_nil hash.assoc(nil).should be_nil hash.assoc(0).should be_nil end it 'returns nil even if there is a default' do H.new(a: 1, b: 2) { fail }.assoc(:c).should be_nil end it 'uses #== to compare keys with provided object' do hash.assoc(EqualNotEql.new).should_not be_nil hash.assoc(EqlNotEqual.new).should be_nil end end describe '#rassoc' do it 'searches for a key/val pair with a given value' do hash.rassoc(1).should == [:c, 1] hash.rassoc(2).should == [:b, 2] hash.rassoc(3).should == [:a, 3] end it 'returns nil if a matching value is not found' do hash.rassoc(0).should be_nil hash.rassoc(4).should be_nil hash.rassoc(nil).should be_nil end it 'returns nil even if there is a default' do H.new(a: 1, b: 2) { fail }.rassoc(3).should be_nil end it 'uses #== to compare values with provided object' do hash.rassoc(EqualNotEql.new).should_not be_nil hash.rassoc(EqlNotEqual.new).should be_nil end end end immutable-ruby-master/spec/lib/immutable/hash/hash_spec.rb0000644000175000017500000000163314201005456023547 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do describe '#hash' do it 'values are sufficiently distributed' do (1..4000).each_slice(4).map { |ka, va, kb, vb| H[ka => va, kb => vb].hash }.uniq.size.should == 1000 end it 'differs given the same keys and different values' do H['ka' => 'va'].hash.should_not == H['ka' => 'vb'].hash end it 'differs given the same values and different keys' do H['ka' => 'va'].hash.should_not == H['kb' => 'va'].hash end it 'generates the same hash value for a hash regardless of the order things were added to it' do key1 = DeterministicHash.new('abc', 1) key2 = DeterministicHash.new('xyz', 1) H.empty.put(key1, nil).put(key2, nil).hash.should == H.empty.put(key2, nil).put(key1, nil).hash end describe 'on an empty hash' do it 'returns 0' do H.empty.hash.should == 0 end end end end immutable-ruby-master/spec/lib/immutable/hash/has_key_spec.rb0000644000175000017500000000160214201005456024243 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do [:key?, :has_key?, :include?, :member?].each do |method| describe "##{method}" do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see', nil => 'NIL', 2.0 => 'two'] } ['A', 'B', 'C', nil, 2.0].each do |key| it "returns true for an existing key (#{key.inspect})" do hash.send(method, key).should == true end end it 'returns false for a non-existing key' do hash.send(method, 'D').should == false end it 'uses #eql? for equality' do hash.send(method, 2).should == false end it 'returns true if the key is found and maps to nil' do H['A' => nil].send(method, 'A').should == true end it 'returns true if the key is found and maps to false' do H['A' => false].send(method, 'A').should == true end end end end immutable-ruby-master/spec/lib/immutable/hash/copying_spec.rb0000644000175000017500000000042414201005456024271 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['A' => 'aye', 'B' => 'bee', 'C' => 'see'] } [:dup, :clone].each do |method| describe "##{method}" do it 'returns self' do hash.send(method).should equal(hash) end end end end immutable-ruby-master/spec/lib/immutable/hash/partition_spec.rb0000644000175000017500000000212614201005456024633 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Hash do let(:hash) { H['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4] } let(:partition) { hash.partition { |k,v| v % 2 == 0 }} describe '#partition' do it 'returns a pair of Immutable::Hashes' do partition.each { |h| h.class.should be(Immutable::Hash) } partition.should be_frozen end it 'returns key/val pairs for which predicate is true in first Hash' do partition[0].should == {'b' => 2, 'd' => 4} end it 'returns key/val pairs for which predicate is false in second Hash' do partition[1].should == {'a' => 1, 'c' => 3} end it "doesn't modify the original Hash" do partition hash.should eql(H['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]) end context 'from a subclass' do it 'should return instances of the subclass' do subclass = Class.new(Immutable::Hash) instance = subclass.new('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4) partition = instance.partition { |k,v| v % 2 == 0 } partition.each { |h| h.class.should be(subclass) } end end end end immutable-ruby-master/spec/lib/immutable/vector/0000755000175000017500000000000014201005456021641 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/vector/new_spec.rb0000644000175000017500000000250714201005456023775 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '.new' do it 'accepts a single enumerable argument and creates a new vector' do vector = Immutable::Vector.new([1,2,3]) vector.size.should be(3) vector[0].should be(1) vector[1].should be(2) vector[2].should be(3) end it 'makes a defensive copy of a non-frozen mutable Array passed in' do array = [1,2,3] vector = Immutable::Vector.new(array) array[0] = 'changed' vector[0].should be(1) end it 'is amenable to overriding of #initialize' do class SnazzyVector < Immutable::Vector def initialize super(['SNAZZY!!!']) end end vector = SnazzyVector.new vector.size.should be(1) vector.should == ['SNAZZY!!!'] end context 'from a subclass' do it 'returns a frozen instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new(['some', 'values']) instance.class.should be subclass instance.frozen?.should be true end end end describe '.[]' do it 'accepts a variable number of items and creates a new vector' do vector = Immutable::Vector['a', 'b'] vector.size.should be(2) vector[0].should == 'a' vector[1].should == 'b' end end end immutable-ruby-master/spec/lib/immutable/vector/to_ary_spec.rb0000644000175000017500000000131714201005456024477 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#to_ary' do let(:values) { %w[A B C D] } it 'converts using block parameters' do def expectations(&block) yield(vector) end expectations do |a, b, *c| expect(a).to eq('A') expect(b).to eq('B') expect(c).to eq(%w[C D]) end end it 'converts using method arguments' do def expectations(a, b, *c) expect(a).to eq('A') expect(b).to eq('B') expect(c).to eq(%w[C D]) end expectations(*vector) end it 'converts using splat' do array = *vector expect(array).to eq(%w[A B C D]) end end end immutable-ruby-master/spec/lib/immutable/vector/delete_at_spec.rb0000644000175000017500000000317214201005456025131 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#delete_at' do let(:vector) { V[1,2,3,4,5] } it 'removes the element at the specified index' do vector.delete_at(0).should eql(V[2,3,4,5]) vector.delete_at(2).should eql(V[1,2,4,5]) vector.delete_at(-1).should eql(V[1,2,3,4]) end it 'makes no modification if the index is out of range' do vector.delete_at(5).should eql(vector) vector.delete_at(-6).should eql(vector) end it 'works when deleting last item at boundary where vector trie needs to get shallower' do vector = Immutable::Vector.new(1..33) vector.delete_at(32).size.should == 32 vector.delete_at(32).to_a.should eql((1..32).to_a) end it 'works on an empty vector' do V.empty.delete_at(0).should be(V.empty) V.empty.delete_at(1).should be(V.empty) end it 'works on a vector with 1 item' do V[10].delete_at(0).should eql(V.empty) V[10].delete_at(1).should eql(V[10]) end it 'works on a vector with 32 items' do V.new(1..32).delete_at(0).should eql(V.new(2..32)) V.new(1..32).delete_at(31).should eql(V.new(1..31)) end it 'has the right size and contents after many deletions' do array = (1..2000).to_a # we use an Array as standard of correctness vector = Immutable::Vector.new(array) 500.times do index = rand(vector.size) vector = vector.delete_at(index) array.delete_at(index) vector.size.should == array.size ary = vector.to_a ary.size.should == vector.size ary.should eql(array) end end end end immutable-ruby-master/spec/lib/immutable/vector/drop_spec.rb0000644000175000017500000000210414201005456024141 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#drop' do [ [[], 10, []], [['A'], 10, []], [['A'], 1, []], [['A'], 0, ['A']], [%w[A B C], 0, %w[A B C]], [%w[A B C], 2, ['C']], [(1..32), 3, (4..32)], [(1..33), 32, [33]] ].each do |values, number, expected| describe "#{number} from #{values.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.drop(number) vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.drop(number).should eql(V[*expected]) end end end it 'raises an ArgumentError if number of elements specified is negative' do -> { V[1, 2, 3].drop(-1) }.should raise_error(ArgumentError) -> { V[1, 2, 3].drop(-3) }.should raise_error(ArgumentError) end context 'when number of elements specified is zero' do let(:vector) { V[1, 2, 3, 4, 5, 6] } it 'returns self' do vector.drop(0).should be(vector) end end end end immutable-ruby-master/spec/lib/immutable/vector/pop_spec.rb0000644000175000017500000000103314201005456023773 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#pop' do [ [[], []], [['A'], []], [%w[A B C], %w[A B]], [1..32, 1..31], [1..33, 1..32] ].each do |values, expected| context "on #{values.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.pop vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.pop.should eql(V[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/vector/reject_spec.rb0000644000175000017500000000245514201005456024462 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:reject, :delete_if].each do |method| describe "##{method}" do [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B C]], [%w[A b C], %w[A C]], [%w[a b c], []], ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } context 'with a block' do it "returns #{expected.inspect}" do vector.send(method) { |item| item == item.downcase }.should eql(V[*expected]) end end context 'without a block' do it 'returns an Enumerator' do vector.send(method).class.should be(Enumerator) vector.send(method).each { |item| item == item.downcase }.should eql(V[*expected]) end end end end it 'works with a variety of inputs' do [1, 2, 10, 31, 32, 33, 1023, 1024, 1025].each do |size| [0, 5, 32, 50, 500, 800, 1024].each do |threshold| vector = V.new(1..size) result = vector.send(method) { |item| item > threshold } result.size.should == [size, threshold].min result.should eql(V.new(1..[size, threshold].min)) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/flatten_spec.rb0000644000175000017500000000330614201005456024637 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#flatten' do it 'recursively flattens nested vectors into containing vector' do V[V[1], V[2]].flatten.should eql(V[1,2]) V[V[V[V[V[V[1,2,3]]]]]].flatten.should eql(V[1,2,3]) V[V[V[1]], V[V[V[2]]]].flatten.should eql(V[1,2]) end it 'flattens nested arrays as well' do V[[1,2,3],[[4],[5,6]]].flatten.should eql(V[1,2,3,4,5,6]) end context 'with an integral argument' do it 'only flattens down to the specified depth' do V[V[V[1,2]]].flatten(1).should eql(V[V[1,2]]) V[V[V[V[1]], V[2], V[3]]].flatten(2).should eql(V[V[1], 2, 3]) end end context 'with an argument of zero' do it 'returns self' do vector = V[1,2,3] vector.flatten(0).should be(vector) end end context 'on a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2]) instance.flatten.class.should be(subclass) end end context 'on a vector with no nested vectors' do it 'returns an unchanged vector' do vector = V[1,2,3] vector.flatten.should.eql?(V[1,2,3]) end context 'on a Vector larger than 32 items initialized with Vector.new' do # Regression test, for problem discovered while working on GH issue #182 it 'returns an unchanged vector' do vector1,vector2 = 2.times.collect { V.new(0..33) } vector1.flatten.should eql(vector2) end end end it 'leaves the original unmodified' do vector = V[1,2,3] vector.flatten vector.should eql(V[1,2,3]) end end end immutable-ruby-master/spec/lib/immutable/vector/dig_spec.rb0000644000175000017500000000154314201005456023746 0ustar boutilboutilrequire 'spec_helper' require 'immutable/vector' describe Immutable::Vector do let(:v) { V[1, 2, V[3, 4]] } describe '#dig' do it 'returns value at the index with one argument' do expect(v.dig(0)).to eq(1) end it 'returns value at index in nested arrays' do expect(v.dig(2, 0)).to eq(3) end # This is different from Hash#dig, but it matches the behavior of Ruby's # built-in Array#dig (except that Array#dig raises a TypeError) it 'raises an error when indexing deeper than possible' do expect { (v.dig(0, 0)) }.to raise_error(NoMethodError) end it 'returns nil if you index past the end of an array' do expect(v.dig(5)).to eq(nil) end it "raises an error when indexing with a key vectors don't understand" do expect { v.dig(:foo) }.to raise_error(ArgumentError) end end end immutable-ruby-master/spec/lib/immutable/vector/reverse_spec.rb0000644000175000017500000000101614201005456024651 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#reverse' do [ [[], []], [[1], [1]], [[1,2], [2,1]], [(1..32).to_a, (1..32).to_a.reverse], [(1..33).to_a, (1..33).to_a.reverse], [(1..100).to_a, (1..100).to_a.reverse], [(1..1024).to_a, (1..1024).to_a.reverse] ].each do |initial, expected| describe "on #{initial}" do it "returns #{expected}" do V.new(initial).reverse.should eql(V.new(expected)) end end end end end immutable-ruby-master/spec/lib/immutable/vector/take_while_spec.rb0000644000175000017500000000154414201005456025320 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#take_while' do [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B]] ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } let(:result) { vector.take_while { |item| item < 'C' }} describe 'with a block' do it "returns #{expected.inspect}" do result.should eql(V[*expected]) end it 'preserves the original' do result vector.should eql(V[*values]) end end describe 'without a block' do it 'returns an Enumerator' do vector.take_while.class.should be(Enumerator) vector.take_while.each { |item| item < 'C' }.should eql(V[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/multiply_spec.rb0000644000175000017500000000243614201005456025064 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#*' do let(:vector) { V[1, 2, 3] } context 'with a String argument' do it 'acts just like #join' do (vector * 'boo').should eql(vector.join('boo')) end end context 'with an Integer argument' do it 'concatenates n copies of the array' do (vector * 0).should eql(V.empty) (vector * 1).should eql(vector) (vector * 2).should eql(V[1,2,3,1,2,3]) (vector * 3).should eql(V[1,2,3,1,2,3,1,2,3]) end it 'raises an ArgumentError if integer is negative' do -> { vector * -1 }.should raise_error(ArgumentError) end it 'works on large vectors' do array = (1..50).to_a (V.new(array) * 25).should eql(V.new(array * 25)) end end context 'with a subclass of Vector' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) (instance * 10).class.should be(subclass) end end it 'raises a TypeError if passed nil' do -> { vector * nil }.should raise_error(TypeError) end it 'raises an ArgumentError if passed no arguments' do -> { vector.* }.should raise_error(ArgumentError) end end end immutable-ruby-master/spec/lib/immutable/vector/transpose_spec.rb0000644000175000017500000000411514201005456025217 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#transpose' do it 'takes a vector of vectors and transposes rows and columns' do V[V[1, 'a'], V[2, 'b'], V[3, 'c']].transpose.should eql(V[V[1, 2, 3], V['a', 'b', 'c']]) V[V[1, 2, 3], V['a', 'b', 'c']].transpose.should eql(V[V[1, 'a'], V[2, 'b'], V[3, 'c']]) V[].transpose.should eql(V[]) V[V[]].transpose.should eql(V[]) V[V[], V[]].transpose.should eql(V[]) V[V[0]].transpose.should eql(V[V[0]]) V[V[0], V[1]].transpose.should eql(V[V[0, 1]]) end it 'raises an IndexError if the vectors are not of the same length' do -> { V[V[1,2], V[:a]].transpose }.should raise_error(IndexError) end it 'also works on Vectors of Arrays' do V[[1,2,3], [4,5,6]].transpose.should eql(V[V[1,4], V[2,5], V[3,6]]) end [10, 31, 32, 33, 1000, 1023, 1024, 1025, 2000].each do |size| context "on #{size}-item vectors" do it 'behaves like Array#transpose' do array = rand(10).times.map { size.times.map { rand(10000) }} vector = V.new(array) result = vector.transpose # Array#== uses Object#== to compare corresponding elements, # so although Vector#== does type coercion, it does not consider # nested Arrays and corresponding nested Vectors to be equal # That is why the following ".map { |a| V.new(a) }" is needed result.should == array.transpose.map { |a| V.new(a) } result.each { |v| v.class.should be(Immutable::Vector) } end end end context 'on a subclass of Vector' do it 'returns instances of the subclass' do subclass = Class.new(V) instance = subclass.new([[1,2,3], [4,5,6]]) instance.transpose.class.should be(subclass) instance.transpose.each { |v| v.class.should be(subclass) } end end context 'if an item does not respond to #size and #[]' do it 'raises TypeError' do expect { V[[1, 2], [2, 3], nil].transpose }.to raise_error(TypeError) end end end end immutable-ruby-master/spec/lib/immutable/vector/sample_spec.rb0000644000175000017500000000054014201005456024460 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#sample' do let(:vector) { V.new(1..10) } it 'returns a randomly chosen item' do chosen = 100.times.map { vector.sample } chosen.each { |item| vector.include?(item).should == true } vector.each { |item| chosen.include?(item).should == true } end end end immutable-ruby-master/spec/lib/immutable/vector/any_spec.rb0000644000175000017500000000271714201005456023776 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#any?' do let(:any?) { vector.any?(&block) } context 'when created with no values' do let(:values) { [] } context 'with a block' do let(:block) { ->(item) { item + 1 } } it 'returns false' do expect(any?).to be(false) end end context 'with a block' do let(:block) { nil } it 'returns false' do expect(any?).to be(false) end end end context 'when created with values' do let(:values) { ['A', 'B', 3, nil] } context 'with a block that returns true' do let(:block) { ->(item) { item == 3 } } it 'returns true' do expect(any?).to be(true) end end context "with a block that doesn't return true" do let(:block) { ->(item) { item == 'D' } } it 'returns false' do expect(any?).to be(false) end end context 'without a block' do let(:block) { nil } context 'with some values that are truthy' do let(:values) { [nil, false, 'B'] } it 'returns true' do expect(any?).to be(true) end end context 'with all values that are falsey' do let(:values) { [nil, false] } it 'returns false' do expect(any?).to be(false) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/shuffle_spec.rb0000644000175000017500000000225114201005456024634 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#shuffle' do let(:vector) { V[1,2,3,4] } it 'returns the same values, in a usually different order' do different = false 10.times do shuffled = vector.shuffle shuffled.sort.should eql(vector) different ||= (shuffled != vector) end different.should be(true) end it 'leaves the original unchanged' do vector.shuffle vector.should eql(V[1,2,3,4]) end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.shuffle.class.should be(subclass) end end [32, 33, 1023, 1024, 1025].each do |size| context "on a #{size}-item vector" do it 'works correctly' do vector = V.new(1..size) shuffled = vector.shuffle shuffled = vector.shuffle while shuffled.eql?(vector) # in case we get the same vector.should eql(V.new(1..size)) shuffled.size.should == vector.size shuffled.sort.should eql(vector) end end end end end immutable-ruby-master/spec/lib/immutable/vector/ltlt_spec.rb0000644000175000017500000000324414201005456024162 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#<<' do let(:ltlt) { vector << added_value } shared_examples 'checking adding values' do let(:added_vector) { V[*added_values] } it 'preserves the original' do original = vector vector << added_value expect(original).to eq(vector) end it 'ltlts the item to the vector' do expect(ltlt).to eq(added_vector) end end context 'with a empty array adding a single item' do let(:values) { [] } let(:added_value) { 'A' } let(:added_values) { ['A'] } include_examples 'checking adding values' end context 'with a single-item array adding a different item' do let(:values) { ['A'] } let(:added_value) { 'B' } let(:added_values) { %w[A B] } include_examples 'checking adding values' end context 'with a single-item array adding a duplicate item' do let(:values) { ['A'] } let(:added_value) { 'A' } let(:added_values) { %w[A A] } include_examples 'checking adding values' end [31, 32, 33, 1023, 1024, 1025].each do |size| context "with a #{size}-item vector adding a different item" do let(:values) { (1..size).to_a } let(:added_value) { size+1 } let(:added_values) { (1..(size+1)).to_a } include_examples 'checking adding values' end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass[1,2,3] (instance << 4).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/get_spec.rb0000644000175000017500000000434414201005456023764 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:get, :at].each do |method| describe "##{method}" do context 'when empty' do it 'always returns nil' do (-1..1).each do |i| V.empty.send(method, i).should be_nil end end end context 'when not empty' do let(:vector) { V[*(1..1025)] } context 'with a positive index' do context 'within the absolute bounds of the vector' do it 'returns the value at the specified index from the head' do (0..(vector.size - 1)).each do |i| vector.send(method, i).should == i + 1 end end end context 'outside the absolute bounds of the vector' do it 'returns nil' do vector.send(method, vector.size).should be_nil end end end context 'with a negative index' do context 'within the absolute bounds of the vector' do it 'returns the value at the specified index from the tail' do (-vector.size..-1).each do |i| vector.send(method, i).should == vector.size + i + 1 end end end context 'outside the absolute bounds of the vector' do it 'returns nil' do vector.send(method, -vector.size.next).should be_nil end end end end [1, 10, 31, 32, 33, 1024, 1025, 2000].each do |size| context "on a #{size}-item vector" do it 'works correctly, even after various addings and removings' do array = size.times.map { rand(10000) } vector = V.new(array) 100.times do if rand(2) == 0 value, index = rand(10000), rand(size) array[index] = value vector = vector.set(index, value) else index = rand(array.size) array.delete_at(index) vector = vector.delete_at(index) end end 0.upto(array.size) do |i| array[i].should == vector.send(method, i) end end end end end end end immutable-ruby-master/spec/lib/immutable/vector/minimum_spec.rb0000644000175000017500000000143114201005456024652 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#min' do context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ni'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].min { |minimum, item| minimum.length <=> item.length }.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].min.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/vector/inspect_spec.rb0000644000175000017500000000227214201005456024650 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#inspect' do let(:inspect) { vector.inspect } shared_examples 'checking output' do it 'returns its contents as a programmer-readable string' do expect(inspect).to eq(output) end it "returns a string which can be eval'd to get back an equivalent vector" do expect(eval(inspect)).to eql(vector) end end context 'with an empty array' do let(:output) { 'Immutable::Vector[]' } let(:values) { [] } include_examples 'checking output' end context 'with a single item array' do let(:output) { 'Immutable::Vector["A"]' } let(:values) { %w[A] } include_examples 'checking output' end context 'with a multi-item array' do let(:output) { 'Immutable::Vector["A", "B"]' } let(:values) { %w[A B] } include_examples 'checking output' end context 'from a subclass' do MyVector = Class.new(Immutable::Vector) let(:vector) { MyVector.new(values) } let(:output) { 'MyVector[1, 2]' } let(:values) { [1, 2] } include_examples 'checking output' end end end immutable-ruby-master/spec/lib/immutable/vector/product_spec.rb0000644000175000017500000000447714201005456024674 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#product' do context 'when passed no arguments' do it 'multiplies all items in vector' do [ [[], 1], [[2], 2], [[1, 3, 5, 7, 11], 1155], ].each do |values, expected| V[*values].product.should == expected end end end context 'when passed one or more vectors' do let(:vector) { V[1,2,3] } context 'when passed a block' do it 'yields an array for each combination of items from the vectors' do yielded = [] vector.product(vector) { |obj| yielded << obj } yielded.should eql([[1,1], [1,2], [1,3], [2,1], [2,2], [2,3], [3,1], [3,2], [3,3]]) yielded = [] vector.product(V[3,4,5], V[6,8]) { |obj| yielded << obj } yielded.should eql( [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8], [3, 3, 6], [3, 3, 8], [3, 4, 6], [3, 4, 8], [3, 5, 6], [3, 5, 8]]) end it 'returns self' do vector.product(V.empty) {}.should be(vector) vector.product(V[1,2], V[3]) {}.should be(vector) V.empty.product(vector) {}.should be(V.empty) end end context 'when not passed a block' do it 'returns the cartesian product in an array' do V[1,2].product(V[3,4,5], V[6,8]).should eql( [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]]) end end context 'when one of the arguments is empty' do it 'returns an empty array' do vector.product(V.empty, V[4,5,6]).should eql([]) end end context 'when the receiver is empty' do it 'returns an empty array' do V.empty.product(vector, V[4,5,6]).should eql([]) end end end context 'when passed one or more Arrays' do it 'also calculates the cartesian product correctly' do V[1,2].product([3,4,5], [6,8]).should eql( [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]]) end end end end immutable-ruby-master/spec/lib/immutable/vector/marshal_spec.rb0000644000175000017500000000162514201005456024633 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#marshal_dump/#marshal_load' do let(:ruby) do File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) end let(:child_cmd) do %Q|#{ruby} -I lib -r immutable -e 'vector = Immutable::Vector[5, 10, 15]; $stdout.write(Marshal.dump(vector))'| end let(:reloaded_vector) do IO.popen(child_cmd, 'r+') do |child| reloaded_vector = Marshal.load(child) child.close reloaded_vector end end it 'can survive dumping and loading into a new process' do expect(reloaded_vector).to eql(V[5, 10, 15]) end it 'is still possible to find items by index after loading' do expect(reloaded_vector[0]).to eq(5) expect(reloaded_vector[1]).to eq(10) expect(reloaded_vector[2]).to eq(15) expect(reloaded_vector.size).to eq(3) end end end immutable-ruby-master/spec/lib/immutable/vector/flat_map_spec.rb0000644000175000017500000000247514201005456024773 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#flat_map' do let(:block) { ->(item) { [item, item + 1, item * item] } } let(:flat_map) { vector.flat_map(&block) } let(:flattened_vector) { V[*flattened_values] } shared_examples 'checking flattened result' do it 'returns the flattened values as an Immutable::Vector' do expect(flat_map).to eq(flattened_vector) end it 'returns an Immutable::Vector' do expect(flat_map).to be_a(Immutable::Vector) end end context 'with an empty vector' do let(:values) { [] } let(:flattened_values) { [] } include_examples 'checking flattened result' end context 'with a block that returns an empty vector' do let(:block) { ->(item) { [] } } let(:values) { [1, 2, 3] } let(:flattened_values) { [] } include_examples 'checking flattened result' end context 'with a vector of one item' do let(:values) { [7] } let(:flattened_values) { [7, 8, 49] } include_examples 'checking flattened result' end context 'with a vector of multiple items' do let(:values) { [1, 2, 3] } let(:flattened_values) { [1, 2, 1, 2, 3, 4, 3, 4, 9] } include_examples 'checking flattened result' end end end immutable-ruby-master/spec/lib/immutable/vector/empty_spec.rb0000644000175000017500000000204014201005456024332 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#empty?' do [ [[], true], [['A'], false], [%w[A B C], false], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].empty?.should == expected end end end end describe '.empty' do it 'returns the canonical empty vector' do V.empty.size.should be(0) V.empty.object_id.should be(V.empty.object_id) end context 'from a subclass' do it 'returns an empty instance of the subclass' do subclass = Class.new(Immutable::Vector) subclass.empty.class.should be(subclass) subclass.empty.should be_empty end it 'calls overridden #initialize when creating empty Hash' do subclass = Class.new(Immutable::Vector) do def initialize @variable = 'value' end end subclass.empty.instance_variable_get(:@variable).should == 'value' end end end end immutable-ruby-master/spec/lib/immutable/vector/to_a_spec.rb0000644000175000017500000000155114201005456024124 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#to_a' do let(:to_a) { vector.to_a } shared_examples 'checking to_a values' do it 'returns the values' do expect(to_a).to eq(values) end end context 'with an empty vector' do let(:values) { [] } include_examples 'checking to_a values' end context 'with an single item vector' do let(:values) { %w[A] } include_examples 'checking to_a values' end context 'with an multi-item vector' do let(:values) { %w[A B] } include_examples 'checking to_a values' end [10, 31, 32, 33, 1000, 1023, 1024, 1025].each do |size| context "with a #{size}-item vector" do let(:values) { (1..size).to_a } include_examples 'checking to_a values' end end end end immutable-ruby-master/spec/lib/immutable/vector/fetch_spec.rb0000644000175000017500000000401114201005456024265 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#fetch' do let(:vector) { V['a', 'b', 'c'] } context 'with no default provided' do context 'when the index exists' do it 'returns the value at the index' do vector.fetch(0).should == 'a' vector.fetch(1).should == 'b' vector.fetch(2).should == 'c' end end context 'when the key does not exist' do it 'raises an IndexError' do -> { vector.fetch(3) }.should raise_error(IndexError) -> { vector.fetch(-4) }.should raise_error(IndexError) end end end context 'with a default value' do context 'when the index exists' do it 'returns the value at the index' do vector.fetch(0, 'default').should == 'a' vector.fetch(1, 'default').should == 'b' vector.fetch(2, 'default').should == 'c' end end context 'when the index does not exist' do it 'returns the default value' do vector.fetch(3, 'default').should == 'default' vector.fetch(-4, 'default').should == 'default' end end end context 'with a default block' do context 'when the index exists' do it 'returns the value at the index' do vector.fetch(0) { 'default'.upcase }.should == 'a' vector.fetch(1) { 'default'.upcase }.should == 'b' vector.fetch(2) { 'default'.upcase }.should == 'c' end end context 'when the index does not exist' do it 'invokes the block with the missing index as parameter' do vector.fetch(3) { |index| index.should == 3} vector.fetch(-4) { |index| index.should == -4 } vector.fetch(3) { 'default'.upcase }.should == 'DEFAULT' vector.fetch(-4) { 'default'.upcase }.should == 'DEFAULT' end end end it 'gives precedence to default block over default argument if passed both' do vector.fetch(3, 'one') { 'two' }.should == 'two' end end end immutable-ruby-master/spec/lib/immutable/vector/update_in_spec.rb0000644000175000017500000000471514201005456025157 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#update_in' do let(:vector) { Immutable::Vector[ 100, 101, 102, Immutable::Vector[200, 201, Immutable::Vector[300, 301, 302]], Immutable::Hash['A' => 'alpha', 'B' => 'bravo'], [400, 401, 402] ] } context 'with one level on existing key' do it 'passes the value to the block' do vector.update_in(1) { |value| value.should == 101 } end it 'replaces the value with the result of the block' do result = vector.update_in(1) { |value| 'FLIBBLE' } result.get(1).should == 'FLIBBLE' end it 'should preserve the original' do result = vector.update_in(1) { |value| 'FLIBBLE' } vector.get(1).should == 101 end end context 'with multi-level vectors on existing keys' do it 'passes the value to the block' do vector.update_in(3, 2, 0) { |value| value.should == 300 } end it 'replaces the value with the result of the block' do result = vector.update_in(3, 2, 0) { |value| 'FLIBBLE' } result[3][2][0].should == 'FLIBBLE' end it 'should preserve the original' do result = vector.update_in(3, 2, 0) { |value| 'FLIBBLE' } vector[3][2][0].should == 300 end end context "with multi-level creating sub-hashes when keys don't exist" do it 'passes nil to the block' do vector.update_in(3, 3, 'X', 'Y') { |value| value.should be_nil } end it 'creates subhashes on the way to set the value' do result = vector.update_in(3, 3, 'X', 'Y') { |value| 'NEWVALUE' } result[3][3]['X']['Y'].should == 'NEWVALUE' result[3][2][0].should == 300 end end context 'with multi-level including hash with existing keys' do it 'passes the value to the block' do vector.update_in(4, 'B') { |value| value.should == 'bravo' } end it 'replaces the value with the result of the block' do result = vector.update_in(4, 'B') { |value| 'FLIBBLE' } result[4]['B'].should == 'FLIBBLE' end it 'should preserve the original' do result = vector.update_in(4, 'B') { |value| 'FLIBBLE' } vector[4]['B'].should == 'bravo' end end context 'with empty key_path' do it 'raises ArguemntError' do expect { vector.update_in() { |v| 42 } }.to raise_error(ArgumentError) end end end end immutable-ruby-master/spec/lib/immutable/vector/each_index_spec.rb0000644000175000017500000000173414201005456025274 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#each_index' do let(:vector) { V[1,2,3,4] } context 'with a block' do it 'yields all the valid indices into the vector' do result = [] vector.each_index { |i| result << i } result.should eql([0,1,2,3]) end it 'returns self' do vector.each_index {}.should be(vector) end end context 'without a block' do it 'returns an Enumerator' do vector.each_index.class.should be(Enumerator) vector.each_index.to_a.should eql([0,1,2,3]) end end context 'on an empty vector' do it "doesn't yield anything" do V.empty.each_index { fail } end end [1, 2, 10, 31, 32, 33, 1000, 1024, 1025].each do |size| context "on a #{size}-item vector" do it 'yields all valid indices' do V.new(1..size).each_index.to_a.should == (0..(size-1)).to_a end end end end end immutable-ruby-master/spec/lib/immutable/vector/each_spec.rb0000644000175000017500000000210014201005456024071 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#each' do describe 'with no block' do let(:vector) { V['A', 'B', 'C'] } it 'returns an Enumerator' do vector.each.class.should be(Enumerator) vector.each.to_a.should == vector end end [31, 32, 33, 1023, 1024, 1025].each do |size| context "on a #{size}-item vector" do describe 'with a block' do let(:vector) { V.new(1..size) } it 'returns self' do items = [] vector.each { |item| items << item }.should be(vector) end it 'yields all the items' do items = [] vector.each { |item| items << item } items.should == (1..size).to_a end it 'iterates over the items in order' do vector.each.first.should == 1 vector.each.to_a.last.should == size end end end end context 'on an empty vector' do it "doesn't yield anything" do V.empty.each { fail } end end end end immutable-ruby-master/spec/lib/immutable/vector/last_spec.rb0000644000175000017500000000170514201005456024146 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#last' do let(:last) { vector.last } shared_examples 'checking values' do it 'returns the last item' do expect(last).to eq(last_item) end end context 'with an empty vector' do let(:last_item) { nil } let(:values) { [] } include_examples 'checking values' end context 'with a single item vector' do let(:last_item) { 'A' } let(:values) { %w[A] } include_examples 'checking values' end context 'with a multi-item vector' do let(:last_item) { 'B' } let(:values) { %w[A B] } include_examples 'checking values' end [31, 32, 33, 1023, 1024, 1025].each do |size| context "with a #{size}-item vector" do let(:last_item) { size } let(:values) { (1..size).to_a } include_examples 'checking values' end end end end immutable-ruby-master/spec/lib/immutable/vector/delete_spec.rb0000644000175000017500000000166514201005456024452 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#delete' do it 'removes elements that are #== to the argument' do V[1,2,3].delete(1).should eql(V[2,3]) V[1,2,3].delete(2).should eql(V[1,3]) V[1,2,3].delete(3).should eql(V[1,2]) V[1,2,3].delete(0).should eql(V[1,2,3]) V['a','b','a','c','a','a','d'].delete('a').should eql(V['b','c','d']) V[EqualNotEql.new, EqualNotEql.new].delete(:something).should eql(V.empty) V[EqlNotEqual.new, EqlNotEqual.new].delete(:something).should_not be_empty end context 'on an empty vector' do it 'returns self' do V.empty.delete(1).should be(V.empty) end end context 'on a subclass of Vector' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.delete(1).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/slice_spec.rb0000644000175000017500000002572414201005456024311 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[1,2,3,4] } let(:big) { V.new(1..10000) } [:slice, :[]].each do |method| describe "##{method}" do context 'when passed a positive integral index' do it 'returns the element at that index' do vector.send(method, 0).should be(1) vector.send(method, 1).should be(2) vector.send(method, 2).should be(3) vector.send(method, 3).should be(4) vector.send(method, 4).should be(nil) vector.send(method, 10).should be(nil) big.send(method, 0).should be(1) big.send(method, 9999).should be(10000) end it 'leaves the original unchanged' do vector.should eql(V[1,2,3,4]) end end context 'when passed a negative integral index' do it 'returns the element which is number (index.abs) counting from the end of the vector' do vector.send(method, -1).should be(4) vector.send(method, -2).should be(3) vector.send(method, -3).should be(2) vector.send(method, -4).should be(1) vector.send(method, -5).should be(nil) vector.send(method, -10).should be(nil) big.send(method, -1).should be(10000) big.send(method, -10000).should be(1) end end context 'when passed a positive integral index and count' do it "returns 'count' elements starting from 'index'" do vector.send(method, 0, 0).should eql(V.empty) vector.send(method, 0, 1).should eql(V[1]) vector.send(method, 0, 2).should eql(V[1,2]) vector.send(method, 0, 4).should eql(V[1,2,3,4]) vector.send(method, 0, 6).should eql(V[1,2,3,4]) vector.send(method, 0, -1).should be_nil vector.send(method, 0, -2).should be_nil vector.send(method, 0, -4).should be_nil vector.send(method, 2, 0).should eql(V.empty) vector.send(method, 2, 1).should eql(V[3]) vector.send(method, 2, 2).should eql(V[3,4]) vector.send(method, 2, 4).should eql(V[3,4]) vector.send(method, 2, -1).should be_nil vector.send(method, 4, 0).should eql(V.empty) vector.send(method, 4, 2).should eql(V.empty) vector.send(method, 4, -1).should be_nil vector.send(method, 5, 0).should be_nil vector.send(method, 5, 2).should be_nil vector.send(method, 5, -1).should be_nil vector.send(method, 6, 0).should be_nil vector.send(method, 6, 2).should be_nil vector.send(method, 6, -1).should be_nil big.send(method, 0, 3).should eql(V[1,2,3]) big.send(method, 1023, 4).should eql(V[1024,1025,1026,1027]) big.send(method, 1024, 4).should eql(V[1025,1026,1027,1028]) end it 'leaves the original unchanged' do vector.should eql(V[1,2,3,4]) end end context 'when passed a negative integral index and count' do it "returns 'count' elements, starting from index which is number 'index.abs' counting from the end of the array" do vector.send(method, -1, 0).should eql(V.empty) vector.send(method, -1, 1).should eql(V[4]) vector.send(method, -1, 2).should eql(V[4]) vector.send(method, -1, -1).should be_nil vector.send(method, -2, 0).should eql(V.empty) vector.send(method, -2, 1).should eql(V[3]) vector.send(method, -2, 2).should eql(V[3,4]) vector.send(method, -2, 4).should eql(V[3,4]) vector.send(method, -2, -1).should be_nil vector.send(method, -4, 0).should eql(V.empty) vector.send(method, -4, 1).should eql(V[1]) vector.send(method, -4, 2).should eql(V[1,2]) vector.send(method, -4, 4).should eql(V[1,2,3,4]) vector.send(method, -4, 6).should eql(V[1,2,3,4]) vector.send(method, -4, -1).should be_nil vector.send(method, -5, 0).should be_nil vector.send(method, -5, 1).should be_nil vector.send(method, -5, 10).should be_nil vector.send(method, -5, -1).should be_nil big.send(method, -1, 1).should eql(V[10000]) big.send(method, -1, 2).should eql(V[10000]) big.send(method, -6, 2).should eql(V[9995,9996]) end end context 'when passed a Range' do it 'returns the elements whose indexes are within the given Range' do vector.send(method, 0..-1).should eql(V[1,2,3,4]) vector.send(method, 0..-10).should eql(V.empty) vector.send(method, 0..0).should eql(V[1]) vector.send(method, 0..1).should eql(V[1,2]) vector.send(method, 0..2).should eql(V[1,2,3]) vector.send(method, 0..3).should eql(V[1,2,3,4]) vector.send(method, 0..4).should eql(V[1,2,3,4]) vector.send(method, 0..10).should eql(V[1,2,3,4]) vector.send(method, 2..-10).should eql(V.empty) vector.send(method, 2..0).should eql(V.empty) vector.send(method, 2..2).should eql(V[3]) vector.send(method, 2..3).should eql(V[3,4]) vector.send(method, 2..4).should eql(V[3,4]) vector.send(method, 3..0).should eql(V.empty) vector.send(method, 3..3).should eql(V[4]) vector.send(method, 3..4).should eql(V[4]) vector.send(method, 4..0).should eql(V.empty) vector.send(method, 4..4).should eql(V.empty) vector.send(method, 4..5).should eql(V.empty) vector.send(method, 5..0).should be_nil vector.send(method, 5..5).should be_nil vector.send(method, 5..6).should be_nil big.send(method, 159..162).should eql(V[160,161,162,163]) big.send(method, 160..162).should eql(V[161,162,163]) big.send(method, 161..162).should eql(V[162,163]) big.send(method, 9999..10100).should eql(V[10000]) big.send(method, 10000..10100).should eql(V.empty) big.send(method, 10001..10100).should be_nil vector.send(method, 0...-1).should eql(V[1,2,3]) vector.send(method, 0...-10).should eql(V.empty) vector.send(method, 0...0).should eql(V.empty) vector.send(method, 0...1).should eql(V[1]) vector.send(method, 0...2).should eql(V[1,2]) vector.send(method, 0...3).should eql(V[1,2,3]) vector.send(method, 0...4).should eql(V[1,2,3,4]) vector.send(method, 0...10).should eql(V[1,2,3,4]) vector.send(method, 2...-10).should eql(V.empty) vector.send(method, 2...0).should eql(V.empty) vector.send(method, 2...2).should eql(V.empty) vector.send(method, 2...3).should eql(V[3]) vector.send(method, 2...4).should eql(V[3,4]) vector.send(method, 3...0).should eql(V.empty) vector.send(method, 3...3).should eql(V.empty) vector.send(method, 3...4).should eql(V[4]) vector.send(method, 4...0).should eql(V.empty) vector.send(method, 4...4).should eql(V.empty) vector.send(method, 4...5).should eql(V.empty) vector.send(method, 5...0).should be_nil vector.send(method, 5...5).should be_nil vector.send(method, 5...6).should be_nil big.send(method, 159...162).should eql(V[160,161,162]) big.send(method, 160...162).should eql(V[161,162]) big.send(method, 161...162).should eql(V[162]) big.send(method, 9999...10100).should eql(V[10000]) big.send(method, 10000...10100).should eql(V.empty) big.send(method, 10001...10100).should be_nil vector.send(method, -1..-1).should eql(V[4]) vector.send(method, -1...-1).should eql(V.empty) vector.send(method, -1..3).should eql(V[4]) vector.send(method, -1...3).should eql(V.empty) vector.send(method, -1..4).should eql(V[4]) vector.send(method, -1...4).should eql(V[4]) vector.send(method, -1..10).should eql(V[4]) vector.send(method, -1...10).should eql(V[4]) vector.send(method, -1..0).should eql(V.empty) vector.send(method, -1..-4).should eql(V.empty) vector.send(method, -1...-4).should eql(V.empty) vector.send(method, -1..-6).should eql(V.empty) vector.send(method, -1...-6).should eql(V.empty) vector.send(method, -2..-2).should eql(V[3]) vector.send(method, -2...-2).should eql(V.empty) vector.send(method, -2..-1).should eql(V[3,4]) vector.send(method, -2...-1).should eql(V[3]) vector.send(method, -2..10).should eql(V[3,4]) vector.send(method, -2...10).should eql(V[3,4]) big.send(method, -1..-1).should eql(V[10000]) big.send(method, -1..9999).should eql(V[10000]) big.send(method, -1...9999).should eql(V.empty) big.send(method, -2...9999).should eql(V[9999]) big.send(method, -2..-1).should eql(V[9999,10000]) vector.send(method, -4..-4).should eql(V[1]) vector.send(method, -4..-2).should eql(V[1,2,3]) vector.send(method, -4...-2).should eql(V[1,2]) vector.send(method, -4..-1).should eql(V[1,2,3,4]) vector.send(method, -4...-1).should eql(V[1,2,3]) vector.send(method, -4..3).should eql(V[1,2,3,4]) vector.send(method, -4...3).should eql(V[1,2,3]) vector.send(method, -4..4).should eql(V[1,2,3,4]) vector.send(method, -4...4).should eql(V[1,2,3,4]) vector.send(method, -4..0).should eql(V[1]) vector.send(method, -4...0).should eql(V.empty) vector.send(method, -4..1).should eql(V[1,2]) vector.send(method, -4...1).should eql(V[1]) vector.send(method, -5..-5).should be_nil vector.send(method, -5...-5).should be_nil vector.send(method, -5..-4).should be_nil vector.send(method, -5..-1).should be_nil vector.send(method, -5..10).should be_nil big.send(method, -10001..-1).should be_nil end it 'leaves the original unchanged' do vector.should eql(V[1,2,3,4]) end end end context 'when passed a subclass of Range' do it 'works the same as with a Range' do subclass = Class.new(Range) vector.send(method, subclass.new(1,2)).should eql(V[2,3]) vector.send(method, subclass.new(-3,-1,true)).should eql(V[2,3]) end end context 'on a subclass of Vector' do it 'with index and count or a range, returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.send(method, 0, 0).class.should be(subclass) instance.send(method, 0, 2).class.should be(subclass) instance.send(method, 0..0).class.should be(subclass) instance.send(method, 1..-1).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/join_spec.rb0000644000175000017500000000275214201005456024145 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#join' do context 'with a separator' do [ [[], ''], [['A'], 'A'], [[DeterministicHash.new('A', 1), DeterministicHash.new('B', 2), DeterministicHash.new('C', 3)], 'A|B|C'] ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.join('|') vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.join('|').should == expected end end end end context 'without a separator' do [ [[], ''], [['A'], 'A'], [[DeterministicHash.new('A', 1), DeterministicHash.new('B', 2), DeterministicHash.new('C', 3)], 'ABC'] ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.join vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.join.should == expected end end end end context 'without a separator (with global default separator set)' do before { $, = '**' } after { $, = nil } describe 'on ["A", "B", "C"]' do it 'returns "A**B**C"' do V['A', 'B', 'C'].join.should == 'A**B**C' end end end end end immutable-ruby-master/spec/lib/immutable/vector/concat_spec.rb0000644000175000017500000000207714201005456024455 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:+, :concat].each do |method| describe "##{method}" do let(:vector) { V.new(1..100) } it 'preserves the original' do vector.concat([1,2,3]) vector.should eql(V.new(1..100)) end it 'appends the elements in the other enumerable' do vector.concat([1,2,3]).should eql(V.new((1..100).to_a + [1,2,3])) vector.concat(1..1000).should eql(V.new((1..100).to_a + (1..1000).to_a)) vector.concat(1..200).size.should == 300 vector.concat(vector).should eql(V.new((1..100).to_a * 2)) vector.concat(V.empty).should eql(vector) V.empty.concat(vector).should eql(vector) end [1, 31, 32, 33, 1023, 1024, 1025].each do |size| context "on a #{size}-item vector" do it 'works the same' do vector = V.new(1..size) result = vector.concat((size+1)..size+10) result.size.should == size + 10 result.should eql(V.new(1..(size+10))) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/each_with_index_spec.rb0000644000175000017500000000213414201005456026322 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#each_with_index' do describe 'with no block' do let(:vector) { V['A', 'B', 'C'] } it 'returns an Enumerator' do vector.each_with_index.class.should be(Enumerator) vector.each_with_index.to_a.should == [['A', 0], ['B', 1], ['C', 2]] end end [1, 2, 31, 32, 33, 1023, 1024, 1025].each do |size| context "on a #{size}-item vector" do describe 'with a block' do let(:vector) { V.new(1..size) } it 'returns self' do pairs = [] vector.each_with_index { |item, index| pairs << [item, index] }.should be(vector) end it 'iterates over the items in order' do pairs = [] vector.each_with_index { |item, index| pairs << [item, index] }.should be(vector) pairs.should == (1..size).zip(0..size.pred) end end end end context 'on an empty vector' do it "doesn't yield anything" do V.empty.each_with_index { |item, index| fail } end end end end immutable-ruby-master/spec/lib/immutable/vector/add_spec.rb0000644000175000017500000000360114201005456023730 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } [:add, :<<, :push].each do |method| describe "##{method}" do shared_examples 'checking adding values' do let(:added_vector) { V[*added_values] } it 'preserves the original' do original = vector vector.send(method, added_value) expect(original).to eq(vector) end it 'adds the item to the vector' do result = vector.send(method, added_value) expect(result).to eq(added_vector) expect(result.size).to eq(vector.size + 1) end end context 'with a empty vector adding a single item' do let(:values) { [] } let(:added_value) { 'A' } let(:added_values) { ['A'] } include_examples 'checking adding values' end context 'with a single-item vector adding a different item' do let(:values) { ['A'] } let(:added_value) { 'B' } let(:added_values) { %w[A B] } include_examples 'checking adding values' end context 'with a single-item vector adding a duplicate item' do let(:values) { ['A'] } let(:added_value) { 'A' } let(:added_values) { %w[A A] } include_examples 'checking adding values' end [31, 32, 33, 1023, 1024, 1025].each do |size| context "with a #{size}-item vector adding a different item" do let(:values) { (1..size).to_a } let(:added_value) { size+1 } let(:added_values) { (1..(size+1)).to_a } include_examples 'checking adding values' end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass[1,2,3] instance.add(4).class.should be(subclass) end end end end end immutable-ruby-master/spec/lib/immutable/vector/take_spec.rb0000644000175000017500000000210014201005456024115 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#take' do [ [[], 10, []], [['A'], 10, ['A']], [%w[A B C], 0, []], [%w[A B C], 2, %w[A B]], [(1..32), 1, [1]], [(1..33), 32, (1..32)], [(1..100), 40, (1..40)] ].each do |values, number, expected| describe "#{number} from #{values.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.take(number) vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.take(number).should eql(V[*expected]) end end end context 'when number of elements specified is identical to size' do let(:vector) { V[1, 2, 3, 4, 5, 6] } it 'returns self' do vector.take(vector.size).should be(vector) end end context 'when number of elements specified is bigger than size' do let(:vector) { V[1, 2, 3, 4, 5, 6] } it 'returns self' do vector.take(vector.size + 1).should be(vector) end end end end immutable-ruby-master/spec/lib/immutable/vector/values_at_spec.rb0000644000175000017500000000164514201005456025171 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#values_at' do let(:vector) { V['a', 'b', 'c'] } it 'accepts any number of indices, and returns a vector of items at those indices' do vector.values_at(0).should eql(V['a']) vector.values_at(1,2).should eql(V['b', 'c']) end context 'when passed invalid indices' do it 'fills in with nils' do vector.values_at(1,2,3).should eql(V['b', 'c', nil]) vector.values_at(-10,10).should eql(V[nil, nil]) end end context 'when passed no arguments' do it 'returns an empty vector' do vector.values_at.should eql(V.empty) end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.values_at(1,2).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/select_spec.rb0000644000175000017500000000360214201005456024460 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:select, :find_all].each do |method| describe "##{method}" do let(:vector) { V['A', 'B', 'C'] } describe 'with a block' do it 'preserves the original' do vector.send(method) { |item| item == 'A' } vector.should eql(V['A', 'B', 'C']) end it 'returns a vector with the matching values' do vector.send(method) { |item| item == 'A' }.should eql(V['A']) end end describe 'with no block' do it 'returns an Enumerator' do vector.send(method).class.should be(Enumerator) vector.send(method).each { |item| item == 'A' }.should eql(V['A']) end end describe 'when nothing matches' do it 'preserves the original' do vector.send(method) { |item| false } vector.should eql(V['A', 'B', 'C']) end it 'returns an empty vector' do vector.send(method) { |item| false }.should equal(V.empty) end end context 'on an empty vector' do it 'returns self' do V.empty.send(method) { |item| true }.should be(V.empty) end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass[1,2,3] instance.send(method) { |x| x > 1 }.class.should be(subclass) end end it 'works with a variety of inputs' do [1, 2, 10, 31, 32, 33, 1023, 1024, 1025].each do |size| [0, 5, 32, 50, 500, 800, 1024].each do |threshold| vector = V.new(1..size) result = vector.send(method) { |item| item <= threshold } result.size.should == [size, threshold].min result.should eql(V.new(1..[size, threshold].min)) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/uniq_spec.rb0000644000175000017500000000426314201005456024161 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#uniq' do let(:vector) { V['a', 'b', 'a', 'a', 'c', 'b'] } it 'returns a vector with no duplicates' do vector.uniq.should eql(V['a', 'b', 'c']) end it 'leaves the original unmodified' do vector.uniq vector.should eql(V['a', 'b', 'a', 'a', 'c', 'b']) end it 'uses #eql? semantics' do V[1.0, 1].uniq.should eql(V[1.0, 1]) end it 'also uses #hash when determining which values are duplicates' do x = double(1) x.should_receive(:hash).at_least(1).times.and_return(1) y = double(2) y.should_receive(:hash).at_least(1).times.and_return(2) V[x, y].uniq end it 'keeps the first of each group of duplicate values' do x, y, z = 'a', 'a', 'a' result = V[x, y, z].uniq result.size.should == 1 result[0].should be(x) end context 'when passed a block' do it 'uses the return value of the block to determine which items are duplicate' do v = V['a', 'A', 'B', 'b'] v.uniq(&:upcase).should == V['a', 'B'] end end context 'on a vector with no duplicates' do it 'returns an unchanged vector' do V[1, 2, 3].uniq.should eql(V[1, 2, 3]) end context 'if the vector has more than 32 elements and is initialized with Vector.new' do # Regression test for GitHub issue #182 it 'returns an unchanged vector' do vector1,vector2 = 2.times.collect { V.new(0..36) } vector1.uniq.should eql(vector2) end end end [10, 31, 32, 33, 1000, 1023, 1024, 1025, 2000].each do |size| context "on a #{size}-item vector" do it 'behaves like Array#uniq' do array = size.times.map { rand(size*2) } vector = V.new(array) result = vector.uniq result.should == array.uniq result.class.should be(Immutable::Vector) end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.uniq.class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/sorting_spec.rb0000644000175000017500000000305714201005456024672 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [ [:sort, ->(left, right) { left.length <=> right.length }], [:sort_by, ->(item) { item.length }], ].each do |method, comparator| describe "##{method}" do [ [[], []], [['A'], ['A']], [%w[Ichi Ni San], %w[Ni San Ichi]], ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } context 'with a block' do it 'preserves the original' do vector.send(method, &comparator) vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.send(method, &comparator).should eql(V[*expected]) end end context 'without a block' do it 'preserves the original' do vector.send(method) vector.should eql(V[*values]) end it "returns #{expected.sort.inspect}" do vector.send(method).should eql(V[*expected.sort]) end end end end [10, 31, 32, 33, 1023, 1024, 1025].each do |size| context "on a #{size}-item vector" do it "behaves like Array#{method}" do array = size.times.map { rand(10000) } vector = V.new(array) if method == :sort vector.sort.should == array.sort else vector.sort_by(&:-@).should == array.sort_by(&:-@) end end end end end end end immutable-ruby-master/spec/lib/immutable/vector/eql_spec.rb0000644000175000017500000000421614201005456023764 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#eql' do let(:vector) { V['A', 'B', 'C'] } it 'returns false when comparing with an array with the same contents' do vector.eql?(%w[A B C]).should == false end it 'returns false when comparing with an arbitrary object' do vector.eql?(Object.new).should == false end it 'returns false when comparing an empty vector with an empty array' do V.empty.eql?([]).should == false end it 'returns false when comparing with a subclass of Immutable::Vector' do vector.eql?(Class.new(Immutable::Vector).new(%w[A B C])).should == false end end describe '#==' do let(:vector) { V['A', 'B', 'C'] } it 'returns true when comparing with an array with the same contents' do (vector == %w[A B C]).should == true end it 'returns false when comparing with an arbitrary object' do (vector == Object.new).should == false end it 'returns true when comparing an empty vector with an empty array' do (V.empty == []).should == true end it 'returns true when comparing with a subclass of Immutable::Vector' do (vector == Class.new(Immutable::Vector).new(%w[A B C])).should == true end it 'works on larger vectors' do array = 2000.times.map { rand(10000) } (V.new(array.dup) == array).should == true end end [:eql?, :==].each do |method| describe "##{method}" do [ [[], [], true], [[], [nil], false], [['A'], [], false], [['A'], ['A'], true], [['A'], ['B'], false], [%w[A B], ['A'], false], [%w[A B C], %w[A B C], true], [%w[C A B], %w[A B C], false], ].each do |a, b, expected| describe "returns #{expected.inspect}" do let(:vector_a) { V[*a] } let(:vector_b) { V[*b] } it "for vectors #{a.inspect} and #{b.inspect}" do vector_a.send(method, vector_b).should == expected end it "for vectors #{b.inspect} and #{a.inspect}" do vector_b.send(method, vector_a).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/vector/rotate_spec.rb0000644000175000017500000000401314201005456024474 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#rotate' do let(:vector) { V[1,2,3,4,5] } context 'when passed no argument' do it 'returns a new vector with the first element moved to the end' do vector.rotate.should eql(V[2,3,4,5,1]) end end context 'with an integral argument n' do it 'returns a new vector with the first (n % size) elements moved to the end' do vector.rotate(2).should eql(V[3,4,5,1,2]) vector.rotate(3).should eql(V[4,5,1,2,3]) vector.rotate(4).should eql(V[5,1,2,3,4]) vector.rotate(5).should eql(V[1,2,3,4,5]) vector.rotate(-1).should eql(V[5,1,2,3,4]) end end context 'with a floating-point argument n' do it 'coerces the argument to integer using to_int' do vector.rotate(2.1).should eql(V[3,4,5,1,2]) end end context 'with a non-numeric argument' do it 'raises a TypeError' do -> { vector.rotate('hello') }.should raise_error(TypeError) end end context 'with an argument of zero' do it 'returns self' do vector.rotate(0).should be(vector) end end context "with an argument equal to the vector's size" do it 'returns self' do vector.rotate(5).should be(vector) end end [31, 32, 33, 1000, 1023, 1024, 1025].each do |size| context "on a #{size}-item vector" do it 'behaves like Array#rotate' do array = (1..size).to_a vector = V.new(array) 10.times do offset = rand(size) vector.rotate(offset).should == array.rotate(offset) end end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.rotate(2).class.should be(subclass) end end it 'leaves the original unmodified' do vector.rotate(3) vector.should eql(V[1,2,3,4,5]) end end end immutable-ruby-master/spec/lib/immutable/vector/include_spec.rb0000644000175000017500000000145514201005456024630 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:include?, :member?].each do |method| describe "##{method}" do [ [[], 'A', false], [[], nil, false], [['A'], 'A', true], [['A'], 'B', false], [['A'], nil, false], [['A', 'B', nil], 'A', true], [['A', 'B', nil], 'B', true], [['A', 'B', nil], nil, true], [['A', 'B', nil], 'C', false], [['A', 'B', false], false, true], [[2], 2, true], [[2], 2.0, true], [[2.0], 2.0, true], [[2.0], 2, true], ].each do |values, item, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].send(method, item).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/vector/group_by_spec.rb0000644000175000017500000000315714201005456025034 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#group_by' do context 'with a block' do [ [[], []], [[1], [true => V[1]]], [[1, 2, 3, 4], [true => V[1, 3], false => V[2, 4]]], ].each do |values, expected| context "on #{values.inspect}" do let(:vector) { V[*values] } it "returns #{expected.inspect}" do vector.group_by(&:odd?).should eql(H[*expected]) vector.should eql(V.new(values)) # make sure it hasn't changed end end end end context 'without a block' do [ [[], []], [[1], [1 => V[1]]], [[1, 2, 3, 4], [1 => V[1], 2 => V[2], 3 => V[3], 4 => V[4]]], ].each do |values, expected| context "on #{values.inspect}" do let(:vector) { V[*values] } it "returns #{expected.inspect}" do vector.group_by.should eql(H[*expected]) vector.should eql(V.new(values)) # make sure it hasn't changed end end end end context 'on an empty vector' do it 'returns an empty hash' do V.empty.group_by { |x| x }.should eql(H.empty) end end it 'returns a hash without default proc' do V[1,2,3].group_by { |x| x }.default_proc.should be_nil end context 'from a subclass' do it 'returns an Hash whose values are instances of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1, 'string', :symbol]) instance.group_by(&:class).values.each { |v| v.class.should be(subclass) } end end end end immutable-ruby-master/spec/lib/immutable/vector/clear_spec.rb0000644000175000017500000000136014201005456024266 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#clear' do [ [], ['A'], %w[A B C], ].each do |values| describe "on #{values}" do let(:vector) { V[*values] } it 'preserves the original' do vector.clear vector.should eql(V[*values]) end it 'returns an empty vector' do vector.clear.should equal(V.empty) end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new(%w{a b c}) instance.clear.class.should be(subclass) instance.clear.should be_empty end end end end end immutable-ruby-master/spec/lib/immutable/vector/to_list_spec.rb0000644000175000017500000000117214201005456024656 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#to_list' do [ [], ['A'], %w[A B C], ].each do |values| describe "on #{values.inspect}" do let(:vector) { V.new(values) } let(:list) { vector.to_list } it 'returns a list' do list.is_a?(Immutable::List).should == true end describe 'the returned list' do it 'has the correct length' do list.size.should == values.size end it 'contains all values' do list.to_a.should == values end end end end end end immutable-ruby-master/spec/lib/immutable/vector/repeated_combination_spec.rb0000644000175000017500000000506014201005456027354 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#repeated_combination' do let(:vector) { V[1,2,3,4] } context 'with no block' do it 'returns an Enumerator' do vector.repeated_combination(2).class.should be(Enumerator) end end context 'with a block' do it 'returns self' do vector.repeated_combination(2) {}.should be(vector) end end context 'with a negative argument' do it 'yields nothing and returns self' do result = [] vector.repeated_combination(-1) { |obj| result << obj }.should be(vector) result.should eql([]) end end context 'with a zero argument' do it 'yields an empty array' do result = [] vector.repeated_combination(0) { |obj| result << obj } result.should eql([[]]) end end context 'with a argument of 1' do it 'yields each item in the vector, as single-item vectors' do result = [] vector.repeated_combination(1) { |obj| result << obj } result.should eql([[1],[2],[3],[4]]) end end context 'on an empty vector, with an argument greater than zero' do it 'yields nothing' do result = [] V.empty.repeated_combination(1) { |obj| result << obj } result.should eql([]) end end context 'with a positive argument, greater than 1' do it 'yields all combinations of the given size (where a single element can appear more than once in a row)' do vector.repeated_combination(2).to_a.should == [[1,1], [1,2], [1,3], [1,4], [2,2], [2,3], [2,4], [3,3], [3,4], [4,4]] vector.repeated_combination(3).to_a.should == [[1,1,1], [1,1,2], [1,1,3], [1,1,4], [1,2,2], [1,2,3], [1,2,4], [1,3,3], [1,3,4], [1,4,4], [2,2,2], [2,2,3], [2,2,4], [2,3,3], [2,3,4], [2,4,4], [3,3,3], [3,3,4], [3,4,4], [4,4,4]] V[1,2,3].repeated_combination(3).to_a.should == [[1,1,1], [1,1,2], [1,1,3], [1,2,2], [1,2,3], [1,3,3], [2,2,2], [2,2,3], [2,3,3], [3,3,3]] end end it 'leaves the original unmodified' do vector.repeated_combination(2) {} vector.should eql(V[1,2,3,4]) end it 'behaves like Array#repeated_combination' do 0.upto(5) do |comb_size| array = 10.times.map { rand(1000) } V.new(array).repeated_combination(comb_size).to_a.should == array.repeated_combination(comb_size).to_a end array = 18.times.map { rand(1000) } V.new(array).repeated_combination(2).to_a.should == array.repeated_combination(2).to_a end end end immutable-ruby-master/spec/lib/immutable/vector/compact_spec.rb0000644000175000017500000000132714201005456024631 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#compact' do it 'returns a new Vector with all nils removed' do V[1, nil, 2, nil].compact.should eql(V[1, 2]) V[1, 2, 3].compact.should eql(V[1, 2, 3]) V[nil].compact.should eql(V.empty) end context 'on an empty vector' do it 'returns self' do V.empty.compact.should be(V.empty) end end it "doesn't remove false" do V[false].compact.should eql(V[false]) end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(V) instance = subclass[1, nil, 2] instance.compact.class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/zip_spec.rb0000644000175000017500000000344514201005456024010 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#zip' do let(:vector) { V[1,2,3,4] } context 'with a block' do it 'yields arrays of one corresponding element from each input sequence' do result = [] vector.zip(['a', 'b', 'c', 'd']) { |obj| result << obj } result.should eql([[1,'a'], [2,'b'], [3,'c'], [4,'d']]) end it 'fills in the missing values with nils' do result = [] vector.zip(['a', 'b']) { |obj| result << obj } result.should eql([[1,'a'], [2,'b'], [3,nil], [4,nil]]) end it 'returns nil' do vector.zip([2,3,4]) {}.should be_nil end it 'can handle multiple inputs, of different classes' do result = [] vector.zip(V[2,3,4,5], [5,6,7,8]) { |obj| result << obj } result.should eql([[1,2,5], [2,3,6], [3,4,7], [4,5,8]]) end end context 'without a block' do it 'returns a vector of arrays (one corresponding element from each input sequence)' do vector.zip([2,3,4,5]).should eql(V[[1,2], [2,3], [3,4], [4,5]]) end end [10, 31, 32, 33, 1000, 1023, 1024, 1025].each do |size| context "on #{size}-item vectors" do it 'behaves like Array#zip' do array = (rand(9)+1).times.map { size.times.map { rand(10000) }} vectors = array.map { |a| V.new(a) } result = vectors.first.zip(*vectors.drop(1)) result.class.should be(Immutable::Vector) result.should == array[0].zip(*array.drop(1)) end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass.new([1,2,3]) instance.zip([4,5,6]).class.should be(subclass) end end end end immutable-ruby-master/spec/lib/immutable/vector/drop_while_spec.rb0000644000175000017500000000271614201005456025342 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#drop_while' do [ [[], []], [['A'], []], [%w[A B C], ['C']], ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } describe 'with a block' do let(:result) { vector.drop_while { |item| item < 'C' } } it 'preserves the original' do result vector.should eql(V[*values]) end it "returns #{expected.inspect}" do result.should eql(V[*expected]) end end describe 'without a block' do it 'returns an Enumerator' do vector.drop_while.class.should be(Enumerator) vector.drop_while.each { |item| item < 'C' }.should eql(V[*expected]) end end end end context 'on an empty vector' do it 'returns an empty vector' do V.empty.drop_while { false }.should eql(V.empty) end end it 'returns an empty vector if block is always true' do V.new(1..32).drop_while { true }.should eql(V.empty) V.new(1..100).drop_while { true }.should eql(V.empty) end it 'stops dropping items if block returns nil' do V[1, 2, 3, nil, 4, 5].drop_while { |x| x }.should eql(V[nil, 4, 5]) end it 'stops dropping items if block returns false' do V[1, 2, 3, false, 4, 5].drop_while { |x| x }.should eql(V[false, 4, 5]) end end end immutable-ruby-master/spec/lib/immutable/vector/fill_spec.rb0000644000175000017500000000540214201005456024127 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#fill' do let(:vector) { V[1, 2, 3, 4, 5, 6] } it 'can replace a range of items at the beginning of a vector' do vector.fill(:a, 0, 3).should eql(V[:a, :a, :a, 4, 5, 6]) end it 'can replace a range of items in the middle of a vector' do vector.fill(:a, 3, 2).should eql(V[1, 2, 3, :a, :a, 6]) end it 'can replace a range of items at the end of a vector' do vector.fill(:a, 4, 2).should eql(V[1, 2, 3, 4, :a, :a]) end it 'can replace all the items in a vector' do vector.fill(:a, 0, 6).should eql(V[:a, :a, :a, :a, :a, :a]) end it 'can fill past the end of the vector' do vector.fill(:a, 3, 6).should eql(V[1, 2, 3, :a, :a, :a, :a, :a, :a]) end context 'with 1 argument' do it 'replaces all the items in the vector by default' do vector.fill(:a).should eql(V[:a, :a, :a, :a, :a, :a]) end end context 'with 2 arguments' do it 'replaces up to the end of the vector by default' do vector.fill(:a, 4).should eql(V[1, 2, 3, 4, :a, :a]) end end context 'when index and length are 0' do it 'leaves the vector unmodified' do vector.fill(:a, 0, 0).should eql(vector) end end context 'when expanding a vector past boundary where vector trie needs to deepen' do it 'works the same' do vector.fill(:a, 32, 3).size.should == 35 vector.fill(:a, 32, 3).to_a.size.should == 35 end end [1000, 1023, 1024, 1025, 2000].each do |size| context "on a #{size}-item vector" do it 'works the same' do array = (0..size).to_a vector = V.new(array) [[:a, 0, 5], [:b, 31, 2], [:c, 32, 60], [:d, 1000, 20], [:e, 1024, 33], [:f, 1200, 35]].each do |obj, index, length| next if index > size vector = vector.fill(obj, index, length) array.fill(obj, index, length) vector.size.should == array.size ary = vector.to_a ary.size.should == vector.size ary.should eql(array) end end end end it 'behaves like Array#fill, on a variety of inputs' do 50.times do array = rand(100).times.map { rand(1000) } index = rand(array.size) length = rand(50) V.new(array).fill(:a, index, length).should == array.fill(:a, index, length) end 10.times do array = rand(100).times.map { rand(10000) } length = rand(100) V.new(array).fill(:a, array.size, length).should == array.fill(:a, array.size, length) end 10.times do array = rand(100).times.map { rand(10000) } V.new(array).fill(:a).should == array.fill(:a) end end end end immutable-ruby-master/spec/lib/immutable/vector/maximum_spec.rb0000644000175000017500000000143214201005456024655 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#max' do context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].max { |maximum, item| maximum.length <=> item.length }.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'San'], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].max.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/vector/insert_spec.rb0000644000175000017500000000404214201005456024504 0ustar boutilboutilrequire 'spec_helper' require 'pry' describe Immutable::Vector do describe '#insert' do let(:original) { V[1, 2, 3] } it 'can add items at the beginning of a vector' do vector = original.insert(0, :a, :b) vector.size.should be(5) vector.at(0).should be(:a) vector.at(2).should be(1) end it 'can add items in the middle of a vector' do vector = original.insert(1, :a, :b, :c) vector.size.should be(6) vector.to_a.should == [1, :a, :b, :c, 2, 3] end it 'can add items at the end of a vector' do vector = original.insert(3, :a, :b, :c) vector.size.should be(6) vector.to_a.should == [1, 2, 3, :a, :b, :c] end it 'can add items past the end of a vector' do vector = original.insert(6, :a, :b) vector.size.should be(8) vector.to_a.should == [1, 2, 3, nil, nil, nil, :a, :b] end it 'accepts a negative index, which counts back from the end of the vector' do vector = original.insert(-2, :a) vector.size.should be(4) vector.to_a.should == [1, :a, 2, 3] end it 'raises IndexError if a negative index is too great' do expect { original.insert(-4, :a) }.to raise_error(IndexError) end it 'works when adding an item past boundary when vector trie needs to deepen' do vector = original.insert(32, :a, :b) vector.size.should == 34 vector.to_a.size.should == 34 end it 'works when adding to an empty Vector' do V.empty.insert(0, :a).should eql(V[:a]) end it 'has the right size and contents after many insertions' do array = (1..4000).to_a # we use an Array as standard of correctness vector = Immutable::Vector.new(array) 100.times do items = rand(10).times.map { rand(10000) } index = rand(vector.size) vector = vector.insert(index, *items) array.insert(index, *items) vector.size.should == array.size ary = vector.to_a ary.size.should == vector.size ary.should eql(array) end end end end immutable-ruby-master/spec/lib/immutable/vector/length_spec.rb0000644000175000017500000000164114201005456024463 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#length' do let(:length) { vector.length } shared_examples 'checking size' do it 'returns the values' do expect(length).to eq(size) end end context 'with an empty vector' do let(:values) { [] } let(:size) { 0 } include_examples 'checking size' end context 'with a single item vector' do let(:values) { %w[A] } let(:size) { 1 } include_examples 'checking size' end context 'with a multi-item vector' do let(:values) { %w[A B] } let(:size) { 2 } include_examples 'checking size' end [31, 32, 33, 1023, 1024, 1025].each do |size| context "with a #{size}-item vector" do let(:values) { (1..size).to_a } let(:size) { size } include_examples 'checking size' end end end end immutable-ruby-master/spec/lib/immutable/vector/compare_spec.rb0000644000175000017500000000127614201005456024634 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#<=>' do [ [[], [1]], [[1], [2]], [[1], [1, 2]], [[2, 3, 4], [3, 4, 5]], [[[0]], [[1]]] ].each do |items1, items2| describe "with #{items1} and #{items2}" do it 'returns -1' do (V.new(items1) <=> V.new(items2)).should be(-1) end end describe "with #{items2} and #{items1}" do it 'returns 1' do (V.new(items2) <=> V.new(items1)).should be(1) end end describe "with #{items1} and #{items1}" do it 'returns 0' do (V.new(items1) <=> V.new(items1)).should be(0) end end end end end immutable-ruby-master/spec/lib/immutable/vector/first_spec.rb0000644000175000017500000000055714201005456024336 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#first' do [ [[], nil], [['A'], 'A'], [%w[A B C], 'A'], [(1..32), 1], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].first.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/vector/combination_spec.rb0000644000175000017500000000445014201005456025505 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#combination' do let(:vector) { V[1,2,3,4] } context 'with a block' do it 'returns self' do vector.combination(2) {}.should be(vector) end end context 'with no block' do it 'returns an Enumerator' do vector.combination(2).class.should be(Enumerator) vector.combination(2).to_a.should == vector.to_a.combination(2).to_a end end context 'when passed an argument which is out of bounds' do it 'yields nothing and returns self' do vector.combination(5) { fail }.should be(vector) vector.combination(-1) { fail }.should be(vector) end end context 'when passed an argument zero' do it 'yields an empty array' do result = [] vector.combination(0) { |obj| result << obj } result.should eql([[]]) end end context "when passed an argument equal to the vector's length" do it 'yields self as an array' do result = [] vector.combination(4) { |obj| result << obj } result.should eql([vector.to_a]) end end context 'when passed an argument 1' do it 'yields each item in the vector, as single-item vectors' do result = [] vector.combination(1) { |obj| result << obj } result.should eql([[1], [2], [3], [4]]) end end context 'when passed another integral argument' do it 'yields all combinations of the given length' do result = [] vector.combination(3) { |obj| result << obj } result.should eql([[1,2,3], [1,2,4], [1,3,4], [2,3,4]]) end end context 'on an empty vector' do it 'works the same' do V.empty.combination(0).to_a.should == [[]] V.empty.combination(1).to_a.should == [] end end it 'works on many combinations of input' do 0.upto(5) do |comb_size| array = 12.times.map { rand(1000) } V.new(array).combination(comb_size).to_a.should == array.combination(comb_size).to_a end array = 20.times.map { rand(1000) } V.new(array).combination(2).to_a.should == array.combination(2).to_a end it 'leaves the original unmodified' do vector.combination(2) {} vector.should eql(V[1,2,3,4]) end end end immutable-ruby-master/spec/lib/immutable/vector/count_spec.rb0000644000175000017500000000067014201005456024333 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#count' do it 'returns the number of elements' do V[:a, :b, :c].count.should == 3 end it 'returns the number of elements that equal the argument' do V[:a, :b, :b, :c].count(:b).should == 2 end it 'returns the number of element for which the block evaluates to true' do V[:a, :b, :c].count { |s| s != :b }.should == 2 end end end immutable-ruby-master/spec/lib/immutable/vector/set_spec.rb0000644000175000017500000001247214201005456024001 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[*values] } describe '#set' do context 'when empty' do let(:vector) { V.empty } it 'raises an error for index -1' do expect { vector.set(-1, :a) }.to raise_error end it 'allows indexes 0 and 1 to be set' do vector.set(0, :a).should eql(V[:a]) vector.set(1, :a).should eql(V[nil, :a]) end end context 'when not empty' do let(:vector) { V['A', 'B', 'C'] } context 'with a block' do context 'and a positive index' do context 'within the absolute bounds of the vector' do it 'passes the current value to the block' do vector.set(1) { |value| value.should == 'B' } end it 'replaces the value with the result of the block' do result = vector.set(1) { |value| 'FLIBBLE' } result.should eql(V['A', 'FLIBBLE', 'C']) end it 'supports to_proc methods' do result = vector.set(1, &:downcase) result.should eql(V['A', 'b', 'C']) end end context 'just past the end of the vector' do it 'passes nil to the block and adds a new value' do result = vector.set(3) { |value| value.should be_nil; 'D' } result.should eql(V['A', 'B', 'C', 'D']) end end context 'further outside the bounds of the vector' do it 'passes nil to the block, fills up missing nils, and adds a new value' do result = vector.set(5) { |value| value.should be_nil; 'D' } result.should eql(V['A', 'B', 'C', nil, nil, 'D']) end end end context 'and a negative index' do context 'within the absolute bounds of the vector' do it 'passes the current value to the block' do vector.set(-2) { |value| value.should == 'B' } end it 'replaces the value with the result of the block' do result = vector.set(-2) { |value| 'FLIBBLE' } result.should eql(V['A', 'FLIBBLE', 'C']) end it 'supports to_proc methods' do result = vector.set(-2, &:downcase) result.should eql(V['A', 'b', 'C']) end end context 'outside the absolute bounds of the vector' do it 'raises an error' do expect { vector.set(-vector.size.next) {} }.to raise_error end end end end context 'with a value' do context 'and a positive index' do context 'within the absolute bounds of the vector' do let(:set) { vector.set(1, 'FLIBBLE') } it 'preserves the original' do vector.should eql(V['A', 'B', 'C']) end it 'sets the new value at the specified index' do set.should eql(V['A', 'FLIBBLE', 'C']) end end context 'just past the end of the vector' do it 'adds a new value' do result = vector.set(3, 'FLIBBLE') result.should eql(V['A', 'B', 'C', 'FLIBBLE']) end end context 'outside the absolute bounds of the vector' do it 'fills up with nils' do result = vector.set(5, 'FLIBBLE') result.should eql(V['A', 'B', 'C', nil, nil, 'FLIBBLE']) end end end context 'with a negative index' do let(:set) { vector.set(-2, 'FLIBBLE') } it 'preserves the original' do set vector.should eql(V['A', 'B', 'C']) end it 'sets the new value at the specified index' do set.should eql(V['A', 'FLIBBLE', 'C']) end end context 'outside the absolute bounds of the vector' do it 'raises an error' do expect { vector.set(-vector.size.next, 'FLIBBLE') }.to raise_error end end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass[1,2,3] instance.set(1, 2.5).class.should be(subclass) end end [10, 31, 32, 33, 1000, 1023, 1024, 1025, 2000].each do |size| context "on a #{size}-item vector" do it 'works correctly' do array = (1..size).to_a vector = V.new(array) [0, 1, 10, 31, 32, 33, 100, 500, 1000, 1023, 1024, 1025, 1998, 1999].select { |n| n < size }.each do |i| value = rand(10000) array[i] = value vector = vector.set(i, value) vector[i].should be(value) end 0.upto(size-1) do |i| vector.get(i).should == array[i] end end end end context 'with an identical value to an existing item' do [1, 2, 5, 31,32, 33, 100, 200].each do |size| context "on a #{size}-item vector" do let(:array) { (0...size).map { |x| x * x} } let(:vector) { V.new(array) } it 'returns self' do (0...size).each do |index| vector.set(index, index * index).should equal(vector) end end end end end end end immutable-ruby-master/spec/lib/immutable/vector/reduce_spec.rb0000644000175000017500000000277414201005456024461 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:reduce, :inject].each do |method| describe "##{method}" do [ [[], 10, 10], [[1], 10, 9], [[1, 2, 3], 10, 4], ].each do |values, initial, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } describe "with an initial value of #{initial}" do describe 'and a block' do it "returns #{expected.inspect}" do vector.send(method, initial) { |memo, item| memo - item }.should == expected end end end end end [ [[], nil], [[1], 1], [[1, 2, 3], -4], ].each do |values, expected| describe "on #{values.inspect}" do let(:vector) { V[*values] } describe 'with no initial value' do describe 'and a block' do it "returns #{expected.inspect}" do vector.send(method) { |memo, item| memo - item }.should == expected end end end end end describe 'with no block and a symbol argument' do it 'uses the symbol as the name of a method to reduce with' do V[1, 2, 3].send(method, :+).should == 6 end end describe 'with no block and a string argument' do it 'uses the string as the name of a method to reduce with' do V[1, 2, 3].send(method, '+').should == 6 end end end end end immutable-ruby-master/spec/lib/immutable/vector/shift_spec.rb0000644000175000017500000000106714201005456024321 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#shift' do [ [[], []], [['A'], []], [%w[A B C], %w[B C]], [1..31, 2..31], [1..32, 2..32], [1..33, 2..33] ].each do |values, expected| context "on #{values.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.shift vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.shift.should eql(V[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/vector/sum_spec.rb0000644000175000017500000000052714201005456024010 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#sum' do [ [[], 0], [[2], 2], [[1, 3, 5, 7, 11], 27], ].each do |values, expected| describe "on #{values.inspect}" do it "returns #{expected.inspect}" do V[*values].sum.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/vector/rindex_spec.rb0000644000175000017500000000175314201005456024477 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#rindex' do let(:vector) { V[1,2,3,3,2,1] } context 'when passed an object present in the vector' do it 'returns the last index where the object is present' do vector.rindex(1).should be(5) vector.rindex(2).should be(4) vector.rindex(3).should be(3) end end context 'when passed an object not present in the vector' do it 'returns nil' do vector.rindex(0).should be_nil vector.rindex(nil).should be_nil vector.rindex('string').should be_nil end end context 'with a block' do it 'returns the last index of an object which the predicate is true for' do vector.rindex { |n| n > 2 }.should be(3) end end context 'without an argument OR block' do it 'returns an Enumerator' do vector.rindex.class.should be(Enumerator) vector.rindex.each { |n| n > 2 }.should be(3) end end end end immutable-ruby-master/spec/lib/immutable/vector/map_spec.rb0000644000175000017500000000260614201005456023761 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:map, :collect].each do |method| describe "##{method}" do context 'when empty' do let(:vector) { V.empty } it 'returns self' do vector.send(method) {}.should equal(vector) end end context 'when not empty' do let(:vector) { V['A', 'B', 'C'] } context 'with a block' do it 'preserves the original values' do vector.send(method, &:downcase) vector.should eql(V['A', 'B', 'C']) end it 'returns a new vector with the mapped values' do vector.send(method, &:downcase).should eql(V['a', 'b', 'c']) end end context 'with no block' do it 'returns an Enumerator' do vector.send(method).class.should be(Enumerator) vector.send(method).each(&:downcase).should eql(V['a', 'b', 'c']) end end end context 'from a subclass' do it 'returns an instance of the subclass' do subclass = Class.new(Immutable::Vector) instance = subclass[1,2,3] instance.map { |x| x + 1 }.class.should be(subclass) end end context 'on a large vector' do it 'works' do V.new(1..2000).map { |x| x * 2 }.should eql(V.new((1..2000).map { |x| x * 2})) end end end end end immutable-ruby-master/spec/lib/immutable/vector/reverse_each_spec.rb0000644000175000017500000000155414201005456025640 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#reverse_each' do [2, 31, 32, 33, 1000, 1024, 1025, 2000].each do |size| context "on a #{size}-item vector" do let(:vector) { V[1..size] } context 'with a block (internal iteration)' do it 'returns self' do vector.reverse_each {}.should be(vector) end it 'yields all items in the opposite order as #each' do result = [] vector.reverse_each { |item| result << item } result.should eql(vector.to_a.reverse) end end context 'with no block' do it 'returns an Enumerator' do result = vector.reverse_each result.class.should be(Enumerator) result.to_a.should eql(vector.to_a.reverse) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/assoc_spec.rb0000644000175000017500000000236414201005456024315 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do let(:vector) { V[[:a, 3], [:b, 2], [:c, 1]] } describe '#assoc' do it 'searches for a 2-element array with a given 1st item' do vector.assoc(:b).should == [:b, 2] end it 'returns nil if a matching 1st item is not found' do vector.assoc(:d).should be_nil end it 'uses #== to compare 1st items with provided object' do vector.assoc(EqualNotEql.new).should_not be_nil vector.assoc(EqlNotEqual.new).should be_nil end it 'skips elements which are not indexable' do V[false, true, nil].assoc(:b).should be_nil V[[1,2], nil].assoc(3).should be_nil end end describe '#rassoc' do it 'searches for a 2-element array with a given 2nd item' do vector.rassoc(1).should == [:c, 1] end it 'returns nil if a matching 2nd item is not found' do vector.rassoc(4).should be_nil end it 'uses #== to compare 2nd items with provided object' do vector.rassoc(EqualNotEql.new).should_not be_nil vector.rassoc(EqlNotEqual.new).should be_nil end it 'skips elements which are not indexable' do V[false, true, nil].rassoc(:b).should be_nil V[[1,2], nil].rassoc(3).should be_nil end end end immutable-ruby-master/spec/lib/immutable/vector/unshift_spec.rb0000644000175000017500000000127114201005456024661 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#unshift' do [ [[], 'A', ['A']], [['A'], 'B', %w[B A]], [['A'], 'A', %w[A A]], [%w[A B C], 'D', %w[D A B C]], [1..31, 0, 0..31], [1..32, 0, 0..32], [1..33, 0, 0..33] ].each do |values, new_value, expected| context "on #{values.inspect} with #{new_value.inspect}" do let(:vector) { V[*values] } it 'preserves the original' do vector.unshift(new_value) vector.should eql(V[*values]) end it "returns #{expected.inspect}" do vector.unshift(new_value).should eql(V[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/vector/bsearch_spec.rb0000644000175000017500000000364714201005456024621 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#bsearch' do let(:vector) { V[5,10,20,30] } context 'with a block which returns false for elements below desired position, and true for those at/above' do it 'returns the first element for which the predicate is true' do vector.bsearch { |x| x > 10 }.should be(20) vector.bsearch { |x| x > 1 }.should be(5) vector.bsearch { |x| x > 25 }.should be(30) end context 'if the block always returns false' do it 'returns nil' do vector.bsearch { false }.should be_nil end end context 'if the block always returns true' do it 'returns the first element' do vector.bsearch { true }.should be(5) end end end context 'with a block which returns a negative number for elements below desired position, zero for the right element, and positive for those above' do it 'returns the element for which the block returns zero' do vector.bsearch { |x| x <=> 10 }.should be(10) end context 'if the block always returns positive' do it 'returns nil' do vector.bsearch { 1 }.should be_nil end end context 'if the block always returns negative' do it 'returns nil' do vector.bsearch { -1 }.should be_nil end end context 'if the block returns sometimes positive, sometimes negative, but never zero' do it 'returns nil' do vector.bsearch { |x| x <=> 11 }.should be_nil end end context 'if not passed a block' do it 'returns an Enumerator' do enum = vector.bsearch enum.should be_a(Enumerator) enum.each { |x| x <=> 10 }.should == 10 end end end context 'on an empty vector' do it 'returns nil' do V.empty.bsearch { |x| x > 5 }.should be_nil end end end end immutable-ruby-master/spec/lib/immutable/vector/copying_spec.rb0000644000175000017500000000056114201005456024652 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do [:dup, :clone].each do |method| [ [], ['A'], %w[A B C], (1..32), ].each do |values| describe "on #{values.inspect}" do let(:vector) { V[*values] } it 'returns self' do vector.send(method).should equal(vector) end end end end end immutable-ruby-master/spec/lib/immutable/vector/partition_spec.rb0000644000175000017500000000270214201005456025212 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#partition' do [ [[], [], []], [[1], [1], []], [[1, 2], [1], [2]], [[1, 2, 3], [1, 3], [2]], [[1, 2, 3, 4], [1, 3], [2, 4]], [[2, 3, 4], [3], [2, 4]], [[3, 4], [3], [4]], [[4], [], [4]], ].each do |values, expected_matches, expected_remainder| describe "on #{values.inspect}" do let(:vector) { V[*values] } describe 'with a block' do let(:result) { vector.partition(&:odd?) } let(:matches) { result.first } let(:remainder) { result.last } it 'preserves the original' do result vector.should eql(V[*values]) end it 'returns a frozen array with two items' do result.class.should be(Array) result.should be_frozen result.size.should be(2) end it 'correctly identifies the matches' do matches.should eql(V[*expected_matches]) end it 'correctly identifies the remainder' do remainder.should eql(V[*expected_remainder]) end end describe 'without a block' do it 'returns an Enumerator' do vector.partition.class.should be(Enumerator) vector.partition.each(&:odd?).should eql([V.new(expected_matches), V.new(expected_remainder)]) end end end end end end immutable-ruby-master/spec/lib/immutable/vector/to_set_spec.rb0000644000175000017500000000060614201005456024477 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#to_set' do [ [], ['A'], %w[A B C], (1..10), (1..32), (1..33), (1..1000) ].each do |values| describe "on #{values.inspect}" do it 'returns a set with the same values' do V[*values].to_set.should eql(S[*values]) end end end end end immutable-ruby-master/spec/lib/immutable/vector/repeated_permutation_spec.rb0000644000175000017500000000652114201005456027424 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#repeated_permutation' do let(:vector) { V[1,2,3,4] } context 'without a block' do context 'and without argument' do it 'returns an Enumerator of all repeated permutations' do vector.repeated_permutation.class.should be(Enumerator) vector.repeated_permutation.to_a.sort.should eql(vector.to_a.repeated_permutation(4).to_a.sort) end end context 'with an integral argument' do it 'returns an Enumerator of all repeated permutations of the given length' do vector.repeated_permutation(2).class.should be(Enumerator) vector.repeated_permutation(2).to_a.sort.should eql(vector.to_a.repeated_permutation(2).to_a.sort) vector.repeated_permutation(3).class.should be(Enumerator) vector.repeated_permutation(3).to_a.sort.should eql(vector.to_a.repeated_permutation(3).to_a.sort) end end end context 'with a block' do it 'returns self' do vector.repeated_permutation {}.should be(vector) end context 'on an empty vector' do it 'yields the empty permutation' do yielded = [] V.empty.repeated_permutation { |obj| yielded << obj } yielded.should eql([[]]) end end context 'with an argument of zero' do it 'yields the empty permutation' do yielded = [] vector.repeated_permutation(0) { |obj| yielded << obj } yielded.should eql([[]]) end end context 'with no argument' do it 'yields all repeated permutations' do yielded = [] V[1,2,3].repeated_permutation { |obj| yielded << obj } yielded.sort.should eql([[1,1,1], [1,1,2], [1,1,3], [1,2,1], [1,2,2], [1,2,3], [1,3,1], [1,3,2], [1,3,3], [2,1,1], [2,1,2], [2,1,3], [2,2,1], [2,2,2], [2,2,3], [2,3,1], [2,3,2], [2,3,3], [3,1,1], [3,1,2], [3,1,3], [3,2,1], [3,2,2], [3,2,3], [3,3,1], [3,3,2], [3,3,3]]) end end context 'with a positive integral argument' do it 'yields all repeated permutations of the given length' do yielded = [] vector.repeated_permutation(2) { |obj| yielded << obj } yielded.sort.should eql([[1,1], [1,2], [1,3], [1,4], [2,1], [2,2], [2,3], [2,4], [3,1], [3,2], [3,3], [3,4], [4,1], [4,2], [4,3], [4,4]]) end end end it 'handles duplicate elements correctly' do V[10,11,10].repeated_permutation(2).sort.should eql([[10, 10], [10, 10], [10, 10], [10, 10], [10, 11], [10, 11], [11, 10], [11, 10], [11, 11]]) end it 'allows permutations larger than the number of elements' do V[1,2].repeated_permutation(3).sort.should eql( [[1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2], [2, 1, 1], [2, 1, 2], [2, 2, 1], [2, 2, 2]]) end it 'leaves the original unmodified' do vector.repeated_permutation(2) {} vector.should eql(V[1,2,3,4]) end it 'behaves like Array#repeated_permutation' do 10.times do array = rand(8).times.map { rand(10000) } vector = V.new(array) perm_size = array.size == 0 ? 0 : rand(array.size) array.repeated_permutation(perm_size).to_a.should == vector.repeated_permutation(perm_size).to_a end end end end immutable-ruby-master/spec/lib/immutable/vector/permutation_spec.rb0000644000175000017500000000566114201005456025557 0ustar boutilboutilrequire 'spec_helper' describe Immutable::Vector do describe '#permutation' do let(:vector) { V[1,2,3,4] } context 'without a block or arguments' do it 'returns an Enumerator of all permutations' do vector.permutation.class.should be(Enumerator) vector.permutation.to_a.should eql(vector.to_a.permutation.to_a) end end context 'without a block, but with integral argument' do it 'returns an Enumerator of all permutations of given length' do vector.permutation(2).class.should be(Enumerator) vector.permutation(2).to_a.should eql(vector.to_a.permutation(2).to_a) vector.permutation(3).class.should be(Enumerator) vector.permutation(3).to_a.should eql(vector.to_a.permutation(3).to_a) end end context 'with a block' do it 'returns self' do vector.permutation {}.should be(vector) end context 'and no argument' do it 'yields all permutations' do yielded = [] vector.permutation { |obj| yielded << obj } yielded.sort.should eql([[1,2,3,4], [1,2,4,3], [1,3,2,4], [1,3,4,2], [1,4,2,3], [1,4,3,2], [2,1,3,4], [2,1,4,3], [2,3,1,4], [2,3,4,1], [2,4,1,3], [2,4,3,1], [3,1,2,4], [3,1,4,2], [3,2,1,4], [3,2,4,1], [3,4,1,2], [3,4,2,1], [4,1,2,3], [4,1,3,2], [4,2,1,3], [4,2,3,1], [4,3,1,2], [4,3,2,1]]) end end context 'and an integral argument' do it 'yields all permutations of the given length' do yielded = [] vector.permutation(2) { |obj| yielded << obj } yielded.sort.should eql([[1,2], [1,3], [1,4], [2,1], [2,3], [2,4], [3,1], [3,2], [3,4], [4,1], [4,2], [4,3]]) end end end context 'on an empty vector' do it 'yields the empty permutation' do yielded = [] V.empty.permutation { |obj| yielded << obj } yielded.should eql([[]]) end end context 'with an argument of zero' do it 'yields the empty permutation' do yielded = [] vector.permutation(0) { |obj| yielded << obj } yielded.should eql([[]]) end end context 'with a length greater than the size of the vector' do it 'yields no permutations' do vector.permutation(5) { |obj| fail } end end it 'handles duplicate elements correctly' do V[1,2,3,1].permutation(2).sort.should eql([[1,1], [1,1], [1,2], [1,2], [1,3], [1,3], [2,1],[2,1],[2,3], [3,1],[3,1],[3,2]]) end it 'leaves the original unmodified' do vector.permutation(2) {} vector.should eql(V[1,2,3,4]) end it 'behaves like Array#permutation' do 10.times do array = rand(8).times.map { rand(10000) } vector = V.new(array) perm_size = array.size == 0 ? 0 : rand(array.size) array.permutation(perm_size).to_a.should == vector.permutation(perm_size).to_a end end end end immutable-ruby-master/spec/lib/immutable/list/0000755000175000017500000000000014201005456021312 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/list/to_ary_spec.rb0000644000175000017500000000154614201005456024154 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do let(:list) { L['A', 'B', 'C', 'D'] } describe '#to_ary' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.to_ary }.should_not raise_error end end context 'enables implicit conversion to' do it 'block parameters' do def func(&block) yield(list) end func do |a, b, *c| expect(a).to eq('A') expect(b).to eq('B') expect(c).to eq(%w[C D]) end end it 'method arguments' do def func(a, b, *c) expect(a).to eq('A') expect(b).to eq('B') expect(c).to eq(%w[C D]) end func(*list) end it 'works with splat' do array = *list expect(array).to eq(%w[A B C D]) end end end end immutable-ruby-master/spec/lib/immutable/list/at_spec.rb0000644000175000017500000000125214201005456023255 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#at' do context 'on a really big list' do let(:list) { BigList } it "doesn't run out of stack" do -> { list.at(STACK_OVERFLOW_DEPTH) }.should_not raise_error end end [ [[], 10, nil], [['A'], 10, nil], [%w[A B C], 0, 'A'], [%w[A B C], 2, 'C'], [%w[A B C], -1, 'C'], [%w[A B C], -2, 'B'], [%w[A B C], -4, nil] ].each do |values, number, expected| describe "#{values.inspect} with #{number}" do it "returns #{expected.inspect}" do L[*values].at(number).should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/delete_at_spec.rb0000644000175000017500000000074114201005456024601 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#delete_at' do let(:list) { L[1,2,3,4,5] } it 'removes the element at the specified index' do list.delete_at(0).should eql(L[2,3,4,5]) list.delete_at(2).should eql(L[1,2,4,5]) list.delete_at(-1).should eql(L[1,2,3,4]) end it 'makes no modification if the index is out of range' do list.delete_at(5).should eql(list) list.delete_at(-6).should eql(list) end end end immutable-ruby-master/spec/lib/immutable/list/size_spec.rb0000644000175000017500000000107614201005456023627 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:size, :length].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.size }.should_not raise_error end end [ [[], 0], [['A'], 1], [%w[A B C], 3], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].send(method).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/list/construction_spec.rb0000644000175000017500000000625714201005456025415 0ustar boutilboutilrequire 'spec_helper' describe Immutable do describe '.list' do context 'with no arguments' do it 'always returns the same instance' do L.empty.should equal(L.empty) end it 'returns an empty list' do L.empty.should be_empty end end context 'with a number of items' do it 'always returns a different instance' do L['A', 'B', 'C'].should_not equal(L['A', 'B', 'C']) end it 'is the same as repeatedly using #cons' do L['A', 'B', 'C'].should eql(L.empty.cons('C').cons('B').cons('A')) end end end describe '.stream' do context 'with no block' do it 'returns an empty list' do Immutable.stream.should eql(L.empty) end end context 'with a block' do let(:list) { count = 0; Immutable.stream { count += 1 }} it 'repeatedly calls the block' do list.take(5).should eql(L[1, 2, 3, 4, 5]) end end end describe '.interval' do context 'for numbers' do it 'is equivalent to a list with explicit values' do Immutable.interval(98, 102).should eql(L[98, 99, 100, 101, 102]) end end context 'for strings' do it 'is equivalent to a list with explicit values' do Immutable.interval('A', 'AA').should eql(L['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA']) end end end describe '.repeat' do it 'returns an infinite list with specified value for each element' do Immutable.repeat('A').take(5).should eql(L['A', 'A', 'A', 'A', 'A']) end end describe '.replicate' do it 'returns a list with the specified value repeated the specified number of times' do Immutable.replicate(5, 'A').should eql(L['A', 'A', 'A', 'A', 'A']) end end describe '.iterate' do it 'returns an infinite list where the first item is calculated by applying the block on the initial argument, the second item by applying the function on the previous result and so on' do Immutable.iterate(1) { |item| item * 2 }.take(10).should eql(L[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]) end end describe '.enumerate' do let(:enum) do Enumerator.new do |yielder| yielder << 1 yielder << 2 yielder << 3 raise 'list fully realized' end end let(:list) { Immutable.enumerate(enum) } it 'returns a list based on the values yielded from the enumerator' do expect(list.take(2)).to eq L[1, 2] end it 'realizes values as they are needed' do # this example shows that Lists are not as lazy as they could be # if Lists were fully lazy, you would have to take(4) to hit the exception expect { list.take(3).to_a }.to raise_exception(RuntimeError) end end describe '[]' do it 'takes a variable number of items and returns a list' do list = Immutable::List[1,2,3] list.should be_kind_of(Immutable::List) list.size.should be(3) list.to_a.should == [1,2,3] end it 'returns an empty list when called without arguments' do L[].should be_kind_of(Immutable::List) L[].should be_empty end end end immutable-ruby-master/spec/lib/immutable/list/drop_spec.rb0000644000175000017500000000126714201005456023623 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#drop' do it 'is lazy' do -> { Immutable.stream { fail }.drop(1) }.should_not raise_error end [ [[], 10, []], [['A'], 10, []], [['A'], -1, ['A']], [%w[A B C], 0, %w[A B C]], [%w[A B C], 2, ['C']], ].each do |values, number, expected| context "with #{number} from #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.drop(number) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.drop(number).should == L[*expected] end end end end end immutable-ruby-master/spec/lib/immutable/list/pop_spec.rb0000644000175000017500000000072114201005456023447 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do let(:list) { L[*values] } describe '#pop' do let(:pop) { list.pop } context 'with an empty list' do let(:values) { [] } it 'returns an empty list' do expect(pop).to eq(L.empty) end end context 'with a list with a few items' do let(:values) { %w[a b c] } it 'removes the last item' do expect(pop).to eq(L['a', 'b']) end end end end immutable-ruby-master/spec/lib/immutable/list/init_spec.rb0000644000175000017500000000115514201005456023616 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#init' do it 'is lazy' do -> { Immutable.stream { false }.init }.should_not raise_error end [ [[], []], [['A'], []], [%w[A B C], %w[A B]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.init list.should eql(L[*values]) end it "returns the list without the last element: #{expected.inspect}" do list.init.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/multithreading_spec.rb0000644000175000017500000000234514201005456025675 0ustar boutilboutilrequire 'spec_helper' require 'concurrent/atomics' describe Immutable::List do it 'ensures each node of a lazy list will only be realized on ONE thread, even when accessed by multiple threads' do counter = Concurrent::Atom.new(0) list = (1..10000).to_list.map { |x| counter.swap { |count| count + 1 }; x * 2 } threads = 10.times.collect do Thread.new do node = list node = node.tail until node.empty? end end threads.each(&:join) counter.value.should == 10000 list.sum.should == 100010000 end it "doesn't go into an infinite loop if lazy list block raises an exception" do list = (1..10).to_list.map { raise 'Oops!' } threads = 10.times.collect do Thread.new do -> { list.head }.should raise_error(RuntimeError) end end threads.each(&:join) end it "doesn't give horrendously bad performance if thread realizing the list sleeps" do start = Time.now list = (1..100).to_list.map { |x| sleep(0.001); x * 2 } threads = 10.times.collect do Thread.new do node = list node = node.tail until node.empty? end end threads.each(&:join) elapsed = Time.now - start elapsed.should_not > 0.3 end end immutable-ruby-master/spec/lib/immutable/list/chunk_spec.rb0000644000175000017500000000115214201005456023760 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#chunk' do it 'is lazy' do -> { Immutable.stream { fail }.chunk(2) }.should_not raise_error end [ [[], []], [['A'], [L['A']]], [%w[A B C], [L['A', 'B'], L['C']]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.chunk(2) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.chunk(2).should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/reject_spec.rb0000644000175000017500000000230414201005456024124 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:reject, :delete_if].each do |method| describe "##{method}" do it 'is lazy' do -> { Immutable.stream { fail }.send(method) { |item| false } }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B C]], [%w[A b C], %w[A C]], [%w[a b c], []], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it "returns #{expected.inspect}" do list.send(method) { |item| item == item.downcase }.should eql(L[*expected]) end it 'is lazy' do count = 0 list.send(method) do |item| count += 1 false end count.should <= 1 end end context 'without a block' do it 'returns an Enumerator' do list.send(method).class.should be(Enumerator) list.send(method).each { |item| item == item.downcase }.should eql(L[*expected]) end end end end end end end immutable-ruby-master/spec/lib/immutable/list/flatten_spec.rb0000644000175000017500000000123214201005456024304 0ustar boutilboutilrequire 'spec_helper' describe Immutable do describe '#flatten' do it 'is lazy' do -> { Immutable.stream { fail }.flatten }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B C]], [['A', L['B'], 'C'], %w[A B C]], [[L['A'], L['B'], L['C']], %w[A B C]], ].each do |values, expected| context "on #{values}" do let(:list) { L[*values] } it 'preserves the original' do list.flatten list.should eql(L[*values]) end it 'returns an empty list' do list.flatten.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/reverse_spec.rb0000644000175000017500000000140614201005456024325 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#reverse' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.reverse }.should_not raise_error end end it 'is lazy' do -> { Immutable.stream { fail }.reverse }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[A B C], %w[C B A]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.reverse(&:downcase) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.reverse(&:downcase).should == L[*expected] end end end end end immutable-ruby-master/spec/lib/immutable/list/tails_spec.rb0000644000175000017500000000116314201005456023766 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#tails' do it 'is lazy' do -> { Immutable.stream { fail }.tails }.should_not raise_error end [ [[], []], [['A'], [L['A']]], [%w[A B C], [L['A', 'B', 'C'], L['B', 'C'], L['C']]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.tails list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.tails.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/take_while_spec.rb0000644000175000017500000000220514201005456024764 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#take_while' do it 'is lazy' do -> { Immutable.stream { fail }.take_while { false } }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it "returns #{expected.inspect}" do list.take_while { |item| item < 'C' }.should eql(L[*expected]) end it 'preserves the original' do list.take_while { |item| item < 'C' } list.should eql(L[*values]) end it 'is lazy' do count = 0 list.take_while do |item| count += 1 true end count.should <= 1 end end context 'without a block' do it 'returns an Enumerator' do list.take_while.class.should be(Enumerator) list.take_while.each { |item| item < 'C' }.should eql(L[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/list/merge_spec.rb0000644000175000017500000000253614201005456023756 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do context 'without a comparator' do context 'on an empty list' do subject { L.empty } it 'returns an empty list' do subject.merge.should be_empty end end context 'on a single list' do let(:list) { L[1, 2, 3] } subject { L[list] } it 'returns the list' do subject.merge.should == list end end context 'with multiple lists' do subject { L[L[3, 6, 7, 8], L[1, 2, 4, 5, 9]] } it 'merges the lists based on natural sort order' do subject.merge.should == L[1, 2, 3, 4, 5, 6, 7, 8, 9] end end end context 'with a comparator' do context 'on an empty list' do subject { L.empty } it 'returns an empty list' do subject.merge { |a, b| fail('should never be called') }.should be_empty end end context 'on a single list' do let(:list) { L[1, 2, 3] } subject { L[list] } it 'returns the list' do subject.merge { |a, b| fail('should never be called') }.should == list end end context 'with multiple lists' do subject { L[L[8, 7, 6, 3], L[9, 5, 4, 2, 1]] } it 'merges the lists based on the specified comparator' do subject.merge { |a, b| b <=> a }.should == L[9, 8, 7, 6, 5, 4, 3, 2, 1] end end end end immutable-ruby-master/spec/lib/immutable/list/transpose_spec.rb0000644000175000017500000000133114201005456024665 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#transpose' do it 'takes a list of lists and returns a list of all the first elements, all the 2nd elements, and so on' do L[L[1, 'a'], L[2, 'b'], L[3, 'c']].transpose.should eql(L[L[1, 2, 3], L['a', 'b', 'c']]) L[L[1, 2, 3], L['a', 'b', 'c']].transpose.should eql(L[L[1, 'a'], L[2, 'b'], L[3, 'c']]) L[].transpose.should eql(L[]) L[L[]].transpose.should eql(L[]) L[L[], L[]].transpose.should eql(L[]) L[L[0]].transpose.should eql(L[L[0]]) L[L[0], L[1]].transpose.should eql(L[L[0, 1]]) end it 'only goes as far as the shortest list' do L[L[1,2,3], L[2]].transpose.should eql(L[L[1,2]]) end end end immutable-ruby-master/spec/lib/immutable/list/sample_spec.rb0000644000175000017500000000053114201005456024131 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#sample' do let(:list) { (1..10).to_list } it 'returns a randomly chosen item' do chosen = 100.times.map { list.sample } chosen.each { |item| list.include?(item).should == true } list.each { |item| chosen.include?(item).should == true } end end end immutable-ruby-master/spec/lib/immutable/list/any_spec.rb0000644000175000017500000000232614201005456023443 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#any?' do context 'on a really big list' do let(:list) { BigList } it "doesn't run out of stack" do -> { list.any? { false } }.should_not raise_error end end context 'when empty' do it 'with a block returns false' do L.empty.any? {}.should == false end it 'with no block returns false' do L.empty.any?.should == false end end context 'when not empty' do context 'with a block' do let(:list) { L['A', 'B', 'C', nil] } ['A', 'B', 'C', nil].each do |value| it "returns true if the block ever returns true (#{value.inspect})" do list.any? { |item| item == value }.should == true end end it 'returns false if the block always returns false' do list.any? { |item| item == 'D' }.should == false end end context 'with no block' do it 'returns true if any value is truthy' do L[nil, false, 'A', true].any?.should == true end it 'returns false if all values are falsey' do L[nil, false].any?.should == false end end end end end immutable-ruby-master/spec/lib/immutable/list/ltlt_spec.rb0000644000175000017500000000066714201005456023641 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#<<' do it 'adds an item onto the end of a list' do list = L['a', 'b'] (list << 'c').should eql(L['a', 'b', 'c']) list.should eql(L['a', 'b']) end context 'on an empty list' do it 'returns a list with one item' do list = L.empty (list << 'c').should eql(L['c']) list.should eql(L.empty) end end end end immutable-ruby-master/spec/lib/immutable/list/minimum_spec.rb0000644000175000017500000000164714201005456024334 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#min' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.min }.should_not raise_error end end context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ni'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].min { |minimum, item| minimum.length <=> item.length }.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].min.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/list/inspect_spec.rb0000644000175000017500000000133214201005456024315 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#inspect' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.inspect }.should_not raise_error end end [ [[], 'Immutable::List[]'], [['A'], 'Immutable::List["A"]'], [%w[A B C], 'Immutable::List["A", "B", "C"]'] ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it "returns #{expected.inspect}" do list.inspect.should == expected end it "returns a string which can be eval'd to get an equivalent object" do eval(list.inspect).should eql(list) end end end end end immutable-ruby-master/spec/lib/immutable/list/product_spec.rb0000644000175000017500000000076414201005456024340 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#product' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.product }.should_not raise_error end end [ [[], 1], [[2], 2], [[1, 3, 5, 7, 11], 1155], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].product.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/flat_map_spec.rb0000644000175000017500000000244414201005456024440 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do let(:list) { L[*values] } describe '#flat_map' do let(:block) { ->(item) { [item, item + 1, item * item] } } let(:flat_map) { list.flat_map(&block) } let(:flattened_list) { L[*flattened_values] } shared_examples 'checking flattened result' do it 'returns the flattened values as a Immutable::List' do expect(flat_map).to eq(flattened_list) end it 'returns a Immutable::List' do expect(flat_map).to be_a(Immutable::List) end end context 'with an empty list' do let(:values) { [] } let(:flattened_values) { [] } include_examples 'checking flattened result' end context 'with a block that returns an empty list' do let(:block) { ->(item) { [] } } let(:values) { [1, 2, 3] } let(:flattened_values) { [] } include_examples 'checking flattened result' end context 'with a list of one item' do let(:values) { [7] } let(:flattened_values) { [7, 8, 49] } include_examples 'checking flattened result' end context 'with a list of multiple items' do let(:values) { [1, 2, 3] } let(:flattened_values) { [1, 2, 1, 2, 3, 4, 3, 4, 9] } include_examples 'checking flattened result' end end end immutable-ruby-master/spec/lib/immutable/list/find_spec.rb0000644000175000017500000000221514201005456023571 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:find, :detect].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.send(method) { false } }.should_not raise_error end end [ [[], 'A', nil], [[], nil, nil], [['A'], 'A', 'A'], [['A'], 'B', nil], [['A'], nil, nil], [['A', 'B', nil], 'A', 'A'], [['A', 'B', nil], 'B', 'B'], [['A', 'B', nil], nil, nil], [['A', 'B', nil], 'C', nil], ].each do |values, item, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it "returns #{expected.inspect}" do list.send(method) { |x| x == item }.should == expected end end context 'without a block' do it 'returns an Enumerator' do list.send(method).class.should be(Enumerator) list.send(method).each { |x| x == item }.should == expected end end end end end end end immutable-ruby-master/spec/lib/immutable/list/empty_spec.rb0000644000175000017500000000100314201005456024001 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#empty?' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.select(&:nil?).empty? }.should_not raise_error end end [ [[], true], [['A'], false], [%w[A B C], false], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].empty?.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/to_a_spec.rb0000644000175000017500000000164714201005456023603 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:to_a, :entries].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.to_a }.should_not raise_error end end [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do let(:list) { L[*values] } it "returns #{values.inspect}" do list.send(method).should == values end it 'leaves the original unchanged' do list.send(method) list.should eql(L[*values]) end it 'returns a mutable array' do result = list.send(method) expect(result.last).to_not eq('The End') result << 'The End' result.last.should == 'The End' end end end end end end immutable-ruby-master/spec/lib/immutable/list/each_spec.rb0000644000175000017500000000164514201005456023557 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#each' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.each { |item| } }.should_not raise_error end end [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it 'iterates over the items in order' do yielded = [] list.each { |item| yielded << item } yielded.should == values end it 'returns nil' do list.each { |item| item }.should be_nil end end context 'without a block' do it 'returns an Enumerator' do list.each.class.should be(Enumerator) Immutable::List[*list.each].should eql(list) end end end end end end immutable-ruby-master/spec/lib/immutable/list/intersperse_spec.rb0000644000175000017500000000120714201005456025214 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#intersperse' do it 'is lazy' do -> { Immutable.stream { fail }.intersperse('') }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[A B C], ['A', '|', 'B', '|', 'C']] ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.intersperse('|') list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.intersperse('|').should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/all_spec.rb0000644000175000017500000000250414201005456023422 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#all?' do context 'on a really big list' do let(:list) { BigList } it "doesn't run out of stack" do -> { list.all? }.should_not raise_error end end context 'when empty' do it 'with a block returns true' do L.empty.all? {}.should == true end it 'with no block returns true' do L.empty.all?.should == true end end context 'when not empty' do context 'with a block' do let(:list) { L['A', 'B', 'C'] } context 'if the block always returns true' do it 'returns true' do list.all? { |item| true }.should == true end end context 'if the block ever returns false' do it 'returns false' do list.all? { |item| item == 'D' }.should == false end end end context 'with no block' do context 'if all values are truthy' do it 'returns true' do L[true, 'A'].all?.should == true end end [nil, false].each do |value| context "if any value is #{value.inspect}" do it 'returns false' do L[value, true, 'A'].all?.should == false end end end end end end end immutable-ruby-master/spec/lib/immutable/list/last_spec.rb0000644000175000017500000000075114201005456023617 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#last' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.last }.should_not raise_error end end [ [[], nil], [['A'], 'A'], [%w[A B C], 'C'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].last.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/delete_spec.rb0000644000175000017500000000106414201005456024114 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#delete' do it 'removes elements that are #== to the argument' do L[1,2,3].delete(1).should eql(L[2,3]) L[1,2,3].delete(2).should eql(L[1,3]) L[1,2,3].delete(3).should eql(L[1,2]) L[1,2,3].delete(0).should eql(L[1,2,3]) L['a','b','a','c','a','a','d'].delete('a').should eql(L['b','c','d']) L[EqualNotEql.new, EqualNotEql.new].delete(:something).should eql(L[]) L[EqlNotEqual.new, EqlNotEqual.new].delete(:something).should_not be_empty end end end immutable-ruby-master/spec/lib/immutable/list/slice_spec.rb0000644000175000017500000002406014201005456023752 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do let(:list) { L[1,2,3,4] } let(:big) { (1..10000).to_list } [:slice, :[]].each do |method| describe "##{method}" do context 'when passed a positive integral index' do it 'returns the element at that index' do list.send(method, 0).should be(1) list.send(method, 1).should be(2) list.send(method, 2).should be(3) list.send(method, 3).should be(4) list.send(method, 4).should be(nil) list.send(method, 10).should be(nil) big.send(method, 0).should be(1) big.send(method, 9999).should be(10000) end it 'leaves the original unchanged' do list.should eql(L[1,2,3,4]) end end context 'when passed a negative integral index' do it 'returns the element which is number (index.abs) counting from the end of the list' do list.send(method, -1).should be(4) list.send(method, -2).should be(3) list.send(method, -3).should be(2) list.send(method, -4).should be(1) list.send(method, -5).should be(nil) list.send(method, -10).should be(nil) big.send(method, -1).should be(10000) big.send(method, -10000).should be(1) end end context 'when passed a positive integral index and count' do it "returns 'count' elements starting from 'index'" do list.send(method, 0, 0).should eql(L.empty) list.send(method, 0, 1).should eql(L[1]) list.send(method, 0, 2).should eql(L[1,2]) list.send(method, 0, 4).should eql(L[1,2,3,4]) list.send(method, 0, 6).should eql(L[1,2,3,4]) list.send(method, 0, -1).should be_nil list.send(method, 0, -2).should be_nil list.send(method, 0, -4).should be_nil list.send(method, 2, 0).should eql(L.empty) list.send(method, 2, 1).should eql(L[3]) list.send(method, 2, 2).should eql(L[3,4]) list.send(method, 2, 4).should eql(L[3,4]) list.send(method, 2, -1).should be_nil list.send(method, 4, 0).should eql(L.empty) list.send(method, 4, 2).should eql(L.empty) list.send(method, 4, -1).should be_nil list.send(method, 5, 0).should be_nil list.send(method, 5, 2).should be_nil list.send(method, 5, -1).should be_nil list.send(method, 6, 0).should be_nil list.send(method, 6, 2).should be_nil list.send(method, 6, -1).should be_nil big.send(method, 0, 3).should eql(L[1,2,3]) big.send(method, 1023, 4).should eql(L[1024,1025,1026,1027]) big.send(method, 1024, 4).should eql(L[1025,1026,1027,1028]) end it 'leaves the original unchanged' do list.should eql(L[1,2,3,4]) end end context 'when passed a negative integral index and count' do it "returns 'count' elements, starting from index which is number 'index.abs' counting from the end of the array" do list.send(method, -1, 0).should eql(L.empty) list.send(method, -1, 1).should eql(L[4]) list.send(method, -1, 2).should eql(L[4]) list.send(method, -1, -1).should be_nil list.send(method, -2, 0).should eql(L.empty) list.send(method, -2, 1).should eql(L[3]) list.send(method, -2, 2).should eql(L[3,4]) list.send(method, -2, 4).should eql(L[3,4]) list.send(method, -2, -1).should be_nil list.send(method, -4, 0).should eql(L.empty) list.send(method, -4, 1).should eql(L[1]) list.send(method, -4, 2).should eql(L[1,2]) list.send(method, -4, 4).should eql(L[1,2,3,4]) list.send(method, -4, 6).should eql(L[1,2,3,4]) list.send(method, -4, -1).should be_nil list.send(method, -5, 0).should be_nil list.send(method, -5, 1).should be_nil list.send(method, -5, 10).should be_nil list.send(method, -5, -1).should be_nil big.send(method, -1, 1).should eql(L[10000]) big.send(method, -1, 2).should eql(L[10000]) big.send(method, -6, 2).should eql(L[9995,9996]) end end context 'when passed a Range' do it 'returns the elements whose indexes are within the given Range' do list.send(method, 0..-1).should eql(L[1,2,3,4]) list.send(method, 0..-10).should eql(L.empty) list.send(method, 0..0).should eql(L[1]) list.send(method, 0..1).should eql(L[1,2]) list.send(method, 0..2).should eql(L[1,2,3]) list.send(method, 0..3).should eql(L[1,2,3,4]) list.send(method, 0..4).should eql(L[1,2,3,4]) list.send(method, 0..10).should eql(L[1,2,3,4]) list.send(method, 2..-10).should eql(L.empty) list.send(method, 2..0).should eql(L.empty) list.send(method, 2..2).should eql(L[3]) list.send(method, 2..3).should eql(L[3,4]) list.send(method, 2..4).should eql(L[3,4]) list.send(method, 3..0).should eql(L.empty) list.send(method, 3..3).should eql(L[4]) list.send(method, 3..4).should eql(L[4]) list.send(method, 4..0).should eql(L.empty) list.send(method, 4..4).should eql(L.empty) list.send(method, 4..5).should eql(L.empty) list.send(method, 5..0).should be_nil list.send(method, 5..5).should be_nil list.send(method, 5..6).should be_nil big.send(method, 159..162).should eql(L[160,161,162,163]) big.send(method, 160..162).should eql(L[161,162,163]) big.send(method, 161..162).should eql(L[162,163]) big.send(method, 9999..10100).should eql(L[10000]) big.send(method, 10000..10100).should eql(L.empty) big.send(method, 10001..10100).should be_nil list.send(method, 0...-1).should eql(L[1,2,3]) list.send(method, 0...-10).should eql(L.empty) list.send(method, 0...0).should eql(L.empty) list.send(method, 0...1).should eql(L[1]) list.send(method, 0...2).should eql(L[1,2]) list.send(method, 0...3).should eql(L[1,2,3]) list.send(method, 0...4).should eql(L[1,2,3,4]) list.send(method, 0...10).should eql(L[1,2,3,4]) list.send(method, 2...-10).should eql(L.empty) list.send(method, 2...0).should eql(L.empty) list.send(method, 2...2).should eql(L.empty) list.send(method, 2...3).should eql(L[3]) list.send(method, 2...4).should eql(L[3,4]) list.send(method, 3...0).should eql(L.empty) list.send(method, 3...3).should eql(L.empty) list.send(method, 3...4).should eql(L[4]) list.send(method, 4...0).should eql(L.empty) list.send(method, 4...4).should eql(L.empty) list.send(method, 4...5).should eql(L.empty) list.send(method, 5...0).should be_nil list.send(method, 5...5).should be_nil list.send(method, 5...6).should be_nil big.send(method, 159...162).should eql(L[160,161,162]) big.send(method, 160...162).should eql(L[161,162]) big.send(method, 161...162).should eql(L[162]) big.send(method, 9999...10100).should eql(L[10000]) big.send(method, 10000...10100).should eql(L.empty) big.send(method, 10001...10100).should be_nil list.send(method, -1..-1).should eql(L[4]) list.send(method, -1...-1).should eql(L.empty) list.send(method, -1..3).should eql(L[4]) list.send(method, -1...3).should eql(L.empty) list.send(method, -1..4).should eql(L[4]) list.send(method, -1...4).should eql(L[4]) list.send(method, -1..10).should eql(L[4]) list.send(method, -1...10).should eql(L[4]) list.send(method, -1..0).should eql(L.empty) list.send(method, -1..-4).should eql(L.empty) list.send(method, -1...-4).should eql(L.empty) list.send(method, -1..-6).should eql(L.empty) list.send(method, -1...-6).should eql(L.empty) list.send(method, -2..-2).should eql(L[3]) list.send(method, -2...-2).should eql(L.empty) list.send(method, -2..-1).should eql(L[3,4]) list.send(method, -2...-1).should eql(L[3]) list.send(method, -2..10).should eql(L[3,4]) list.send(method, -2...10).should eql(L[3,4]) big.send(method, -1..-1).should eql(L[10000]) big.send(method, -1..9999).should eql(L[10000]) big.send(method, -1...9999).should eql(L.empty) big.send(method, -2...9999).should eql(L[9999]) big.send(method, -2..-1).should eql(L[9999,10000]) list.send(method, -4..-4).should eql(L[1]) list.send(method, -4..-2).should eql(L[1,2,3]) list.send(method, -4...-2).should eql(L[1,2]) list.send(method, -4..-1).should eql(L[1,2,3,4]) list.send(method, -4...-1).should eql(L[1,2,3]) list.send(method, -4..3).should eql(L[1,2,3,4]) list.send(method, -4...3).should eql(L[1,2,3]) list.send(method, -4..4).should eql(L[1,2,3,4]) list.send(method, -4...4).should eql(L[1,2,3,4]) list.send(method, -4..0).should eql(L[1]) list.send(method, -4...0).should eql(L.empty) list.send(method, -4..1).should eql(L[1,2]) list.send(method, -4...1).should eql(L[1]) list.send(method, -5..-5).should be_nil list.send(method, -5...-5).should be_nil list.send(method, -5..-4).should be_nil list.send(method, -5..-1).should be_nil list.send(method, -5..10).should be_nil big.send(method, -10001..-1).should be_nil end it 'leaves the original unchanged' do list.should eql(L[1,2,3,4]) end end end context 'when passed a subclass of Range' do it 'works the same as with a Range' do subclass = Class.new(Range) list.send(method, subclass.new(1,2)).should eql(L[2,3]) list.send(method, subclass.new(-3,-1,true)).should eql(L[2,3]) end end end end immutable-ruby-master/spec/lib/immutable/list/join_spec.rb0000644000175000017500000000266414201005456023620 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#join' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.join }.should_not raise_error end end context 'with a separator' do [ [[], ''], [['A'], 'A'], [%w[A B C], 'A|B|C'] ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.join('|') list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.join('|').should == expected end end end end context 'without a separator' do [ [[], ''], [['A'], 'A'], [%w[A B C], 'ABC'] ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.join list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.join.should == expected end end end end context 'without a separator (with global default separator set)' do before { $, = '**' } let(:list) { L['A', 'B', 'C'] } after { $, = nil } it 'uses the default global separator' do list.join.should == 'A**B**C' end end end end immutable-ruby-master/spec/lib/immutable/list/split_at_spec.rb0000644000175000017500000000214114201005456024466 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#split_at' do it 'is lazy' do -> { Immutable.stream { fail }.split_at(1) }.should_not raise_error end [ [[], [], []], [[1], [1], []], [[1, 2], [1, 2], []], [[1, 2, 3], [1, 2], [3]], [[1, 2, 3, 4], [1, 2], [3, 4]], ].each do |values, expected_prefix, expected_remainder| context "on #{values.inspect}" do let(:list) { L[*values] } let(:result) { list.split_at(2) } let(:prefix) { result.first } let(:remainder) { result.last } it 'preserves the original' do result list.should eql(L[*values]) end it 'returns a frozen array with two items' do result.class.should be(Array) result.should be_frozen result.size.should be(2) end it 'correctly identifies the matches' do prefix.should eql(L[*expected_prefix]) end it 'correctly identifies the remainder' do remainder.should eql(L[*expected_remainder]) end end end end end immutable-ruby-master/spec/lib/immutable/list/each_with_index_spec.rb0000644000175000017500000000142014201005456025770 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#each_with_index' do context 'with no block' do let(:list) { L['A', 'B', 'C'] } it 'returns an Enumerator' do list.each_with_index.class.should be(Enumerator) list.each_with_index.to_a.should == [['A', 0], ['B', 1], ['C', 2]] end end context 'with a block' do let(:list) { Immutable.interval(1, 1025) } it 'returns self' do list.each_with_index { |item, index| item }.should be(list) end it 'iterates over the items in order, yielding item and index' do yielded = [] list.each_with_index { |item, index| yielded << [item, index] } yielded.should == (1..list.size).zip(0..list.size.pred) end end end end immutable-ruby-master/spec/lib/immutable/list/add_spec.rb0000644000175000017500000000113114201005456023375 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#add' do [ [[], 'A', ['A']], [['A'], 'B', %w[B A]], [['A'], 'A', %w[A A]], [%w[A B C], 'D', %w[D A B C]], ].each do |values, new_value, expected| context "on #{values.inspect} with #{new_value.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.add(new_value) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.add(new_value).should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/find_all_spec.rb0000644000175000017500000000325714201005456024430 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do let(:list) { L[*values] } let(:found_list) { L[*found_values] } describe '#find_all' do it 'is lazy' do expect { Immutable.stream { fail }.find_all { |item| false } }.to_not raise_error end shared_examples 'checking values' do context 'with a block' do let(:find_all) { list.find_all { |item| item == item.upcase } } it 'preserves the original' do expect(list).to eq(L[*values]) end it 'returns the found list' do expect(find_all).to eq(found_list) end end context 'without a block' do let(:find_all) { list.find_all } it 'returns an Enumerator' do expect(find_all.class).to be(Enumerator) expect(find_all.each { |item| item == item.upcase }).to eq(found_list) end end end context 'with an empty array' do let(:values) { [] } let(:found_values) { [] } include_examples 'checking values' end context 'with a single item array' do let(:values) { ['A'] } let(:found_values) { ['A'] } include_examples 'checking values' end context 'with a multi-item array' do let(:values) { %w[A B] } let(:found_values) { %w[A B] } include_examples 'checking values' end context 'with a multi-item single find_allable array' do let(:values) { %w[A b] } let(:found_values) { ['A'] } include_examples 'checking values' end context 'with a multi-item multi-find_allable array' do let(:values) { %w[a b] } let(:found_values) { [] } include_examples 'checking values' end end end immutable-ruby-master/spec/lib/immutable/list/head_spec.rb0000644000175000017500000000064614201005456023560 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:head, :first].each do |method| describe "##{method}" do [ [[], nil], [['A'], 'A'], [%w[A B C], 'A'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].send(method).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/list/grep_spec.rb0000644000175000017500000000212614201005456023607 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#grep' do it 'is lazy' do -> { Immutable.stream { fail }.grep(Object) { |item| item } }.should_not raise_error end context 'without a block' do [ [[], []], [['A'], ['A']], [[1], []], [['A', 2, 'C'], %w[A C]], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].grep(String).should eql(L[*expected]) end end end end context 'with a block' do [ [[], []], [['A'], ['a']], [[1], []], [['A', 2, 'C'], %w[a c]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.grep(String, &:downcase) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.grep(String, &:downcase).should eql(L[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/list/take_spec.rb0000644000175000017500000000125714201005456023602 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#take' do it 'is lazy' do -> { Immutable.stream { fail }.take(1) }.should_not raise_error end [ [[], 10, []], [['A'], 10, ['A']], [['A'], -1, []], [%w[A B C], 0, []], [%w[A B C], 2, %w[A B]], ].each do |values, number, expected| context "#{number} from #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.take(number) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.take(number).should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/find_index_spec.rb0000644000175000017500000000167414201005456024770 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:find_index, :index].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.send(method) { |item| false } }.should_not raise_error end end [ [[], 'A', nil], [[], nil, nil], [['A'], 'A', 0], [['A'], 'B', nil], [['A'], nil, nil], [['A', 'B', nil], 'A', 0], [['A', 'B', nil], 'B', 1], [['A', 'B', nil], nil, 2], [['A', 'B', nil], 'C', nil], [[2], 2, 0], [[2], 2.0, 0], [[2.0], 2.0, 0], [[2.0], 2, 0], ].each do |values, item, expected| context "looking for #{item.inspect} in #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].send(method) { |x| x == item }.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/list/select_spec.rb0000644000175000017500000000326714201005456024140 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do let(:list) { L[*values] } let(:selected_list) { L[*selected_values] } describe '#select' do it 'is lazy' do expect { Immutable.stream { fail }.select { |item| false } }.to_not raise_error end shared_examples 'checking values' do context 'with a block' do let(:select) { list.select { |item| item == item.upcase } } it 'preserves the original' do expect(list).to eq(L[*values]) end it 'returns the selected list' do expect(select).to eq(selected_list) end end context 'without a block' do let(:select) { list.select } it 'returns an Enumerator' do expect(select.class).to be(Enumerator) expect(select.each { |item| item == item.upcase }).to eq(selected_list) end end end context 'with an empty array' do let(:values) { [] } let(:selected_values) { [] } include_examples 'checking values' end context 'with a single item array' do let(:values) { ['A'] } let(:selected_values) { ['A'] } include_examples 'checking values' end context 'with a multi-item array' do let(:values) { %w[A B] } let(:selected_values) { %w[A B] } include_examples 'checking values' end context 'with a multi-item single selectable array' do let(:values) { %w[A b] } let(:selected_values) { ['A'] } include_examples 'checking values' end context 'with a multi-item multi-selectable array' do let(:values) { %w[a b] } let(:selected_values) { [] } include_examples 'checking values' end end end immutable-ruby-master/spec/lib/immutable/list/uniq_spec.rb0000644000175000017500000000145214201005456023627 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#uniq' do it 'is lazy' do -> { Immutable.stream { fail }.uniq }.should_not raise_error end context 'when passed a block' do it 'uses the block to identify duplicates' do L['a', 'A', 'b'].uniq(&:upcase).should eql(Immutable::List['a', 'b']) end end [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B C]], [%w[A B A C C], %w[A B C]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.uniq list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.uniq.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/sorting_spec.rb0000644000175000017500000000232514201005456024340 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [ [:sort, ->(left, right) { left.length <=> right.length }], [:sort_by, ->(item) { item.length }], ].each do |method, comparator| describe "##{method}" do it 'is lazy' do -> { Immutable.stream { fail }.send(method, &comparator) }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[Ichi Ni San], %w[Ni San Ichi]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it 'preserves the original' do list.send(method, &comparator) list.should == L[*values] end it "returns #{expected.inspect}" do list.send(method, &comparator).should == L[*expected] end end context 'without a block' do it 'preserves the original' do list.send(method) list.should eql(L[*values]) end it "returns #{expected.sort.inspect}" do list.send(method).should == L[*expected.sort] end end end end end end end immutable-ruby-master/spec/lib/immutable/list/eql_spec.rb0000644000175000017500000000406514201005456023437 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#eql?' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.eql?(Immutable.interval(0, STACK_OVERFLOW_DEPTH)) }.should_not raise_error end end end shared_examples 'equal using eql?' do |a, b| specify "#{a.inspect} should eql? #{b.inspect}" do expect(a).to eql b end specify "#{a.inspect} should == #{b.inspect}" do expect(a).to eq b end end shared_examples 'not equal using eql?' do |a, b| specify "#{a.inspect} should not eql? #{b.inspect}" do expect(a).to_not eql b end end shared_examples 'equal using ==' do |a, b| specify "#{a.inspect} should == #{b.inspect}" do expect(a).to eq b end end shared_examples 'not equal using ==' do |a, b| specify "#{a.inspect} should not == #{b.inspect}" do expect(a).to_not eq b end end include_examples 'equal using ==' , L['A', 'B', 'C'], %w[A B C] include_examples 'not equal using eql?' , L['A', 'B', 'C'], %w[A B C] include_examples 'not equal using ==' , L['A', 'B', 'C'], Object.new include_examples 'not equal using eql?' , L['A', 'B', 'C'], Object.new include_examples 'equal using ==' , L.empty, [] include_examples 'not equal using eql?' , L.empty, [] include_examples 'equal using eql?' , L.empty, L.empty include_examples 'not equal using eql?' , L.empty, L[nil] include_examples 'not equal using eql?' , L['A'], L.empty include_examples 'equal using eql?' , L['A'], L['A'] include_examples 'not equal using eql?' , L['A'], L['B'] include_examples 'not equal using eql?' , L['A', 'B'], L['A'] include_examples 'equal using eql?' , L['A', 'B', 'C'], L['A', 'B', 'C'] include_examples 'not equal using eql?' , L['C', 'A', 'B'], L['A', 'B', 'C'] include_examples 'equal using ==' , L['A'], ['A'] include_examples 'equal using ==' , ['A'], L['A'] include_examples 'not equal using eql?' , L['A'], ['A'] include_examples 'not equal using eql?' , ['A'], L['A'] end immutable-ruby-master/spec/lib/immutable/list/rotate_spec.rb0000644000175000017500000000204214201005456024145 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#rotate' do let(:list) { L[1,2,3,4,5] } context 'when passed no argument' do it 'returns a new list with the first element moved to the end' do list.rotate.should eql(L[2,3,4,5,1]) end end context 'with an integral argument n' do it 'returns a new list with the first (n % size) elements moved to the end' do list.rotate(2).should eql(L[3,4,5,1,2]) list.rotate(3).should eql(L[4,5,1,2,3]) list.rotate(4).should eql(L[5,1,2,3,4]) list.rotate(5).should eql(L[1,2,3,4,5]) list.rotate(-1).should eql(L[5,1,2,3,4]) end end context 'with a non-numeric argument' do it 'raises a TypeError' do -> { list.rotate('hello') }.should raise_error(TypeError) end end context 'with an argument of zero (or one evenly divisible by list length)' do it 'it returns self' do list.rotate(0).should be(list) list.rotate(5).should be(list) end end end end immutable-ruby-master/spec/lib/immutable/list/include_spec.rb0000644000175000017500000000165214201005456024300 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:include?, :member?].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.send(method, nil) }.should_not raise_error end end [ [[], 'A', false], [[], nil, false], [['A'], 'A', true], [['A'], 'B', false], [['A'], nil, false], [['A', 'B', nil], 'A', true], [['A', 'B', nil], 'B', true], [['A', 'B', nil], nil, true], [['A', 'B', nil], 'C', false], [[2], 2, true], [[2], 2.0, true], [[2.0], 2.0, true], [[2.0], 2, true], ].each do |values, item, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].send(method, item).should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/list/group_by_spec.rb0000644000175000017500000000214414201005456024500 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:group_by, :group].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.send(method) }.should_not raise_error end end context 'with a block' do [ [[], []], [[1], [true => L[1]]], [[1, 2, 3, 4], [true => L[3, 1], false => L[4, 2]]], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].send(method, &:odd?).should eql(H[*expected]) end end end end context 'without a block' do [ [[], []], [[1], [1 => L[1]]], [[1, 2, 3, 4], [1 => L[1], 2 => L[2], 3 => L[3], 4 => L[4]]], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].send(method).should eql(H[*expected]) end end end end end end end immutable-ruby-master/spec/lib/immutable/list/clear_spec.rb0000644000175000017500000000067314201005456023745 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#clear' do [ [], ['A'], %w[A B C], ].each do |values| describe "on #{values}" do let(:list) { L[*values] } it 'preserves the original' do list.clear list.should eql(L[*values]) end it 'returns an empty list' do list.clear.should equal(L.empty) end end end end end immutable-ruby-master/spec/lib/immutable/list/break_spec.rb0000644000175000017500000000350214201005456023735 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#break' do it 'is lazy' do -> { Immutable.stream { fail }.break { |item| false } }.should_not raise_error end [ [[], [], []], [[1], [1], []], [[1, 2], [1, 2], []], [[1, 2, 3], [1, 2], [3]], [[1, 2, 3, 4], [1, 2], [3, 4]], [[2, 3, 4], [2], [3, 4]], [[3, 4], [], [3, 4]], [[4], [], [4]], ].each do |values, expected_prefix, expected_remainder| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do let(:result) { list.break { |item| item > 2 }} let(:prefix) { result.first } let(:remainder) { result.last } it 'preserves the original' do result list.should eql(L[*values]) end it 'returns a frozen array with two items' do result.class.should be(Array) result.should be_frozen result.size.should be(2) end it 'correctly identifies the prefix' do prefix.should eql(L[*expected_prefix]) end it 'correctly identifies the remainder' do remainder.should eql(L[*expected_remainder]) end end context 'without a block' do let(:result) { list.break } let(:prefix) { result.first } let(:remainder) { result.last } it 'returns a frozen array with two items' do result.class.should be(Array) result.should be_frozen result.size.should be(2) end it 'returns self as the prefix' do prefix.should equal(list) end it 'leaves the remainder empty' do remainder.should be_empty end end end end end end immutable-ruby-master/spec/lib/immutable/list/tail_spec.rb0000644000175000017500000000121314201005456023577 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#tail' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.select(&:nil?).tail }.should_not raise_error end end [ [[], []], [['A'], []], [%w[A B C], %w[B C]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.tail list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.tail.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/merge_by_spec.rb0000644000175000017500000000233414201005456024444 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do context 'without a comparator' do context 'on an empty list' do it 'returns an empty list' do L.empty.merge_by.should be_empty end end context 'on a single list' do let(:list) { L[1, 2, 3] } it 'returns the list' do L[list].merge_by.should eql(list) end end context 'with multiple lists' do subject { L[L[3, 6, 7, 8], L[1, 2, 4, 5, 9]] } it 'merges the lists based on natural sort order' do subject.merge_by.should == L[1, 2, 3, 4, 5, 6, 7, 8, 9] end end end context 'with a comparator' do context 'on an empty list' do it 'returns an empty list' do L.empty.merge_by { |item| fail('should never be called') }.should be_empty end end context 'on a single list' do let(:list) { L[1, 2, 3] } it 'returns the list' do L[list].merge_by(&:-@).should == L[1, 2, 3] end end context 'with multiple lists' do subject { L[L[8, 7, 6, 3], L[9, 5, 4, 2, 1]] } it 'merges the lists based on the specified transformer' do subject.merge_by(&:-@).should == L[9, 8, 7, 6, 5, 4, 3, 2, 1] end end end end immutable-ruby-master/spec/lib/immutable/list/span_spec.rb0000644000175000017500000000470014201005456023613 0ustar boutilboutilrequire 'spec_helper' describe 'List#span' do it 'is lazy' do -> { Immutable.stream { |item| fail }.span { true } }.should_not raise_error end describe <<-DESC do given a predicate (in the form of a block), splits the list into two lists (returned as an array) such that elements in the first list (the prefix) are taken from the head of the list while the predicate is satisfied, and elements in the second list (the remainder) are the remaining elements from the list once the predicate is not satisfied. For example: DESC [ [[], [], []], [[1], [1], []], [[1, 2], [1, 2], []], [[1, 2, 3], [1, 2], [3]], [[1, 2, 3, 4], [1, 2], [3, 4]], [[2, 3, 4], [2], [3, 4]], [[3, 4], [], [3, 4]], [[4], [], [4]], ].each do |values, expected_prefix, expected_remainder| context "given the list #{values.inspect}" do let(:list) { L[*values] } context 'and a predicate that returns true for values <= 2' do let(:result) { list.span { |item| item <= 2 }} let(:prefix) { result.first } let(:remainder) { result.last } it 'preserves the original' do result list.should eql(L[*values]) end it "returns the prefix as #{expected_prefix.inspect}" do prefix.should eql(L[*expected_prefix]) end it "returns the remainder as #{expected_remainder.inspect}" do remainder.should eql(L[*expected_remainder]) end it 'calls the block only once for each element' do count = 0 result = list.span { |item| count += 1; item <= 2 } # force realization of lazy lists result.first.size.should == expected_prefix.size result.last.size.should == expected_remainder.size # it may not need to call the block on every element, just up to the # point where the block first returns a false value count.should <= values.size end end context 'without a predicate' do it 'returns a frozen array' do list.span.class.should be(Array) list.span.should be_frozen end it 'returns self as the prefix' do list.span.first.should equal(list) end it 'returns an empty list as the remainder' do list.span.last.should be_empty end end end end end end immutable-ruby-master/spec/lib/immutable/list/to_list_spec.rb0000644000175000017500000000051314201005456024325 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#to_list' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do let(:list) { L[*values] } it 'returns self' do list.to_list.should equal(list) end end end end end immutable-ruby-master/spec/lib/immutable/list/union_spec.rb0000644000175000017500000000146714201005456024011 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:union, :|].each do |method| describe "##{method}" do it 'is lazy' do -> { Immutable.stream { fail }.union(Immutable.stream { fail }) }.should_not raise_error end [ [[], [], []], [['A'], [], ['A']], [%w[A B C], [], %w[A B C]], [%w[A A], ['A'], ['A']], ].each do |a, b, expected| context "returns #{expected.inspect}" do let(:list_a) { L[*a] } let(:list_b) { L[*b] } it "for #{a.inspect} and #{b.inspect}" do list_a.send(method, list_b).should eql(L[*expected]) end it "for #{b.inspect} and #{a.inspect}" do list_b.send(method, list_a).should eql(L[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/list/compact_spec.rb0000644000175000017500000000137514201005456024305 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#compact' do it 'is lazy' do -> { Immutable.stream { fail }.compact }.should_not raise_error end [ [[], []], [['A'], ['A']], [%w[A B C], %w[A B C]], [[nil], []], [[nil, 'B'], ['B']], [['A', nil], ['A']], [[nil, nil], []], [['A', nil, 'C'], %w[A C]], [[nil, 'B', nil], ['B']], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.compact list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.compact.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/zip_spec.rb0000644000175000017500000000121414201005456023451 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#zip' do it 'is lazy' do -> { Immutable.stream { fail }.zip(Immutable.stream { fail }) }.should_not raise_error end [ [[], [], []], [['A'], ['aye'], [L['A', 'aye']]], [['A'], [], [L['A', nil]]], [[], ['A'], [L[nil, 'A']]], [%w[A B C], %w[aye bee see], [L['A', 'aye'], L['B', 'bee'], L['C', 'see']]], ].each do |left, right, expected| context "on #{left.inspect} and #{right.inspect}" do it "returns #{expected.inspect}" do L[*left].zip(L[*right]).should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/none_spec.rb0000644000175000017500000000230014201005456023603 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#none?' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.none? { false } }.should_not raise_error end end context 'when empty' do it 'with a block returns true' do L.empty.none? {}.should == true end it 'with no block returns true' do L.empty.none?.should == true end end context 'when not empty' do context 'with a block' do let(:list) { L['A', 'B', 'C', nil] } ['A', 'B', 'C', nil].each do |value| it "returns false if the block ever returns true (#{value.inspect})" do list.none? { |item| item == value }.should == false end end it 'returns true if the block always returns false' do list.none? { |item| item == 'D' }.should == true end end context 'with no block' do it 'returns false if any value is truthy' do L[nil, false, true, 'A'].none?.should == false end it 'returns true if all values are falsey' do L[nil, false].none?.should == true end end end end end immutable-ruby-master/spec/lib/immutable/list/drop_while_spec.rb0000644000175000017500000000175014201005456025010 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#drop_while' do it 'is lazy' do -> { Immutable.stream { fail }.drop_while { false } }.should_not raise_error end [ [[], []], [['A'], []], [%w[A B C], ['C']], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it 'preserves the original' do list.drop_while { |item| item < 'C' } list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.drop_while { |item| item < 'C' }.should eql(L[*expected]) end end context 'without a block' do it 'returns an Enumerator' do list.drop_while.class.should be(Enumerator) list.drop_while.each { false }.should eql(list) list.drop_while.each { true }.should be_empty end end end end end end immutable-ruby-master/spec/lib/immutable/list/fill_spec.rb0000644000175000017500000000253614201005456023605 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#fill' do let(:list) { L[1, 2, 3, 4, 5, 6] } it 'can replace a range of items at the beginning of a list' do list.fill(:a, 0, 3).should eql(L[:a, :a, :a, 4, 5, 6]) end it 'can replace a range of items in the middle of a list' do list.fill(:a, 3, 2).should eql(L[1, 2, 3, :a, :a, 6]) end it 'can replace a range of items at the end of a list' do list.fill(:a, 4, 2).should eql(L[1, 2, 3, 4, :a, :a]) end it 'can replace all the items in a list' do list.fill(:a, 0, 6).should eql(L[:a, :a, :a, :a, :a, :a]) end it 'can fill past the end of the list' do list.fill(:a, 3, 6).should eql(L[1, 2, 3, :a, :a, :a, :a, :a, :a]) end context 'with 1 argument' do it 'replaces all the items in the list by default' do list.fill(:a).should eql(L[:a, :a, :a, :a, :a, :a]) end end context 'with 2 arguments' do it 'replaces up to the end of the list by default' do list.fill(:a, 4).should eql(L[1, 2, 3, 4, :a, :a]) end end context 'when index and length are 0' do it 'leaves the list unmodified' do list.fill(:a, 0, 0).should eql(list) end end it 'is lazy' do -> { Immutable.stream { fail }.fill(:a, 0, 1) }.should_not raise_error end end end immutable-ruby-master/spec/lib/immutable/list/maximum_spec.rb0000644000175000017500000000165014201005456024330 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#max' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.max }.should_not raise_error end end context 'with a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'Ichi'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].max { |maximum, item| maximum.length <=> item.length }.should == expected end end end end context 'without a block' do [ [[], nil], [['A'], 'A'], [%w[Ichi Ni San], 'San'], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].max.should == expected end end end end end end immutable-ruby-master/spec/lib/immutable/list/cons_spec.rb0000644000175000017500000000113414201005456023612 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#cons' do [ [[], 'A', ['A']], [['A'], 'B', %w[B A]], [['A'], 'A', %w[A A]], [%w[A B C], 'D', %w[D A B C]], ].each do |values, new_value, expected| context "on #{values.inspect} with #{new_value.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.cons(new_value) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.cons(new_value).should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/cadr_spec.rb0000644000175000017500000000163414201005456023566 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [ [[], :car, nil], [['A'], :car, 'A'], [%w[A B C], :car, 'A'], [%w[A B C], :cadr, 'B'], [%w[A B C], :caddr, 'C'], [%w[A B C], :cadddr, nil], [%w[A B C], :caddddr, nil], [[], :cdr, L.empty], [['A'], :cdr, L.empty], [%w[A B C], :cdr, L['B', 'C']], [%w[A B C], :cddr, L['C']], [%w[A B C], :cdddr, L.empty], [%w[A B C], :cddddr, L.empty], ].each do |values, method, expected| describe "##{method}" do it 'is responded to' do L.empty.respond_to?(method).should == true end context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.send(method) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.send(method).should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/insert_spec.rb0000644000175000017500000000241714201005456024161 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#insert' do let(:original) { L[1, 2, 3] } it 'can add items at the beginning of a list' do list = original.insert(0, :a, :b) list.size.should be(5) list.at(0).should be(:a) list.at(2).should be(1) end it 'can add items in the middle of a list' do list = original.insert(1, :a, :b, :c) list.size.should be(6) list.to_a.should == [1, :a, :b, :c, 2, 3] end it 'can add items at the end of a list' do list = original.insert(3, :a, :b, :c) list.size.should be(6) list.to_a.should == [1, 2, 3, :a, :b, :c] end it 'can add items past the end of a list' do list = original.insert(6, :a, :b) list.size.should be(8) list.to_a.should == [1, 2, 3, nil, nil, nil, :a, :b] end it 'accepts a negative index, which counts back from the end of the list' do list = original.insert(-2, :a) list.size.should be(4) list.to_a.should == [1, :a, 2, 3] end it 'raises IndexError if a negative index is too great' do expect { original.insert(-4, :a) }.to raise_error(IndexError) end it 'is lazy' do -> { Immutable.stream { fail }.insert(0, :a) }.should_not raise_error end end end immutable-ruby-master/spec/lib/immutable/list/compare_spec.rb0000644000175000017500000000122114201005456024273 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#<=>' do [ [[], [1]], [[1], [2]], [[1], [1, 2]], [[2, 3, 4], [3, 4, 5]] ].each do |items1, items2| context "with #{items1} and #{items2}" do it 'returns -1' do (L[*items1] <=> L[*items2]).should be(-1) end end context "with #{items2} and #{items1}" do it 'returns 1' do (L[*items2] <=> L[*items1]).should be(1) end end context "with #{items1} and #{items1}" do it 'returns 0' do (L[*items1] <=> L[*items1]).should be(0) end end end end end immutable-ruby-master/spec/lib/immutable/list/append_spec.rb0000644000175000017500000000177314201005456024130 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:append, :concat, :+].each do |method| describe "##{method}" do it 'is lazy' do -> { Immutable.stream { fail }.append(Immutable.stream { fail }) }.should_not raise_error end [ [[], [], []], [['A'], [], ['A']], [[], ['A'], ['A']], [%w[A B], %w[C D], %w[A B C D]], ].each do |left_values, right_values, expected| context "on #{left_values.inspect} and #{right_values.inspect}" do let(:left) { L[*left_values] } let(:right) { L[*right_values] } let(:result) { left.append(right) } it 'preserves the left' do result left.should eql(L[*left_values]) end it 'preserves the right' do result right.should eql(L[*right_values]) end it "returns #{expected.inspect}" do result.should eql(L[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/list/combination_spec.rb0000644000175000017500000000175714201005456025165 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#combination' do it 'is lazy' do -> { Immutable.stream { fail }.combination(2) }.should_not raise_error end [ [%w[A B C D], 1, [L['A'], L['B'], L['C'], L['D']]], [%w[A B C D], 2, [L['A','B'], L['A','C'], L['A','D'], L['B','C'], L['B','D'], L['C','D']]], [%w[A B C D], 3, [L['A','B','C'], L['A','B','D'], L['A','C','D'], L['B','C','D']]], [%w[A B C D], 4, [L['A', 'B', 'C', 'D']]], [%w[A B C D], 0, [EmptyList]], [%w[A B C D], 5, []], [[], 0, [EmptyList]], [[], 1, []], ].each do |values, number, expected| context "on #{values.inspect} in groups of #{number}" do let(:list) { L[*values] } it 'preserves the original' do list.combination(number) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.combination(number).should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/inits_spec.rb0000644000175000017500000000116314201005456024000 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#inits' do it 'is lazy' do -> { Immutable.stream { fail }.inits }.should_not raise_error end [ [[], []], [['A'], [L['A']]], [%w[A B C], [L['A'], L['A', 'B'], L['A', 'B', 'C']]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } it 'preserves the original' do list.inits list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.inits.should eql(L[*expected]) end end end end end immutable-ruby-master/spec/lib/immutable/list/subsequences_spec.rb0000644000175000017500000000140114201005456025352 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#subsequences' do let(:list) { L[1,2,3,4,5] } it 'yields all sublists with 1 or more consecutive items' do result = [] list.subsequences { |l| result << l } result.size.should == (5 + 4 + 3 + 2 + 1) result.sort.should == [[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5], [2], [2,3], [2,3,4], [2,3,4,5], [3], [3,4], [3,4,5], [4], [4,5], [5]] end context 'with no block' do it 'returns an Enumerator' do list.subsequences.class.should be(Enumerator) list.subsequences.to_a.sort.should == [[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5], [2], [2,3], [2,3,4], [2,3,4,5], [3], [3,4], [3,4,5], [4], [4,5], [5]] end end end end immutable-ruby-master/spec/lib/immutable/list/index_spec.rb0000644000175000017500000000145314201005456023763 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#index' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.index(nil) }.should_not raise_error end end [ [[], 'A', nil], [[], nil, nil], [['A'], 'A', 0], [['A'], 'B', nil], [['A'], nil, nil], [['A', 'B', nil], 'A', 0], [['A', 'B', nil], 'B', 1], [['A', 'B', nil], nil, 2], [['A', 'B', nil], 'C', nil], [[2], 2, 0], [[2], 2.0, 0], [[2.0], 2.0, 0], [[2.0], 2, 0], ].each do |values, item, expected| context "looking for #{item.inspect} in #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].index(item).should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/one_spec.rb0000644000175000017500000000236414201005456023437 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#one?' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.one? { false } }.should_not raise_error end end context 'when empty' do it 'with a block returns false' do L.empty.one? {}.should == false end it 'with no block returns false' do L.empty.one?.should == false end end context 'when not empty' do context 'with a block' do let(:list) { L['A', 'B', 'C'] } it 'returns false if the block returns true more than once' do list.one? { |item| true }.should == false end it 'returns false if the block never returns true' do list.one? { |item| false }.should == false end it 'returns true if the block only returns true once' do list.one? { |item| item == 'A' }.should == true end end context 'with no block' do it 'returns false if more than one value is truthy' do L[nil, true, 'A'].one?.should == false end it 'returns true if only one value is truthy' do L[nil, true, false].one?.should == true end end end end end immutable-ruby-master/spec/lib/immutable/list/count_spec.rb0000644000175000017500000000142314201005456024001 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#count' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.count }.should_not raise_error end end [ [[], 0], [[1], 1], [[1, 2], 1], [[1, 2, 3], 2], [[1, 2, 3, 4], 2], [[1, 2, 3, 4, 5], 3], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it "returns #{expected.inspect}" do list.count(&:odd?).should == expected end end context 'without a block' do it 'returns length' do list.count.should == list.length end end end end end end immutable-ruby-master/spec/lib/immutable/list/reduce_spec.rb0000644000175000017500000000277014201005456024126 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:reduce, :inject].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.send(method, &:+) }.should_not raise_error end end [ [[], 10, 10], [[1], 10, 9], [[1, 2, 3], 10, 4], ].each do |values, initial, expected| context "on #{values.inspect}" do context "with an initial value of #{initial} and a block" do it "returns #{expected.inspect}" do L[*values].send(method, initial) { |memo, item| memo - item }.should == expected end end end end [ [[], nil], [[1], 1], [[1, 2, 3], -4], ].each do |values, expected| context "on #{values.inspect}" do context 'with no initial value and a block' do it "returns #{expected.inspect}" do L[*values].send(method) { |memo, item| memo - item }.should == expected end end end end context 'with no block and a symbol argument' do it 'uses the symbol as the name of a method to reduce with' do L[1, 2, 3].send(method, :+).should == 6 end end context 'with no block and a string argument' do it 'uses the string as the name of a method to reduce with' do L[1, 2, 3].send(method, '+').should == 6 end end end end end immutable-ruby-master/spec/lib/immutable/list/sum_spec.rb0000644000175000017500000000074614201005456023464 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#sum' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.sum }.should_not raise_error end end [ [[], 0], [[2], 2], [[1, 3, 5, 7, 11], 27], ].each do |values, expected| context "on #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].sum.should == expected end end end end end immutable-ruby-master/spec/lib/immutable/list/cycle_spec.rb0000644000175000017500000000117014201005456023747 0ustar boutilboutilrequire 'spec_helper' describe Immutable do describe '#cycle' do it 'is lazy' do -> { Immutable.stream { fail }.cycle }.should_not raise_error end context 'with an empty list' do it 'returns an empty list' do L.empty.cycle.should be_empty end end context 'with a non-empty list' do let(:list) { L['A', 'B', 'C'] } it 'preserves the original' do list.cycle list.should == L['A', 'B', 'C'] end it 'infinitely cycles through all values' do list.cycle.take(7).should == L['A', 'B', 'C', 'A', 'B', 'C', 'A'] end end end end immutable-ruby-master/spec/lib/immutable/list/map_spec.rb0000644000175000017500000000225714201005456023434 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:map, :collect].each do |method| describe "##{method}" do it 'is lazy' do -> { Immutable.stream { fail }.map { |item| item } }.should_not raise_error end [ [[], []], [['A'], ['a']], [%w[A B C], %w[a b c]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it 'preserves the original' do list.send(method, &:downcase) list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.send(method, &:downcase).should eql(L[*expected]) end it 'is lazy' do count = 0 list.send(method) { |item| count += 1 } count.should <= 1 end end context 'without a block' do it 'returns an Enumerator' do list.send(method).class.should be(Enumerator) list.send(method).each(&:downcase).should eql(L[*expected]) end end end end end end end immutable-ruby-master/spec/lib/immutable/list/each_slice_spec.rb0000644000175000017500000000256714201005456024742 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:each_chunk, :each_slice].each do |method| describe "##{method}" do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.send(method, 1) { |item| } }.should_not raise_error end end [ [[], []], [['A'], [L['A']]], [%w[A B C], [L['A', 'B'], L['C']]], ].each do |values, expected| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do it 'preserves the original' do list.should eql(L[*values]) end it 'iterates over the items in order' do yielded = [] list.send(method, 2) { |item| yielded << item } yielded.should eql(expected) end it 'returns self' do list.send(method, 2) { |item| item }.should be(list) end end context 'without a block' do it 'preserves the original' do list.send(method, 2) list.should eql(L[*values]) end it 'returns an Enumerator' do list.send(method, 2).class.should be(Enumerator) list.send(method, 2).to_a.should eql(expected) end end end end end end end immutable-ruby-master/spec/lib/immutable/list/hash_spec.rb0000644000175000017500000000076014201005456023577 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#hash' do context 'on a really big list' do it "doesn't run out of stack" do -> { BigList.hash }.should_not raise_error end end context 'on an empty list' do it 'returns 0' do expect(L.empty.hash).to eq(0) end end it 'values are sufficiently distributed' do (1..4000).each_slice(4).map { |a, b, c, d| L[a, b, c, d].hash }.uniq.size.should == 1000 end end end immutable-ruby-master/spec/lib/immutable/list/indices_spec.rb0000644000175000017500000000315114201005456024267 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#indices' do context 'when called with a block' do it 'is lazy' do count = 0 Immutable.stream { count += 1 }.indices { |item| true } count.should <= 1 end context "on a large list which doesn't contain desired item" do it "doesn't blow the stack" do -> { BigList.indices { |x| x < 0 }.size }.should_not raise_error end end [ [[], 'A', []], [['A'], 'B', []], [%w[A B A], 'B', [1]], [%w[A B A], 'A', [0, 2]], [[2], 2, [0]], [[2], 2.0, [0]], [[2.0], 2.0, [0]], [[2.0], 2, [0]], ].each do |values, item, expected| context "looking for #{item.inspect} in #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].indices { |x| x == item }.should eql(L[*expected]) end end end end context 'when called with a single argument' do it 'is lazy' do count = 0 Immutable.stream { count += 1 }.indices(nil) count.should <= 1 end [ [[], 'A', []], [['A'], 'B', []], [%w[A B A], 'B', [1]], [%w[A B A], 'A', [0, 2]], [[2], 2, [0]], [[2], 2.0, [0]], [[2.0], 2.0, [0]], [[2.0], 2, [0]], ].each do |values, item, expected| context "looking for #{item.inspect} in #{values.inspect}" do it "returns #{expected.inspect}" do L[*values].indices(item).should eql(L[*expected]) end end end end end end immutable-ruby-master/spec/lib/immutable/list/copying_spec.rb0000644000175000017500000000053114201005456024320 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do [:dup, :clone].each do |method| [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do let(:list) { L[*values] } it 'returns self' do list.send(method).should equal(list) end end end end end immutable-ruby-master/spec/lib/immutable/list/partition_spec.rb0000644000175000017500000000727714201005456024677 0ustar boutilboutilrequire 'spec_helper' require 'thread' describe Immutable::List do describe '#partition' do it 'is lazy' do -> { Immutable.stream { fail }.partition }.should_not raise_error end it 'calls the passed block only once for each item' do count = 0 a,b = L[1, 2, 3].partition { |item| count += 1; item.odd? } (a.size + b.size).should be(3) # force realization of lazy lists count.should be(3) end # note: Lists are not as lazy as they could be! # they always realize elements a bit ahead of the current one it 'returns a lazy list of items for which predicate is true' do count = 0 a,b = L[1, 2, 3, 4].partition { |item| count += 1; item.odd? } a.take(1).should == [1] count.should be(3) # would be 1 if lists were lazier a.take(2).should == [1, 3] count.should be(4) # would be 3 if lists were lazier end it 'returns a lazy list of items for which predicate is false' do count = 0 a,b = L[1, 2, 3, 4].partition { |item| count += 1; item.odd? } b.take(1).should == [2] count.should be(4) # would be 2 if lists were lazier b.take(2).should == [2, 4] count.should be(4) end it 'calls the passed block only once for each item, even with multiple threads' do mutex = Mutex.new yielded = [] # record all the numbers yielded to the block, to make sure each is yielded only once list = Immutable.iterate(0) do |n| sleep(rand / 500) # give another thread a chance to get in mutex.synchronize { yielded << n } sleep(rand / 500) n + 1 end left, right = list.partition(&:odd?) 10.times.collect do |i| Thread.new do # half of the threads will consume the "left" lazy list, while half consume # the "right" lazy list # make sure that only one thread will run the above "iterate" block at a # time, regardless if i % 2 == 0 left.take(100).sum.should == 10000 else right.take(100).sum.should == 9900 end end end.each(&:join) # if no threads "stepped on" each other, the following should be true # make some allowance for "lazy" lists which actually realize a little bit ahead: (200..203).include?(yielded.size).should == true yielded.should == (0..(yielded.size-1)).to_a end [ [[], [], []], [[1], [1], []], [[1, 2], [1], [2]], [[1, 2, 3], [1, 3], [2]], [[1, 2, 3, 4], [1, 3], [2, 4]], [[2, 3, 4], [3], [2, 4]], [[3, 4], [3], [4]], [[4], [], [4]], ].each do |values, expected_matches, expected_remainder| context "on #{values.inspect}" do let(:list) { L[*values] } context 'with a block' do let(:result) { list.partition(&:odd?) } let(:matches) { result.first } let(:remainder) { result.last } it 'preserves the original' do list.should eql(L[*values]) end it 'returns a frozen array with two items' do result.class.should be(Array) result.should be_frozen result.size.should be(2) end it 'correctly identifies the matches' do matches.should eql(L[*expected_matches]) end it 'correctly identifies the remainder' do remainder.should eql(L[*expected_remainder]) end end context 'without a block' do it 'returns an Enumerator' do list.partition.class.should be(Enumerator) list.partition.each(&:odd?).should eql([L[*expected_matches], L[*expected_remainder]]) end end end end end end immutable-ruby-master/spec/lib/immutable/list/to_set_spec.rb0000644000175000017500000000050614201005456024147 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#to_set' do [ [], ['A'], %w[A B C], ].each do |values| context "on #{values.inspect}" do it 'returns a set with the same values' do L[*values].to_set.should eql(S[*values]) end end end end end immutable-ruby-master/spec/lib/immutable/list/permutation_spec.rb0000644000175000017500000000311614201005456025221 0ustar boutilboutilrequire 'spec_helper' describe Immutable::List do describe '#permutation' do let(:list) { L[1,2,3,4] } context 'with no block' do it 'returns an Enumerator' do list.permutation.class.should be(Enumerator) list.permutation.to_a.sort.should == [1,2,3,4].permutation.to_a.sort end end context 'with no argument' do it 'yields all permutations of the list' do perms = list.permutation.to_a perms.size.should be(24) perms.sort.should == [1,2,3,4].permutation.to_a.sort perms.each { |item| item.should be_kind_of(Immutable::List) } end end context 'with a length argument' do it 'yields all N-size permutations of the list' do perms = list.permutation(2).to_a perms.size.should be(12) perms.sort.should == [1,2,3,4].permutation(2).to_a.sort perms.each { |item| item.should be_kind_of(Immutable::List) } end end context 'with a length argument greater than length of list' do it 'yields nothing' do list.permutation(5).to_a.should be_empty end end context 'with a length argument of 0' do it 'yields an empty list' do perms = list.permutation(0).to_a perms.size.should be(1) perms[0].should be_kind_of(Immutable::List) perms[0].should be_empty end end context 'with a block' do it 'returns the original list' do list.permutation(0) {}.should be(list) list.permutation(1) {}.should be(list) list.permutation {}.should be(list) end end end end immutable-ruby-master/spec/lib/immutable/nested/0000755000175000017500000000000014201005456021621 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/nested/construction_spec.rb0000644000175000017500000000725414201005456025722 0ustar boutilboutilrequire 'spec_helper' require 'set' describe Immutable do expectations = [ # [Ruby, Immutable] [ { 'a' => 1, 'b' => [2, {'c' => 3}, 4], 'd' => ::Set.new([5, 6, 7]), 'e' => {'f' => 8, 'g' => 9}, 'h' => Regexp.new('ijk') }, Immutable::Hash[ 'a' => 1, 'b' => Immutable::Vector[2, Immutable::Hash['c' => 3], 4], 'd' => Immutable::Set[5, 6, 7], 'e' => Immutable::Hash['f' => 8, 'g' => 9], 'h' => Regexp.new('ijk') ] ], [ {}, Immutable::Hash[] ], [ {'a' => 1, 'b' => 2, 'c' => 3}, Immutable::Hash['a' => 1, 'b' => 2, 'c' => 3] ], [ [], Immutable::Vector[] ], [ [1, 2, 3], Immutable::Vector[1, 2, 3] ], [ ::Set.new, Immutable::Set[] ], [ ::Set.new([1, 2, 3]), Immutable::Set[1, 2, 3] ], [ 42, 42 ], [ STDOUT, STDOUT ], # Struct conversion is one-way (from Ruby core Struct to Immutable::Hash), not back again! [ Struct::Customer.new, Immutable::Hash[name: nil, address: nil], true ], [ Struct::Customer.new('Dave', '123 Main'), Immutable::Hash[name: 'Dave', address: '123 Main'], true ] ] describe '.from' do expectations.each do |input, expected_result| context "with #{input.inspect} as input" do it "should return #{expected_result.inspect}" do Immutable.from(input).should eql(expected_result) end end end context 'with mixed object' do it 'should return Immutable data' do input = { 'a' => 'b', 'c' => {'d' => 'e'}, 'f' => Immutable::Vector['g', 'h', []], 'i' => Immutable::Hash['j' => {}, 'k' => Immutable::Set[[], {}]] } expected_result = Immutable::Hash[ 'a' => 'b', 'c' => Immutable::Hash['d' => 'e'], 'f' => Immutable::Vector['g', 'h', Immutable::EmptyVector], 'i' => Immutable::Hash['j' => Immutable::EmptyHash, 'k' => Immutable::Set[Immutable::EmptyVector, Immutable::EmptyHash]] ] Immutable.from(input).should eql(expected_result) end end end describe '.to_ruby' do expectations.each do |expected_result, input, one_way| next if one_way context "with #{input.inspect} as input" do it "should return #{expected_result.inspect}" do Immutable.to_ruby(input).should eql(expected_result) end end end context 'with Immutable::Deque[] as input' do it 'should return []' do Immutable.to_ruby(Immutable::Deque[]).should eql([]) end end context 'with Immutable::Deque[Immutable::Hash["a" => 1]] as input' do it 'should return [{"a" => 1}]' do Immutable.to_ruby(Immutable::Deque[Immutable::Hash['a' => 1]]).should eql([{'a' => 1}]) end end context 'with Immutable::SortedSet[] as input' do it 'should return ::SortedSet.new' do Immutable.to_ruby(Immutable::SortedSet[]).should == ::SortedSet.new end end context 'with Immutable::SortedSet[1, 2, 3] as input' do it 'should return ::SortedSet.new' do Immutable.to_ruby(Immutable::SortedSet[1, 2, 3]).should == ::SortedSet.new([1, 2, 3]) end end context 'with mixed object' do it 'should return Ruby data structures' do input = Immutable::Hash[ 'a' => 'b', 'c' => {'d' => 'e'}, 'f' => Immutable::Vector['g', 'h'], 'i' => {'j' => Immutable::EmptyHash, 'k' => Set.new([Immutable::EmptyVector, Immutable::EmptyHash])}] expected_result = { 'a' => 'b', 'c' => {'d' => 'e'}, 'f' => ['g', 'h'], 'i' => {'j' => {}, 'k' => Set.new([[], {}])} } Immutable.to_ruby(input).should eql(expected_result) end end end end immutable-ruby-master/spec/lib/immutable/core_ext/0000755000175000017500000000000014201005456022147 5ustar boutilboutilimmutable-ruby-master/spec/lib/immutable/core_ext/array_spec.rb0000644000175000017500000000036514201005456024630 0ustar boutilboutilrequire 'spec_helper' describe Array do let(:array) { %w[A B C] } describe '#to_list' do let(:to_list) { array.to_list } it 'returns an equivalent Immutable list' do expect(to_list).to eq(L['A', 'B', 'C']) end end end immutable-ruby-master/spec/lib/immutable/core_ext/enumerable_spec.rb0000644000175000017500000000102014201005456025616 0ustar boutilboutilrequire 'spec_helper' describe Enumerable do class TestEnumerable include Enumerable def initialize(*values) @values = values end def each(&block) @values.each(&block) end end let(:enumerable) { TestEnumerable.new('A', 'B', 'C') } describe '#to_list' do let(:to_list) { enumerable.to_list } it 'returns an equivalent list' do expect(to_list).to eq(L['A', 'B', 'C']) end it 'works on Ranges' do expect((1..3).to_list).to eq(L[1, 2, 3]) end end end immutable-ruby-master/spec/lib/immutable/core_ext/io_spec.rb0000644000175000017500000000106114201005456024113 0ustar boutilboutilrequire 'spec_helper' describe IO do describe '#to_list' do let(:list) { L["A\n", "B\n", "C\n"] } let(:to_list) { io.to_list } after(:each) do io.close end context 'with a File' do let(:io) { File.new(fixture_path('io_spec.txt')) } it 'returns an equivalent list' do expect(to_list).to eq(list) end end context 'with a StringIO' do let(:io) { StringIO.new(fixture('io_spec.txt')) } it 'returns an equivalent list' do expect(to_list).to eq(list) end end end end immutable-ruby-master/spec/lib/load_spec.rb0000644000175000017500000000257014201005456020642 0ustar boutilboutil# It should be possible to require any one Immutable structure, # without loading all the others immutable_lib_dir = File.join(File.dirname(__FILE__), '..', '..', 'lib') describe :Immutable do describe :Hash do it 'can be loaded separately' do system(%{ruby -e "$:.unshift('#{immutable_lib_dir}'); require 'immutable/hash'; Immutable::Hash.new"}).should be(true) end end describe :Set do it 'can be loaded separately' do system(%{ruby -e "$:.unshift('#{immutable_lib_dir}'); require 'immutable/set'; Immutable::Set.new"}).should be(true) end end describe :Vector do it 'can be loaded separately' do system(%{ruby -e "$:.unshift('#{immutable_lib_dir}'); require 'immutable/vector'; Immutable::Vector.new"}).should be(true) end end describe :List do it 'can be loaded separately' do system(%{ruby -e "$:.unshift('#{immutable_lib_dir}'); require 'immutable/list'; Immutable::List[]"}).should be(true) end end describe :SortedSet do it 'can be loaded separately' do system(%{ruby -e "$:.unshift('#{immutable_lib_dir}'); require 'immutable/sorted_set'; Immutable::SortedSet.new"}).should be(true) end end describe :Deque do it 'can be loaded separately' do system(%{ruby -e "$:.unshift('#{immutable_lib_dir}'); require 'immutable/deque'; Immutable::Deque.new"}).should be(true) end end end immutable-ruby-master/Gemfile0000644000175000017500000000015614201005456016155 0ustar boutilboutil#!/usr/bin/env ruby source 'https://rubygems.org/' # Dependencies are specified in immutable.gemspec gemspec immutable-ruby-master/.ruby-gemset0000644000175000017500000000001214201005456017115 0ustar boutilboutilimmutable immutable-ruby-master/lib/0000755000175000017500000000000014201005456015426 5ustar boutilboutilimmutable-ruby-master/lib/immutable.rb0000644000175000017500000000036214201005456017733 0ustar boutilboutilrequire 'immutable/core_ext' require 'immutable/list' require 'immutable/deque' require 'immutable/hash' require 'immutable/set' require 'immutable/vector' require 'immutable/sorted_set' require 'immutable/nested' require 'immutable/version' immutable-ruby-master/lib/immutable/0000755000175000017500000000000014201005456017405 5ustar boutilboutilimmutable-ruby-master/lib/immutable/enumerable.rb0000644000175000017500000001230314201005456022050 0ustar boutilboutilmodule Immutable # Helper module for immutable-ruby's sequential collections # # Classes including `Immutable::Enumerable` must implement: # # - `#each` (just like `::Enumerable`). # - `#select`, which takes a block, and returns an instance of the same class # with only the items for which the block returns a true value module Enumerable include ::Enumerable # Return a new collection with all the elements for which the block returns false. def reject return enum_for(:reject) if not block_given? select { |item| !yield(item) } end alias delete_if reject # Return a new collection with all `nil` elements removed. def compact select { |item| !item.nil? } end # Search the collection for elements which are `#===` to `item`. Yield them to # the optional code block if provided, and return them as a new collection. def grep(pattern, &block) result = select { |item| pattern === item } result = result.map(&block) if block_given? result end # Search the collection for elements which are not `#===` to `item`. Yield # them to the optional code block if provided, and return them as a new # collection. def grep_v(pattern, &block) result = select { |item| !(pattern === item) } result = result.map(&block) if block_given? result end # Yield all integers from 0 up to, but not including, the number of items in # this collection. For collections which provide indexed access, these are all # the valid, non-negative indices into the collection. def each_index(&block) return enum_for(:each_index) unless block_given? 0.upto(size-1, &block) self end # Multiply all the items (presumably numeric) in this collection together. def product reduce(1, &:*) end # Add up all the items (presumably numeric) in this collection. def sum reduce(0, &:+) end # Return 2 collections, the first containing all the elements for which the block # evaluates to true, the second containing the rest. def partition return enum_for(:partition) if not block_given? a,b = super [self.class.new(a), self.class.new(b)].freeze end # Groups the collection into sub-collections by the result of yielding them to # the block. Returns a {Hash} where the keys are return values from the block, # and the values are sub-collections. All the sub-collections are built up from # `empty_group`, which should respond to `#add` by returning a new collection # with an added element. def group_by_with(empty_group, &block) block ||= lambda { |item| item } reduce(Immutable::EmptyHash) do |hash, item| key = block.call(item) group = hash.get(key) || empty_group hash.put(key, group.add(item)) end end protected :group_by_with # Groups the collection into sub-collections by the result of yielding them to # the block. Returns a {Hash} where the keys are return values from the block, # and the values are sub-collections (of the same type as this one). def group_by(&block) group_by_with(self.class.empty, &block) end # Compare with `other`, and return 0, 1, or -1 if it is (respectively) equal to, # greater than, or less than this collection. def <=>(other) return 0 if equal?(other) enum1, enum2 = to_enum, other.to_enum loop do item1 = enum1.next item2 = enum2.next comp = (item1 <=> item2) return comp if comp != 0 end size1, size2 = size, other.size return 0 if size1 == size2 size1 > size2 ? 1 : -1 end # Return true if `other` contains the same elements, in the same order. # @return [Boolean] def ==(other) eql?(other) || (other.respond_to?(:to_ary) && to_ary == other.to_ary) end # Convert all the elements into strings and join them together, separated by # `separator`. By default, the `separator` is `$,`, the global default string # separator, which is normally `nil`. def join(separator = $,) result = '' if separator each_with_index { |obj, i| result << separator if i > 0; result << obj.to_s } else each { |obj| result << obj.to_s } end result end # Convert this collection to a {Set}. def to_set Immutable::Set.new(self) end # Convert this collection to a programmer-readable `String` representation. def inspect result = "#{self.class}[" each_with_index { |obj, i| result << ', ' if i > 0; result << obj.inspect } result << ']' end # @private def pretty_print(pp) pp.group(1, "#{self.class}[", ']') do pp.breakable '' pp.seplist(self) { |obj| obj.pretty_print(pp) } end end alias to_ary to_a alias index find_index ## Compatibility fixes if RUBY_ENGINE == 'rbx' # Rubinius implements Enumerable#sort_by using Enumerable#map # Because we do our own, custom implementations of #map, that doesn't work well # @private def sort_by(&block) result = to_a result.frozen? ? result.sort_by(&block) : result.sort_by!(&block) end end end end immutable-ruby-master/lib/immutable/set.rb0000644000175000017500000004463114201005456020535 0ustar boutilboutilrequire '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 immutable-ruby-master/lib/immutable/version.rb0000644000175000017500000000025114201005456021415 0ustar boutilboutilmodule 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' end immutable-ruby-master/lib/immutable/hash.rb0000644000175000017500000007665714201005456020702 0ustar boutilboutilrequire '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] 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 immutable-ruby-master/lib/immutable/deque.rb0000644000175000017500000001772414201005456021050 0ustar boutilboutilrequire 'immutable/list' module Immutable # A `Deque` (or double-ended queue) is an ordered, sequential collection of # objects, which allows elements to be retrieved, added and removed at the # front and end of the sequence in constant time. This makes `Deque` perfect # for use as an immutable queue or stack. # # A `Deque` differs from a {Vector} in that vectors allow indexed access to # any element in the collection. `Deque`s only allow access to the first and # last element. But adding and removing from the ends of a `Deque` is faster # than adding and removing from the ends of a {Vector}. # # To create a new `Deque`: # # Immutable::Deque.new([:first, :second, :third]) # Immutable::Deque[1, 2, 3, 4, 5] # # Or you can start with an empty deque and build it up: # # Immutable::Deque.empty.push('b').push('c').unshift('a') # # Like all `immutable-ruby` collections, `Deque` is immutable. The four basic # operations that "modify" deques ({#push}, {#pop}, {#shift}, and # {#unshift}) all return a new collection and leave the existing one # unchanged. # # @example # deque = Immutable::Deque.empty # => Immutable::Deque[] # deque = deque.push('a').push('b').push('c') # => Immutable::Deque['a', 'b', 'c'] # deque.first # => 'a' # deque.last # => 'c' # deque = deque.shift # => Immutable::Deque['b', 'c'] # # @see http://en.wikipedia.org/wiki/Deque "Deque" on Wikipedia # class Deque class << self # Create a new `Deque` populated with the given items. # @return [Deque] def [](*items) items.empty? ? empty : new(items) end # Return an empty `Deque`. If used on a subclass, returns an empty instance # of that class. # # @return [Deque] def empty @empty ||= new end # "Raw" allocation of a new `Deque`. Used internally to create a new # instance quickly after consing onto the front/rear lists or taking their # tails. # # @return [Deque] # @private def alloc(front, rear) result = allocate result.instance_variable_set(:@front, front) result.instance_variable_set(:@rear, rear) result.freeze end end def initialize(items=[]) @front = List.from_enum(items) @rear = EmptyList freeze end # Return `true` if this `Deque` contains no items. # @return [Boolean] def empty? @front.empty? && @rear.empty? end # Return the number of items in this `Deque`. # # @example # Immutable::Deque["A", "B", "C"].size # => 3 # # @return [Integer] def size @front.size + @rear.size end alias length size # Return the first item in the `Deque`. If the deque is empty, return `nil`. # # @example # Immutable::Deque["A", "B", "C"].first # => "A" # # @return [Object] def first return @front.head unless @front.empty? @rear.last # memoize? end # Return the last item in the `Deque`. If the deque is empty, return `nil`. # # @example # Immutable::Deque["A", "B", "C"].last # => "C" # # @return [Object] def last return @rear.head unless @rear.empty? @front.last # memoize? end # Return a new `Deque` with elements rotated by `n` positions. # A positive rotation moves elements to the right, negative to the left, and 0 is a no-op. # # @example # Immutable::Deque["A", "B", "C"].rotate(1) # # => Immutable::Deque["C", "A", "B"] # Immutable::Deque["A", "B", "C"].rotate(-1) # # => Immutable::Deque["B", "C", "A"] # # @param n [Integer] number of positions to move elements by # @return [Deque] def rotate(n) return self.class.empty if empty? n %= size return self if n == 0 a, b = @front, @rear if b.size >= n n.times { a = a.cons(b.head); b = b.tail } else (size - n).times { b = b.cons(a.head); a = a.tail } end self.class.alloc(a, b) end # Return a new `Deque` with `item` added at the end. # # @example # Immutable::Deque["A", "B", "C"].push("Z") # # => Immutable::Deque["A", "B", "C", "Z"] # # @param item [Object] The item to add # @return [Deque] def push(item) self.class.alloc(@front, @rear.cons(item)) end alias enqueue push # Return a new `Deque` with the last item removed. # # @example # Immutable::Deque["A", "B", "C"].pop # # => Immutable::Deque["A", "B"] # # @return [Deque] def pop front, rear = @front, @rear if rear.empty? return self.class.empty if front.empty? front, rear = EmptyList, front.reverse end self.class.alloc(front, rear.tail) end # Return a new `Deque` with `item` added at the front. # # @example # Immutable::Deque["A", "B", "C"].unshift("Z") # # => Immutable::Deque["Z", "A", "B", "C"] # # @param item [Object] The item to add # @return [Deque] def unshift(item) self.class.alloc(@front.cons(item), @rear) end # Return a new `Deque` with the first item removed. # # @example # Immutable::Deque["A", "B", "C"].shift # # => Immutable::Deque["B", "C"] # # @return [Deque] def shift front, rear = @front, @rear if front.empty? return self.class.empty if rear.empty? front, rear = rear.reverse, EmptyList end self.class.alloc(front.tail, rear) end alias dequeue shift # Return an empty `Deque` instance, of the same class as this one. Useful if you # have multiple subclasses of `Deque` and want to treat them polymorphically. # # @return [Deque] def clear self.class.empty end # Return a new `Deque` with the same items, but in reverse order. # # @return [Deque] def reverse self.class.alloc(@rear, @front) end # Return true if `other` has the same type and contents as this `Deque`. # # @param other [Object] The collection to compare with # @return [Boolean] def eql?(other) return true if other.equal?(self) instance_of?(other.class) && to_ary.eql?(other.to_ary) end alias == eql? # Return an `Array` with the same elements, in the same order. # @return [Array] def to_a @front.to_a.concat(@rear.to_a.tap(&:reverse!)) end alias entries to_a alias to_ary to_a # Return a {List} with the same elements, in the same order. # @return [Immutable::List] def to_list @front.append(@rear.reverse) end # Return the contents of this `Deque` as a programmer-readable `String`. If all the # items in the deque are serializable as Ruby literal strings, the returned string can # be passed to `eval` to reconstitute an equivalent `Deque`. # # @return [String] def inspect result = "#{self.class}[" i = 0 @front.each { |obj| result << ', ' if i > 0; result << obj.inspect; i += 1 } @rear.to_a.tap(&:reverse!).each { |obj| result << ', ' if i > 0; result << obj.inspect; i += 1 } result << ']' end # Return `self`. Since this is an immutable object duplicates are # equivalent. # @return [Deque] def dup self end alias clone dup # @private def pretty_print(pp) pp.group(1, "#{self.class}[", ']') do pp.breakable '' pp.seplist(to_a) { |obj| obj.pretty_print(pp) } end end # @return [::Array] # @private def marshal_dump to_a end # @private def marshal_load(array) initialize(array) end end # The canonical empty `Deque`. Returned by `Deque[]` when # invoked with no arguments; also returned by `Deque.empty`. Prefer using this # one rather than creating many empty deques using `Deque.new`. # # @private EmptyDeque = Immutable::Deque.empty end immutable-ruby-master/lib/immutable/list.rb0000644000175000017500000013603514201005456020715 0ustar boutilboutilrequire 'thread' require 'set' require 'concurrent' require 'immutable/undefined' require 'immutable/enumerable' require 'immutable/hash' require 'immutable/set' module Immutable class << self # Create a lazy, infinite list. # # The given block is called as necessary to return successive elements of the list. # # @example # Immutable.stream { :hello }.take(3) # # => Immutable::List[:hello, :hello, :hello] # # @return [List] def stream(&block) return EmptyList unless block_given? LazyList.new { Cons.new(yield, stream(&block)) } end # Construct a list of consecutive integers. # # @example # Immutable.interval(5,9) # # => Immutable::List[5, 6, 7, 8, 9] # # @param from [Integer] Start value, inclusive # @param to [Integer] End value, inclusive # @return [List] def interval(from, to) return EmptyList if from > to interval_exclusive(from, to.next) end # Create an infinite list repeating the same item indefinitely # # @example # Immutable.repeat(:chunky).take(4) # => Immutable::List[:chunky, :chunky, :chunky, :chunky] # # @return [List] def repeat(item) LazyList.new { Cons.new(item, repeat(item)) } end # Create a list that contains a given item a fixed number of times # # @example # Immutable.replicate(3, :hamster) # #=> Immutable::List[:hamster, :hamster, :hamster] # # @return [List] def replicate(number, item) repeat(item).take(number) end # Create an infinite list where each item is derived from the previous one, # using the provided block # # @example # Immutable.iterate(0) { |i| i.next }.take(5) # # => Immutable::List[0, 1, 2, 3, 4] # # @param [Object] item Starting value # @yieldparam [Object] previous The previous value # @yieldreturn [Object] The next value # @return [List] def iterate(item, &block) LazyList.new { Cons.new(item, iterate(yield(item), &block)) } end # Turn an `Enumerator` into a `Immutable::List`. The result is a lazy # collection where the values are memoized as they are generated. # # If your code uses multiple threads, you need to make sure that the returned # lazy collection is realized on a single thread only. Otherwise, a `FiberError` # will be raised. After the collection is realized, it can be used from other # threads as well. # # @example # def rg; loop { yield rand(100) }; end # Immutable.enumerate(to_enum(:rg)).take(10) # # @param enum [Enumerator] The object to iterate over # @return [List] def enumerate(enum) LazyList.new do begin Cons.new(enum.next, enumerate(enum)) rescue StopIteration EmptyList end end end private def interval_exclusive(from, to) return EmptyList if from == to LazyList.new { Cons.new(from, interval_exclusive(from.next, to)) } end end # A `List` can be constructed with {List.[] List[]}, or {Enumerable#to_list}. # It consists of a *head* (the first element) and a *tail* (which itself is also # a `List`, containing all the remaining elements). # # This is a singly linked list. Prepending to the list with {List#add} runs # in constant time. Traversing the list from front to back is efficient, # however, indexed access runs in linear time because the list needs to be # traversed to find the element. # module List include Immutable::Enumerable # @private CADR = /^c([ad]+)r$/ # Create a new `List` populated with the given items. # # @example # list = Immutable::List[:a, :b, :c] # # => Immutable::List[:a, :b, :c] # # @return [List] def self.[](*items) from_enum(items) end # Return an empty `List`. # # @return [List] def self.empty EmptyList end # This method exists distinct from `.[]` since it is ~30% faster # than splatting the argument. # # Marking as private only because it was introduced for an internal # refactoring. It could potentially be made public with a good name. # # @private def self.from_enum(items) # use destructive operations to build up a new list, like Common Lisp's NCONC # this is a very fast way to build up a linked list list = tail = Cons.allocate items.each do |item| new_node = Cons.allocate new_node.instance_variable_set(:@head, item) tail.instance_variable_set(:@tail, new_node) tail = new_node end tail.instance_variable_set(:@tail, EmptyList) list.tail end # Return the number of items in this `List`. # @return [Integer] def size result, list = 0, self until list.empty? if list.cached_size? return result + list.size else result += 1 end list = list.tail end result end alias length size # Create a new `List` with `item` added at the front. This is a constant # time operation. # # @example # Immutable::List[:b, :c].add(:a) # # => Immutable::List[:a, :b, :c] # # @param item [Object] The item to add # @return [List] def add(item) Cons.new(item, self) end alias cons add # Create a new `List` with `item` added at the end. This is much less efficient # than adding items at the front. # # @example # Immutable::List[:a, :b] << :c # # => Immutable::List[:a, :b, :c] # # @param item [Object] The item to add # @return [List] def <<(item) append(List[item]) end # Call the given block once for each item in the list, passing each # item from first to last successively to the block. If no block is given, # returns an `Enumerator`. # # @return [self] # @yield [item] def each return to_enum unless block_given? list = self until list.empty? yield(list.head) list = list.tail end end # Return a `List` in which each element is derived from the corresponding # element in this `List`, transformed through the given block. If no block # is given, returns an `Enumerator`. # # @example # Immutable::List[3, 2, 1].map { |e| e * e } # => Immutable::List[9, 4, 1] # # @return [List, Enumerator] # @yield [item] def map(&block) return enum_for(:map) unless block_given? LazyList.new do next self if empty? Cons.new(yield(head), tail.map(&block)) end end alias collect map # Return a `List` which is realized by transforming each item into a `List`, # and flattening the resulting lists. # # @example # Immutable::List[1, 2, 3].flat_map { |x| Immutable::List[x, 100] } # # => Immutable::List[1, 100, 2, 100, 3, 100] # # @return [List] def flat_map(&block) return enum_for(:flat_map) unless block_given? LazyList.new do next self if empty? head_list = List.from_enum(yield(head)) next tail.flat_map(&block) if head_list.empty? Cons.new(head_list.first, head_list.drop(1).append(tail.flat_map(&block))) end end # Return a `List` which contains all the items for which the given block # returns true. # # @example # Immutable::List["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } # # => Immutable::List["Bird", "Elephant"] # # @return [List] # @yield [item] Once for each item. def select(&block) return enum_for(:select) unless block_given? LazyList.new do list = self loop do break list if list.empty? break Cons.new(list.head, list.tail.select(&block)) if yield(list.head) list = list.tail end end end alias find_all select alias keep_if select # Return a `List` which contains all elements up to, but not including, the # first element for which the block returns `nil` or `false`. # # @example # Immutable::List[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 } # # => Immutable::List[1, 3] # # @return [List, Enumerator] # @yield [item] def take_while(&block) return enum_for(:take_while) unless block_given? LazyList.new do next self if empty? next Cons.new(head, tail.take_while(&block)) if yield(head) EmptyList end end # Return a `List` which contains all elements starting from the # first element for which the block returns `nil` or `false`. # # @example # Immutable::List[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 } # # => Immutable::List[5, 7, 6, 4, 2] # # @return [List, Enumerator] # @yield [item] def drop_while(&block) return enum_for(:drop_while) unless block_given? LazyList.new do list = self list = list.tail while !list.empty? && yield(list.head) list end end # Return a `List` containing the first `number` items from this `List`. # # @example # Immutable::List[1, 3, 5, 7, 6, 4, 2].take(3) # # => Immutable::List[1, 3, 5] # # @param number [Integer] The number of items to retain # @return [List] def take(number) LazyList.new do next self if empty? next Cons.new(head, tail.take(number - 1)) if number > 0 EmptyList end end # Return a `List` containing all but the last item from this `List`. # # @example # Immutable::List["A", "B", "C"].pop # => Immutable::List["A", "B"] # # @return [List] def pop LazyList.new do next self if empty? new_size = size - 1 next Cons.new(head, tail.take(new_size - 1)) if new_size >= 1 EmptyList end end # Return a `List` containing all items after the first `number` items from # this `List`. # # @example # Immutable::List[1, 3, 5, 7, 6, 4, 2].drop(3) # # => Immutable::List[7, 6, 4, 2] # # @param number [Integer] The number of items to skip over # @return [List] def drop(number) LazyList.new do list = self while !list.empty? && number > 0 number -= 1 list = list.tail end list end end # Return a `List` with all items from this `List`, followed by all items from # `other`. # # @example # Immutable::List[1, 2, 3].append(Immutable::List[4, 5]) # # => Immutable::List[1, 2, 3, 4, 5] # # @param other [List] The list to add onto the end of this one # @return [List] def append(other) LazyList.new do next other if empty? Cons.new(head, tail.append(other)) end end alias concat append alias + append # Return a `List` with the same items, but in reverse order. # # @example # Immutable::List["A", "B", "C"].reverse # => Immutable::List["C", "B", "A"] # # @return [List] def reverse LazyList.new { reduce(EmptyList) { |list, item| list.cons(item) }} end # Combine two lists by "zipping" them together. The corresponding elements # from this `List` and each of `others` (that is, the elements with the # same indices) will be gathered into lists. # # If `others` contains fewer elements than this list, `nil` will be used # for padding. # # @example # Immutable::List["A", "B", "C"].zip(Immutable::List[1, 2, 3]) # # => Immutable::List[Immutable::List["A", 1], Immutable::List["B", 2], Immutable::List["C", 3]] # # @param others [List] The list to zip together with this one # @return [List] def zip(others) LazyList.new do next self if empty? && others.empty? Cons.new(Cons.new(head, Cons.new(others.head)), tail.zip(others.tail)) end end # Gather the first element of each nested list into a new `List`, then the second # element of each nested list, then the third, and so on. In other words, if each # nested list is a "row", return a `List` of "columns" instead. # # Although the returned list is lazy, each returned nested list (each "column") # is strict. So while each nested list in the input can be infinite, the parent # `List` must not be, or trying to realize the first element in the output will # cause an infinite loop. # # @example # # First let's create some infinite lists # list1 = Immutable.iterate(1, &:next) # list2 = Immutable.iterate(2) { |n| n * 2 } # list3 = Immutable.iterate(3) { |n| n * 3 } # # # Now we transpose our 3 infinite "rows" into an infinite series of 3-element "columns" # Immutable::List[list1, list2, list3].transpose.take(4) # # => Immutable::List[ # # Immutable::List[1, 2, 3], # # Immutable::List[2, 4, 9], # # Immutable::List[3, 8, 27], # # Immutable::List[4, 16, 81]] # # @return [List] def transpose return EmptyList if empty? LazyList.new do next EmptyList if any?(&:empty?) heads, tails = EmptyList, EmptyList reverse_each { |list| heads, tails = heads.cons(list.head), tails.cons(list.tail) } Cons.new(heads, tails.transpose) end end # Concatenate an infinite series of copies of this `List` together into a # new `List`. Or, if empty, just return an empty list. # # @example # Immutable::List[1, 2, 3].cycle.take(10) # # => Immutable::List[1, 2, 3, 1, 2, 3, 1, 2, 3, 1] # # @return [List] def cycle LazyList.new do next self if empty? Cons.new(head, tail.append(cycle)) end end # Return a new `List` with the same elements, but rotated so that the one at # index `count` is the first element of the new list. 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 # l = Immutable::List["A", "B", "C", "D", "E", "F"] # l.rotate(2) # => Immutable::List["C", "D", "E", "F", "A", "B"] # l.rotate(-1) # => Immutable::List["F", "A", "B", "C", "D", "E"] # # @param count [Integer] The number of positions to shift items by # @return [Vector] # @raise [TypeError] if count is not an integer. def rotate(count = 1) raise TypeError, 'expected Integer' if not count.is_a?(Integer) return self if empty? || (count % size) == 0 count = (count >= 0) ? count % size : (size - (~count % size) - 1) drop(count).append(take(count)) end # Return two `List`s, one of the first `number` items, and another with the # remaining. # # @example # Immutable::List["a", "b", "c", "d"].split_at(2) # # => [Immutable::List["a", "b"], Immutable::List["c", "d"]] # # @param number [Integer] The index at which to split this list # @return [Array] def split_at(number) [take(number), drop(number)].freeze end # Return two `List`s, one up to (but not including) the first item for which the # block returns `nil` or `false`, and another of all the remaining items. # # @example # Immutable::List[4, 3, 5, 2, 1].span { |x| x > 2 } # # => [Immutable::List[4, 3, 5], Immutable::List[2, 1]] # # @return [Array] # @yield [item] def span(&block) return [self, EmptyList].freeze unless block_given? splitter = Splitter.new(self, block) mutex = Mutex.new [Splitter::Left.new(splitter, splitter.left, mutex), Splitter::Right.new(splitter, mutex)].freeze end # Return two `List`s, one up to (but not including) the first item for which the # block returns true, and another of all the remaining items. # # @example # Immutable::List[1, 3, 4, 2, 5].break { |x| x > 3 } # # => [Immutable::List[1, 3], Immutable::List[4, 2, 5]] # # @return [Array] # @yield [item] def break(&block) return span unless block_given? span { |item| !yield(item) } end # Return an empty `List`. If used on a subclass, returns an empty instance # of that class. # # @return [List] def clear EmptyList end # Return a new `List` with the same items, but sorted. # # @overload sort # Compare elements with their natural sort key (`#<=>`). # # @example # Immutable::List["Elephant", "Dog", "Lion"].sort # # => Immutable::List["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::List["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } # # => Immutable::List["Dog", "Lion", "Elephant"] # # @return [List] def sort(&comparator) LazyList.new { List.from_enum(super(&comparator)) } end # Return a new `List` 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::List["Elephant", "Dog", "Lion"].sort_by { |e| e.size } # # => Immutable::List["Dog", "Lion", "Elephant"] # # @return [List] def sort_by(&transformer) return sort unless block_given? LazyList.new { List.from_enum(super(&transformer)) } end # Return a new `List` with `sep` inserted between each of the existing elements. # # @example # Immutable::List["one", "two", "three"].intersperse(" ") # # => Immutable::List["one", " ", "two", " ", "three"] # # @return [List] def intersperse(sep) LazyList.new do next self if tail.empty? Cons.new(head, Cons.new(sep, tail.intersperse(sep))) end end # Return a `List` with the same items, but all duplicates removed. # Use `#hash` and `#eql?` to determine which items are duplicates. # # @example # Immutable::List[:a, :b, :a, :c, :b].uniq # => Immutable::List[:a, :b, :c] # Immutable::List["a", "A", "b"].uniq(&:upcase) # => Immutable::List["a", "b"] # # @return [List] def uniq(&block) _uniq(::Set.new, &block) end # @private # Separate from `uniq` so as not to expose `items` in the public API. def _uniq(items, &block) if block_given? LazyList.new do next self if empty? if items.add?(block.call(head)) Cons.new(head, tail._uniq(items, &block)) else tail._uniq(items, &block) end end else LazyList.new do next self if empty? next tail._uniq(items) if items.include?(head) Cons.new(head, tail._uniq(items.add(head))) end end end protected :_uniq # Return a `List` with all the elements from both this list and `other`, # with all duplicates removed. # # @example # Immutable::List[1, 2].union(Immutable::List[2, 3]) # => Immutable::List[1, 2, 3] # # @param other [List] The list to merge with # @return [List] def union(other, items = ::Set.new) LazyList.new do next other._uniq(items) if empty? next tail.union(other, items) if items.include?(head) Cons.new(head, tail.union(other, items.add(head))) end end alias | union # Return a `List` with all elements except the last one. # # @example # Immutable::List["a", "b", "c"].init # => Immutable::List["a", "b"] # # @return [List] def init return EmptyList if tail.empty? LazyList.new { Cons.new(head, tail.init) } end # Return the last item in this list. # @return [Object] def last list = self list = list.tail until list.tail.empty? list.head end # Return a `List` of all suffixes of this list. # # @example # Immutable::List[1,2,3].tails # # => Immutable::List[ # # Immutable::List[1, 2, 3], # # Immutable::List[2, 3], # # Immutable::List[3]] # # @return [List] def tails LazyList.new do next self if empty? Cons.new(self, tail.tails) end end # Return a `List` of all prefixes of this list. # # @example # Immutable::List[1,2,3].inits # # => Immutable::List[ # # Immutable::List[1], # # Immutable::List[1, 2], # # Immutable::List[1, 2, 3]] # # @return [List] def inits LazyList.new do next self if empty? Cons.new(List[head], tail.inits.map { |list| list.cons(head) }) end end # Return a `List` of all combinations of length `n` of items from this `List`. # # @example # Immutable::List[1,2,3].combination(2) # # => Immutable::List[ # # Immutable::List[1, 2], # # Immutable::List[1, 3], # # Immutable::List[2, 3]] # # @return [List] def combination(n) return Cons.new(EmptyList) if n == 0 LazyList.new do next self if empty? tail.combination(n - 1).map { |list| list.cons(head) }.append(tail.combination(n)) end end # Split the items in this list in groups of `number`. Return a list of lists. # # @example # ("a".."o").to_list.chunk(5) # # => Immutable::List[ # # Immutable::List["a", "b", "c", "d", "e"], # # Immutable::List["f", "g", "h", "i", "j"], # # Immutable::List["k", "l", "m", "n", "o"]] # # @return [List] def chunk(number) LazyList.new do next self if empty? first, remainder = split_at(number) Cons.new(first, remainder.chunk(number)) end end # Split the items in this list in groups of `number`, and yield each group # to the block (as a `List`). If no block is given, returns an # `Enumerator`. # # @return [self, Enumerator] # @yield [list] Once for each chunk. def each_chunk(number, &block) return enum_for(:each_chunk, number) unless block_given? chunk(number).each(&block) self end alias each_slice each_chunk # Return a new `List` with all nested lists recursively "flattened out", # that is, their elements inserted into the new `List` in the place where # the nested list originally was. # # @example # Immutable::List[Immutable::List[1, 2], Immutable::List[3, 4]].flatten # # => Immutable::List[1, 2, 3, 4] # # @return [List] def flatten LazyList.new do next self if empty? next head.append(tail.flatten) if head.is_a?(List) Cons.new(head, tail.flatten) end end # Passes each item to the block, and gathers them into a {Hash} where the # keys are return values from the block, and the values are `List`s of items # for which the block returned that value. # # @return [Hash] # @yield [item] # @example # Immutable::List["a", "b", "ab"].group_by { |e| e.size } # # Immutable::Hash[ # # 1 => Immutable::List["b", "a"], # # 2 => Immutable::List["ab"] # # ] def group_by(&block) group_by_with(EmptyList, &block) end alias group group_by # Retrieve the item at `index`. Negative indices count back from the end of # the list (-1 is the last item). If `index` is invalid (either too high or # too low), return `nil`. # # @param index [Integer] The index to retrieve # @return [Object] def at(index) index += size if index < 0 return nil if index < 0 node = self while index > 0 node = node.tail index -= 1 end node.head end # Return specific objects from the `List`. All overloads return `nil` if # the starting index is out of range. # # @overload list.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 # l = Immutable::List["A", "B", "C", "D", "E", "F"] # l[2] # => "C" # l[-1] # => "F" # l[6] # => nil # # @overload list.slice(index, length) # Return a sublist starting at `index` and continuing for `length` # elements or until the end of the `List`, 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 [List] # @example # l = Immutable::List["A", "B", "C", "D", "E", "F"] # l[2, 3] # => Immutable::List["C", "D", "E"] # l[-2, 3] # => Immutable::List["E", "F"] # l[20, 1] # => nil # # @overload list.slice(index..end) # Return a sublist starting at `index` and continuing to index # `end` or the end of the `List`, whichever occurs first. # # @param range [Range] The range of indices to retrieve. # @return [Vector] # @example # l = Immutable::List["A", "B", "C", "D", "E", "F"] # l[2..3] # => Immutable::List["C", "D"] # l[-2..100] # => Immutable::List["E", "F"] # l[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 return nil if from < 0 to += size if to < 0 to += 1 if !arg.exclude_end? length = to - from length = 0 if length < 0 list = self while from > 0 return nil if list.empty? list = list.tail from -= 1 end list.take(length) else at(arg) end else return nil if length < 0 arg += size if arg < 0 return nil if arg < 0 list = self while arg > 0 return nil if list.empty? list = list.tail arg -= 1 end list.take(length) end end alias [] slice # Return a `List` of indices of matching objects. # # @overload indices(object) # Return a `List` of indices where `object` is found. Use `#==` for # testing equality. # # @example # Immutable::List[1, 2, 3, 4].indices(2) # # => Immutable::List[1] # # @overload indices # Pass each item successively to the block. Return a list of indices # where the block returns true. # # @yield [item] # @example # Immutable::List[1, 2, 3, 4].indices { |e| e.even? } # # => Immutable::List[1, 3] # # @return [List] def indices(object = Undefined, i = 0, &block) return indices { |item| item == object } if not block_given? return EmptyList if empty? LazyList.new do node = self loop do break Cons.new(i, node.tail.indices(Undefined, i + 1, &block)) if yield(node.head) node = node.tail break EmptyList if node.empty? i += 1 end end end # Merge all the nested lists into a single list, using the given comparator # block to determine the order which items should be shifted out of the nested # lists and into the output list. # # @example # list_1 = Immutable::List[1, -3, -5] # list_2 = Immutable::List[-2, 4, 6] # Immutable::List[list_1, list_2].merge { |a,b| a.abs <=> b.abs } # # => Immutable::List[1, -2, -3, 4, -5, 6] # # @return [List] # @yield [a, b] Pairs of items from matching indices in each list. # @yieldreturn [Integer] Negative if the first element should be selected # first, positive if the latter element, or zero if # either. def merge(&comparator) return merge_by unless block_given? LazyList.new do sorted = reject(&:empty?).sort do |a, b| yield(a.head, b.head) end next EmptyList if sorted.empty? Cons.new(sorted.head.head, sorted.tail.cons(sorted.head.tail).merge(&comparator)) end end # Merge all the nested lists into a single list, using sort keys generated # by mapping the items in the nested lists through the given block to determine the # order which items should be shifted out of the nested lists and into the output # list. Whichever nested list's `#head` has the "lowest" sort key (according to # their natural order) will be the first in the merged `List`. # # @example # list_1 = Immutable::List[1, -3, -5] # list_2 = Immutable::List[-2, 4, 6] # Immutable::List[list_1, list_2].merge_by { |x| x.abs } # # => Immutable::List[1, -2, -3, 4, -5, 6] # # @return [List] # @yield [item] Once for each item in either list. # @yieldreturn [Object] A sort key for the element. def merge_by(&transformer) return merge_by { |item| item } unless block_given? LazyList.new do sorted = reject(&:empty?).sort_by do |list| yield(list.head) end next EmptyList if sorted.empty? Cons.new(sorted.head.head, sorted.tail.cons(sorted.head.tail).merge_by(&transformer)) end end # Return a randomly chosen element from this list. # @return [Object] def sample at(rand(size)) end # Return a new `List` with the given items inserted before the item at `index`. # # @example # Immutable::List["A", "D", "E"].insert(1, "B", "C") # => Immutable::List["A", "B", "C", "D", "E"] # # @param index [Integer] The index where the new items should go # @param items [Array] The items to add # @return [List] def insert(index, *items) if index == 0 return List.from_enum(items).append(self) elsif index > 0 LazyList.new do Cons.new(head, tail.insert(index-1, *items)) end else raise IndexError if index < -size insert(index + size, *items) end end # Return a `List` with all elements equal to `obj` removed. `#==` is used # for testing equality. # # @example # Immutable::List[:a, :b, :a, :a, :c].delete(:a) # => Immutable::List[:b, :c] # # @param obj [Object] The object to remove. # @return [List] def delete(obj) list = self list = list.tail while list.head == obj && !list.empty? return EmptyList if list.empty? LazyList.new { Cons.new(list.head, list.tail.delete(obj)) } end # Return a `List` containing the same items, minus the one at `index`. # If `index` is negative, it counts back from the end of the list. # # @example # Immutable::List[1, 2, 3].delete_at(1) # => Immutable::List[1, 3] # Immutable::List[1, 2, 3].delete_at(-1) # => Immutable::List[1, 2] # # @param index [Integer] The index of the item to remove # @return [List] def delete_at(index) if index == 0 tail elsif index < 0 index += size if index < 0 return self if index < 0 delete_at(index) else LazyList.new { Cons.new(head, tail.delete_at(index - 1)) } end end # Replace a range of indexes with the given object. # # @overload fill(object) # Return a new `List` of the same size, with every index set to `object`. # # @param [Object] object Fill value. # @example # Immutable::List["A", "B", "C", "D", "E", "F"].fill("Z") # # => Immutable::List["Z", "Z", "Z", "Z", "Z", "Z"] # # @overload fill(object, index) # Return a new `List` with all indexes from `index` to the end of the # vector set to `obj`. # # @param [Object] object Fill value. # @param [Integer] index Starting index. May be negative. # @example # Immutable::List["A", "B", "C", "D", "E", "F"].fill("Z", 3) # # => Immutable::List["A", "B", "C", "Z", "Z", "Z"] # # @overload fill(object, index, length) # Return a new `List` with `length` indexes, beginning from `index`, # set to `obj`. Expands the `List` 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::List["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2) # # => Immutable::List["A", "B", "C", "Z", "Z", "F"] # Immutable::List["A", "B"].fill("Z", 1, 5) # # => Immutable::List["A", "Z", "Z", "Z", "Z", "Z"] # # @return [List] # @raise [IndexError] if index is out of negative range. def fill(obj, index = 0, length = nil) if index == 0 length ||= size if length > 0 LazyList.new do Cons.new(obj, tail.fill(obj, 0, length-1)) end else self end elsif index > 0 LazyList.new do Cons.new(head, tail.fill(obj, index-1, length)) end else raise IndexError if index < -size fill(obj, index + size, length) end end # Yields all permutations of length `n` of the items in the list, and then # returns `self`. If no length `n` is specified, permutations of the entire # list 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 # Immutable::List[1, 2, 3].permutation.to_a # # => [Immutable::List[1, 2, 3], # # Immutable::List[2, 1, 3], # # Immutable::List[2, 3, 1], # # Immutable::List[1, 3, 2], # # Immutable::List[3, 1, 2], # # Immutable::List[3, 2, 1]] # # @return [self, Enumerator] # @yield [list] Once for each permutation. def permutation(length = size, &block) return enum_for(:permutation, length) if not block_given? if length == 0 yield EmptyList elsif length == 1 each { |obj| yield Cons.new(obj, EmptyList) } elsif not empty? if length < size tail.permutation(length, &block) end tail.permutation(length-1) do |p| 0.upto(length-1) do |i| left,right = p.split_at(i) yield left.append(right.cons(head)) end end end self end # Yield every non-empty sublist to the given block. (The entire `List` also # counts as one sublist.) # # @example # Immutable::List[1, 2, 3].subsequences { |list| p list } # # prints: # # Immutable::List[1] # # Immutable::List[1, 2] # # Immutable::List[1, 2, 3] # # Immutable::List[2] # # Immutable::List[2, 3] # # Immutable::List[3] # # @yield [sublist] One or more contiguous elements from this list # @return [self] def subsequences(&block) return enum_for(:subsequences) if not block_given? if not empty? 1.upto(size) do |n| yield take(n) end tail.subsequences(&block) end self end # Return two `List`s, the first containing all the elements for which the # block evaluates to true, the second containing the rest. # # @example # Immutable::List[1, 2, 3, 4, 5, 6].partition { |x| x.even? } # # => [Immutable::List[2, 4, 6], Immutable::List[1, 3, 5]] # # @return [List] # @yield [item] Once for each item. def partition(&block) return enum_for(:partition) if not block_given? partitioner = Partitioner.new(self, block) mutex = Mutex.new [Partitioned.new(partitioner, partitioner.left, mutex), Partitioned.new(partitioner, partitioner.right, mutex)].freeze 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) list = self loop do return true if other.equal?(list) return false unless other.is_a?(List) return other.empty? if list.empty? return false if other.empty? return false unless other.head.eql?(list.head) list = list.tail other = other.tail end 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 [List] def dup self end alias clone dup # Return `self`. # @return [List] def to_list self end # Return the contents of this `List` as a programmer-readable `String`. If all the # items in the list are serializable as Ruby literal strings, the returned string can # be passed to `eval` to reconstitute an equivalent `List`. # # @return [String] def inspect result = 'Immutable::List[' each_with_index { |obj, i| result << ', ' if i > 0; result << obj.inspect } result << ']' end # Allows this `List` 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, 'Immutable::List[', ']') do pp.breakable '' pp.seplist(self) { |obj| obj.pretty_print(pp) } end end # @private def respond_to?(name, include_private = false) super || !!name.to_s.match(CADR) end # Return `true` if the size of this list can be obtained in constant time (without # traversing the list). # @return [Integer] def cached_size? false end private # Perform compositions of `car` and `cdr` operations (traditional shorthand # for `head` and `tail` respectively). Their names consist of a `c`, # followed by at least one `a` or `d`, and finally an `r`. The series of # `a`s and `d`s in the method name identify the series of `car` and `cdr` # operations performed, in inverse order. # # @return [Object, List] # @example # l = Immutable::List[nil, Immutable::List[1]] # l.car # => nil # l.cdr # => Immutable::List[Immutable::List[1]] # l.cadr # => Immutable::List[1] # l.caadr # => 1 def method_missing(name, *args, &block) if name.to_s.match(CADR) code = "def #{name}; self." code << Regexp.last_match[1].reverse.chars.map do |char| {'a' => 'head', 'd' => 'tail'}[char] end.join('.') code << '; end' List.class_eval(code) send(name, *args, &block) else super end end end # The basic building block for constructing lists # # A Cons, also known as a "cons cell", has a "head" and a "tail", where # the head is an element in the list, and the tail is a reference to the # rest of the list. This way a singly linked list can be constructed, with # each `Cons` holding a single element and a pointer to the next # `Cons`. # # The last `Cons` instance in the chain has the {EmptyList} as its tail. # # @private class Cons include List attr_reader :head, :tail def initialize(head, tail = EmptyList) @head = head @tail = tail @size = tail.cached_size? ? tail.size + 1 : nil end def empty? false end def size @size ||= super end alias length size def cached_size? @size != nil end end # A `LazyList` takes a block that returns a `List`, i.e. an object that responds # to `#head`, `#tail` and `#empty?`. The list is only realized (i.e. the block is # only called) when one of these operations is performed. # # By returning a `Cons` that in turn has a {LazyList} as its tail, one can # construct infinite `List`s. # # @private class LazyList include List def initialize(&block) @head = block # doubles as storage for block while yet unrealized @tail = nil @atomic = Concurrent::Atom.new(0) # haven't yet run block @size = nil end def head realize if @atomic.value != 2 @head end alias first head def tail realize if @atomic.value != 2 @tail end def empty? realize if @atomic.value != 2 @size == 0 end def size @size ||= super end alias length size def cached_size? @size != nil end private QUEUE = ConditionVariable.new MUTEX = Mutex.new def realize loop do # try to "claim" the right to run the block which realizes target if @atomic.compare_and_set(0,1) # full memory barrier here begin list = @head.call if list.empty? @head, @tail, @size = nil, self, 0 else @head, @tail = list.head, list.tail end rescue @atomic.reset(0) MUTEX.synchronize { QUEUE.broadcast } raise end @atomic.reset(2) MUTEX.synchronize { QUEUE.broadcast } return end # we failed to "claim" it, another thread must be running it if @atomic.value == 1 # another thread is running the block MUTEX.synchronize do # check value of @atomic again, in case another thread already changed it # *and* went past the call to QUEUE.broadcast before we got here QUEUE.wait(MUTEX) if @atomic.value == 1 end elsif @atomic.value == 2 # another thread finished the block return end end end end # Common behavior for other classes which implement various kinds of `List`s # @private class Realizable include List def initialize @head, @tail, @size = Undefined, Undefined, nil end def head realize if @head == Undefined @head end alias first head def tail realize if @tail == Undefined @tail end def empty? realize if @head == Undefined @size == 0 end def size @size ||= super end alias length size def cached_size? @size != nil end def realized? @head != Undefined end end # This class can divide a collection into 2 `List`s, one of items # for which the block returns true, and another for false # At the same time, it guarantees the block will only be called ONCE for each item # # @private class Partitioner attr_reader :left, :right def initialize(list, block) @list, @block, @left, @right = list, block, [], [] end def next_item unless @list.empty? item = @list.head (@block.call(item) ? @left : @right) << item @list = @list.tail end end def done? @list.empty? end end # One of the `List`s which gets its items from a Partitioner # @private class Partitioned < Realizable def initialize(partitioner, buffer, mutex) super() @partitioner, @buffer, @mutex = partitioner, buffer, mutex end def realize # another thread may get ahead of us and null out @mutex mutex = @mutex mutex && mutex.synchronize do return if @head != Undefined # another thread got ahead of us loop do if !@buffer.empty? @head = @buffer.shift @tail = Partitioned.new(@partitioner, @buffer, @mutex) # don't hold onto references # tail will keep references alive until end of list is reached @partitioner, @buffer, @mutex = nil, nil, nil return elsif @partitioner.done? @head, @size, @tail = nil, 0, self @partitioner, @buffer, @mutex = nil, nil, nil # allow them to be GC'd return else @partitioner.next_item end end end end end # This class can divide a list up into 2 `List`s, one for the prefix of # elements for which the block returns true, and another for all the elements # after that. It guarantees that the block will only be called ONCE for each # item # # @private class Splitter attr_reader :left, :right def initialize(list, block) @list, @block, @left, @right = list, block, [], EmptyList end def next_item unless @list.empty? item = @list.head if @block.call(item) @left << item @list = @list.tail else @right = @list @list = EmptyList end end end def done? @list.empty? end # @private class Left < Realizable def initialize(splitter, buffer, mutex) super() @splitter, @buffer, @mutex = splitter, buffer, mutex end def realize # another thread may get ahead of us and null out @mutex mutex = @mutex mutex && mutex.synchronize do return if @head != Undefined # another thread got ahead of us loop do if !@buffer.empty? @head = @buffer.shift @tail = Left.new(@splitter, @buffer, @mutex) @splitter, @buffer, @mutex = nil, nil, nil return elsif @splitter.done? @head, @size, @tail = nil, 0, self @splitter, @buffer, @mutex = nil, nil, nil return else @splitter.next_item end end end end end # @private class Right < Realizable def initialize(splitter, mutex) super() @splitter, @mutex = splitter, mutex end def realize mutex = @mutex mutex && mutex.synchronize do return if @head != Undefined @splitter.next_item until @splitter.done? if @splitter.right.empty? @head, @size, @tail = nil, 0, self else @head, @tail = @splitter.right.head, @splitter.right.tail end @splitter, @mutex = nil, nil end end end end # A list without any elements. This is a singleton, since all empty lists are equivalent. # @private module EmptyList class << self include List # There is no first item in an empty list, so return `nil`. # @return [nil] def head nil end alias first head # There are no subsequent elements, so return an empty list. # @return [self] def tail self end def empty? true end # Return the number of items in this `List`. # @return [Integer] def size 0 end alias length size def cached_size? true end end end.freeze end immutable-ruby-master/lib/immutable/trie.rb0000644000175000017500000002246414201005456020705 0ustar boutilboutilmodule Immutable # @private class Trie def self.[](pairs) result = new(0) pairs.each { |key, val| result.put!(key, val) } result end # Returns the number of key-value pairs in the trie. attr_reader :size def initialize(bitshift, size = 0, entries = [], children = []) @bitshift = bitshift @entries = entries @children = children @size = size end # Returns true if the trie contains no key-value pairs. def empty? @size == 0 end # Returns true if the given key is present in the trie. def key?(key) !!get(key) end # Calls block once for each entry in the trie, passing the key-value pair as parameters. def each(&block) @entries.each { |entry| yield entry if entry } @children.each do |child| child.each(&block) if child end nil end def reverse_each(&block) @children.reverse_each do |child| child.reverse_each(&block) if child end @entries.reverse_each { |entry| yield(entry) if entry } nil end def reduce(memo) each { |entry| memo = yield(memo, entry) } memo end def select keys_to_delete = [] each { |entry| keys_to_delete << entry[0] unless yield(entry) } bulk_delete(keys_to_delete) end # @return [Trie] A copy of `self` with the given value associated with the # key (or `self` if no modification was needed because an identical # key-value pair was already stored def put(key, value) index = index_for(key) entry = @entries[index] if !entry entries = @entries.dup key = key.dup.freeze if key.is_a?(String) && !key.frozen? entries[index] = [key, value].freeze Trie.new(@bitshift, @size + 1, entries, @children) elsif entry[0].eql?(key) if entry[1].equal?(value) self else entries = @entries.dup key = key.dup.freeze if key.is_a?(String) && !key.frozen? entries[index] = [key, value].freeze Trie.new(@bitshift, @size, entries, @children) end else child = @children[index] if child new_child = child.put(key, value) if new_child.equal?(child) self else children = @children.dup children[index] = new_child new_self_size = @size + (new_child.size - child.size) Trie.new(@bitshift, new_self_size, @entries, children) end else children = @children.dup children[index] = Trie.new(@bitshift + 5).put!(key, value) Trie.new(@bitshift, @size + 1, @entries, children) end end end # Put multiple elements into a Trie. This is more efficient than several # calls to `#put`. # # @param key_value_pairs Enumerable of pairs (`[key, value]`) # @return [Trie] A copy of `self` after associated the given keys and # values (or `self` if no modifications where needed). def bulk_put(key_value_pairs) new_entries = nil new_children = nil new_size = @size key_value_pairs.each do |key, value| index = index_for(key) entry = (new_entries || @entries)[index] if !entry new_entries ||= @entries.dup key = key.dup.freeze if key.is_a?(String) && !key.frozen? new_entries[index] = [key, value].freeze new_size += 1 elsif entry[0].eql?(key) if !entry[1].equal?(value) new_entries ||= @entries.dup key = key.dup.freeze if key.is_a?(String) && !key.frozen? new_entries[index] = [key, value].freeze end else child = (new_children || @children)[index] if child new_child = child.put(key, value) if !new_child.equal?(child) new_children ||= @children.dup new_children[index] = new_child new_size += new_child.size - child.size end else new_children ||= @children.dup new_children[index] = Trie.new(@bitshift + 5).put!(key, value) new_size += 1 end end end if new_entries || new_children Trie.new(@bitshift, new_size, new_entries || @entries, new_children || @children) else self end end # Returns self after overwriting the element associated with the specified key. def put!(key, value) index = index_for(key) entry = @entries[index] if !entry @size += 1 key = key.dup.freeze if key.is_a?(String) && !key.frozen? @entries[index] = [key, value].freeze elsif entry[0].eql?(key) key = key.dup.freeze if key.is_a?(String) && !key.frozen? @entries[index] = [key, value].freeze else child = @children[index] if child old_child_size = child.size @children[index] = child.put!(key, value) @size += child.size - old_child_size else @children[index] = Trie.new(@bitshift + 5).put!(key, value) @size += 1 end end self end # Retrieves the entry corresponding to the given key. If not found, returns nil. def get(key) index = index_for(key) entry = @entries[index] if entry && entry[0].eql?(key) entry else child = @children[index] child.get(key) if child end end # Returns a copy of self with the given key (and associated value) deleted. If not found, returns self. def delete(key) find_and_delete(key) || Trie.new(@bitshift) end # Delete multiple elements from a Trie. This is more efficient than # several calls to `#delete`. # # @param keys [Enumerable] The keys to delete # @return [Trie] def bulk_delete(keys) new_entries = nil new_children = nil new_size = @size keys.each do |key| index = index_for(key) entry = (new_entries || @entries)[index] if !entry next elsif entry[0].eql?(key) new_entries ||= @entries.dup child = (new_children || @children)[index] if child # Bring up the first entry from the child into entries new_children ||= @children.dup new_children[index] = child.delete_at do |entry| new_entries[index] = entry end else new_entries[index] = nil end new_size -= 1 else child = (new_children || @children)[index] if child copy = child.find_and_delete(key) unless copy.equal?(child) new_children ||= @children.dup new_children[index] = copy new_size -= (child.size - copy_size(copy)) end end end end if new_entries || new_children Trie.new(@bitshift, new_size, new_entries || @entries, new_children || @children) else self end end def include?(key, value) entry = get(key) entry && value.eql?(entry[1]) end def at(index) @entries.each do |entry| if entry return entry if index == 0 index -= 1 end end @children.each do |child| if child if child.size >= index+1 return child.at(index) else index -= child.size end end end nil end # Returns true if . eql? is synonymous with == def eql?(other) return true if equal?(other) return false unless instance_of?(other.class) && size == other.size each do |entry| return false unless other.include?(entry[0], entry[1]) end true end alias == eql? protected # Returns a replacement instance after removing the specified key. # If not found, returns self. # If empty, returns nil. def find_and_delete(key) index = index_for(key) entry = @entries[index] if entry && entry[0].eql?(key) return delete_at(index) else child = @children[index] if child copy = child.find_and_delete(key) unless copy.equal?(child) children = @children.dup children[index] = copy new_size = @size - (child.size - copy_size(copy)) return Trie.new(@bitshift, new_size, @entries, children) end end end self end # Returns a replacement instance after removing the specified entry. If empty, returns nil def delete_at(index = @entries.index { |e| e }) yield(@entries[index]) if block_given? if size > 1 entries = @entries.dup child = @children[index] if child children = @children.dup children[index] = child.delete_at do |entry| entries[index] = entry end else entries[index] = nil end Trie.new(@bitshift, @size - 1, entries, children || @children) end end private def index_for(key) (key.hash.abs >> @bitshift) & 31 end def copy_size(copy) copy ? copy.size : 0 end end # @private EmptyTrie = Trie.new(0).freeze end immutable-ruby-master/lib/immutable/vector.rb0000644000175000017500000014353014201005456021242 0ustar boutilboutilrequire '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 immutable-ruby-master/lib/immutable/nested.rb0000644000175000017500000000543014201005456021216 0ustar boutilboutilrequire 'set' require 'sorted_set' require 'immutable/hash' require 'immutable/set' require 'immutable/vector' require 'immutable/sorted_set' require 'immutable/list' require 'immutable/deque' module Immutable class << self # Create a nested Immutable data structure from a nested Ruby object `obj`. # This method recursively "walks" the Ruby object, converting Ruby `Hash` to # {Immutable::Hash}, Ruby `Array` to {Immutable::Vector}, Ruby `Set` to # {Immutable::Set}, and Ruby `SortedSet` to {Immutable::SortedSet}. Other # objects are left as-is. # # @example # h = Immutable.from({ "a" => [1, 2], "b" => "c" }) # # => Immutable::Hash["a" => Immutable::Vector[1, 2], "b" => "c"] # # @return [Hash, Vector, Set, SortedSet, Object] def from(obj) case obj when ::Hash res = obj.map { |key, value| [from(key), from(value)] } Immutable::Hash.new(res) when Immutable::Hash obj.map { |key, value| [from(key), from(value)] } when ::Array res = obj.map { |element| from(element) } Immutable::Vector.new(res) when ::Struct from(obj.to_h) when ::SortedSet # This clause must go before ::Set clause, since ::SortedSet is a ::Set. res = obj.map { |element| from(element) } Immutable::SortedSet.new(res) when ::Set res = obj.map { |element| from(element) } Immutable::Set.new(res) when Immutable::Vector, Immutable::Set, Immutable::SortedSet obj.map { |element| from(element) } else obj end end # Create a Ruby object from Immutable data. This method recursively "walks" # the Immutable object, converting {Immutable::Hash} to Ruby `Hash`, # {Immutable::Vector} and {Immutable::Deque} to Ruby `Array`, {Immutable::Set} # to Ruby `Set`, and {Immutable::SortedSet} to Ruby `SortedSet`. Other # objects are left as-is. # # @example # h = Immutable.to_ruby(Immutable.from({ "a" => [1, 2], "b" => "c" })) # # => { "a" => [1, 2], "b" => "c" } # # @return [::Hash, ::Array, ::Set, ::SortedSet, Object] def to_ruby(obj) case obj when Immutable::Hash, ::Hash obj.each_with_object({}) { |keyval, hash| hash[to_ruby(keyval[0])] = to_ruby(keyval[1]) } when Immutable::Vector, ::Array obj.each_with_object([]) { |element, arr| arr << to_ruby(element) } when Immutable::Set, ::Set obj.each_with_object(::Set.new) { |element, set| set << to_ruby(element) } when Immutable::SortedSet, ::SortedSet obj.each_with_object(::SortedSet.new) { |element, set| set << to_ruby(element) } when Immutable::Deque obj.to_a.tap { |arr| arr.map! { |element| to_ruby(element) }} else obj end end end end immutable-ruby-master/lib/immutable/sorted_set.rb0000644000175000017500000013301214201005456022105 0ustar boutilboutilrequire 'immutable/enumerable' module Immutable # A `SortedSet` is a collection of ordered values with no duplicates. Unlike a # {Vector}, in which items can appear in any arbitrary order, a `SortedSet` always # keeps items either in their natural order, or in an order defined by a comparator # block which is provided at initialization time. # # `SortedSet` uses `#<=>` (or its comparator block) to determine which items are # equivalent. If the comparator indicates that an existing item and a new item are # equal, any attempt to insert the new item will have no effect. # # This means that *all* the items inserted into any one `SortedSet` must all be # comparable. For example, you cannot put `String`s and `Integer`s in the same # `SortedSet`. This is unlike {Set}, which can store items of any type, as long # as they all support `#hash` and `#eql?`. # # A `SortedSet` can be created in either of the following ways: # # Immutable::SortedSet.new([1, 2, 3]) # any Enumerable can be used to initialize # Immutable::SortedSet['A', 'B', 'C', 'D'] # # Or if you want to use a custom ordering: # # Immutable::SortedSet.new([1,2,3]) { |a, b| -a <=> -b } # Immutable::SortedSet.new([1, 2, 3]) { |num| -num } # # `SortedSet` can use a 2-parameter block which returns 0, 1, or -1 # as a comparator (like `Array#sort`), *or* use a 1-parameter block to derive sort # keys (like `Array#sort_by`) which will be compared using `#<=>`. # # Like all `immutable-ruby` collections, `SortedSet`s are immutable. Any operation # which you might expect to "modify" a `SortedSet` will actually return a new # collection and leave the existing one unchanged. # # `SortedSet` supports the same basic set-theoretic operations as {Set}, including # {#union}, {#intersection}, {#difference}, and {#exclusion}, as well as {#subset?}, # {#superset?}, and so on. Unlike {Set}, it does not define comparison operators like # `#>` or `#<` as aliases for the superset/subset predicates. Instead, these comparison # operators do a item-by-item comparison between the `SortedSet` and another sequential # collection. (See `Array#<=>` for details.) # # Additionally, since `SortedSet`s are ordered, they also support indexed retrieval # of items using {#at} or {#[]}. Like {Vector}, # negative indices count back from the end of the `SortedSet`. # # Getting the {#max} or {#min} item from a `SortedSet`, as defined by its comparator, # is a constant time operation. # class SortedSet include Immutable::Enumerable class << self # Create a new `SortedSet` populated with the given items. This method does not # accept a comparator block. # # @return [SortedSet] def [](*items) new(items) end # Return an empty `SortedSet`. If used on a subclass, returns an empty instance # of that class. # # @return [SortedSet] def empty @empty ||= alloc(PlainAVLNode::EmptyNode) end # "Raw" allocation of a new `SortedSet`. Used internally to create a new # instance quickly after obtaining a modified binary tree. # # @return [Set] # @private def alloc(node) allocate.tap { |s| s.instance_variable_set(:@node, node) }.freeze end # @private # Unfortunately, Ruby's stdlib doesn't do this for us # array must be sorted def uniq_by_comparator!(array, comparator) to_check, shift, sz, prev_obj = 1, 0, array.size, array[0] while to_check < sz next_obj = array[to_check] if comparator.call(prev_obj, next_obj) == 0 shift += 1 else if shift > 0 array[to_check - shift] = next_obj end prev_obj = next_obj end to_check += 1 end array.pop(shift) if shift > 0 end end def initialize(items=[], &block) items = items.to_a if block # In Ruby 2, &:method blocks have arity -1; in Ruby 3, it's -2 if block.arity == 1 || block.arity == -1 || block.arity == -2 items = items.uniq(&block) items.sort_by!(&block) comparator = lambda { |a,b| block.call(a) <=> block.call(b) } elsif block.arity == 2 || block.arity == -3 items = items.sort(&block) SortedSet.uniq_by_comparator!(items, block) comparator = block else raise "Comparator block for Immutable::SortedSet must accept 1 or 2 arguments" end @node = AVLNode.from_items(items, comparator) else @node = PlainAVLNode.from_items(items.uniq.sort!) end freeze end # Return `true` if this `SortedSet` contains no items. # # @return [Boolean] def empty? @node.empty? end # Return the number of items in this `SortedSet`. # # @example # Immutable::SortedSet["A", "B", "C"].size # => 3 # # @return [Integer] def size @node.size end alias length size # Return a new `SortedSet` with `item` added. If `item` is already in the set, # return `self`. # # @example # Immutable::SortedSet["Dog", "Lion"].add("Elephant") # # => Immutable::SortedSet["Dog", "Elephant", "Lion"] # # @param item [Object] The object to add # @return [SortedSet] def add(item) catch :present do node = @node.insert(item) return self.class.alloc(node) end self end alias << add # If `item` is not a member of this `SortedSet`, return a new `SortedSet` with # `item` added. Otherwise, return `false`. # # @example # Immutable::SortedSet["Dog", "Lion"].add?("Elephant") # # => Immutable::SortedSet["Dog", "Elephant", "Lion"] # Immutable::SortedSet["Dog", "Lion"].add?("Lion") # # => false # # @param item [Object] The object to add # @return [SortedSet, false] def add?(item) !include?(item) && add(item) end # Return a new `SortedSet` with `item` removed. If `item` is not a member of the set, # return `self`. # # @example # Immutable::SortedSet["A", "B", "C"].delete("B") # # => Immutable::SortedSet["A", "C"] # # @param item [Object] The object to remove # @return [SortedSet] def delete(item) catch :not_present do node = @node.delete(item) if node.empty? && node.natural_order? return self.class.empty else return self.class.alloc(node) end end self end # If `item` is a member of this `SortedSet`, return a new `SortedSet` with # `item` removed. Otherwise, return `false`. # # @example # Immutable::SortedSet["A", "B", "C"].delete?("B") # # => Immutable::SortedSet["A", "C"] # Immutable::SortedSet["A", "B", "C"].delete?("Z") # # => false # # @param item [Object] The object to remove # @return [SortedSet, false] def delete?(item) include?(item) && delete(item) end # Return a new `SortedSet` with the item at `index` removed. If the given `index` # does not exist (if it is too high or too low), return `self`. # # @example # Immutable::SortedSet["A", "B", "C", "D"].delete_at(2) # # => Immutable::SortedSet["A", "B", "D"] # # @param index [Integer] The index to remove # @return [SortedSet] def delete_at(index) (item = at(index)) ? delete(item) : self end # Retrieve the item at `index`. If there is none (either the provided index # is too high or too low), return `nil`. # # @example # s = Immutable::SortedSet["A", "B", "C", "D", "E", "F"] # s.at(2) # => "C" # s.at(-2) # => "E" # s.at(6) # => nil # # @param index [Integer] The index to retrieve # @return [Object] def at(index) index += @node.size if index < 0 return nil if index >= @node.size || index < 0 @node.at(index) end # 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 # s = Immutable::SortedSet["A", "B", "C", "D"] # s.fetch(2) # => "C" # s.fetch(-1) # => "D" # s.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 # s = Immutable::SortedSet["A", "B", "C", "D"] # s.fetch(2) { |i| i * i } # => "C" # s.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 # s = Immutable::SortedSet["A", "B", "C", "D"] # s.fetch(2, "Z") # => "C" # s.fetch(4, "Z") # => "Z" # # @return [Object] def fetch(index, default = (missing_default = true)) if index >= -@node.size && index < @node.size at(index) elsif block_given? yield(index) elsif !missing_default default else raise IndexError, "index #{index} outside of sorted set bounds" end end # Return specific objects from the `Vector`. All overloads return `nil` if # the starting index is out of range. # # @overload set.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 # s = Immutable::SortedSet["A", "B", "C", "D", "E", "F"] # s[2] # => "C" # s[-1] # => "F" # s[6] # => nil # # @overload set.slice(index, length) # Return a subset starting at `index` and continuing for `length` # elements or until the end of the `SortedSet`, 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 [SortedSet] # @example # s = Immutable::SortedSet["A", "B", "C", "D", "E", "F"] # s[2, 3] # => Immutable::SortedSet["C", "D", "E"] # s[-2, 3] # => Immutable::SortedSet["E", "F"] # s[20, 1] # => nil # # @overload set.slice(index..end) # Return a subset starting at `index` and continuing to index # `end` or the end of the `SortedSet`, whichever occurs first. # # @param range [Range] The range of indices to retrieve. # @return [SortedSet] # @example # s = Immutable::SortedSet["A", "B", "C", "D", "E", "F"] # s[2..3] # => Immutable::SortedSet["C", "D"] # s[-2..100] # => Immutable::SortedSet["E", "F"] # s[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 += @node.size if from < 0 to += @node.size if to < 0 to += 1 if !arg.exclude_end? length = to - from length = 0 if length < 0 subsequence(from, length) else at(arg) end else arg += @node.size if arg < 0 subsequence(arg, length) end end alias [] slice # Return a new `SortedSet` with only the elements at the given `indices`. # If any of the `indices` do not exist, they will be skipped. # # @example # s = Immutable::SortedSet["A", "B", "C", "D", "E", "F"] # s.values_at(2, 4, 5) # => Immutable::SortedSet["C", "E", "F"] # # @param indices [Array] The indices to retrieve and gather into a new `SortedSet` # @return [SortedSet] def values_at(*indices) indices.select! { |i| i >= -@node.size && i < @node.size } self.class.new(indices.map! { |i| at(i) }) end # Call the given block once for each item in the set, passing each # item from first to last successively to the block. If no block is # provided, returns an `Enumerator`. # # @example # Immutable::SortedSet["A", "B", "C"].each { |e| puts "Element: #{e}" } # # Element: A # Element: B # Element: C # # => Immutable::SortedSet["A", "B", "C"] # # @yield [item] # @return [self, Enumerator] def each(&block) return @node.to_enum if not block_given? @node.each(&block) self end # Call the given block once for each item in the set, passing each # item starting from the last, and counting back to the first, successively to # the block. # # @example # Immutable::SortedSet["A", "B", "C"].reverse_each { |e| puts "Element: #{e}" } # # Element: C # Element: B # Element: A # # => Immutable::SortedSet["A", "B", "C"] # # @return [self] def reverse_each(&block) return @node.enum_for(:reverse_each) if not block_given? @node.reverse_each(&block) self end # Return the "lowest" element in this set, as determined by its sort order. # Or, if a block is provided, use the block as a comparator to find the # "lowest" element. (See `Enumerable#min`.) # # @example # Immutable::SortedSet["A", "B", "C"].min # => "A" # # @return [Object] # @yield [a, b] Any number of times with different pairs of elements. def min block_given? ? super : @node.min end # Return the "lowest" element in this set, as determined by its sort order. # @return [Object] def first @node.min end # Return the "highest" element in this set, as determined by its sort order. # Or, if a block is provided, use the block as a comparator to find the # "highest" element. (See `Enumerable#max`.) # # @example # Immutable::SortedSet["A", "B", "C"].max # => "C" # # @yield [a, b] Any number of times with different pairs of elements. # @return [Object] def max block_given? ? super : @node.max end # Return the "highest" element in this set, as determined by its sort order. # @return [Object] def last @node.max end # Return a new `SortedSet` containing all elements for which the given block returns # true. # # @example # Immutable::SortedSet["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } # # => Immutable::SortedSet["Bird", "Elephant"] # # @return [SortedSet] # @yield [item] Once for each item. def select return enum_for(:select) unless block_given? items_to_delete = [] each { |item| items_to_delete << item unless yield(item) } derive_new_sorted_set(@node.bulk_delete(items_to_delete)) end alias find_all select alias keep_if select # Invoke the given block once for each item in the set, and return a new # `SortedSet` containing the values returned by the block. If no block is # given, returns an `Enumerator`. # # @example # Immutable::SortedSet[1, 2, 3].map { |e| -(e * e) } # # => Immutable::SortedSet[-9, -4, -1] # # @return [SortedSet, Enumerator] # @yield [item] Once for each item. def map return enum_for(:map) if not block_given? return self if empty? self.class.alloc(@node.from_items(super)) end alias collect map # Return `true` if the given item is present in this `SortedSet`. More precisely, # return `true` if an object which compares as "equal" using this set's # comparator is present. # # @example # Immutable::SortedSet["A", "B", "C"].include?("B") # => true # # @param item [Object] The object to check for # @return [Boolean] def include?(item) @node.include?(item) end alias member? include? # Return a new `SortedSet` with the same items, but a sort order determined # by the given block. # # @example # Immutable::SortedSet["Bird", "Cow", "Elephant"].sort { |a, b| a.size <=> b.size } # # => Immutable::SortedSet["Cow", "Bird", "Elephant"] # Immutable::SortedSet["Bird", "Cow", "Elephant"].sort_by { |e| e.size } # # => Immutable::SortedSet["Cow", "Bird", "Elephant"] # # @return [SortedSet] def sort(&block) if block self.class.new(to_a, &block) elsif @node.natural_order? self else self.class.new(self) end end alias sort_by sort # Find the index of a given object or an element that satisfies the given # block. # # @overload find_index(obj) # Return the index of the first object in this set which is equal to # `obj`. Rather than using `#==`, we use `#<=>` (or our comparator block) # for comparisons. This means we can find the index in `O(log N)` time, # rather than `O(N)`. # @param obj [Object] The object to search for # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.find_index(8) # => 3 # @overload find_index # Return the index of the first object in this sorted set for which the # block returns to true. This takes `O(N)` time. # @yield [element] An element in the sorted set # @yieldreturn [Boolean] True if this is element matches # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.find_index { |e| e > 7 } # => 3 # # @return [Integer] The index of the object, or `nil` if not found. def find_index(obj = (missing_obj = true), &block) if !missing_obj # Enumerable provides a default implementation, but this is more efficient node = @node index = node.left.size while !node.empty? direction = node.direction(obj) if direction > 0 node = node.right index += (node.left.size + 1) elsif direction < 0 node = node.left index -= (node.right.size + 1) else return index end end nil else super(&block) end end alias index find_index # Drop the first `n` elements and return the rest in a new `SortedSet`. # # @example # Immutable::SortedSet["A", "B", "C", "D", "E", "F"].drop(2) # # => Immutable::SortedSet["C", "D", "E", "F"] # # @param n [Integer] The number of elements to remove # @return [SortedSet] def drop(n) derive_new_sorted_set(@node.drop(n)) end # Return only the first `n` elements in a new `SortedSet`. # # @example # Immutable::SortedSet["A", "B", "C", "D", "E", "F"].take(4) # # => Immutable::SortedSet["A", "B", "C", "D"] # # @param n [Integer] The number of elements to retain # @return [SortedSet] def take(n) derive_new_sorted_set(@node.take(n)) 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 # `SortedSet`. If no block is given, an `Enumerator` is returned instead. # # @example # Immutable::SortedSet[2, 4, 6, 7, 8, 9].drop_while { |e| e.even? } # # => Immutable::SortedSet[7, 8, 9] # # @yield [item] # @return [SortedSet, Enumerator] def drop_while return enum_for(:drop_while) if not block_given? n = 0 each do |item| break unless yield item n += 1 end drop(n) 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 `SortedSet`. If no block # is given, an `Enumerator` is returned instead. # # @example # Immutable::SortedSet[2, 4, 6, 7, 8, 9].take_while { |e| e.even? } # # => Immutable::SortedSet[2, 4, 6] # # @return [SortedSet, Enumerator] # @yield [item] def take_while return enum_for(:take_while) if not block_given? n = 0 each do |item| break unless yield item n += 1 end take(n) end # Return a new `SortedSet` which contains all the members of both this set and `other`. # `other` can be any `Enumerable` object. # # @example # Immutable::SortedSet[1, 2] | Immutable::SortedSet[2, 3] # # => Immutable::SortedSet[1, 2, 3] # # @param other [Enumerable] The collection to merge with # @return [SortedSet] def union(other) self.class.alloc(@node.bulk_insert(other)) end alias | union alias + union alias merge union # Return a new `SortedSet` which contains all the items which are members of both # this set and `other`. `other` can be any `Enumerable` object. # # @example # Immutable::SortedSet[1, 2] & Immutable::SortedSet[2, 3] # # => Immutable::SortedSet[2] # # @param other [Enumerable] The collection to intersect with # @return [SortedSet] def intersection(other) self.class.alloc(@node.keep_only(other)) end alias & intersection # Return a new `SortedSet` with all the items in `other` removed. `other` can be # any `Enumerable` object. # # @example # Immutable::SortedSet[1, 2] - Immutable::SortedSet[2, 3] # # => Immutable::SortedSet[1] # # @param other [Enumerable] The collection to subtract from this set # @return [SortedSet] def difference(other) self.class.alloc(@node.bulk_delete(other)) end alias subtract difference alias - difference # Return a new `SortedSet` with all the items which are members of this # set or of `other`, but not both. `other` can be any `Enumerable` object. # # @example # Immutable::SortedSet[1, 2] ^ Immutable::SortedSet[2, 3] # # => Immutable::SortedSet[1, 3] # # @param other [Enumerable] The collection to take the exclusive disjunction of # @return [SortedSet] def exclusion(other) ((self | other) - (self & other)) end alias ^ exclusion # Return `true` if all items in this set are also in `other`. # # @example # Immutable::SortedSet[2, 3].subset?(Immutable::SortedSet[1, 2, 3]) # => true # # @param other [Enumerable] # @return [Boolean] def subset?(other) return false if other.size < size all? { |item| other.include?(item) } end # Return `true` if all items in `other` are also in this set. # # @example # Immutable::SortedSet[1, 2, 3].superset?(Immutable::SortedSet[2, 3]) # => true # # @param other [Enumerable] # @return [Boolean] def superset?(other) other.subset?(self) end # Returns `true` if `other` contains all the items in this set, plus at least # one item which is not in this set. # # @example # Immutable::SortedSet[2, 3].proper_subset?(Immutable::SortedSet[1, 2, 3]) # => true # Immutable::SortedSet[1, 2, 3].proper_subset?(Immutable::SortedSet[1, 2, 3]) # => false # # @param other [Enumerable] # @return [Boolean] def proper_subset?(other) return false if other.size <= size all? { |item| other.include?(item) } end # Returns `true` if this set contains all the items in `other`, plus at least # one item which is not in `other`. # # @example # Immutable::SortedSet[1, 2, 3].proper_superset?(Immutable::SortedSet[2, 3]) # => true # Immutable::SortedSet[1, 2, 3].proper_superset?(Immutable::SortedSet[1, 2, 3]) # => false # # @param other [Enumerable] # @return [Boolean] def proper_superset?(other) other.proper_subset?(self) end # Return `true` if this set and `other` do not share any items. # # @example # Immutable::SortedSet[1, 2].disjoint?(Immutable::SortedSet[3, 4]) # => true # # @param other [Enumerable] # @return [Boolean] def disjoint?(other) if size < other.size each { |item| return false if other.include?(item) } else other.each { |item| return false if include?(item) } end true end # Return `true` if this set and `other` have at least one item in common. # # @example # Immutable::SortedSet[1, 2].intersect?(Immutable::SortedSet[2, 3]) # => true # # @param other [Enumerable] # @return [Boolean] def intersect?(other) !disjoint?(other) end alias group group_by alias classify group_by # Select elements greater than a value. # # @overload above(item) # Return a new `SortedSet` containing all items greater than `item`. # @return [SortedSet] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.above(6) # # => Immutable::SortedSet[8, 10] # # @overload above(item) # @yield [item] Once for each item greater than `item`, in order from # lowest to highest. # @return [nil] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.above(6) { |e| puts "Element: #{e}" } # # Element: 8 # Element: 10 # # => nil # # @param item [Object] def above(item, &block) if block_given? @node.each_greater(item, false, &block) else self.class.alloc(@node.suffix(item, false)) end end # Select elements less than a value. # # @overload below(item) # Return a new `SortedSet` containing all items less than `item`. # @return [SortedSet] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.below(6) # # => Immutable::SortedSet[2, 4] # # @overload below(item) # @yield [item] Once for each item less than `item`, in order from lowest # to highest. # @return [nil] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.below(6) { |e| puts "Element: #{e}" } # # Element: 2 # Element: 4 # # => nil # # @param item [Object] def below(item, &block) if block_given? @node.each_less(item, false, &block) else self.class.alloc(@node.prefix(item, false)) end end # Select elements greater than or equal to a value. # # @overload from(item) # Return a new `SortedSet` containing all items greater than or equal `item`. # @return [SortedSet] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.from(6) # # => Immutable::SortedSet[6, 8, 10] # # @overload from(item) # @yield [item] Once for each item greater than or equal to `item`, in # order from lowest to highest. # @return [nil] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.from(6) { |e| puts "Element: #{e}" } # # Element: 6 # Element: 8 # Element: 10 # # => nil # # @param item [Object] def from(item, &block) if block_given? @node.each_greater(item, true, &block) else self.class.alloc(@node.suffix(item, true)) end end # Select elements less than or equal to a value. # # @overload up_to(item) # Return a new `SortedSet` containing all items less than or equal to # `item`. # # @return [SortedSet] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.upto(6) # # => Immutable::SortedSet[2, 4, 6] # # @overload up_to(item) # @yield [item] Once for each item less than or equal to `item`, in order # from lowest to highest. # @return [nil] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.up_to(6) { |e| puts "Element: #{e}" } # # Element: 2 # Element: 4 # Element: 6 # # => nil # # @param item [Object] def up_to(item, &block) if block_given? @node.each_less(item, true, &block) else self.class.alloc(@node.prefix(item, true)) end end # Select elements between two values. # # @overload between(from, to) # Return a new `SortedSet` containing all items less than or equal to # `to` and greater than or equal to `from`. # # @return [SortedSet] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.between(5, 8) # # => Immutable::SortedSet[6, 8] # # @overload between(item) # @yield [item] Once for each item less than or equal to `to` and greater # than or equal to `from`, in order from lowest to highest. # @return [nil] # @example # s = Immutable::SortedSet[2, 4, 6, 8, 10] # s.between(5, 8) { |e| puts "Element: #{e}" } # # Element: 6 # Element: 8 # # => nil # # @param from [Object] # @param to [Object] def between(from, to, &block) if block_given? @node.each_between(from, to, &block) else self.class.alloc(@node.between(from, to)) end end # Return a randomly chosen item from this set. If the set is empty, return `nil`. # # @example # Immutable::SortedSet[1, 2, 3, 4, 5].sample # => 2 # # @return [Object] def sample @node.at(rand(@node.size)) end # Return an empty `SortedSet` instance, of the same class as this one. Useful if you # have multiple subclasses of `SortedSet` and want to treat them polymorphically. # # @return [SortedSet] def clear if @node.natural_order? self.class.empty else self.class.alloc(@node.clear) end end # Return true if `other` has the same type and contents as this `SortedSet`. # # @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) return false if size != other.size a, b = to_enum, other.to_enum loop do return false if !a.next.eql?(b.next) end true 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 [SortedSet] def dup self end alias clone dup # @return [::Array] # @private def marshal_dump if @node.natural_order? to_a else raise TypeError, "can't dump SortedSet with custom sort order" end end # @private def marshal_load(array) initialize(array) end private def subsequence(from, length) return nil if from > @node.size || from < 0 || length < 0 length = @node.size - from if @node.size < from + length if length == 0 if @node.natural_order? return self.class.empty else return self.class.alloc(@node.clear) end end self.class.alloc(@node.slice(from, length)) end # Return a new `SortedSet` which is derived from this one, using a modified # {AVLNode}. The new `SortedSet` will retain the existing comparator, if # there is one. def derive_new_sorted_set(node) if node.equal?(@node) self elsif node.empty? clear else self.class.alloc(node) end end # @private class AVLNode def self.from_items(items, comparator, from = 0, to = items.size-1) # items must be sorted, without duplicates (as determined by comparator) size = to - from + 1 if size >= 3 middle = (to + from) / 2 AVLNode.new(items[middle], comparator, AVLNode.from_items(items, comparator, from, middle-1), AVLNode.from_items(items, comparator, middle+1, to)) elsif size == 2 empty = AVLNode::Empty.new(comparator) AVLNode.new(items[from], comparator, empty, AVLNode.new(items[from+1], comparator, empty, empty)) elsif size == 1 empty = AVLNode::Empty.new(comparator) AVLNode.new(items[from], comparator, empty, empty) elsif size == 0 AVLNode::Empty.new(comparator) end end def initialize(item, comparator, left, right) @item, @comparator, @left, @right = item, comparator, left, right @height = ((right.height > left.height) ? right.height : left.height) + 1 @size = right.size + left.size + 1 end attr_reader :item, :left, :right, :height, :size # Used to implement #map # Takes advantage of the fact that Enumerable#map allocates a new Array def from_items(items) items.sort!(&@comparator) SortedSet.uniq_by_comparator!(items, @comparator) AVLNode.from_items(items, @comparator) end def natural_order? false end def empty? false end def clear AVLNode::Empty.new(@comparator) end def derive(item, left, right) AVLNode.new(item, @comparator, left, right) end def insert(item) dir = direction(item) if dir == 0 throw :present elsif dir > 0 rebalance_right(@left, @right.insert(item)) else rebalance_left(@left.insert(item), @right) end end def bulk_insert(items) return self if items.empty? if items.size == 1 catch :present do return insert(items.first) end return self end left, right = partition(items) if right.size > left.size rebalance_right(@left.bulk_insert(left), @right.bulk_insert(right)) else rebalance_left(@left.bulk_insert(left), @right.bulk_insert(right)) end end def delete(item) dir = direction(item) if dir == 0 if @right.empty? return @left # replace this node with its only child elsif @left.empty? return @right # likewise end if balance > 0 # tree is leaning to the left. replace with highest node on that side replace_with = @left.max derive(replace_with, @left.delete(replace_with), @right) else # tree is leaning to the right. replace with lowest node on that side replace_with = @right.min derive(replace_with, @left, @right.delete(replace_with)) end elsif dir > 0 rebalance_left(@left, @right.delete(item)) else rebalance_right(@left.delete(item), @right) end end def bulk_delete(items) return self if items.empty? if items.size == 1 catch :not_present do return delete(items.first) end return self end left, right, keep_item = [], [], true items.each do |item| dir = direction(item) if dir > 0 right << item elsif dir < 0 left << item else keep_item = false end end left = @left.bulk_delete(left) right = @right.bulk_delete(right) finish_removal(keep_item, left, right) end def keep_only(items) return clear if items.empty? left, right, keep_item = [], [], false items.each do |item| dir = direction(item) if dir > 0 right << item elsif dir < 0 left << item else keep_item = true end end left = @left.keep_only(left) right = @right.keep_only(right) finish_removal(keep_item, left, right) end def finish_removal(keep_item, left, right) # deletion of items may have occurred on left and right sides # now we may also need to delete the current item if keep_item rebalance(left, right) # no need to delete the current item elsif left.empty? right elsif right.empty? left elsif left.height > right.height replace_with = left.max derive(replace_with, left.delete(replace_with), right) else replace_with = right.min derive(replace_with, left, right.delete(replace_with)) end end def prefix(item, inclusive) dir = direction(item) if dir > 0 || (inclusive && dir == 0) rebalance_left(@left, @right.prefix(item, inclusive)) else @left.prefix(item, inclusive) end end def suffix(item, inclusive) dir = direction(item) if dir < 0 || (inclusive && dir == 0) rebalance_right(@left.suffix(item, inclusive), @right) else @right.suffix(item, inclusive) end end def between(from, to) if direction(from) > 0 # all on the right @right.between(from, to) elsif direction(to) < 0 # all on the left @left.between(from, to) else left = @left.suffix(from, true) right = @right.prefix(to, true) rebalance(left, right) end end def each_less(item, inclusive, &block) dir = direction(item) if dir > 0 || (inclusive && dir == 0) @left.each(&block) yield @item @right.each_less(item, inclusive, &block) else @left.each_less(item, inclusive, &block) end end def each_greater(item, inclusive, &block) dir = direction(item) if dir < 0 || (inclusive && dir == 0) @left.each_greater(item, inclusive, &block) yield @item @right.each(&block) else @right.each_greater(item, inclusive, &block) end end def each_between(from, to, &block) if direction(from) > 0 # all on the right @right.each_between(from, to, &block) elsif direction(to) < 0 # all on the left @left.each_between(from, to, &block) else @left.each_greater(from, true, &block) yield @item @right.each_less(to, true, &block) end end def each(&block) @left.each(&block) yield @item @right.each(&block) end def reverse_each(&block) @right.reverse_each(&block) yield @item @left.reverse_each(&block) end def drop(n) if n >= @size clear elsif n <= 0 self elsif @left.size >= n rebalance_right(@left.drop(n), @right) elsif @left.size + 1 == n @right else @right.drop(n - @left.size - 1) end end def take(n) if n >= @size self elsif n <= 0 clear elsif @left.size >= n @left.take(n) else rebalance_left(@left, @right.take(n - @left.size - 1)) end end def include?(item) dir = direction(item) if dir == 0 true elsif dir > 0 @right.include?(item) else @left.include?(item) end end def at(index) if index < @left.size @left.at(index) elsif index > @left.size @right.at(index - @left.size - 1) else @item end end def max @right.empty? ? @item : @right.max end def min @left.empty? ? @item : @left.min end def balance @left.height - @right.height end def slice(from, length) if length <= 0 clear elsif from + length <= @left.size @left.slice(from, length) elsif from > @left.size @right.slice(from - @left.size - 1, length) else left = @left.slice(from, @left.size - from) right = @right.slice(0, from + length - @left.size - 1) rebalance(left, right) end end def partition(items) left, right = [], [] items.each do |item| dir = direction(item) if dir > 0 right << item elsif dir < 0 left << item end end [left, right] end def rebalance(left, right) if left.height > right.height rebalance_left(left, right) else rebalance_right(left, right) end end def rebalance_left(left, right) # the tree might be unbalanced to the left (paths on the left too long) balance = left.height - right.height if balance >= 2 if left.balance > 0 # single right rotation derive(left.item, left.left, derive(@item, left.right, right)) else # left rotation, then right derive(left.right.item, derive(left.item, left.left, left.right.left), derive(@item, left.right.right, right)) end else derive(@item, left, right) end end def rebalance_right(left, right) # the tree might be unbalanced to the right (paths on the right too long) balance = left.height - right.height if balance <= -2 if right.balance > 0 # right rotation, then left derive(right.left.item, derive(@item, left, right.left.left), derive(right.item, right.left.right, right.right)) else # single left rotation derive(right.item, derive(@item, left, right.left), right.right) end else derive(@item, left, right) end end def direction(item) @comparator.call(item, @item) end # @private class Empty def initialize(comparator); @comparator = comparator; end def natural_order?; false; end def left; self; end def right; self; end def height; 0; end def size; 0; end def min; nil; end def max; nil; end def each; end def reverse_each; end def at(index); nil; end def insert(item) AVLNode.new(item, @comparator, self, self) end def bulk_insert(items) items = items.to_a if !items.is_a?(Array) items = items.sort(&@comparator) SortedSet.uniq_by_comparator!(items, @comparator) AVLNode.from_items(items, @comparator) end def bulk_delete(items); self; end def keep_only(items); self; end def delete(item); throw :not_present; end def include?(item); false; end def prefix(item, inclusive); self; end def suffix(item, inclusive); self; end def between(from, to); self; end def each_greater(item, inclusive); end def each_less(item, inclusive); end def each_between(item, inclusive); end def drop(n); self; end def take(n); self; end def empty?; true; end def slice(from, length); self; end end end # @private # AVL node which does not use a comparator function; it keeps items sorted # in their natural order class PlainAVLNode < AVLNode def self.from_items(items, from = 0, to = items.size-1) # items must be sorted, with no duplicates size = to - from + 1 if size >= 3 middle = (to + from) / 2 PlainAVLNode.new(items[middle], PlainAVLNode.from_items(items, from, middle-1), PlainAVLNode.from_items(items, middle+1, to)) elsif size == 2 PlainAVLNode.new(items[from], PlainAVLNode::EmptyNode, PlainAVLNode.new(items[from+1], PlainAVLNode::EmptyNode, PlainAVLNode::EmptyNode)) elsif size == 1 PlainAVLNode.new(items[from], PlainAVLNode::EmptyNode, PlainAVLNode::EmptyNode) elsif size == 0 PlainAVLNode::EmptyNode end end def initialize(item, left, right) @item, @left, @right = item, left, right @height = ((right.height > left.height) ? right.height : left.height) + 1 @size = right.size + left.size + 1 end attr_reader :item, :left, :right, :height, :size # Used to implement #map # Takes advantage of the fact that Enumerable#map allocates a new Array def from_items(items) items.uniq! items.sort! PlainAVLNode.from_items(items) end def natural_order? true end def clear PlainAVLNode::EmptyNode end def derive(item, left, right) PlainAVLNode.new(item, left, right) end def direction(item) item <=> @item end # @private class Empty < AVLNode::Empty def initialize; end def natural_order?; true; end def insert(item) PlainAVLNode.new(item, self, self) end def bulk_insert(items) items = items.to_a if !items.is_a?(Array) PlainAVLNode.from_items(items.uniq.sort!) end end EmptyNode = PlainAVLNode::Empty.new end end # The canonical empty `SortedSet`. Returned by `SortedSet[]` # when invoked with no arguments; also returned by `SortedSet.empty`. Prefer using # this one rather than creating many empty sorted sets using `SortedSet.new`. # # @private EmptySortedSet = Immutable::SortedSet.empty end immutable-ruby-master/lib/immutable/undefined.rb0000644000175000017500000000007314201005456021673 0ustar boutilboutilmodule Immutable # @private module Undefined end end immutable-ruby-master/lib/immutable/core_ext/0000755000175000017500000000000014201005456021215 5ustar boutilboutilimmutable-ruby-master/lib/immutable/core_ext/enumerable.rb0000644000175000017500000000050114201005456023655 0ustar boutilboutilrequire 'immutable/list' # Monkey-patches to Ruby's built-in `Enumerable` module. # @see http://www.ruby-doc.org/core/Enumerable.html module Enumerable # Return a new {Immutable::List} populated with the items in this `Enumerable` object. # @return [List] def to_list Immutable::List.from_enum(self) end end immutable-ruby-master/lib/immutable/core_ext/io.rb0000644000175000017500000000105414201005456022151 0ustar boutilboutilrequire 'immutable/list' # Monkey-patches to Ruby's built-in `IO` class. # @see http://www.ruby-doc.org/core/IO.html class IO # Return a lazy list of "records" read from this IO stream. # "Records" are delimited by `$/`, the global input record separator string. # By default, it is `"\n"`, a newline. # # @return [List] def to_list(sep = $/) # global input record separator Immutable::LazyList.new do line = gets(sep) if line Immutable::Cons.new(line, to_list) else EmptyList end end end end immutable-ruby-master/lib/immutable/core_ext.rb0000644000175000017500000000011014201005456021532 0ustar boutilboutilrequire 'immutable/core_ext/enumerable' require 'immutable/core_ext/io' immutable-ruby-master/FAQ.md0000644000175000017500000000522714201005456015617 0ustar boutilboutilFAQ === **But I still don't understand why I should care?** As mentioned earlier, persistent data structures perform a copy whenever they are modified meaning there is never any chance that two threads could be modifying the same instance at any one time. And, because they are very efficient copies, you don't need to worry about using up gobs of memory in the process. Even if threading isn't a concern, because they're immutable, you can pass them around between objects, methods, and functions in the same thread and never worry about data corruption; no more defensive calls to `Object#dup`! **What's the downside--there's always a downside?** There's a potential performance hit when compared with MRI's built-in, native, hand-crafted C-code implementation of Hash. For example: ``` ruby hash = Immutable::Hash.empty (1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s (1..10000).each { |i| hash.get(i) } # => 0.008s ``` vs. ``` ruby hash = {} (1..10000).each { |i| hash[i] = i } # => 0.004s (1..10000).each { |i| hash[i] } # => 0.001s ``` The previous comparison wasn't really fair. Sure, if all you want to do is replace your existing uses of `Hash` in single- threaded environments then don't even bother. However, if you need something that can be used efficiently in concurrent environments where multiple threads are accessing--reading AND writing--the contents things get much better. A more realistic comparison might look like: ``` ruby hash = Immutable::Hash.empty (1..10000).each { |i| hash = hash.put(i, i) } # => 0.05s (1..10000).each { |i| hash.get(i) } # => 0.008s ``` versus ``` ruby hash = {} (1..10000).each { |i| hash = hash.dup; hash[i] = i } # => 19.8s (1..10000).each { |i| hash[i] } # => 0.001s ``` What's even better -- or worse depending on your perspective -- is that after all that, the native `Hash` version still isn't thread-safe and still requires some synchronization around it slowing it down even further. The immutable version, on the other hand, was unchanged from the original whilst remaining inherently thread-safe, and 3 orders of magnitude faster. **You still need synchronisation so why bother with the copying?** Well, I could show you one but I'd have to re-write/wrap most Hash methods to make them generic, or at the very least write some application-specific code that synchronized using a ` Mutex` and ... well ... it's hard, I always make mistakes, I always end up with weird edge cases and race conditions so, I'll leave that as an exercise for you :) And don't forget that even if threading isn't a concern for you, the safety provided by immutability alone is worth it, not to mention the lazy implementations. immutable-ruby-master/.travis.yml0000644000175000017500000000031614201005456016771 0ustar boutilboutillanguage: ruby rvm: - 2.4.10 - 2.5.8 - 2.6.6 - 2.7.1 - 3.0.1 - ruby-head - jruby-9.0.0.0 - jruby-9.1.16.0 - jruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head immutable-ruby-master/.rspec0000644000175000017500000000006614201005456015777 0ustar boutilboutil--colour --format documentation --fail-fast --profile immutable-ruby-master/Rakefile0000644000175000017500000000570414201005456016333 0ustar boutilboutil#!/usr/bin/env rake require 'bundler/gem_tasks' require 'rspec/core/rake_task' require 'yard' require 'pathname' IMMUTABLE_ROOT = Pathname.new(__FILE__).dirname desc 'Run all the tests in spec/' RSpec::Core::RakeTask.new(:spec) do |config| config.verbose = false end desc 'Generate all of the docs' YARD::Rake::YardocTask.new do |config| config.files = Dir['lib/**/*.rb'] end def bench_suites Dir[IMMUTABLE_ROOT.join('bench/*')].map(&method(:Pathname)).select(&:directory?) end def bench_files(suite) Dir[File.join(suite, '/**/*.rb')].map(&method(:Pathname)) end def bench_task_name(file_name) file_name.relative_path_from(IMMUTABLE_ROOT).sub(/\_bench.rb$/, '').to_s.tr('/', ':') end bench_suites.each do |suite| bench_files(suite).each do |bench_file| name = bench_task_name(bench_file) desc "Benchmark #{name}" task name do begin $LOAD_PATH.unshift IMMUTABLE_ROOT.join('lib') load bench_file rescue LoadError => e if e.message == /benchmark\/ips/ $stderr.puts 'Please install the benchmark-ips gem' else $stderr.puts e end exit 69 end end end desc "Benchmark #{bench_task_name(suite)}" task bench_task_name(suite) => bench_files(suite).map(&method(:bench_task_name)) end desc 'Run all benchmarks' task bench: bench_suites.map(&method(:bench_task_name)) desc 'Generate file dependency graph' task :dependency_graph do if `which dot`.empty? raise 'dot is not installed or not on your system path' end dependencies = Hash.new { |h,k| h[k] = Set.new } trim_fn = ->(fn) { fn.sub(/^lib\//, '').sub(/\.rb$/, '') } Dir['lib/**/*.rb'].each do |path| File.readlines(path).each do |line| if line =~ /^\s*require\s+('|")([^'"]*)('|")/ dependency = $2 dependencies[trim_fn[path]] << dependency end end end require 'set' cycles = Set.new reachable = Hash.new { |h,k| h[k] = Set.new } find_reachable = ->(from, to, pathsofar) do to.each do |t| if t == from reachable[from].add(t) pathsofar.push(t).each_cons(2) { |vector| cycles << vector } elsif reachable[from].add?(t) && dependencies.key?(t) find_reachable[from, dependencies[t], pathsofar.dup.push(t)] end end end dependencies.each { |from,to| find_reachable[from,to,[from]] } dot = %|digraph { graph [label="Immutable srcfile dependencies"]\n| dependencies.each do |from,to| dot << %|"#{from}" [color=red]\n| if reachable[from].include?(from) to.each do |t| dot << %|"#{from}" -> "#{t}" #{'[color=red]' if cycles.include?([from,t])}\n| end end dot << "\n}" require 'tempfile' Tempfile.open('immutable-depgraph') do |f| f.write(dot) f.flush message = `dot -Tgif #{f.path} -o depgraph.gif` f.unlink puts message unless message.empty? puts 'Dependency graph is in depgraph.gif' end end desc 'Default: run tests and generate docs' task default: [ :spec, :yard ] immutable-ruby-master/CONDUCT.md0000644000175000017500000000261614201005456016306 0ustar boutilboutilCode of Conduct =============== As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)