pax_global_header 0000666 0000000 0000000 00000000064 14610664721 0014521 g ustar 00root root 0000000 0000000 52 comment=7d20d25fd8e215922e0e054f54172f7a2d85351b
immutable-ruby-immutable-ruby-7d20d25/ 0000775 0000000 0000000 00000000000 14610664721 0017744 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/.gitignore 0000664 0000000 0000000 00000001123 14610664721 0021731 0 ustar 00root root 0000000 0000000 # 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-workspace immutable-ruby-immutable-ruby-7d20d25/.rspec 0000664 0000000 0000000 00000000066 14610664721 0021063 0 ustar 00root root 0000000 0000000 --colour
--format documentation
--fail-fast
--profile
immutable-ruby-immutable-ruby-7d20d25/.ruby-gemset 0000664 0000000 0000000 00000000012 14610664721 0022201 0 ustar 00root root 0000000 0000000 immutable
immutable-ruby-immutable-ruby-7d20d25/.ruby-version 0000664 0000000 0000000 00000000006 14610664721 0022405 0 ustar 00root root 0000000 0000000 3.1.4
immutable-ruby-immutable-ruby-7d20d25/.travis.yml 0000664 0000000 0000000 00000000316 14610664721 0022055 0 ustar 00root root 0000000 0000000 language: 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-immutable-ruby-7d20d25/.yardopts 0000664 0000000 0000000 00000000123 14610664721 0021606 0 ustar 00root root 0000000 0000000 --no-private
--markup=markdown
--readme=YARD-README.md
-
LICENSE
FAQ.md
CONDUCT.md
immutable-ruby-immutable-ruby-7d20d25/CONDUCT.md 0000664 0000000 0000000 00000002616 14610664721 0021372 0 ustar 00root root 0000000 0000000 Code 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/) immutable-ruby-immutable-ruby-7d20d25/FAQ.md 0000664 0000000 0000000 00000005227 14610664721 0020703 0 ustar 00root root 0000000 0000000 FAQ
===
**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-immutable-ruby-7d20d25/Gemfile 0000664 0000000 0000000 00000000156 14610664721 0021241 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
source 'https://rubygems.org/'
# Dependencies are specified in immutable.gemspec
gemspec
immutable-ruby-immutable-ruby-7d20d25/LICENSE 0000664 0000000 0000000 00000002072 14610664721 0020752 0 ustar 00root root 0000000 0000000 Licensing
=========
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-immutable-ruby-7d20d25/README.md 0000664 0000000 0000000 00000033023 14610664721 0021224 0 ustar 00root root 0000000 0000000 Immutable Ruby
==============
[](https://rubygems.org/gems/immutable-ruby)
[](http://github.com/immutable-ruby/immutable-ruby/issues)
[](http://opensource.org/licenses/MIT)
[](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"
```
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.
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.
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.
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.
`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.
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-immutable-ruby-7d20d25/Rakefile 0000664 0000000 0000000 00000005704 14610664721 0021417 0 ustar 00root root 0000000 0000000 #!/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-immutable-ruby-7d20d25/YARD-README.md 0000664 0000000 0000000 00000027500 14610664721 0021764 0 ustar 00root root 0000000 0000000 Immutable 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-immutable-ruby-7d20d25/bench/ 0000775 0000000 0000000 00000000000 14610664721 0021023 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/bench/hash/ 0000775 0000000 0000000 00000000000 14610664721 0021746 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/bench/hash/each_bench.rb 0000664 0000000 0000000 00000001246 14610664721 0024335 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/bench/hash/get_bench.rb 0000664 0000000 0000000 00000002063 14610664721 0024212 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/bench/hash/put_bench.rb 0000664 0000000 0000000 00000001262 14610664721 0024243 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/bench/list/ 0000775 0000000 0000000 00000000000 14610664721 0021776 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/bench/list/at_bench.rb 0000664 0000000 0000000 00000001350 14610664721 0024065 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/bench/list/cons_bench.rb 0000664 0000000 0000000 00000001224 14610664721 0024423 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/bench/set/ 0000775 0000000 0000000 00000000000 14610664721 0021616 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/bench/set/union_bench.rb 0000664 0000000 0000000 00000000474 14610664721 0024437 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/immutable-ruby.gemspec 0000664 0000000 0000000 00000002465 14610664721 0024256 0 ustar 00root root 0000000 0000000 # 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-immutable-ruby-7d20d25/lib/ 0000775 0000000 0000000 00000000000 14610664721 0020512 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/lib/immutable.rb 0000664 0000000 0000000 00000000362 14610664721 0023017 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/ 0000775 0000000 0000000 00000000000 14610664721 0022471 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/lib/immutable/_core.rb 0000664 0000000 0000000 00000306634 14610664721 0024121 0 ustar 00root root 0000000 0000000 require 'immutable/undefined'
require 'immutable/enumerable'
require 'immutable/trie'
require 'immutable/sorted_set'
require 'set'
module Immutable
# An `Immutable::Hash` maps a set of unique keys to corresponding values, much
# like a dictionary maps from words to definitions. Given a key, it can store
# and retrieve an associated value in constant time. If an existing key is
# stored again, the new value will replace the old. It behaves much like
# Ruby's built-in Hash, which we will call RubyHash for clarity. Like
# RubyHash, two keys that are `#eql?` to each other and have the same
# `#hash` are considered identical in an `Immutable::Hash`.
#
# An `Immutable::Hash` can be created in a couple of ways:
#
# Immutable::Hash.new(font_size: 10, font_family: 'Arial')
# Immutable::Hash[first_name: 'John', last_name: 'Smith']
#
# Any `Enumerable` object which yields two-element `[key, value]` arrays
# can be used to initialize an `Immutable::Hash`:
#
# Immutable::Hash.new([[:first_name, 'John'], [:last_name, 'Smith']])
#
# Key/value pairs can be added using {#put}. A new hash is returned and the
# existing one is left unchanged:
#
# hash = Immutable::Hash[a: 100, b: 200]
# hash.put(:c, 500) # => Immutable::Hash[:a => 100, :b => 200, :c => 500]
# hash # => Immutable::Hash[:a => 100, :b => 200]
#
# {#put} can also take a block, which is used to calculate the value to be
# stored.
#
# hash.put(:a) { |current| current + 200 } # => Immutable::Hash[:a => 300, :b => 200]
#
# Since it is immutable, all methods which you might expect to "modify" a
# `Immutable::Hash` actually return a new hash and leave the existing one
# unchanged. This means that the `hash[key] = value` syntax from RubyHash
# *cannot* be used with `Immutable::Hash`.
#
# Nested data structures can easily be updated using {#update_in}:
#
# hash = Immutable::Hash["a" => Immutable::Vector[Immutable::Hash["c" => 42]]]
# hash.update_in("a", 0, "c") { |value| value + 5 }
# # => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]]
#
# While an `Immutable::Hash` can iterate over its keys or values, it does not
# guarantee any specific iteration order (unlike RubyHash). Methods like
# {#flatten} do not guarantee the order of returned key/value pairs.
#
# Like RubyHash, an `Immutable::Hash` can have a default block which is used
# when looking up a key that does not exist. Unlike RubyHash, the default
# block will only be passed the missing key, without the hash itself:
#
# hash = Immutable::Hash.new { |missing_key| missing_key * 10 }
# hash[5] # => 50
class Hash
include Immutable::Enumerable
class << self
# Create a new `Hash` populated with the given key/value pairs.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2] # => Immutable::Hash["A" => 1, "B" => 2]
# Immutable::Hash[["A", 1], ["B", 2]] # => Immutable::Hash["A" => 1, "B" => 2]
#
# @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided.
# @return [Hash]
def [](pairs = nil)
(pairs.nil? || pairs.empty?) ? empty : new(pairs)
end
# Return an empty `Hash`. If used on a subclass, returns an empty instance
# of that class.
#
# @return [Hash]
def empty
@empty ||= new
end
# "Raw" allocation of a new `Hash`. Used internally to create a new
# instance quickly after obtaining a modified {Trie}.
#
# @return [Hash]
# @private
def alloc(trie = EmptyTrie, block = nil)
obj = allocate
obj.instance_variable_set(:@trie, trie)
obj.instance_variable_set(:@default, block)
obj.freeze
end
end
# @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided.
# @yield [key] Optional _default block_ to be stored and used to calculate the default value of a missing key. It will not be yielded during this method. It will not be preserved when marshalling.
# @yieldparam key Key that was not present in the hash.
def initialize(pairs = nil, &block)
@trie = pairs ? Trie[pairs] : EmptyTrie
@default = block
freeze
end
# Return the default block if there is one. Otherwise, return `nil`.
#
# @return [Proc]
def default_proc
@default
end
# Return the number of key/value pairs in this `Hash`.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].size # => 3
#
# @return [Integer]
def size
@trie.size
end
alias length size
# Return `true` if this `Hash` contains no key/value pairs.
#
# @return [Boolean]
def empty?
@trie.empty?
end
# Return `true` if the given key object is present in this `Hash`. More precisely,
# return `true` if a key with the same `#hash` code, and which is also `#eql?`
# to the given key object is present.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].key?("B") # => true
#
# @param key [Object] The key to check for
# @return [Boolean]
def key?(key)
@trie.key?(key)
end
alias has_key? key?
alias include? key?
alias member? key?
# Return `true` if this `Hash` has one or more keys which map to the provided value.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].value?(2) # => true
#
# @param value [Object] The value to check for
# @return [Boolean]
def value?(value)
each { |k,v| return true if value == v }
false
end
alias has_value? value?
# Retrieve the value corresponding to the provided key object. If not found, and
# this `Hash` has a default block, the default block is called to provide the
# value. Otherwise, return `nil`.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h["B"] # => 2
# h.get("B") # => 2
# h.get("Elephant") # => nil
#
# # Immutable Hash with a default proc:
# h = Immutable::Hash.new("A" => 1, "B" => 2, "C" => 3) { |key| key.size }
# h.get("B") # => 2
# h.get("Elephant") # => 8
#
# @param key [Object] The key to look up
# @return [Object]
def get(key)
entry = @trie.get(key)
if entry
entry[1]
elsif @default
@default.call(key)
end
end
alias [] get
# Retrieve the value corresponding to the given key object, or use the provided
# default value or block, or otherwise raise a `KeyError`.
#
# @overload fetch(key)
# Retrieve the value corresponding to the given key, or raise a `KeyError`
# if it is not found.
# @param key [Object] The key to look up
# @overload fetch(key) { |key| ... }
# Retrieve the value corresponding to the given key, or call the optional
# code block (with the missing key) and get its return value.
# @yield [key] The key which was not found
# @yieldreturn [Object] Object to return since the key was not found
# @param key [Object] The key to look up
# @overload fetch(key, default)
# Retrieve the value corresponding to the given key, or else return
# the provided `default` value.
# @param key [Object] The key to look up
# @param default [Object] Object to return if the key is not found
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.fetch("B") # => 2
# h.fetch("Elephant") # => KeyError: key not found: "Elephant"
#
# # with a default value:
# h.fetch("B", 99) # => 2
# h.fetch("Elephant", 99) # => 99
#
# # with a block:
# h.fetch("B") { |key| key.size } # => 2
# h.fetch("Elephant") { |key| key.size } # => 8
#
# @return [Object]
def fetch(key, default = Undefined)
entry = @trie.get(key)
if entry
entry[1]
elsif block_given?
yield(key)
elsif default != Undefined
default
else
raise KeyError, "key not found: #{key.inspect}"
end
end
# Return a new `Hash` with the existing key/value associations, plus an association
# between the provided key and value. If an equivalent key is already present, its
# associated value will be replaced with the provided one.
#
# If the `value` argument is missing, but an optional code block is provided,
# it will be passed the existing value (or `nil` if there is none) and what it
# returns will replace the existing value. This is useful for "transforming"
# the value associated with a certain key.
#
# Avoid mutating objects which are used as keys. `String`s are an exception:
# unfrozen `String`s which are used as keys are internally duplicated and
# frozen. This matches RubyHash's behaviour.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2]
# h.put("C", 3)
# # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.put("B") { |value| value * 10 }
# # => Immutable::Hash["A" => 1, "B" => 20]
#
# @param key [Object] The key to store
# @param value [Object] The value to associate it with
# @yield [value] The previously stored value, or `nil` if none.
# @yieldreturn [Object] The new value to store
# @return [Hash]
def put(key, value = yield(get(key)))
new_trie = @trie.put(key, value)
if new_trie.equal?(@trie)
self
else
self.class.alloc(new_trie, @default)
end
end
# @private
# @raise NoMethodError
def []=(*)
raise NoMethodError, "Immutable::Hash doesn't support `[]='; use `put' instead"
end
# Return a new `Hash` with a deeply nested value modified to the result of
# the given code block. When traversing the nested `Hash`es and `Vector`s,
# non-existing keys are created with empty `Hash` values.
#
# The code block receives the existing value of the deeply nested key (or
# `nil` if it doesn't exist). This is useful for "transforming" the value
# associated with a certain key.
#
# Note that the original `Hash` and sub-`Hash`es and sub-`Vector`s are left
# unmodified; new data structure copies are created along the path wherever
# needed.
#
# @example
# hash = Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 42]]]
# hash.update_in("a", "b", "c") { |value| value + 5 }
# # => Immutable::Hash["a" => Immutable::Hash["b" => Immutable::Hash["c" => 47]]]
#
# @param key_path [::Array] List of keys which form the path to the key to be modified
# @yield [value] The previously stored value
# @yieldreturn [Object] The new value to store
# @return [Hash]
def update_in(*key_path, &block)
if key_path.empty?
raise ArgumentError, 'must have at least one key in path'
end
key = key_path[0]
if key_path.size == 1
new_value = block.call(get(key))
else
value = fetch(key, EmptyHash)
new_value = value.update_in(*key_path[1..-1], &block)
end
put(key, new_value)
end
# An alias for {#put} to match RubyHash's API. Does not support {#put}'s
# block form.
#
# @see #put
# @param key [Object] The key to store
# @param value [Object] The value to associate it with
# @return [Hash]
def store(key, value)
put(key, value)
end
# Return a new `Hash` with `key` removed. If `key` is not present, return
# `self`.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].delete("B")
# # => Immutable::Hash["A" => 1, "C" => 3]
#
# @param key [Object] The key to remove
# @return [Hash]
def delete(key)
derive_new_hash(@trie.delete(key))
end
# Call the block once for each key/value pair in this `Hash`, passing the key/value
# pair as parameters. No specific iteration order is guaranteed, though the order will
# be stable for any particular `Hash`.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each { |k, v| puts "k=#{k} v=#{v}" }
#
# k=A v=1
# k=C v=3
# k=B v=2
# # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
#
# @yield [key, value] Once for each key/value pair.
# @return [self]
def each(&block)
return to_enum if not block_given?
@trie.each(&block)
self
end
alias each_pair each
# Call the block once for each key/value pair in this `Hash`, passing the key/value
# pair as parameters. Iteration order will be the opposite of {#each}.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].reverse_each { |k, v| puts "k=#{k} v=#{v}" }
#
# k=B v=2
# k=C v=3
# k=A v=1
# # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
#
# @yield [key, value] Once for each key/value pair.
# @return [self]
def reverse_each(&block)
return enum_for(:reverse_each) if not block_given?
@trie.reverse_each(&block)
self
end
# Call the block once for each key/value pair in this `Hash`, passing the key as a
# parameter. Ordering guarantees are the same as {#each}.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each_key { |k| puts "k=#{k}" }
#
# k=A
# k=C
# k=B
# # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
#
# @yield [key] Once for each key/value pair.
# @return [self]
def each_key
return enum_for(:each_key) if not block_given?
@trie.each { |k,v| yield k }
self
end
# Call the block once for each key/value pair in this `Hash`, passing the value as a
# parameter. Ordering guarantees are the same as {#each}.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].each_value { |v| puts "v=#{v}" }
#
# v=1
# v=3
# v=2
# # => Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
#
# @yield [value] Once for each key/value pair.
# @return [self]
def each_value
return enum_for(:each_value) if not block_given?
@trie.each { |k,v| yield v }
self
end
# Call the block once for each key/value pair in this `Hash`, passing the key/value
# pair as parameters. The block should return a `[key, value]` array each time.
# All the returned `[key, value]` arrays will be gathered into a new `Hash`.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.map { |k, v| ["new-#{k}", v * v] }
# # => Hash["new-C" => 9, "new-B" => 4, "new-A" => 1]
#
# @yield [key, value] Once for each key/value pair.
# @return [Hash]
def map
return enum_for(:map) unless block_given?
return self if empty?
self.class.new(super, &@default)
end
alias collect map
# Return a new `Hash` with all the key/value pairs for which the block returns true.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.select { |k, v| v >= 2 }
# # => Immutable::Hash["B" => 2, "C" => 3]
#
# @yield [key, value] Once for each key/value pair.
# @yieldreturn Truthy if this pair should be present in the new `Hash`.
# @return [Hash]
def select(&block)
return enum_for(:select) unless block_given?
derive_new_hash(@trie.select(&block))
end
alias find_all select
alias keep_if select
# Yield `[key, value]` pairs until one is found for which the block returns true.
# Return that `[key, value]` pair. If the block never returns true, return `nil`.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.find { |k, v| v.even? }
# # => ["B", 2]
#
# @return [Array]
# @yield [key, value] At most once for each key/value pair, until the block returns `true`.
# @yieldreturn Truthy to halt iteration and return the yielded key/value pair.
def find
return enum_for(:find) unless block_given?
each { |entry| return entry if yield entry }
nil
end
alias detect find
# Return a new `Hash` containing all the key/value pairs from this `Hash` and
# `other`. If no block is provided, the value for entries with colliding keys
# will be that from `other`. Otherwise, the value for each duplicate key is
# determined by calling the block.
#
# `other` can be an `Immutable::Hash`, a built-in Ruby `Hash`, or any `Enumerable`
# object which yields `[key, value]` pairs.
#
# @example
# h1 = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h2 = Immutable::Hash["C" => 70, "D" => 80]
# h1.merge(h2)
# # => Immutable::Hash["C" => 70, "A" => 1, "D" => 80, "B" => 2]
# h1.merge(h2) { |key, v1, v2| v1 + v2 }
# # => Immutable::Hash["C" => 73, "A" => 1, "D" => 80, "B" => 2]
#
# @param other [::Enumerable] The collection to merge with
# @yieldparam key [Object] The key which was present in both collections
# @yieldparam my_value [Object] The associated value from this `Hash`
# @yieldparam other_value [Object] The associated value from the other collection
# @yieldreturn [Object] The value to associate this key with in the new `Hash`
# @return [Hash]
def merge(other)
trie = if block_given?
other.reduce(@trie) do |trie, (key, value)|
if (entry = trie.get(key))
trie.put(key, yield(key, entry[1], value))
else
trie.put(key, value)
end
end
else
@trie.bulk_put(other)
end
derive_new_hash(trie)
end
# Retrieve the value corresponding to the given key object, or use the provided
# default value or block, or otherwise raise a `KeyError`.
#
# @overload fetch(key)
# Retrieve the value corresponding to the given key, or raise a `KeyError`
# if it is not found.
# @param key [Object] The key to look up
# @overload fetch(key) { |key| ... }
# Return a sorted {Vector} which contains all the `[key, value]` pairs in
# this `Hash` as two-element `Array`s.
#
# @overload sort
# Uses `#<=>` to determine sorted order.
# @overload sort { |(k1, v1), (k2, v2)| ... }
# Uses the block as a comparator to determine sorted order.
#
# @example
# h = Immutable::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3]
# h.sort { |(k1, v1), (k2, v2)| k1.size <=> k2.size }
# # => Immutable::Vector[["Dog", 1], ["Lion", 3], ["Elephant", 2]]
# @yield [(k1, v1), (k2, v2)] Any number of times with different pairs of key/value associations.
# @yieldreturn [Integer] Negative if the first pair should be sorted
# lower, positive if the latter pair, or 0 if equal.
#
# @see ::Enumerable#sort
#
# @return [Vector]
def sort
Vector.new(super)
end
# Return a {Vector} which contains all the `[key, value]` pairs in this `Hash`
# as two-element Arrays. The order which the pairs will appear in is determined by
# passing each pair to the code block to obtain a sort key object, and comparing
# the sort keys using `#<=>`.
#
# @see ::Enumerable#sort_by
#
# @example
# h = Immutable::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3]
# h.sort_by { |key, value| key.size }
# # => Immutable::Vector[["Dog", 1], ["Lion", 3], ["Elephant", 2]]
#
# @yield [key, value] Once for each key/value pair.
# @yieldreturn a sort key object for the yielded pair.
# @return [Vector]
def sort_by
Vector.new(super)
end
# Return a new `Hash` with the associations for all of the given `keys` removed.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.except("A", "C") # => Immutable::Hash["B" => 2]
#
# @param keys [Array] The keys to remove
# @return [Hash]
def except(*keys)
keys.reduce(self) { |hash, key| hash.delete(key) }
end
# Return a new `Hash` with only the associations for the `wanted` keys retained.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.slice("B", "C") # => Immutable::Hash["B" => 2, "C" => 3]
#
# @param wanted [::Enumerable] The keys to retain
# @return [Hash]
def slice(*wanted)
trie = Trie.new(0)
wanted.each { |key| trie.put!(key, get(key)) if key?(key) }
self.class.alloc(trie, @default)
end
# Return a {Vector} of the values which correspond to the `wanted` keys.
# If any of the `wanted` keys are not present in this `Hash`, `nil` will be
# placed instead, or the result of the default proc (if one is defined),
# similar to the behavior of {#get}.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.values_at("B", "A", "D") # => Immutable::Vector[2, 1, nil]
#
# @param wanted [Array] The keys to retrieve
# @return [Vector]
def values_at(*wanted)
Vector.new(wanted.map { |key| get(key) }.freeze)
end
# Return a {Vector} of the values which correspond to the `wanted` keys.
# If any of the `wanted` keys are not present in this `Hash`, raise `KeyError`
# exception.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.fetch_values("C", "A") # => Immutable::Vector[3, 1]
# h.fetch_values("C", "Z") # => KeyError: key not found: "Z"
#
# @param wanted [Array] The keys to retrieve
# @return [Vector]
def fetch_values(*wanted)
array = wanted.map { |key| fetch(key) }
Vector.new(array.freeze)
end
# Return the value of successively indexing into a nested collection.
# If any of the keys is not present, return `nil`.
#
# @example
# h = Immutable::Hash[a: 9, b: Immutable::Hash[c: 'a', d: 4], e: nil]
# h.dig(:b, :c) # => "a"
# h.dig(:b, :f) # => nil
#
# @return [Object]
def dig(key, *rest)
value = self[key]
if rest.empty? || value.nil?
value
else
value.dig(*rest)
end
end
# Return a new {Set} containing the keys from this `Hash`.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].keys
# # => Immutable::Set["D", "C", "B", "A"]
#
# @return [Set]
def keys
Set.alloc(@trie)
end
# Return a new {Vector} populated with the values from this `Hash`.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].values
# # => Immutable::Vector[2, 3, 2, 1]
#
# @return [Vector]
def values
Vector.new(each_value.to_a.freeze)
end
# Return a new `Hash` created by using keys as values and values as keys.
# If there are multiple values which are equivalent (as determined by `#hash` and
# `#eql?`), only one out of each group of equivalent values will be
# retained. Which one specifically is undefined.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].invert
# # => Immutable::Hash[1 => "A", 3 => "C", 2 => "B"]
#
# @return [Hash]
def invert
pairs = []
each { |k,v| pairs << [v, k] }
self.class.new(pairs, &@default)
end
# Return a new {Vector} which is a one-dimensional flattening of this `Hash`.
# If `level` is 1, all the `[key, value]` pairs in the hash will be concatenated
# into one {Vector}. If `level` is greater than 1, keys or values which are
# themselves `Array`s or {Vector}s will be recursively flattened into the output
# {Vector}. The depth to which that flattening will be recursively applied is
# determined by `level`.
#
# As a special case, if `level` is 0, each `[key, value]` pair will be a
# separate element in the returned {Vector}.
#
# @example
# h = Immutable::Hash["A" => 1, "B" => [2, 3, 4]]
# h.flatten
# # => Immutable::Vector["A", 1, "B", [2, 3, 4]]
# h.flatten(2)
# # => Immutable::Vector["A", 1, "B", 2, 3, 4]
#
# @param level [Integer] The number of times to recursively flatten the `[key, value]` pairs in this `Hash`.
# @return [Vector]
def flatten(level = 1)
return Vector.new(self) if level == 0
array = []
each { |k,v| array << k; array << v }
array.flatten!(level-1) if level > 1
Vector.new(array.freeze)
end
# Searches through the `Hash`, comparing `obj` with each key (using `#==`).
# When a matching key is found, return the `[key, value]` pair as an array.
# Return `nil` if no match is found.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].assoc("B") # => ["B", 2]
#
# @param obj [Object] The key to search for (using #==)
# @return [Array]
def assoc(obj)
each { |entry| return entry if obj == entry[0] }
nil
end
# Searches through the `Hash`, comparing `obj` with each value (using `#==`).
# When a matching value is found, return the `[key, value]` pair as an array.
# Return `nil` if no match is found.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].rassoc(2) # => ["B", 2]
#
# @param obj [Object] The value to search for (using #==)
# @return [Array]
def rassoc(obj)
each { |entry| return entry if obj == entry[1] }
nil
end
# Searches through the `Hash`, comparing `value` with each value (using `#==`).
# When a matching value is found, return its associated key object.
# Return `nil` if no match is found.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].key(2) # => "B"
#
# @param value [Object] The value to search for (using #==)
# @return [Object]
def key(value)
each { |entry| return entry[0] if value == entry[1] }
nil
end
# Return a randomly chosen `[key, value]` pair from this `Hash`. If the hash is empty,
# return `nil`.
#
# @example
# Immutable::Hash["A" => 1, "B" => 2, "C" => 3].sample
# # => ["C", 3]
#
# @return [Array]
def sample
@trie.at(rand(size))
end
# Return an empty `Hash` instance, of the same class as this one. Useful if you
# have multiple subclasses of `Hash` and want to treat them polymorphically.
# Maintains the default block, if there is one.
#
# @return [Hash]
def clear
if @default
self.class.alloc(EmptyTrie, @default)
else
self.class.empty
end
end
# Return true if `other` has the same type and contents as this `Hash`.
#
# @param other [Object] The collection to compare with
# @return [Boolean]
def eql?(other)
return true if other.equal?(self)
instance_of?(other.class) && @trie.eql?(other.instance_variable_get(:@trie))
end
# Return true if `other` has the same contents as this `Hash`. Will convert
# `other` to a Ruby `Hash` using `#to_hash` if necessary.
#
# @param other [Object] The object to compare with
# @return [Boolean]
def ==(other)
eql?(other) || (other.respond_to?(:to_hash) && to_hash == other.to_hash)
end
# Return true if this `Hash` is a proper superset of `other`, which means
# all `other`'s keys are contained in this `Hash` with identical
# values, and the two hashes are not identical.
#
# @param other [Immutable::Hash] The object to compare with
# @return [Boolean]
def >(other)
self != other && self >= other
end
# Return true if this `Hash` is a superset of `other`, which means all
# `other`'s keys are contained in this `Hash` with identical values.
#
# @param other [Immutable::Hash] The object to compare with
# @return [Boolean]
def >=(other)
other.each do |key, value|
if self[key] != value
return false
end
end
true
end
# Return true if this `Hash` is a proper subset of `other`, which means all
# its keys are contained in `other` with the identical values, and the two
# hashes are not identical.
#
# @param other [Immutable::Hash] The object to compare with
# @return [Boolean]
def <(other)
other > self
end
# Return true if this `Hash` is a subset of `other`, which means all its
# keys are contained in `other` with the identical values, and the two
# hashes are not identical.
#
# @param other [Immutable::Hash] The object to compare with
# @return [Boolean]
def <=(other)
other >= self
end
# See `Object#hash`.
# @return [Integer]
def hash
keys.to_a.sort.reduce(0) do |hash, key|
(hash << 32) - hash + key.hash + get(key).hash
end
end
# Return the contents of this `Hash` as a programmer-readable `String`. If all the
# keys and values are serializable as Ruby literal strings, the returned string can
# be passed to `eval` to reconstitute an equivalent `Hash`. The default
# block (if there is one) will be lost when doing this, however.
#
# @return [String]
def inspect
result = "#{self.class}["
i = 0
each do |key, val|
result << ', ' if i > 0
result << key.inspect << ' => ' << val.inspect
i += 1
end
result << ']'
end
# Return `self`. Since this is an immutable object duplicates are
# equivalent.
# @return [Hash]
def dup
self
end
alias clone dup
# Allows this `Hash` to be printed at the `pry` console, or using `pp` (from the
# Ruby standard library), in a way which takes the amount of horizontal space on
# the screen into account, and which indents nested structures to make them easier
# to read.
#
# @private
def pretty_print(pp)
pp.group(1, "#{self.class}[", ']') do
pp.breakable ''
pp.seplist(self, nil) do |key, val|
pp.group do
key.pretty_print(pp)
pp.text ' => '
pp.group(1) do
pp.breakable ''
val.pretty_print(pp)
end
end
end
end
end
# Convert this `Immutable::Hash` to an instance of Ruby's built-in `Hash`.
#
# @return [::Hash]
def to_hash
output = {}
each do |key, value|
output[key] = value
end
output
end
alias to_h to_hash
# Return a `Proc` which accepts a key as an argument and returns the value.
# The `Proc` behaves like {#get} (when the key is missing, it returns nil or
# the result of the default proc).
#
# @example
# h = Immutable::Hash["A" => 1, "B" => 2, "C" => 3]
# h.to_proc.call("B")
# # => 2
# ["A", "C", "X"].map(&h) # The & is short for .to_proc in Ruby
# # => [1, 3, nil]
#
# @return [Proc]
def to_proc
lambda { |key| get(key) }
end
# @return [::Hash]
# @private
def marshal_dump
to_hash
end
# @private
def marshal_load(dictionary)
@trie = Trie[dictionary]
end
private
# Return a new `Hash` which is derived from this one, using a modified {Trie}.
# The new `Hash` will retain the existing default block, if there is one.
#
def derive_new_hash(trie)
if trie.equal?(@trie)
self
elsif trie.empty?
if @default
self.class.alloc(EmptyTrie, @default)
else
self.class.empty
end
else
self.class.alloc(trie, @default)
end
end
end
# The canonical empty `Hash`. Returned by `Hash[]` when
# invoked with no arguments; also returned by `Hash.empty`. Prefer using this
# one rather than creating many empty hashes using `Hash.new`.
#
# @private
EmptyHash = Immutable::Hash.empty
# A `Vector` is an ordered, integer-indexed collection of objects. Like
# Ruby's `Array`, `Vector` indexing starts at zero and negative indexes count
# back from the end.
#
# `Vector` has a similar interface to `Array`. The main difference is methods
# that would destructively update an `Array` (such as {#insert} or
# {#delete_at}) instead return new `Vectors` and leave the existing one
# unchanged.
#
# ### Creating New Vectors
#
# Immutable::Vector.new([:first, :second, :third])
# Immutable::Vector[1, 2, 3, 4, 5]
#
# ### Retrieving Items from Vectors
#
# vector = Immutable::Vector[1, 2, 3, 4, 5]
#
# vector[0] # => 1
# vector[-1] # => 5
# vector[0,3] # => Immutable::Vector[1, 2, 3]
# vector[1..-1] # => Immutable::Vector[2, 3, 4, 5]
# vector.first # => 1
# vector.last # => 5
#
# ### Creating Modified Vectors
#
# vector.add(6) # => Immutable::Vector[1, 2, 3, 4, 5, 6]
# vector.insert(1, :a, :b) # => Immutable::Vector[1, :a, :b, 2, 3, 4, 5]
# vector.delete_at(2) # => Immutable::Vector[1, 2, 4, 5]
# vector + [6, 7] # => Immutable::Vector[1, 2, 3, 4, 5, 6, 7]
#
class Vector
include Immutable::Enumerable
# @private
BLOCK_SIZE = 32
# @private
INDEX_MASK = BLOCK_SIZE - 1
# @private
BITS_PER_LEVEL = 5
# Return the number of items in this `Vector`
# @return [Integer]
attr_reader :size
alias length size
class << self
# Create a new `Vector` populated with the given items.
# @return [Vector]
def [](*items)
new(items.freeze)
end
# Return an empty `Vector`. If used on a subclass, returns an empty instance
# of that class.
#
# @return [Vector]
def empty
@empty ||= new
end
# "Raw" allocation of a new `Vector`. Used internally to create a new
# instance quickly after building a modified trie.
#
# @return [Vector]
# @private
def alloc(root, size, levels)
obj = allocate
obj.instance_variable_set(:@root, root)
obj.instance_variable_set(:@size, size)
obj.instance_variable_set(:@levels, levels)
obj.freeze
end
end
def initialize(items=[].freeze)
items = items.to_a
if items.size <= 32
items = items.dup.freeze if !items.frozen?
@root, @size, @levels = items, items.size, 0
else
root, size, levels = items, items.size, 0
while root.size > 32
root = root.each_slice(32).to_a
levels += 1
end
@root, @size, @levels = root.freeze, size, levels
end
freeze
end
# Return `true` if this `Vector` contains no items.
#
# @return [Boolean]
def empty?
@size == 0
end
# Return the first item in the `Vector`. If the vector is empty, return `nil`.
#
# @example
# Immutable::Vector["A", "B", "C"].first # => "A"
#
# @return [Object]
def first
get(0)
end
# Return the last item in the `Vector`. If the vector is empty, return `nil`.
#
# @example
# Immutable::Vector["A", "B", "C"].last # => "C"
#
# @return [Object]
def last
get(-1)
end
# Return a new `Vector` with `item` added after the last occupied position.
#
# @example
# Immutable::Vector[1, 2].add(99) # => Immutable::Vector[1, 2, 99]
#
# @param item [Object] The object to insert at the end of the vector
# @return [Vector]
def add(item)
update_root(@size, item)
end
alias << add
alias push add
# Return a new `Vector` with a new value at the given `index`. If `index`
# is greater than the length of the vector, the returned vector will be
# padded with `nil`s to the correct size.
#
# @overload set(index, item)
# Return a new `Vector` with the item at `index` replaced by `item`.
#
# @param item [Object] The object to insert into that position
# @example
# Immutable::Vector[1, 2, 3, 4].set(2, 99)
# # => Immutable::Vector[1, 2, 99, 4]
# Immutable::Vector[1, 2, 3, 4].set(-1, 99)
# # => Immutable::Vector[1, 2, 3, 99]
# Immutable::Vector[].set(2, 99)
# # => Immutable::Vector[nil, nil, 99]
#
# @overload set(index)
# Return a new `Vector` with the item at `index` replaced by the return
# value of the block.
#
# @yield (existing) Once with the existing value at the given `index`.
# @example
# Immutable::Vector[1, 2, 3, 4].set(2) { |v| v * 10 }
# # => Immutable::Vector[1, 2, 30, 4]
#
# @param index [Integer] The index to update. May be negative.
# @return [Vector]
def set(index, item = yield(get(index)))
raise IndexError, "index #{index} outside of vector bounds" if index < -@size
index += @size if index < 0
if index > @size
suffix = Array.new(index - @size, nil)
suffix << item
replace_suffix(@size, suffix)
else
update_root(index, item)
end
end
# Return a new `Vector` with a deeply nested value modified to the result
# of the given code block. When traversing the nested `Vector`s and
# `Hash`es, non-existing keys are created with empty `Hash` values.
#
# The code block receives the existing value of the deeply nested key (or
# `nil` if it doesn't exist). This is useful for "transforming" the value
# associated with a certain key.
#
# Note that the original `Vector` and sub-`Vector`s and sub-`Hash`es are
# left unmodified; new data structure copies are created along the path
# wherever needed.
#
# @example
# v = Immutable::Vector[123, 456, 789, Immutable::Hash["a" => Immutable::Vector[5, 6, 7]]]
# v.update_in(3, "a", 1) { |value| value + 9 }
# # => Immutable::Vector[123, 456, 789, Immutable::Hash["a" => Immutable::Vector[5, 15, 7]]]
#
# @param key_path [Object(s)] List of keys which form the path to the key to be modified
# @yield [value] The previously stored value
# @yieldreturn [Object] The new value to store
# @return [Vector]
def update_in(*key_path, &block)
if key_path.empty?
raise ArgumentError, 'must have at least one key in path'
end
key = key_path[0]
if key_path.size == 1
new_value = block.call(get(key))
else
value = fetch(key, Immutable::EmptyHash)
new_value = value.update_in(*key_path[1..-1], &block)
end
set(key, new_value)
end
# Retrieve the item at `index`. If there is none (either the provided index
# is too high or too low), return `nil`.
#
# @example
# v = Immutable::Vector["A", "B", "C", "D"]
# v.get(2) # => "C"
# v.get(-1) # => "D"
# v.get(4) # => nil
#
# @param index [Integer] The index to retrieve
# @return [Object]
def get(index)
return nil if @size == 0
index += @size if index < 0
return nil if index >= @size || index < 0
leaf_node_for(@root, @levels * BITS_PER_LEVEL, index)[index & INDEX_MASK]
end
alias at get
# Retrieve the value at `index` with optional default.
#
# @overload fetch(index)
# Retrieve the value at the given index, or raise an `IndexError` if not
# found.
#
# @param index [Integer] The index to look up
# @raise [IndexError] if index does not exist
# @example
# v = Immutable::Vector["A", "B", "C", "D"]
# v.fetch(2) # => "C"
# v.fetch(-1) # => "D"
# v.fetch(4) # => IndexError: index 4 outside of vector bounds
#
# @overload fetch(index) { |index| ... }
# Retrieve the value at the given index, or return the result of yielding
# the block if not found.
#
# @yield Once if the index is not found.
# @yieldparam [Integer] index The index which does not exist
# @yieldreturn [Object] Default value to return
# @param index [Integer] The index to look up
# @example
# v = Immutable::Vector["A", "B", "C", "D"]
# v.fetch(2) { |i| i * i } # => "C"
# v.fetch(4) { |i| i * i } # => 16
#
# @overload fetch(index, default)
# Retrieve the value at the given index, or return the provided `default`
# value if not found.
#
# @param index [Integer] The index to look up
# @param default [Object] Object to return if the key is not found
# @example
# v = Immutable::Vector["A", "B", "C", "D"]
# v.fetch(2, "Z") # => "C"
# v.fetch(4, "Z") # => "Z"
#
# @return [Object]
def fetch(index, default = (missing_default = true))
if index >= -@size && index < @size
get(index)
elsif block_given?
yield(index)
elsif !missing_default
default
else
raise IndexError, "index #{index} outside of vector bounds"
end
end
# Return the value of successively indexing into a nested collection.
# If any of the keys is not present, return `nil`.
#
# @example
# v = Immutable::Vector[9, Immutable::Hash[c: 'a', d: 4]]
# v.dig(1, :c) # => "a"
# v.dig(1, :f) # => nil
#
# @return [Object]
def dig(key, *rest)
value = self[key]
if rest.empty? || value.nil?
value
else
value.dig(*rest)
end
end
# Return specific objects from the `Vector`. All overloads return `nil` if
# the starting index is out of range.
#
# @overload vector.slice(index)
# Returns a single object at the given `index`. If `index` is negative,
# count backwards from the end.
#
# @param index [Integer] The index to retrieve. May be negative.
# @return [Object]
# @example
# v = Immutable::Vector["A", "B", "C", "D", "E", "F"]
# v[2] # => "C"
# v[-1] # => "F"
# v[6] # => nil
#
# @overload vector.slice(index, length)
# Return a subvector starting at `index` and continuing for `length`
# elements or until the end of the `Vector`, whichever occurs first.
#
# @param start [Integer] The index to start retrieving items from. May be
# negative.
# @param length [Integer] The number of items to retrieve.
# @return [Vector]
# @example
# v = Immutable::Vector["A", "B", "C", "D", "E", "F"]
# v[2, 3] # => Immutable::Vector["C", "D", "E"]
# v[-2, 3] # => Immutable::Vector["E", "F"]
# v[20, 1] # => nil
#
# @overload vector.slice(index..end)
# Return a subvector starting at `index` and continuing to index
# `end` or the end of the `Vector`, whichever occurs first.
#
# @param range [Range] The range of indices to retrieve.
# @return [Vector]
# @example
# v = Immutable::Vector["A", "B", "C", "D", "E", "F"]
# v[2..3] # => Immutable::Vector["C", "D"]
# v[-2..100] # => Immutable::Vector["E", "F"]
# v[20..21] # => nil
def slice(arg, length = (missing_length = true))
if missing_length
if arg.is_a?(Range)
from, to = arg.begin, arg.end
from += @size if from < 0
to += @size if to < 0
to += 1 if !arg.exclude_end?
length = to - from
length = 0 if length < 0
subsequence(from, length)
else
get(arg)
end
else
arg += @size if arg < 0
subsequence(arg, length)
end
end
alias [] slice
# Return a new `Vector` with the given values inserted before the element
# at `index`. If `index` is greater than the current length, `nil` values
# are added to pad the `Vector` to the required size.
#
# @example
# Immutable::Vector["A", "B", "C", "D"].insert(2, "X", "Y", "Z")
# # => Immutable::Vector["A", "B", "X", "Y", "Z", "C", "D"]
# Immutable::Vector[].insert(2, "X", "Y", "Z")
# # => Immutable::Vector[nil, nil, "X", "Y", "Z"]
#
# @param index [Integer] The index where the new items should go
# @param items [Array] The items to add
# @return [Vector]
# @raise [IndexError] if index exceeds negative range.
def insert(index, *items)
raise IndexError if index < -@size
index += @size if index < 0
if index < @size
suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, [])
suffix.unshift(*items)
elsif index == @size
suffix = items
else
suffix = Array.new(index - @size, nil).concat(items)
index = @size
end
replace_suffix(index, suffix)
end
# Return a new `Vector` with the element at `index` removed. If the given `index`
# does not exist, return `self`.
#
# @example
# Immutable::Vector["A", "B", "C", "D"].delete_at(2)
# # => Immutable::Vector["A", "B", "D"]
#
# @param index [Integer] The index to remove
# @return [Vector]
def delete_at(index)
return self if index >= @size || index < -@size
index += @size if index < 0
suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, [])
replace_suffix(index, suffix.tap(&:shift))
end
# Return a new `Vector` with the last element removed. Return `self` if
# empty.
#
# @example
# Immutable::Vector["A", "B", "C"].pop # => Immutable::Vector["A", "B"]
#
# @return [Vector]
def pop
return self if @size == 0
replace_suffix(@size-1, [])
end
# Return a new `Vector` with `object` inserted before the first element,
# moving the other elements upwards.
#
# @example
# Immutable::Vector["A", "B"].unshift("Z")
# # => Immutable::Vector["Z", "A", "B"]
#
# @param object [Object] The value to prepend
# @return [Vector]
def unshift(object)
insert(0, object)
end
# Return a new `Vector` with the first element removed. If empty, return
# `self`.
#
# @example
# Immutable::Vector["A", "B", "C"].shift # => Immutable::Vector["B", "C"]
#
# @return [Vector]
def shift
delete_at(0)
end
# Call the given block once for each item in the vector, passing each
# item from first to last successively to the block. If no block is given,
# an `Enumerator` is returned instead.
#
# @example
# Immutable::Vector["A", "B", "C"].each { |e| puts "Element: #{e}" }
#
# Element: A
# Element: B
# Element: C
# # => Immutable::Vector["A", "B", "C"]
#
# @return [self, Enumerator]
def each(&block)
return to_enum unless block_given?
traverse_depth_first(@root, @levels, &block)
self
end
# Call the given block once for each item in the vector, from last to
# first.
#
# @example
# Immutable::Vector["A", "B", "C"].reverse_each { |e| puts "Element: #{e}" }
#
# Element: C
# Element: B
# Element: A
#
# @return [self]
def reverse_each(&block)
return enum_for(:reverse_each) unless block_given?
reverse_traverse_depth_first(@root, @levels, &block)
self
end
# Return a new `Vector` containing all elements for which the given block returns
# true.
#
# @example
# Immutable::Vector["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 }
# # => Immutable::Vector["Bird", "Elephant"]
#
# @return [Vector]
# @yield [element] Once for each element.
def select
return enum_for(:select) unless block_given?
reduce(self.class.empty) { |vector, item| yield(item) ? vector.add(item) : vector }
end
alias find_all select
alias keep_if select
# Return a new `Vector` with all items which are equal to `obj` removed.
# `#==` is used for checking equality.
#
# @example
# Immutable::Vector["C", "B", "A", "B"].delete("B") # => Immutable::Vector["C", "A"]
#
# @param obj [Object] The object to remove (every occurrence)
# @return [Vector]
def delete(obj)
select { |item| item != obj }
end
# Invoke the given block once for each item in the vector, and return a new
# `Vector` containing the values returned by the block. If no block is
# provided, return an enumerator.
#
# @example
# Immutable::Vector[3, 2, 1].map { |e| e * e } # => Immutable::Vector[9, 4, 1]
#
# @return [Vector, Enumerator]
def map
return enum_for(:map) if not block_given?
return self if empty?
self.class.new(super)
end
alias collect map
# Return a new `Vector` with the concatenated results of running the block once
# for every element in this `Vector`.
#
# @example
# Immutable::Vector[1, 2, 3].flat_map { |x| [x, -x] }
# # => Immutable::Vector[1, -1, 2, -2, 3, -3]
#
# @return [Vector]
def flat_map
return enum_for(:flat_map) if not block_given?
return self if empty?
self.class.new(super)
end
# Return a new `Vector` with the same elements as this one, but randomly permuted.
#
# @example
# Immutable::Vector[1, 2, 3, 4].shuffle # => Immutable::Vector[4, 1, 3, 2]
#
# @return [Vector]
def shuffle
self.class.new(((array = to_a).frozen? ? array.shuffle : array.shuffle!).freeze)
end
# Return a new `Vector` with no duplicate elements, as determined by `#hash` and
# `#eql?`. For each group of equivalent elements, only the first will be retained.
#
# @example
# Immutable::Vector["A", "B", "C", "B"].uniq # => Immutable::Vector["A", "B", "C"]
# Immutable::Vector["a", "A", "b"].uniq(&:upcase) # => Immutable::Vector["a", "b"]
#
# @return [Vector]
def uniq(&block)
array = to_a
if array.frozen?
self.class.new(array.uniq(&block).freeze)
elsif array.uniq!(&block) # returns nil if no changes were made
self.class.new(array.freeze)
else
self
end
end
# Return a new `Vector` with the same elements as this one, but in reverse order.
#
# @example
# Immutable::Vector["A", "B", "C"].reverse # => Immutable::Vector["C", "B", "A"]
#
# @return [Vector]
def reverse
self.class.new(((array = to_a).frozen? ? array.reverse : array.reverse!).freeze)
end
# Return a new `Vector` with the same elements, but rotated so that the one at
# index `count` is the first element of the new vector. If `count` is positive,
# the elements will be shifted left, and those shifted past the lowest position
# will be moved to the end. If `count` is negative, the elements will be shifted
# right, and those shifted past the last position will be moved to the beginning.
#
# @example
# v = Immutable::Vector["A", "B", "C", "D", "E", "F"]
# v.rotate(2) # => Immutable::Vector["C", "D", "E", "F", "A", "B"]
# v.rotate(-1) # => Immutable::Vector["F", "A", "B", "C", "D", "E"]
#
# @param count [Integer] The number of positions to shift items by
# @return [Vector]
def rotate(count = 1)
return self if (count % @size) == 0
self.class.new(((array = to_a).frozen? ? array.rotate(count) : array.rotate!(count)).freeze)
end
# Return a new `Vector` with all nested vectors and arrays recursively "flattened
# out". That is, their elements inserted into the new `Vector` in the place where
# the nested array/vector originally was. If an optional `level` argument is
# provided, the flattening will only be done recursively that number of times.
# A `level` of 0 means not to flatten at all, 1 means to only flatten nested
# arrays/vectors which are directly contained within this `Vector`.
#
# @example
# v = Immutable::Vector["A", Immutable::Vector["B", "C", Immutable::Vector["D"]]]
# v.flatten(1)
# # => Immutable::Vector["A", "B", "C", Immutable::Vector["D"]]
# v.flatten
# # => Immutable::Vector["A", "B", "C", "D"]
#
# @param level [Integer] The depth to which flattening should be applied
# @return [Vector]
def flatten(level = -1)
return self if level == 0
array = to_a
if array.frozen?
self.class.new(array.flatten(level).freeze)
elsif array.flatten!(level) # returns nil if no changes were made
self.class.new(array.freeze)
else
self
end
end
# Return a new `Vector` built by concatenating this one with `other`. `other`
# can be any object which is convertible to an `Array` using `#to_a`.
#
# @example
# Immutable::Vector["A", "B", "C"] + ["D", "E"]
# # => Immutable::Vector["A", "B", "C", "D", "E"]
#
# @param other [Enumerable] The collection to concatenate onto this vector
# @return [Vector]
def +(other)
other = other.to_a
other = other.dup if other.frozen?
replace_suffix(@size, other)
end
alias concat +
# Combine two vectors by "zipping" them together. `others` should be arrays
# and/or vectors. The corresponding elements from this `Vector` and each of
# `others` (that is, the elements with the same indices) will be gathered
# into arrays.
#
# If `others` contains fewer elements than this vector, `nil` will be used
# for padding.
#
# @overload zip(*others)
# Return a new vector containing the new arrays.
#
# @return [Vector]
#
# @overload zip(*others)
# @yield [pair] once for each array
# @return [nil]
#
# @example
# v1 = Immutable::Vector["A", "B", "C"]
# v2 = Immutable::Vector[1, 2]
# v1.zip(v2)
# # => Immutable::Vector[["A", 1], ["B", 2], ["C", nil]]
#
# @param others [Array] The arrays/vectors to zip together with this one
# @return [Vector]
def zip(*others)
if block_given?
super
else
self.class.new(super)
end
end
# Return a new `Vector` with the same items, but sorted.
#
# @overload sort
# Compare elements with their natural sort key (`#<=>`).
#
# @example
# Immutable::Vector["Elephant", "Dog", "Lion"].sort
# # => Immutable::Vector["Dog", "Elephant", "Lion"]
#
# @overload sort
# Uses the block as a comparator to determine sorted order.
#
# @yield [a, b] Any number of times with different pairs of elements.
# @yieldreturn [Integer] Negative if the first element should be sorted
# lower, positive if the latter element, or 0 if
# equal.
# @example
# Immutable::Vector["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size }
# # => Immutable::Vector["Dog", "Lion", "Elephant"]
#
# @return [Vector]
def sort
self.class.new(super)
end
# Return a new `Vector` with the same items, but sorted. The sort order is
# determined by mapping the items through the given block to obtain sort
# keys, and then sorting the keys according to their natural sort order
# (`#<=>`).
#
# @yield [element] Once for each element.
# @yieldreturn a sort key object for the yielded element.
# @example
# Immutable::Vector["Elephant", "Dog", "Lion"].sort_by { |e| e.size }
# # => Immutable::Vector["Dog", "Lion", "Elephant"]
#
# @return [Vector]
def sort_by
self.class.new(super)
end
# Drop the first `n` elements and return the rest in a new `Vector`.
#
# @example
# Immutable::Vector["A", "B", "C", "D", "E", "F"].drop(2)
# # => Immutable::Vector["C", "D", "E", "F"]
#
# @param n [Integer] The number of elements to remove
# @return [Vector]
# @raise ArgumentError if `n` is negative.
def drop(n)
return self if n == 0
return self.class.empty if n >= @size
raise ArgumentError, 'attempt to drop negative size' if n < 0
self.class.new(flatten_suffix(@root, @levels * BITS_PER_LEVEL, n, []))
end
# Return only the first `n` elements in a new `Vector`.
#
# @example
# Immutable::Vector["A", "B", "C", "D", "E", "F"].take(4)
# # => Immutable::Vector["A", "B", "C", "D"]
#
# @param n [Integer] The number of elements to retain
# @return [Vector]
def take(n)
return self if n >= @size
self.class.new(super)
end
# Drop elements up to, but not including, the first element for which the
# block returns `nil` or `false`. Gather the remaining elements into a new
# `Vector`. If no block is given, an `Enumerator` is returned instead.
#
# @example
# Immutable::Vector[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 }
# # => Immutable::Vector[5, 7, 6, 4, 2]
#
# @return [Vector, Enumerator]
def drop_while
return enum_for(:drop_while) if not block_given?
self.class.new(super)
end
# Gather elements up to, but not including, the first element for which the
# block returns `nil` or `false`, and return them in a new `Vector`. If no block
# is given, an `Enumerator` is returned instead.
#
# @example
# Immutable::Vector[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 }
# # => Immutable::Vector[1, 3]
#
# @return [Vector, Enumerator]
def take_while
return enum_for(:take_while) if not block_given?
self.class.new(super)
end
# Repetition. Return a new `Vector` built by concatenating `times` copies
# of this one together.
#
# @example
# Immutable::Vector["A", "B"] * 3
# # => Immutable::Vector["A", "B", "A", "B", "A", "B"]
#
# @param times [Integer] The number of times to repeat the elements in this vector
# @return [Vector]
def *(times)
return self.class.empty if times == 0
return self if times == 1
result = (to_a * times)
result.is_a?(Array) ? self.class.new(result) : result
end
# Replace a range of indexes with the given object.
#
# @overload fill(object)
# Return a new `Vector` of the same size, with every index set to
# `object`.
#
# @param [Object] object Fill value.
# @example
# Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z")
# # => Immutable::Vector["Z", "Z", "Z", "Z", "Z", "Z"]
#
# @overload fill(object, index)
# Return a new `Vector` with all indexes from `index` to the end of the
# vector set to `object`.
#
# @param [Object] object Fill value.
# @param [Integer] index Starting index. May be negative.
# @example
# Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3)
# # => Immutable::Vector["A", "B", "C", "Z", "Z", "Z"]
#
# @overload fill(object, index, length)
# Return a new `Vector` with `length` indexes, beginning from `index`,
# set to `object`. Expands the `Vector` if `length` would extend beyond
# the current length.
#
# @param [Object] object Fill value.
# @param [Integer] index Starting index. May be negative.
# @param [Integer] length
# @example
# Immutable::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2)
# # => Immutable::Vector["A", "B", "C", "Z", "Z", "F"]
# Immutable::Vector["A", "B"].fill("Z", 1, 5)
# # => Immutable::Vector["A", "Z", "Z", "Z", "Z", "Z"]
#
# @return [Vector]
# @raise [IndexError] if index is out of negative range.
def fill(object, index = 0, length = nil)
raise IndexError if index < -@size
index += @size if index < 0
length ||= @size - index # to the end of the array, if no length given
if index < @size
suffix = flatten_suffix(@root, @levels * BITS_PER_LEVEL, index, [])
suffix.fill(object, 0, length)
elsif index == @size
suffix = Array.new(length, object)
else
suffix = Array.new(index - @size, nil).concat(Array.new(length, object))
index = @size
end
replace_suffix(index, suffix)
end
# When invoked with a block, yields all combinations of length `n` of items
# from the `Vector`, and then returns `self`. There is no guarantee about
# which order the combinations will be yielded.
#
# If no block is given, an `Enumerator` is returned instead.
#
# @example
# v = Immutable::Vector[5, 6, 7, 8]
# v.combination(3) { |c| puts "Combination: #{c}" }
#
# Combination: [5, 6, 7]
# Combination: [5, 6, 8]
# Combination: [5, 7, 8]
# Combination: [6, 7, 8]
# #=> Immutable::Vector[5, 6, 7, 8]
#
# @return [self, Enumerator]
def combination(n)
return enum_for(:combination, n) if not block_given?
return self if n < 0 || @size < n
if n == 0
yield []
elsif n == 1
each { |item| yield [item] }
elsif n == @size
yield to_a
else
combos = lambda do |result,index,remaining|
while @size - index > remaining
if remaining == 1
yield result.dup << get(index)
else
combos[result.dup << get(index), index+1, remaining-1]
end
index += 1
end
index.upto(@size-1) { |i| result << get(i) }
yield result
end
combos[[], 0, n]
end
self
end
# When invoked with a block, yields all repeated combinations of length `n` of
# items from the `Vector`, and then returns `self`. A "repeated combination" is
# one in which any item from the `Vector` can appear consecutively any number of
# times.
#
# There is no guarantee about which order the combinations will be yielded in.
#
# If no block is given, an `Enumerator` is returned instead.
#
# @example
# v = Immutable::Vector[5, 6, 7, 8]
# v.repeated_combination(2) { |c| puts "Combination: #{c}" }
#
# Combination: [5, 5]
# Combination: [5, 6]
# Combination: [5, 7]
# Combination: [5, 8]
# Combination: [6, 6]
# Combination: [6, 7]
# Combination: [6, 8]
# Combination: [7, 7]
# Combination: [7, 8]
# Combination: [8, 8]
# # => Immutable::Vector[5, 6, 7, 8]
#
# @return [self, Enumerator]
def repeated_combination(n)
return enum_for(:repeated_combination, n) if not block_given?
if n < 0
# yield nothing
elsif n == 0
yield []
elsif n == 1
each { |item| yield [item] }
elsif @size == 0
# yield nothing
else
combos = lambda do |result,index,remaining|
while index < @size-1
if remaining == 1
yield result.dup << get(index)
else
combos[result.dup << get(index), index, remaining-1]
end
index += 1
end
item = get(index)
remaining.times { result << item }
yield result
end
combos[[], 0, n]
end
self
end
# Yields all permutations of length `n` of items from the `Vector`, and then
# returns `self`. If no length `n` is specified, permutations of all elements
# will be yielded.
#
# There is no guarantee about which order the permutations will be yielded in.
#
# If no block is given, an `Enumerator` is returned instead.
#
# @example
# v = Immutable::Vector[5, 6, 7]
# v.permutation(2) { |p| puts "Permutation: #{p}" }
#
# Permutation: [5, 6]
# Permutation: [5, 7]
# Permutation: [6, 5]
# Permutation: [6, 7]
# Permutation: [7, 5]
# Permutation: [7, 6]
# # => Immutable::Vector[5, 6, 7]
#
# @return [self, Enumerator]
def permutation(n = @size)
return enum_for(:permutation, n) if not block_given?
if n < 0 || @size < n
# yield nothing
elsif n == 0
yield []
elsif n == 1
each { |item| yield [item] }
else
used, result = [], []
perms = lambda do |index|
0.upto(@size-1) do |i|
next if used[i]
result[index] = get(i)
if index < n-1
used[i] = true
perms[index+1]
used[i] = false
else
yield result.dup
end
end
end
perms[0]
end
self
end
# When invoked with a block, yields all repeated permutations of length `n` of
# items from the `Vector`, and then returns `self`. A "repeated permutation" is
# one where any item from the `Vector` can appear any number of times, and in
# any position (not just consecutively)
#
# If no length `n` is specified, permutations of all elements will be yielded.
# There is no guarantee about which order the permutations will be yielded in.
#
# If no block is given, an `Enumerator` is returned instead.
#
# @example
# v = Immutable::Vector[5, 6, 7]
# v.repeated_permutation(2) { |p| puts "Permutation: #{p}" }
#
# Permutation: [5, 5]
# Permutation: [5, 6]
# Permutation: [5, 7]
# Permutation: [6, 5]
# Permutation: [6, 6]
# Permutation: [6, 7]
# Permutation: [7, 5]
# Permutation: [7, 6]
# Permutation: [7, 7]
# # => Immutable::Vector[5, 6, 7]
#
# @return [self, Enumerator]
def repeated_permutation(n = @size)
return enum_for(:repeated_permutation, n) if not block_given?
if n < 0
# yield nothing
elsif n == 0
yield []
elsif n == 1
each { |item| yield [item] }
else
result = []
perms = lambda do |index|
0.upto(@size-1) do |i|
result[index] = get(i)
if index < n-1
perms[index+1]
else
yield result.dup
end
end
end
perms[0]
end
self
end
# Cartesian product or multiplication.
#
# @overload product(*vectors)
# Return a `Vector` of all combinations of elements from this `Vector` and each
# of the given vectors or arrays. The length of the returned `Vector` is the product
# of `self.size` and the size of each argument vector or array.
# @example
# v1 = Immutable::Vector[1, 2, 3]
# v2 = Immutable::Vector["A", "B"]
# v1.product(v2)
# # => [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"], [3, "B"]]
# @overload product
# Return the result of multiplying all the items in this `Vector` together.
#
# @example
# Immutable::Vector[1, 2, 3, 4, 5].product # => 120
#
# @return [Vector]
def product(*vectors)
# if no vectors passed, return "product" as in result of multiplying all items
return super if vectors.empty?
vectors.unshift(self)
if vectors.any?(&:empty?)
return block_given? ? self : []
end
counters = Array.new(vectors.size, 0)
bump_counters = lambda do
i = vectors.size-1
counters[i] += 1
while counters[i] == vectors[i].size
counters[i] = 0
i -= 1
return true if i == -1 # we are done
counters[i] += 1
end
false # not done yet
end
build_array = lambda do
array = []
counters.each_with_index { |index,i| array << vectors[i][index] }
array
end
if block_given?
loop do
yield build_array[]
return self if bump_counters[]
end
else
result = []
loop do
result << build_array[]
return result if bump_counters[]
end
end
end
# Assume all elements are vectors or arrays and transpose the rows and columns.
# In other words, take the first element of each nested vector/array and gather
# them together into a new `Vector`. Do likewise for the second, third, and so on
# down to the end of each nested vector/array. Gather all the resulting `Vectors`
# into a new `Vector` and return it.
#
# This operation is closely related to {#zip}. The result is almost the same as
# calling {#zip} on the first nested vector/array with the others supplied as
# arguments.
#
# @example
# Immutable::Vector[["A", 10], ["B", 20], ["C", 30]].transpose
# # => Immutable::Vector[Immutable::Vector["A", "B", "C"], Immutable::Vector[10, 20, 30]]
#
# @return [Vector]
# @raise [IndexError] if elements are not of the same size.
# @raise [TypeError] if an element does not respond to #size and #[]
def transpose
return self.class.empty if empty?
result = Array.new(first.size) { [] }
0.upto(@size-1) do |i|
source = get(i)
if source.size != result.size
raise IndexError, "element size differs (#{source.size} should be #{result.size})"
end
0.upto(result.size-1) do |j|
result[j].push(source[j])
end
end
result.map! { |a| self.class.new(a) }
self.class.new(result)
rescue NoMethodError
if any? { |x| !x.respond_to?(:size) || !x.respond_to?(:[]) }
bad = find { |x| !x.respond_to?(:size) || !x.respond_to?(:[]) }
raise TypeError, "'#{bad.inspect}' must respond to #size and #[] to be transposed"
else
raise
end
end
# Finds a value from this `Vector` which meets the condition defined by the
# provided block, using a binary search. The vector must already be sorted
# with respect to the block. See Ruby's `Array#bsearch` for details,
# behaviour is equivalent.
#
# @example
# v = Immutable::Vector[1, 3, 5, 7, 9, 11, 13]
# # Block returns true/false for exact element match:
# v.bsearch { |e| e > 4 } # => 5
# # Block returns number to match an element in 4 <= e <= 7:
# v.bsearch { |e| 1 - e / 4 } # => 7
#
# @yield Once for at most `log n` elements, where `n` is the size of the
# vector. The exact elements and ordering are undefined.
# @yieldreturn [Boolean] `true` if this element matches the criteria, `false` otherwise.
# @yieldreturn [Integer] See `Array#bsearch` for details.
# @yieldparam [Object] element element to be evaluated
# @return [Object] The matched element, or `nil` if none found.
# @raise TypeError if the block returns a non-numeric, non-boolean, non-nil
# value.
def bsearch
return enum_for(:bsearch) if not block_given?
low, high, result = 0, @size, nil
while low < high
mid = (low + ((high - low) >> 1))
val = get(mid)
v = yield val
if v.is_a? Numeric
if v == 0
return val
elsif v > 0
high = mid
else
low = mid + 1
end
elsif v == true
result = val
high = mid
elsif !v
low = mid + 1
else
raise TypeError, "wrong argument type #{v.class} (must be numeric, true, false, or nil)"
end
end
result
end
# Return an empty `Vector` instance, of the same class as this one. Useful if you
# have multiple subclasses of `Vector` and want to treat them polymorphically.
#
# @return [Vector]
def clear
self.class.empty
end
# Return a randomly chosen item from this `Vector`. If the vector is empty, return `nil`.
#
# @example
# Immutable::Vector[1, 2, 3, 4, 5].sample # => 2
#
# @return [Object]
def sample
get(rand(@size))
end
# Return a new `Vector` with only the elements at the given `indices`, in the
# order specified by `indices`. If any of the `indices` do not exist, `nil`s will
# appear in their places.
#
# @example
# v = Immutable::Vector["A", "B", "C", "D", "E", "F"]
# v.values_at(2, 4, 5) # => Immutable::Vector["C", "E", "F"]
#
# @param indices [Array] The indices to retrieve and gather into a new `Vector`
# @return [Vector]
def values_at(*indices)
self.class.new(indices.map { |i| get(i) }.freeze)
end
# Find the index of an element, starting from the end of the vector.
# Returns `nil` if no element is found.
#
# @overload rindex(obj)
# Return the index of the last element which is `#==` to `obj`.
#
# @example
# v = Immutable::Vector[7, 8, 9, 7, 8, 9]
# v.rindex(8) # => 4
#
# @overload rindex
# Return the index of the last element for which the block returns true.
#
# @yield [element] Once for each element, last to first, until the block
# returns true.
# @example
# v = Immutable::Vector[7, 8, 9, 7, 8, 9]
# v.rindex { |e| e.even? } # => 4
#
# @return [Integer]
def rindex(obj = (missing_arg = true))
i = @size - 1
if missing_arg
if block_given?
reverse_each { |item| return i if yield item; i -= 1 }
nil
else
enum_for(:rindex)
end
else
reverse_each { |item| return i if item == obj; i -= 1 }
nil
end
end
# Assumes all elements are nested, indexable collections, and searches through them,
# comparing `obj` with the first element of each nested collection. Return the
# first nested collection which matches, or `nil` if none is found.
# Behaviour is undefined when elements do not meet assumptions (i.e. are
# not indexable collections).
#
# @example
# v = Immutable::Vector[["A", 10], ["B", 20], ["C", 30]]
# v.assoc("B") # => ["B", 20]
#
# @param obj [Object] The object to search for
# @return [Object]
def assoc(obj)
each do |array|
next if !array.respond_to?(:[])
return array if obj == array[0]
end
nil
end
# Assumes all elements are nested, indexable collections, and searches through them,
# comparing `obj` with the second element of each nested collection. Return
# the first nested collection which matches, or `nil` if none is found.
# Behaviour is undefined when elements do not meet assumptions (i.e. are
# not indexable collections).
#
# @example
# v = Immutable::Vector[["A", 10], ["B", 20], ["C", 30]]
# v.rassoc(20) # => ["B", 20]
#
# @param obj [Object] The object to search for
# @return [Object]
def rassoc(obj)
each do |array|
next if !array.respond_to?(:[])
return array if obj == array[1]
end
nil
end
# Return an `Array` with the same elements, in the same order. The returned
# `Array` may or may not be frozen.
#
# @return [Array]
def to_a
if @levels == 0
# When initializing a Vector with 32 or less items, we always make
# sure @root is frozen, so we can return it directly here
@root
else
flatten_node(@root, @levels * BITS_PER_LEVEL, [])
end
end
alias to_ary to_a
# Return true if `other` has the same type and contents as this `Vector`.
#
# @param other [Object] The collection to compare with
# @return [Boolean]
def eql?(other)
return true if other.equal?(self)
return false unless instance_of?(other.class) && @size == other.size
@root.eql?(other.instance_variable_get(:@root))
end
# See `Object#hash`.
# @return [Integer]
def hash
reduce(0) { |hash, item| (hash << 5) - hash + item.hash }
end
# Return `self`. Since this is an immutable object duplicates are
# equivalent.
# @return [Vector]
def dup
self
end
alias clone dup
# @return [::Array]
# @private
def marshal_dump
to_a
end
# @private
def marshal_load(array)
initialize(array.freeze)
end
private
def traverse_depth_first(node, level, &block)
return node.each(&block) if level == 0
node.each { |child| traverse_depth_first(child, level - 1, &block) }
end
def reverse_traverse_depth_first(node, level, &block)
return node.reverse_each(&block) if level == 0
node.reverse_each { |child| reverse_traverse_depth_first(child, level - 1, &block) }
end
def leaf_node_for(node, bitshift, index)
while bitshift > 0
node = node[(index >> bitshift) & INDEX_MASK]
bitshift -= BITS_PER_LEVEL
end
node
end
def update_root(index, item)
root, levels = @root, @levels
while index >= (1 << (BITS_PER_LEVEL * (levels + 1)))
root = [root].freeze
levels += 1
end
new_root = update_leaf_node(root, levels * BITS_PER_LEVEL, index, item)
if new_root.equal?(root)
self
else
self.class.alloc(new_root, @size > index ? @size : index + 1, levels)
end
end
def update_leaf_node(node, bitshift, index, item)
slot_index = (index >> bitshift) & INDEX_MASK
if bitshift > 0
old_child = node[slot_index] || []
item = update_leaf_node(old_child, bitshift - BITS_PER_LEVEL, index, item)
end
existing_item = node[slot_index]
if existing_item.equal?(item)
node
else
node.dup.tap { |n| n[slot_index] = item }.freeze
end
end
def flatten_range(node, bitshift, from, to)
from_slot = (from >> bitshift) & INDEX_MASK
to_slot = (to >> bitshift) & INDEX_MASK
if bitshift == 0 # are we at the bottom?
node.slice(from_slot, to_slot-from_slot+1)
elsif from_slot == to_slot
flatten_range(node[from_slot], bitshift - BITS_PER_LEVEL, from, to)
else
# the following bitmask can be used to pick out the part of the from/to indices
# which will be used to direct path BELOW this node
mask = ((1 << bitshift) - 1)
result = []
if from & mask == 0
flatten_node(node[from_slot], bitshift - BITS_PER_LEVEL, result)
else
result.concat(flatten_range(node[from_slot], bitshift - BITS_PER_LEVEL, from, from | mask))
end
(from_slot+1).upto(to_slot-1) do |slot_index|
flatten_node(node[slot_index], bitshift - BITS_PER_LEVEL, result)
end
if to & mask == mask
flatten_node(node[to_slot], bitshift - BITS_PER_LEVEL, result)
else
result.concat(flatten_range(node[to_slot], bitshift - BITS_PER_LEVEL, to & ~mask, to))
end
result
end
end
def flatten_node(node, bitshift, result)
if bitshift == 0
result.concat(node)
elsif bitshift == BITS_PER_LEVEL
node.each { |a| result.concat(a) }
else
bitshift -= BITS_PER_LEVEL
node.each { |a| flatten_node(a, bitshift, result) }
end
result
end
def subsequence(from, length)
return nil if from > @size || from < 0 || length < 0
length = @size - from if @size < from + length
return self.class.empty if length == 0
self.class.new(flatten_range(@root, @levels * BITS_PER_LEVEL, from, from + length - 1))
end
def flatten_suffix(node, bitshift, from, result)
from_slot = (from >> bitshift) & INDEX_MASK
if bitshift == 0
if from_slot == 0
result.concat(node)
else
result.concat(node.slice(from_slot, 32)) # entire suffix of node. excess length is ignored by #slice
end
else
mask = ((1 << bitshift) - 1)
if from & mask == 0
from_slot.upto(node.size-1) do |i|
flatten_node(node[i], bitshift - BITS_PER_LEVEL, result)
end
elsif (child = node[from_slot])
flatten_suffix(child, bitshift - BITS_PER_LEVEL, from, result)
(from_slot+1).upto(node.size-1) do |i|
flatten_node(node[i], bitshift - BITS_PER_LEVEL, result)
end
end
result
end
end
def replace_suffix(from, suffix)
# new suffix can go directly after existing elements
raise IndexError if from > @size
root, levels = @root, @levels
if (from >> (BITS_PER_LEVEL * (@levels + 1))) != 0
# index where new suffix goes doesn't fall within current tree
# we will need to deepen tree
root = [root].freeze
levels += 1
end
new_size = from + suffix.size
root = replace_node_suffix(root, levels * BITS_PER_LEVEL, from, suffix)
if !suffix.empty?
levels.times { suffix = suffix.each_slice(32).to_a }
root.concat(suffix)
while root.size > 32
root = root.each_slice(32).to_a
levels += 1
end
else
while root.size == 1 && levels > 0
root = root[0]
levels -= 1
end
end
self.class.alloc(root.freeze, new_size, levels)
end
def replace_node_suffix(node, bitshift, from, suffix)
from_slot = (from >> bitshift) & INDEX_MASK
if bitshift == 0
if from_slot == 0
suffix.shift(32)
else
node.take(from_slot).concat(suffix.shift(32 - from_slot))
end
else
mask = ((1 << bitshift) - 1)
if from & mask == 0
if from_slot == 0
new_node = suffix.shift(32 * (1 << bitshift))
while bitshift != 0
new_node = new_node.each_slice(32).to_a
bitshift -= BITS_PER_LEVEL
end
new_node
else
result = node.take(from_slot)
remainder = suffix.shift((32 - from_slot) * (1 << bitshift))
while bitshift != 0
remainder = remainder.each_slice(32).to_a
bitshift -= BITS_PER_LEVEL
end
result.concat(remainder)
end
elsif (child = node[from_slot])
result = node.take(from_slot)
result.push(replace_node_suffix(child, bitshift - BITS_PER_LEVEL, from, suffix))
remainder = suffix.shift((31 - from_slot) * (1 << bitshift))
while bitshift != 0
remainder = remainder.each_slice(32).to_a
bitshift -= BITS_PER_LEVEL
end
result.concat(remainder)
else
raise "Shouldn't happen"
end
end
end
end
# The canonical empty `Vector`. Returned by `Vector[]` when
# invoked with no arguments; also returned by `Vector.empty`. Prefer using this
# one rather than creating many empty vectors using `Vector.new`.
#
# @private
EmptyVector = Immutable::Vector.empty
# `Immutable::Set` is a collection of unordered values with no duplicates. Testing whether
# an object is present in the `Set` can be done in constant time. `Set` is also `Enumerable`, so you can
# iterate over the members of the set with {#each}, transform them with {#map}, filter
# them with {#select}, and so on. Some of the `Enumerable` methods are overridden to
# return `immutable-ruby` collections.
#
# Like the `Set` class in Ruby's standard library, which we will call RubySet,
# `Immutable::Set` defines equivalency of objects using `#hash` and `#eql?`. No two
# objects with the same `#hash` code, and which are also `#eql?`, can coexist in the
# same `Set`. If one is already in the `Set`, attempts to add another one will have
# no effect.
#
# `Set`s have no natural ordering and cannot be compared using `#<=>`. However, they
# define {#<}, {#>}, {#<=}, and {#>=} as shorthand for {#proper_subset?},
# {#proper_superset?}, {#subset?}, and {#superset?} respectively.
#
# The basic set-theoretic operations {#union}, {#intersection}, {#difference}, and
# {#exclusion} work with any `Enumerable` object.
#
# A `Set` can be created in either of the following ways:
#
# Immutable::Set.new([1, 2, 3]) # any Enumerable can be used to initialize
# Immutable::Set['A', 'B', 'C', 'D']
#
# The latter 2 forms of initialization can be used with your own, custom subclasses
# of `Immutable::Set`.
#
# Unlike RubySet, all methods which you might expect to "modify" an `Immutable::Set`
# actually return a new set and leave the existing one unchanged.
#
# @example
# set1 = Immutable::Set[1, 2] # => Immutable::Set[1, 2]
# set2 = Immutable::Set[1, 2] # => Immutable::Set[1, 2]
# set1 == set2 # => true
# set3 = set1.add("foo") # => Immutable::Set[1, 2, "foo"]
# set3 - set2 # => Immutable::Set["foo"]
# set3.subset?(set1) # => false
# set1.subset?(set3) # => true
#
class Set
include Immutable::Enumerable
class << self
# Create a new `Set` populated with the given items.
# @return [Set]
def [](*items)
items.empty? ? empty : new(items)
end
# Return an empty `Set`. If used on a subclass, returns an empty instance
# of that class.
#
# @return [Set]
def empty
@empty ||= new
end
# "Raw" allocation of a new `Set`. Used internally to create a new
# instance quickly after obtaining a modified {Trie}.
#
# @return [Set]
# @private
def alloc(trie = EmptyTrie)
allocate.tap { |s| s.instance_variable_set(:@trie, trie) }.freeze
end
end
def initialize(items=[])
@trie = Trie.new(0)
items.each { |item| @trie.put!(item, nil) }
freeze
end
# Return `true` if this `Set` contains no items.
# @return [Boolean]
def empty?
@trie.empty?
end
# Return the number of items in this `Set`.
# @return [Integer]
def size
@trie.size
end
alias length size
# Return a new `Set` with `item` added. If `item` is already in the set,
# return `self`.
#
# @example
# Immutable::Set[1, 2, 3].add(4) # => Immutable::Set[1, 2, 4, 3]
# Immutable::Set[1, 2, 3].add(2) # => Immutable::Set[1, 2, 3]
#
# @param item [Object] The object to add
# @return [Set]
def add(item)
include?(item) ? self : self.class.alloc(@trie.put(item, nil))
end
alias << add
# If `item` is not a member of this `Set`, return a new `Set` with `item` added.
# Otherwise, return `false`.
#
# @example
# Immutable::Set[1, 2, 3].add?(4) # => Immutable::Set[1, 2, 4, 3]
# Immutable::Set[1, 2, 3].add?(2) # => false
#
# @param item [Object] The object to add
# @return [Set, false]
def add?(item)
!include?(item) && add(item)
end
# Return a new `Set` with `item` removed. If `item` is not a member of the set,
# return `self`.
#
# @example
# Immutable::Set[1, 2, 3].delete(1) # => Immutable::Set[2, 3]
# Immutable::Set[1, 2, 3].delete(99) # => Immutable::Set[1, 2, 3]
#
# @param item [Object] The object to remove
# @return [Set]
def delete(item)
trie = @trie.delete(item)
new_trie(trie)
end
# If `item` is a member of this `Set`, return a new `Set` with `item` removed.
# Otherwise, return `false`.
#
# @example
# Immutable::Set[1, 2, 3].delete?(1) # => Immutable::Set[2, 3]
# Immutable::Set[1, 2, 3].delete?(99) # => false
#
# @param item [Object] The object to remove
# @return [Set, false]
def delete?(item)
include?(item) && delete(item)
end
# Call the block once for each item in this `Set`. No specific iteration order
# is guaranteed, but the order will be stable for any particular `Set`. If
# no block is given, an `Enumerator` is returned instead.
#
# @example
# Immutable::Set["Dog", "Elephant", "Lion"].each { |e| puts e }
# Elephant
# Dog
# Lion
# # => Immutable::Set["Dog", "Elephant", "Lion"]
#
# @yield [item] Once for each item.
# @return [self, Enumerator]
def each
return to_enum if not block_given?
@trie.each { |key, _| yield(key) }
self
end
# Call the block once for each item in this `Set`. Iteration order will be
# the opposite of {#each}. If no block is given, an `Enumerator` is
# returned instead.
#
# @example
# Immutable::Set["Dog", "Elephant", "Lion"].reverse_each { |e| puts e }
# Lion
# Dog
# Elephant
# # => Immutable::Set["Dog", "Elephant", "Lion"]
#
# @yield [item] Once for each item.
# @return [self]
def reverse_each
return enum_for(:reverse_each) if not block_given?
@trie.reverse_each { |key, _| yield(key) }
self
end
# Return a new `Set` with all the items for which the block returns true.
#
# @example
# Immutable::Set["Elephant", "Dog", "Lion"].select { |e| e.size >= 4 }
# # => Immutable::Set["Elephant", "Lion"]
# @yield [item] Once for each item.
# @return [Set]
def select
return enum_for(:select) unless block_given?
trie = @trie.select { |key, _| yield(key) }
new_trie(trie)
end
alias find_all select
alias keep_if select
# Call the block once for each item in this `Set`. All the values returned
# from the block will be gathered into a new `Set`. If no block is given,
# an `Enumerator` is returned instead.
#
# @example
# Immutable::Set["Cat", "Elephant", "Dog", "Lion"].map { |e| e.size }
# # => Immutable::Set[8, 4, 3]
#
# @yield [item] Once for each item.
# @return [Set]
def map
return enum_for(:map) if not block_given?
return self if empty?
self.class.new(super)
end
alias collect map
# Return `true` if the given item is present in this `Set`. More precisely,
# return `true` if an object with the same `#hash` code, and which is also `#eql?`
# to the given object is present.
#
# @example
# Immutable::Set["A", "B", "C"].include?("B") # => true
# Immutable::Set["A", "B", "C"].include?("Z") # => false
#
# @param object [Object] The object to check for
# @return [Boolean]
def include?(object)
@trie.key?(object)
end
alias member? include?
# Return a member of this `Set`. The member chosen will be the first one which
# would be yielded by {#each}. If the set is empty, return `nil`.
#
# @example
# Immutable::Set["A", "B", "C"].first # => "C"
#
# @return [Object]
def first
(entry = @trie.at(0)) && entry[0]
end
# Return a {SortedSet} which contains the same items as this `Set`, ordered by
# the given comparator block.
#
# @example
# Immutable::Set["Elephant", "Dog", "Lion"].sort
# # => Immutable::SortedSet["Dog", "Elephant", "Lion"]
# Immutable::Set["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size }
# # => Immutable::SortedSet["Dog", "Lion", "Elephant"]
#
# @yield [a, b] Any number of times with different pairs of elements.
# @yieldreturn [Integer] Negative if the first element should be sorted
# lower, positive if the latter element, or 0 if
# equal.
# @return [SortedSet]
def sort(&comparator)
SortedSet.new(to_a, &comparator)
end
# Return a {SortedSet} which contains the same items as this `Set`, ordered
# by mapping each item through the provided block to obtain sort keys, and
# then sorting the keys.
#
# @example
# Immutable::Set["Elephant", "Dog", "Lion"].sort_by { |e| e.size }
# # => Immutable::SortedSet["Dog", "Lion", "Elephant"]
#
# @yield [item] Once for each item to create the set, and then potentially
# again depending on what operations are performed on the
# returned {SortedSet}. As such, it is recommended that the
# block be a pure function.
# @yieldreturn [Object] sort key for the item
# @return [SortedSet]
def sort_by(&mapper)
SortedSet.new(to_a, &mapper)
end
# Return a new `Set` which contains all the members of both this `Set` and `other`.
# `other` can be any `Enumerable` object.
#
# @example
# Immutable::Set[1, 2] | Immutable::Set[2, 3] # => Immutable::Set[1, 2, 3]
#
# @param other [Enumerable] The collection to merge with
# @return [Set]
def union(other)
if other.is_a?(Immutable::Set)
if other.size > size
small_set_pairs = @trie
large_set_trie = other.instance_variable_get(:@trie)
else
small_set_pairs = other.instance_variable_get(:@trie)
large_set_trie = @trie
end
else
if other.respond_to?(:lazy)
small_set_pairs = other.lazy.map { |e| [e, nil] }
else
small_set_pairs = other.map { |e| [e, nil] }
end
large_set_trie = @trie
end
trie = large_set_trie.bulk_put(small_set_pairs)
new_trie(trie)
end
alias | union
alias + union
alias merge union
# Return a new `Set` which contains all the items which are members of both
# this `Set` and `other`. `other` can be any `Enumerable` object.
#
# @example
# Immutable::Set[1, 2] & Immutable::Set[2, 3] # => Immutable::Set[2]
#
# @param other [Enumerable] The collection to intersect with
# @return [Set]
def intersection(other)
if other.size < @trie.size
if other.is_a?(Immutable::Set)
trie = other.instance_variable_get(:@trie).select { |key, _| include?(key) }
else
trie = Trie.new(0)
other.each { |obj| trie.put!(obj, nil) if include?(obj) }
end
else
trie = @trie.select { |key, _| other.include?(key) }
end
new_trie(trie)
end
alias & intersection
# Return a new `Set` with all the items in `other` removed. `other` can be
# any `Enumerable` object.
#
# @example
# Immutable::Set[1, 2] - Immutable::Set[2, 3] # => Immutable::Set[1]
#
# @param other [Enumerable] The collection to subtract from this set
# @return [Set]
def difference(other)
trie = if (@trie.size <= other.size) && (other.is_a?(Immutable::Set) || (defined?(::Set) && other.is_a?(::Set)))
@trie.select { |key, _| !other.include?(key) }
else
@trie.bulk_delete(other)
end
new_trie(trie)
end
alias subtract difference
alias - difference
# Return a new `Set` which contains all the items which are members of this
# `Set` or of `other`, but not both. `other` can be any `Enumerable` object.
#
# @example
# Immutable::Set[1, 2] ^ Immutable::Set[2, 3] # => Immutable::Set[1, 3]
#
# @param other [Enumerable] The collection to take the exclusive disjunction of
# @return [Set]
def exclusion(other)
((self | other) - (self & other))
end
alias ^ exclusion
# Return `true` if all items in this `Set` are also in `other`.
#
# @example
# Immutable::Set[2, 3].subset?(Immutable::Set[1, 2, 3]) # => true
#
# @param other [Set]
# @return [Boolean]
def subset?(other)
return false if other.size < size
# This method has the potential to be very slow if 'other' is a large Array, so to avoid that,
# we convert those Arrays to Sets before checking presence of items
# Time to convert Array -> Set is linear in array.size
# Time to check for presence of all items in an Array is proportional to set.size * array.size
# Note that both sides of that equation have array.size -- hence those terms cancel out,
# and the break-even point is solely dependent on the size of this collection
# After doing some benchmarking to estimate the constants, it appears break-even is at ~190 items
# We also check other.size, to avoid the more expensive #is_a? checks in cases where it doesn't matter
#
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set))
other = ::Set.new(other)
end
all? { |item| other.include?(item) }
end
alias <= subset?
# Return `true` if all items in `other` are also in this `Set`.
#
# @example
# Immutable::Set[1, 2, 3].superset?(Immutable::Set[2, 3]) # => true
#
# @param other [Set]
# @return [Boolean]
def superset?(other)
other.subset?(self)
end
alias >= superset?
# Returns `true` if `other` contains all the items in this `Set`, plus at least
# one item which is not in this set.
#
# @example
# Immutable::Set[2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => true
# Immutable::Set[1, 2, 3].proper_subset?(Immutable::Set[1, 2, 3]) # => false
#
# @param other [Set]
# @return [Boolean]
def proper_subset?(other)
return false if other.size <= size
# See comments above
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set))
other = ::Set.new(other)
end
all? { |item| other.include?(item) }
end
alias < proper_subset?
# Returns `true` if this `Set` contains all the items in `other`, plus at least
# one item which is not in `other`.
#
# @example
# Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[2, 3]) # => true
# Immutable::Set[1, 2, 3].proper_superset?(Immutable::Set[1, 2, 3]) # => false
#
# @param other [Set]
# @return [Boolean]
def proper_superset?(other)
other.proper_subset?(self)
end
alias > proper_superset?
# Return `true` if this `Set` and `other` do not share any items.
#
# @example
# Immutable::Set[1, 2].disjoint?(Immutable::Set[8, 9]) # => true
#
# @param other [Set]
# @return [Boolean]
def disjoint?(other)
if other.size <= size
other.each { |item| return false if include?(item) }
else
# See comment on #subset?
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(Immutable::Set) || other.is_a?(::Set))
other = ::Set.new(other)
end
each { |item| return false if other.include?(item) }
end
true
end
# Return `true` if this `Set` and `other` have at least one item in common.
#
# @example
# Immutable::Set[1, 2].intersect?(Immutable::Set[2, 3]) # => true
#
# @param other [Set]
# @return [Boolean]
def intersect?(other)
!disjoint?(other)
end
# Recursively insert the contents of any nested `Set`s into this `Set`, and
# remove them.
#
# @example
# Immutable::Set[Immutable::Set[1, 2], Immutable::Set[3, 4]].flatten
# # => Immutable::Set[1, 2, 3, 4]
#
# @return [Set]
def flatten
reduce(self.class.empty) do |set, item|
next set.union(item.flatten) if item.is_a?(Set)
set.add(item)
end
end
alias group group_by
alias classify group_by
# Return a randomly chosen item from this `Set`. If the set is empty, return `nil`.
#
# @example
# Immutable::Set[1, 2, 3, 4, 5].sample # => 3
#
# @return [Object]
def sample
empty? ? nil : @trie.at(rand(size))[0]
end
# Return an empty `Set` instance, of the same class as this one. Useful if you
# have multiple subclasses of `Set` and want to treat them polymorphically.
#
# @return [Set]
def clear
self.class.empty
end
# Return true if `other` has the same type and contents as this `Set`.
#
# @param other [Object] The object to compare with
# @return [Boolean]
def eql?(other)
return true if other.equal?(self)
return false if not instance_of?(other.class)
other_trie = other.instance_variable_get(:@trie)
return false if @trie.size != other_trie.size
@trie.each do |key, _|
return false if !other_trie.key?(key)
end
true
end
alias == eql?
# See `Object#hash`.
# @return [Integer]
def hash
reduce(0) { |hash, item| (hash << 5) - hash + item.hash }
end
# Return `self`. Since this is an immutable object duplicates are
# equivalent.
# @return [Set]
def dup
self
end
alias clone dup
undef :"<=>" # Sets are not ordered, so Enumerable#<=> will give a meaningless result
undef :each_index # Set members cannot be accessed by 'index', so #each_index is not meaningful
# Return `self`.
#
# @return [self]
def to_set
self
end
# @private
def marshal_dump
output = {}
each do |key|
output[key] = nil
end
output
end
# @private
def marshal_load(dictionary)
@trie = dictionary.reduce(EmptyTrie) do |trie, key_value|
trie.put(key_value.first, nil)
end
end
private
def new_trie(trie)
if trie.empty?
self.class.empty
elsif trie.equal?(@trie)
self
else
self.class.alloc(trie)
end
end
end
# The canonical empty `Set`. Returned by `Set[]` when
# invoked with no arguments; also returned by `Set.empty`. Prefer using this
# one rather than creating many empty sets using `Set.new`.
#
# @private
EmptySet = Immutable::Set.empty
end
immutable-ruby-immutable-ruby-7d20d25/lib/immutable/core_ext.rb 0000664 0000000 0000000 00000000110 14610664721 0024616 0 ustar 00root root 0000000 0000000 require 'immutable/core_ext/enumerable'
require 'immutable/core_ext/io'
immutable-ruby-immutable-ruby-7d20d25/lib/immutable/core_ext/ 0000775 0000000 0000000 00000000000 14610664721 0024301 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/lib/immutable/core_ext/enumerable.rb 0000664 0000000 0000000 00000000501 14610664721 0026741 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/core_ext/io.rb 0000664 0000000 0000000 00000001054 14610664721 0025235 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/deque.rb 0000664 0000000 0000000 00000017724 14610664721 0024134 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/enumerable.rb 0000664 0000000 0000000 00000012303 14610664721 0025134 0 ustar 00root root 0000000 0000000 module 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-immutable-ruby-7d20d25/lib/immutable/hash.rb 0000664 0000000 0000000 00000000272 14610664721 0023742 0 ustar 00root root 0000000 0000000 # Definition of Immutable::Hash is in a separate file to avoid circular
# dependency warnings caused by dependencies between Hash ↔ Vector and
# Hash ↔ Set
require 'immutable/_core'
immutable-ruby-immutable-ruby-7d20d25/lib/immutable/list.rb 0000664 0000000 0000000 00000136035 14610664721 0024001 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/nested.rb 0000664 0000000 0000000 00000005430 14610664721 0024302 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/set.rb 0000664 0000000 0000000 00000000172 14610664721 0023611 0 ustar 00root root 0000000 0000000 # Definition of Immutable::Vector is in a separate file to avoid
# circular dependency warnings
require 'immutable/_core'
immutable-ruby-immutable-ruby-7d20d25/lib/immutable/sorted_set.rb 0000664 0000000 0000000 00000133012 14610664721 0025171 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/lib/immutable/trie.rb 0000664 0000000 0000000 00000022464 14610664721 0023771 0 ustar 00root root 0000000 0000000 module 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-immutable-ruby-7d20d25/lib/immutable/undefined.rb 0000664 0000000 0000000 00000000073 14610664721 0024757 0 ustar 00root root 0000000 0000000 module Immutable
# @private
module Undefined
end
end
immutable-ruby-immutable-ruby-7d20d25/lib/immutable/vector.rb 0000664 0000000 0000000 00000000172 14610664721 0024320 0 ustar 00root root 0000000 0000000 # Definition of Immutable::Vector is in a separate file to avoid
# circular dependency warnings
require 'immutable/_core'
immutable-ruby-immutable-ruby-7d20d25/lib/immutable/version.rb 0000664 0000000 0000000 00000000251 14610664721 0024501 0 ustar 00root root 0000000 0000000 module Immutable
# Current released gem version. Note that master will often have the same
# value as a release gem but with different code.
VERSION = '0.2.0'
end
immutable-ruby-immutable-ruby-7d20d25/spec/ 0000775 0000000 0000000 00000000000 14610664721 0020676 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/fixtures/ 0000775 0000000 0000000 00000000000 14610664721 0022547 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/fixtures/io_spec.txt 0000664 0000000 0000000 00000000006 14610664721 0024725 0 ustar 00root root 0000000 0000000 A
B
C
immutable-ruby-immutable-ruby-7d20d25/spec/lib/ 0000775 0000000 0000000 00000000000 14610664721 0021444 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/ 0000775 0000000 0000000 00000000000 14610664721 0023423 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/core_ext/ 0000775 0000000 0000000 00000000000 14610664721 0025233 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/core_ext/array_spec.rb 0000664 0000000 0000000 00000000365 14610664721 0027714 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/core_ext/enumerable_spec.rb 0000664 0000000 0000000 00000001020 14610664721 0030702 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/core_ext/io_spec.rb 0000664 0000000 0000000 00000001061 14610664721 0027177 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/ 0000775 0000000 0000000 00000000000 14610664721 0024526 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/deque/clear_spec.rb 0000664 0000000 0000000 00000001304 14610664721 0027151 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/construction_spec.rb 0000664 0000000 0000000 00000001304 14610664721 0030615 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/copying_spec.rb 0000664 0000000 0000000 00000000535 14610664721 0027540 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/dequeue_spec.rb 0000664 0000000 0000000 00000001530 14610664721 0027521 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/empty_spec.rb 0000664 0000000 0000000 00000001742 14610664721 0027227 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/enqueue_spec.rb 0000664 0000000 0000000 00000001310 14610664721 0027527 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/first_spec.rb 0000664 0000000 0000000 00000000531 14610664721 0027213 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/inspect_spec.rb 0000664 0000000 0000000 00000001114 14610664721 0027527 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/last_spec.rb 0000664 0000000 0000000 00000000531 14610664721 0027027 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/marshal_spec.rb 0000664 0000000 0000000 00000002036 14610664721 0027515 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/new_spec.rb 0000664 0000000 0000000 00000002230 14610664721 0026653 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/pop_spec.rb 0000664 0000000 0000000 00000001512 14610664721 0026662 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/pretty_print_spec.rb 0000664 0000000 0000000 00000001106 14610664721 0030626 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/push_spec.rb 0000664 0000000 0000000 00000001642 14610664721 0027047 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/random_modification_spec.rb 0000664 0000000 0000000 00000001573 14610664721 0032100 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/rotate_spec.rb 0000664 0000000 0000000 00000004111 14610664721 0027360 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/shift_spec.rb 0000664 0000000 0000000 00000001131 14610664721 0027176 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/size_spec.rb 0000664 0000000 0000000 00000000642 14610664721 0027041 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/to_a_spec.rb 0000664 0000000 0000000 00000001155 14610664721 0027011 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/to_ary_spec.rb 0000664 0000000 0000000 00000001326 14610664721 0027364 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/to_list_spec.rb 0000664 0000000 0000000 00000001071 14610664721 0027541 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/deque/unshift_spec.rb 0000664 0000000 0000000 00000001326 14610664721 0027547 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/ 0000775 0000000 0000000 00000000000 14610664721 0024346 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/hash/all_spec.rb 0000664 0000000 0000000 00000002374 14610664721 0026463 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/any_spec.rb 0000664 0000000 0000000 00000002603 14610664721 0026475 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/assoc_spec.rb 0000664 0000000 0000000 00000002631 14610664721 0027017 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/clear_spec.rb 0000664 0000000 0000000 00000002035 14610664721 0026773 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/construction_spec.rb 0000664 0000000 0000000 00000001642 14610664721 0030442 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/copying_spec.rb 0000664 0000000 0000000 00000000424 14610664721 0027355 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/default_proc_spec.rb 0000664 0000000 0000000 00000004007 14610664721 0030355 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/delete_spec.rb 0000664 0000000 0000000 00000002006 14610664721 0027145 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/dig_spec.rb 0000664 0000000 0000000 00000001773 14610664721 0026460 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/each_spec.rb 0000664 0000000 0000000 00000004261 14610664721 0026610 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/each_with_index_spec.rb 0000664 0000000 0000000 00000001676 14610664721 0031041 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/empty_spec.rb 0000664 0000000 0000000 00000002202 14610664721 0027037 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/eql_spec.rb 0000664 0000000 0000000 00000005102 14610664721 0026464 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/except_spec.rb 0000664 0000000 0000000 00000002725 14610664721 0027203 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/fetch_spec.rb 0000664 0000000 0000000 00000003376 14610664721 0027007 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/fetch_values_spec.rb 0000664 0000000 0000000 00000001337 14610664721 0030361 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/find_spec.rb 0000664 0000000 0000000 00000002524 14610664721 0026630 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/flat_map_spec.rb 0000664 0000000 0000000 00000002202 14610664721 0027464 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/flatten_spec.rb 0000664 0000000 0000000 00000007736 14610664721 0027357 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/get_spec.rb 0000664 0000000 0000000 00000004144 14610664721 0026467 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/has_key_spec.rb 0000664 0000000 0000000 00000001602 14610664721 0027327 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/has_value_spec.rb 0000664 0000000 0000000 00000001535 14610664721 0027660 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/hash_spec.rb 0000664 0000000 0000000 00000001633 14610664721 0026633 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/inspect_spec.rb 0000664 0000000 0000000 00000001614 14610664721 0027354 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/invert_spec.rb 0000664 0000000 0000000 00000001463 14610664721 0027220 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/key_spec.rb 0000664 0000000 0000000 00000001355 14610664721 0026501 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/keys_spec.rb 0000664 0000000 0000000 00000000516 14610664721 0026662 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/map_spec.rb 0000664 0000000 0000000 00000002553 14610664721 0026467 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/marshal_spec.rb 0000664 0000000 0000000 00000001504 14610664721 0027334 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/merge_spec.rb 0000664 0000000 0000000 00000004664 14610664721 0027016 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/min_max_spec.rb 0000664 0000000 0000000 00000002210 14610664721 0027330 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/new_spec.rb 0000664 0000000 0000000 00000003657 14610664721 0026511 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/none_spec.rb 0000664 0000000 0000000 00000002367 14610664721 0026654 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/partition_spec.rb 0000664 0000000 0000000 00000002126 14610664721 0027717 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/pretty_print_spec.rb 0000664 0000000 0000000 00000002006 14610664721 0030446 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/put_spec.rb 0000664 0000000 0000000 00000006336 14610664721 0026525 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/reduce_spec.rb 0000664 0000000 0000000 00000002003 14610664721 0027147 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/reject_spec.rb 0000664 0000000 0000000 00000004116 14610664721 0027163 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/reverse_each_spec.rb 0000664 0000000 0000000 00000001261 14610664721 0030340 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/sample_spec.rb 0000664 0000000 0000000 00000000577 14610664721 0027177 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/select_spec.rb 0000664 0000000 0000000 00000003577 14610664721 0027200 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/size_spec.rb 0000664 0000000 0000000 00000002546 14610664721 0026666 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/slice_spec.rb 0000664 0000000 0000000 00000002341 14610664721 0027004 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/sort_spec.rb 0000664 0000000 0000000 00000001330 14610664721 0026671 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/store_spec.rb 0000664 0000000 0000000 00000004331 14610664721 0027042 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/subset_spec.rb 0000664 0000000 0000000 00000002277 14610664721 0027222 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/superset_spec.rb 0000664 0000000 0000000 00000002277 14610664721 0027567 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/take_spec.rb 0000664 0000000 0000000 00000002502 14610664721 0026630 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/to_a_spec.rb 0000664 0000000 0000000 00000000540 14610664721 0026626 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/to_hash_spec.rb 0000664 0000000 0000000 00000001070 14610664721 0027330 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/to_proc_spec.rb 0000664 0000000 0000000 00000002103 14610664721 0027346 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/update_in_spec.rb 0000664 0000000 0000000 00000004750 14610664721 0027663 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/values_at_spec.rb 0000664 0000000 0000000 00000002166 14610664721 0027675 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/hash/values_spec.rb 0000664 0000000 0000000 00000001135 14610664721 0027204 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/ 0000775 0000000 0000000 00000000000 14610664721 0024376 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/list/add_spec.rb 0000664 0000000 0000000 00000001131 14610664721 0026461 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/all_spec.rb 0000664 0000000 0000000 00000002504 14610664721 0026506 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/any_spec.rb 0000664 0000000 0000000 00000002326 14610664721 0026527 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/append_spec.rb 0000664 0000000 0000000 00000001773 14610664721 0027214 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/at_spec.rb 0000664 0000000 0000000 00000001252 14610664721 0026341 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/break_spec.rb 0000664 0000000 0000000 00000003502 14610664721 0027021 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/cadr_spec.rb 0000664 0000000 0000000 00000001634 14610664721 0026652 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/chunk_spec.rb 0000664 0000000 0000000 00000001152 14610664721 0027044 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/clear_spec.rb 0000664 0000000 0000000 00000000673 14610664721 0027031 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/combination_spec.rb 0000664 0000000 0000000 00000001757 14610664721 0030251 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/compact_spec.rb 0000664 0000000 0000000 00000001375 14610664721 0027371 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/compare_spec.rb 0000664 0000000 0000000 00000001221 14610664721 0027357 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/cons_spec.rb 0000664 0000000 0000000 00000001134 14610664721 0026676 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/construction_spec.rb 0000664 0000000 0000000 00000006257 14610664721 0030501 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/copying_spec.rb 0000664 0000000 0000000 00000000531 14610664721 0027404 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/count_spec.rb 0000664 0000000 0000000 00000001423 14610664721 0027065 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/cycle_spec.rb 0000664 0000000 0000000 00000001170 14610664721 0027033 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/delete_at_spec.rb 0000664 0000000 0000000 00000000741 14610664721 0027665 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/delete_spec.rb 0000664 0000000 0000000 00000001064 14610664721 0027200 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/drop_spec.rb 0000664 0000000 0000000 00000001267 14610664721 0026707 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/drop_while_spec.rb 0000664 0000000 0000000 00000001750 14610664721 0030074 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/each_slice_spec.rb 0000664 0000000 0000000 00000002567 14610664721 0030026 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/each_spec.rb 0000664 0000000 0000000 00000001645 14610664721 0026643 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/each_with_index_spec.rb 0000664 0000000 0000000 00000001420 14610664721 0031054 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/empty_spec.rb 0000664 0000000 0000000 00000001003 14610664721 0027065 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/eql_spec.rb 0000664 0000000 0000000 00000004065 14610664721 0026523 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/fill_spec.rb 0000664 0000000 0000000 00000002536 14610664721 0026671 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/find_all_spec.rb 0000664 0000000 0000000 00000003257 14610664721 0027514 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/find_index_spec.rb 0000664 0000000 0000000 00000001674 14610664721 0030054 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/find_spec.rb 0000664 0000000 0000000 00000002215 14610664721 0026655 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/flat_map_spec.rb 0000664 0000000 0000000 00000002444 14610664721 0027524 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/flatten_spec.rb 0000664 0000000 0000000 00000001232 14610664721 0027370 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/grep_spec.rb 0000664 0000000 0000000 00000002126 14610664721 0026673 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/group_by_spec.rb 0000664 0000000 0000000 00000002144 14610664721 0027564 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/hash_spec.rb 0000664 0000000 0000000 00000000760 14610664721 0026663 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/head_spec.rb 0000664 0000000 0000000 00000000646 14610664721 0026644 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/include_spec.rb 0000664 0000000 0000000 00000001652 14610664721 0027364 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/index_spec.rb 0000664 0000000 0000000 00000001453 14610664721 0027047 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/indices_spec.rb 0000664 0000000 0000000 00000003151 14610664721 0027353 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/init_spec.rb 0000664 0000000 0000000 00000001155 14610664721 0026702 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/inits_spec.rb 0000664 0000000 0000000 00000001163 14610664721 0027064 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/insert_spec.rb 0000664 0000000 0000000 00000002417 14610664721 0027245 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/inspect_spec.rb 0000664 0000000 0000000 00000001332 14610664721 0027401 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/intersperse_spec.rb 0000664 0000000 0000000 00000001207 14610664721 0030300 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/join_spec.rb 0000664 0000000 0000000 00000002664 14610664721 0026704 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/last_spec.rb 0000664 0000000 0000000 00000000751 14610664721 0026703 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/ltlt_spec.rb 0000664 0000000 0000000 00000000667 14610664721 0026725 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/map_spec.rb 0000664 0000000 0000000 00000002257 14610664721 0026520 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/maximum_spec.rb 0000664 0000000 0000000 00000001650 14610664721 0027414 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/merge_by_spec.rb 0000664 0000000 0000000 00000002334 14610664721 0027530 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/merge_spec.rb 0000664 0000000 0000000 00000002536 14610664721 0027042 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/minimum_spec.rb 0000664 0000000 0000000 00000001647 14610664721 0027420 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/multithreading_spec.rb 0000664 0000000 0000000 00000002345 14610664721 0030761 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/none_spec.rb 0000664 0000000 0000000 00000002300 14610664721 0026667 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/one_spec.rb 0000664 0000000 0000000 00000002364 14610664721 0026523 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/partition_spec.rb 0000664 0000000 0000000 00000007277 14610664721 0027763 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/permutation_spec.rb 0000664 0000000 0000000 00000003116 14610664721 0030305 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/pop_spec.rb 0000664 0000000 0000000 00000000721 14610664721 0026533 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/product_spec.rb 0000664 0000000 0000000 00000000764 14610664721 0027424 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/reduce_spec.rb 0000664 0000000 0000000 00000002770 14610664721 0027212 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/reject_spec.rb 0000664 0000000 0000000 00000002304 14610664721 0027210 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/reverse_spec.rb 0000664 0000000 0000000 00000001406 14610664721 0027411 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/rotate_spec.rb 0000664 0000000 0000000 00000002042 14610664721 0027231 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/sample_spec.rb 0000664 0000000 0000000 00000000531 14610664721 0027215 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/select_spec.rb 0000664 0000000 0000000 00000003267 14610664721 0027224 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/size_spec.rb 0000664 0000000 0000000 00000001076 14610664721 0026713 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/slice_spec.rb 0000664 0000000 0000000 00000024060 14610664721 0027036 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/sorting_spec.rb 0000664 0000000 0000000 00000002325 14610664721 0027424 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/span_spec.rb 0000664 0000000 0000000 00000004700 14610664721 0026677 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/split_at_spec.rb 0000664 0000000 0000000 00000002141 14610664721 0027552 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/subsequences_spec.rb 0000664 0000000 0000000 00000001401 14610664721 0030436 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/sum_spec.rb 0000664 0000000 0000000 00000000746 14610664721 0026550 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/tail_spec.rb 0000664 0000000 0000000 00000001213 14610664721 0026663 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/tails_spec.rb 0000664 0000000 0000000 00000001163 14610664721 0027052 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/take_spec.rb 0000664 0000000 0000000 00000001257 14610664721 0026666 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/take_while_spec.rb 0000664 0000000 0000000 00000002205 14610664721 0030050 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/to_a_spec.rb 0000664 0000000 0000000 00000001647 14610664721 0026667 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/to_ary_spec.rb 0000664 0000000 0000000 00000001546 14610664721 0027240 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/to_list_spec.rb 0000664 0000000 0000000 00000000513 14610664721 0027411 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/to_set_spec.rb 0000664 0000000 0000000 00000000506 14610664721 0027233 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/transpose_spec.rb 0000664 0000000 0000000 00000001331 14610664721 0027751 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/union_spec.rb 0000664 0000000 0000000 00000001467 14610664721 0027075 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/uniq_spec.rb 0000664 0000000 0000000 00000001452 14610664721 0026713 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/list/zip_spec.rb 0000664 0000000 0000000 00000001214 14610664721 0026535 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/nested/ 0000775 0000000 0000000 00000000000 14610664721 0024705 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/nested/construction_spec.rb 0000664 0000000 0000000 00000007254 14610664721 0031006 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/ 0000775 0000000 0000000 00000000000 14610664721 0024216 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/set/add_spec.rb 0000664 0000000 0000000 00000003724 14610664721 0026313 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/all_spec.rb 0000664 0000000 0000000 00000002504 14610664721 0026326 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/any_spec.rb 0000664 0000000 0000000 00000002555 14610664721 0026353 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/clear_spec.rb 0000664 0000000 0000000 00000001330 14610664721 0026640 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/compact_spec.rb 0000664 0000000 0000000 00000001226 14610664721 0027204 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/construction_spec.rb 0000664 0000000 0000000 00000000660 14610664721 0030311 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/copying_spec.rb 0000664 0000000 0000000 00000000367 14610664721 0027233 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/count_spec.rb 0000664 0000000 0000000 00000001422 14610664721 0026704 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/delete_spec.rb 0000664 0000000 0000000 00000003455 14610664721 0027026 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/difference_spec.rb 0000664 0000000 0000000 00000002621 14610664721 0027650 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/disjoint_spec.rb 0000664 0000000 0000000 00000001212 14610664721 0027374 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/each_spec.rb 0000664 0000000 0000000 00000002027 14610664721 0026456 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/empty_spec.rb 0000664 0000000 0000000 00000002147 14610664721 0026717 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/eqeq_spec.rb 0000664 0000000 0000000 00000004363 14610664721 0026516 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/eql_spec.rb 0000664 0000000 0000000 00000004664 14610664721 0026350 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/exclusion_spec.rb 0000664 0000000 0000000 00000002470 14610664721 0027571 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/find_spec.rb 0000664 0000000 0000000 00000001727 14610664721 0026504 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/first_spec.rb 0000664 0000000 0000000 00000001250 14610664721 0026702 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/flatten_spec.rb 0000664 0000000 0000000 00000002202 14610664721 0027206 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/grep_spec.rb 0000664 0000000 0000000 00000002477 14610664721 0026524 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/grep_v_spec.rb 0000664 0000000 0000000 00000002563 14610664721 0027045 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/group_by_spec.rb 0000664 0000000 0000000 00000003357 14610664721 0027413 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/hash_spec.rb 0000664 0000000 0000000 00000001271 14610664721 0026501 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/include_spec.rb 0000664 0000000 0000000 00000003303 14610664721 0027177 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/inspect_spec.rb 0000664 0000000 0000000 00000002420 14610664721 0027220 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/intersect_spec.rb 0000664 0000000 0000000 00000001215 14610664721 0027554 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/intersection_spec.rb 0000664 0000000 0000000 00000003116 14610664721 0030264 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/join_spec.rb 0000664 0000000 0000000 00000003401 14610664721 0026512 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/map_spec.rb 0000664 0000000 0000000 00000003234 14610664721 0026334 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/marshal_spec.rb 0000664 0000000 0000000 00000001425 14610664721 0027206 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/maximum_spec.rb 0000664 0000000 0000000 00000001525 14610664721 0027235 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/minimum_spec.rb 0000664 0000000 0000000 00000001524 14610664721 0027232 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/new_spec.rb 0000664 0000000 0000000 00000002511 14610664721 0026345 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/none_spec.rb 0000664 0000000 0000000 00000002334 14610664721 0026516 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/one_spec.rb 0000664 0000000 0000000 00000002302 14610664721 0026333 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/partition_spec.rb 0000664 0000000 0000000 00000002652 14610664721 0027573 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/product_spec.rb 0000664 0000000 0000000 00000000735 14610664721 0027242 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/reduce_spec.rb 0000664 0000000 0000000 00000002734 14610664721 0027032 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/reject_spec.rb 0000664 0000000 0000000 00000002730 14610664721 0027033 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/reverse_each_spec.rb 0000664 0000000 0000000 00000001526 14610664721 0030214 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/sample_spec.rb 0000664 0000000 0000000 00000000521 14610664721 0027034 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/select_spec.rb 0000664 0000000 0000000 00000004260 14610664721 0027036 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/size_spec.rb 0000664 0000000 0000000 00000000554 14610664721 0026533 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/sorting_spec.rb 0000664 0000000 0000000 00000003711 14610664721 0027244 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/subset_spec.rb 0000664 0000000 0000000 00000002612 14610664721 0027063 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/sum_spec.rb 0000664 0000000 0000000 00000000723 14610664721 0026363 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/superset_spec.rb 0000664 0000000 0000000 00000002616 14610664721 0027434 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/to_a_spec.rb 0000664 0000000 0000000 00000001401 14610664721 0026473 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/to_list_spec.rb 0000664 0000000 0000000 00000001353 14610664721 0027234 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/to_set_spec.rb 0000664 0000000 0000000 00000000506 14610664721 0027053 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/set/union_spec.rb 0000664 0000000 0000000 00000003651 14610664721 0026712 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/ 0000775 0000000 0000000 00000000000 14610664721 0025576 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/above_spec.rb 0000664 0000000 0000000 00000003112 14610664721 0030226 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/add_spec.rb 0000664 0000000 0000000 00000003260 14610664721 0027666 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/at_spec.rb 0000664 0000000 0000000 00000001100 14610664721 0027531 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/below_spec.rb 0000664 0000000 0000000 00000003105 14610664721 0030244 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/between_spec.rb 0000664 0000000 0000000 00000003224 14610664721 0030567 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/clear_spec.rb 0000664 0000000 0000000 00000002123 14610664721 0030221 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/copying_spec.rb 0000664 0000000 0000000 00000000601 14610664721 0030602 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/delete_at_spec.rb 0000664 0000000 0000000 00000001034 14610664721 0031061 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/delete_spec.rb 0000664 0000000 0000000 00000005001 14610664721 0030373 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/difference_spec.rb 0000664 0000000 0000000 00000001132 14610664721 0031224 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/disjoint_spec.rb 0000664 0000000 0000000 00000001221 14610664721 0030754 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/drop_spec.rb 0000664 0000000 0000000 00000002727 14610664721 0030111 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/drop_while_spec.rb 0000664 0000000 0000000 00000001647 14610664721 0031301 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/each_spec.rb 0000664 0000000 0000000 00000001245 14610664721 0030037 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/empty_spec.rb 0000664 0000000 0000000 00000001457 14610664721 0030302 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/eql_spec.rb 0000664 0000000 0000000 00000005725 14610664721 0027727 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/exclusion_spec.rb 0000664 0000000 0000000 00000001115 14610664721 0031144 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/fetch_spec.rb 0000664 0000000 0000000 00000004132 14610664721 0030226 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/find_index_spec.rb 0000664 0000000 0000000 00000002177 14610664721 0031253 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/first_spec.rb 0000664 0000000 0000000 00000000567 14610664721 0030274 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/from_spec.rb 0000664 0000000 0000000 00000003144 14610664721 0030102 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/group_by_spec.rb 0000664 0000000 0000000 00000003250 14610664721 0030763 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/include_spec.rb 0000664 0000000 0000000 00000001113 14610664721 0030554 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/inspect_spec.rb 0000664 0000000 0000000 00000002040 14610664721 0030576 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/intersect_spec.rb 0000664 0000000 0000000 00000001224 14610664721 0031134 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/intersection_spec.rb 0000664 0000000 0000000 00000001432 14610664721 0031643 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/last_spec.rb 0000664 0000000 0000000 00000001340 14610664721 0030076 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/map_spec.rb 0000664 0000000 0000000 00000002732 14610664721 0027716 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/marshal_spec.rb 0000664 0000000 0000000 00000002123 14610664721 0030562 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/maximum_spec.rb 0000664 0000000 0000000 00000001535 14610664721 0030616 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/minimum_spec.rb 0000664 0000000 0000000 00000000643 14610664721 0030613 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/new_spec.rb 0000664 0000000 0000000 00000010166 14610664721 0027732 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/reverse_each_spec.rb 0000664 0000000 0000000 00000001316 14610664721 0031571 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/sample_spec.rb 0000664 0000000 0000000 00000000606 14610664721 0030420 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/select_spec.rb 0000664 0000000 0000000 00000003646 14610664721 0030425 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/size_spec.rb 0000664 0000000 0000000 00000000563 14610664721 0030113 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/slice_spec.rb 0000664 0000000 0000000 00000030363 14610664721 0030241 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/sorting_spec.rb 0000664 0000000 0000000 00000003305 14610664721 0030623 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/subset_spec.rb 0000664 0000000 0000000 00000002346 14610664721 0030447 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/superset_spec.rb 0000664 0000000 0000000 00000002356 14610664721 0031015 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/take_spec.rb 0000664 0000000 0000000 00000003044 14610664721 0030062 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/take_while_spec.rb 0000664 0000000 0000000 00000001603 14610664721 0031251 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/to_set_spec.rb 0000664 0000000 0000000 00000000514 14610664721 0030432 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/union_spec.rb 0000664 0000000 0000000 00000003477 14610664721 0030300 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/up_to_spec.rb 0000664 0000000 0000000 00000003215 14610664721 0030264 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/util_spec.rb 0000664 0000000 0000000 00000002674 14610664721 0030123 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/sorted_set/values_at_spec.rb 0000664 0000000 0000000 00000001710 14610664721 0031117 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/ 0000775 0000000 0000000 00000000000 14610664721 0024725 5 ustar 00root root 0000000 0000000 immutable-ruby-immutable-ruby-7d20d25/spec/lib/immutable/vector/add_spec.rb 0000664 0000000 0000000 00000003601 14610664721 0027014 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/any_spec.rb 0000664 0000000 0000000 00000002717 14610664721 0027062 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/assoc_spec.rb 0000664 0000000 0000000 00000002364 14610664721 0027401 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/bsearch_spec.rb 0000664 0000000 0000000 00000003647 14610664721 0027705 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/clear_spec.rb 0000664 0000000 0000000 00000001360 14610664721 0027352 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/combination_spec.rb 0000664 0000000 0000000 00000004450 14610664721 0030571 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/compact_spec.rb 0000664 0000000 0000000 00000001327 14610664721 0027715 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/compare_spec.rb 0000664 0000000 0000000 00000001276 14610664721 0027720 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/concat_spec.rb 0000664 0000000 0000000 00000002077 14610664721 0027541 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/copying_spec.rb 0000664 0000000 0000000 00000000561 14610664721 0027736 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/count_spec.rb 0000664 0000000 0000000 00000000670 14610664721 0027417 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/delete_at_spec.rb 0000664 0000000 0000000 00000003172 14610664721 0030215 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/delete_spec.rb 0000664 0000000 0000000 00000001665 14610664721 0027536 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/dig_spec.rb 0000664 0000000 0000000 00000001543 14610664721 0027032 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/drop_spec.rb 0000664 0000000 0000000 00000002104 14610664721 0027225 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/drop_while_spec.rb 0000664 0000000 0000000 00000002716 14610664721 0030426 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/each_index_spec.rb 0000664 0000000 0000000 00000001734 14610664721 0030360 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/each_spec.rb 0000664 0000000 0000000 00000002100 14610664721 0027155 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/each_with_index_spec.rb 0000664 0000000 0000000 00000002134 14610664721 0031406 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/empty_spec.rb 0000664 0000000 0000000 00000002040 14610664721 0027416 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/eql_spec.rb 0000664 0000000 0000000 00000004216 14610664721 0027050 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/fetch_spec.rb 0000664 0000000 0000000 00000004011 14610664721 0027351 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/fill_spec.rb 0000664 0000000 0000000 00000005402 14610664721 0027213 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/first_spec.rb 0000664 0000000 0000000 00000000557 14610664721 0027422 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/flat_map_spec.rb 0000664 0000000 0000000 00000002475 14610664721 0030057 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/flatten_spec.rb 0000664 0000000 0000000 00000003306 14610664721 0027723 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/get_spec.rb 0000664 0000000 0000000 00000004344 14610664721 0027050 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/group_by_spec.rb 0000664 0000000 0000000 00000003157 14610664721 0030120 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/include_spec.rb 0000664 0000000 0000000 00000001455 14610664721 0027714 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/insert_spec.rb 0000664 0000000 0000000 00000004042 14610664721 0027570 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/inspect_spec.rb 0000664 0000000 0000000 00000002272 14610664721 0027734 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/join_spec.rb 0000664 0000000 0000000 00000002752 14610664721 0027231 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/last_spec.rb 0000664 0000000 0000000 00000001705 14610664721 0027232 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/length_spec.rb 0000664 0000000 0000000 00000001641 14610664721 0027547 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/ltlt_spec.rb 0000664 0000000 0000000 00000003244 14610664721 0027246 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/map_spec.rb 0000664 0000000 0000000 00000002606 14610664721 0027045 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/marshal_spec.rb 0000664 0000000 0000000 00000001625 14610664721 0027717 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/maximum_spec.rb 0000664 0000000 0000000 00000001432 14610664721 0027741 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/minimum_spec.rb 0000664 0000000 0000000 00000001431 14610664721 0027736 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/multiply_spec.rb 0000664 0000000 0000000 00000002436 14610664721 0030150 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/new_spec.rb 0000664 0000000 0000000 00000002507 14610664721 0027061 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/partition_spec.rb 0000664 0000000 0000000 00000002702 14610664721 0030276 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/permutation_spec.rb 0000664 0000000 0000000 00000005661 14610664721 0030643 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/pop_spec.rb 0000664 0000000 0000000 00000001033 14610664721 0027057 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/product_spec.rb 0000664 0000000 0000000 00000004477 14610664721 0027760 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/reduce_spec.rb 0000664 0000000 0000000 00000002774 14610664721 0027545 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/reject_spec.rb 0000664 0000000 0000000 00000002455 14610664721 0027546 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/repeated_combination_spec.rb 0000664 0000000 0000000 00000005060 14610664721 0032440 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/repeated_permutation_spec.rb 0000664 0000000 0000000 00000006521 14610664721 0032510 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/reverse_each_spec.rb 0000664 0000000 0000000 00000001554 14610664721 0030724 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/reverse_spec.rb 0000664 0000000 0000000 00000001016 14610664721 0027735 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/rindex_spec.rb 0000664 0000000 0000000 00000001753 14610664721 0027563 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/rotate_spec.rb 0000664 0000000 0000000 00000004013 14610664721 0027560 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/sample_spec.rb 0000664 0000000 0000000 00000000540 14610664721 0027544 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/select_spec.rb 0000664 0000000 0000000 00000003602 14610664721 0027544 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/set_spec.rb 0000664 0000000 0000000 00000012472 14610664721 0027065 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/shift_spec.rb 0000664 0000000 0000000 00000001067 14610664721 0027405 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/shuffle_spec.rb 0000664 0000000 0000000 00000002251 14610664721 0027720 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/slice_spec.rb 0000664 0000000 0000000 00000025724 14610664721 0027375 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/sorting_spec.rb 0000664 0000000 0000000 00000003057 14610664721 0027756 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/sum_spec.rb 0000664 0000000 0000000 00000000527 14610664721 0027074 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/take_spec.rb 0000664 0000000 0000000 00000002100 14610664721 0027201 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/take_while_spec.rb 0000664 0000000 0000000 00000001544 14610664721 0030404 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/to_a_spec.rb 0000664 0000000 0000000 00000001551 14610664721 0027210 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/to_ary_spec.rb 0000664 0000000 0000000 00000001317 14610664721 0027563 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/to_list_spec.rb 0000664 0000000 0000000 00000001172 14610664721 0027742 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/to_set_spec.rb 0000664 0000000 0000000 00000000606 14610664721 0027563 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/transpose_spec.rb 0000664 0000000 0000000 00000004115 14610664721 0030303 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/uniq_spec.rb 0000664 0000000 0000000 00000004263 14610664721 0027245 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/unshift_spec.rb 0000664 0000000 0000000 00000001271 14610664721 0027745 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/update_in_spec.rb 0000664 0000000 0000000 00000004715 14610664721 0030243 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/values_at_spec.rb 0000664 0000000 0000000 00000001645 14610664721 0030255 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/immutable/vector/zip_spec.rb 0000664 0000000 0000000 00000003445 14610664721 0027074 0 ustar 00root root 0000000 0000000 require '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-immutable-ruby-7d20d25/spec/lib/load_spec.rb 0000664 0000000 0000000 00000002570 14610664721 0023726 0 ustar 00root root 0000000 0000000 # 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-immutable-ruby-7d20d25/spec/spec_helper.rb 0000664 0000000 0000000 00000003306 14610664721 0023516 0 ustar 00root root 0000000 0000000 require '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