hamster-3.0.0/0000755000004100000410000000000012663306556013221 5ustar www-datawww-datahamster-3.0.0/spec/0000755000004100000410000000000012663306556014153 5ustar www-datawww-datahamster-3.0.0/spec/spec_helper.rb0000644000004100000410000000324412663306556016774 0ustar www-datawww-datarequire "codeclimate-test-reporter" CodeClimate::TestReporter.start require "pry" require "rspec" require "hamster/hash" require "hamster/set" require "hamster/vector" require "hamster/sorted_set" require "hamster/list" require "hamster/deque" require "hamster/core_ext" # 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 = Hamster::Vector L = Hamster::List H = Hamster::Hash S = Hamster::Set SS = Hamster::SortedSet D = Hamster::Deque EmptyList = Hamster::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 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) && self.value == other.value end alias :eql? :== def <=>(other) self.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 hamster-3.0.0/spec/lib/0000755000004100000410000000000012663306556014721 5ustar www-datawww-datahamster-3.0.0/spec/lib/load_spec.rb0000644000004100000410000000251512663306556017202 0ustar www-datawww-data# It should be possible to require any one Hamster structure, # without loading all the others hamster_lib_dir = File.join(File.dirname(__FILE__), "..", "..", 'lib') describe :Hamster do describe :Hash do it "can be loaded separately" do system(%{ruby -e "$:.unshift('#{hamster_lib_dir}'); require 'hamster/hash'; Hamster::Hash.new"}).should be(true) end end describe :Set do it "can be loaded separately" do system(%{ruby -e "$:.unshift('#{hamster_lib_dir}'); require 'hamster/set'; Hamster::Set.new"}).should be(true) end end describe :Vector do it "can be loaded separately" do system(%{ruby -e "$:.unshift('#{hamster_lib_dir}'); require 'hamster/vector'; Hamster::Vector.new"}).should be(true) end end describe :List do it "can be loaded separately" do system(%{ruby -e "$:.unshift('#{hamster_lib_dir}'); require 'hamster/list'; Hamster::List[]"}).should be(true) end end describe :SortedSet do it "can be loaded separately" do system(%{ruby -e "$:.unshift('#{hamster_lib_dir}'); require 'hamster/sorted_set'; Hamster::SortedSet.new"}).should be(true) end end describe :Deque do it "can be loaded separately" do system(%{ruby -e "$:.unshift('#{hamster_lib_dir}'); require 'hamster/deque'; Hamster::Deque.new"}).should be(true) end end endhamster-3.0.0/spec/lib/hamster/0000755000004100000410000000000012663306556016364 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/nested/0000755000004100000410000000000012663306556017646 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/nested/construction_spec.rb0000644000004100000410000000671112663306556023744 0ustar www-datawww-datarequire "spec_helper" require "hamster/nested" require "hamster/deque" require "set" describe Hamster do expectations = [ # [Ruby, Hamster] [ { "a" => 1, "b" => [2, {"c" => 3}, 4], "d" => ::Set.new([5, 6, 7]), "e" => {"f" => 8, "g" => 9}, "h" => Regexp.new("ijk"), "l" => ::SortedSet.new([1, 2, 3]) }, Hamster::Hash[ "a" => 1, "b" => Hamster::Vector[2, Hamster::Hash["c" => 3], 4], "d" => Hamster::Set[5, 6, 7], "e" => Hamster::Hash["f" => 8, "g" => 9], "h" => Regexp.new("ijk"), "l" => Hamster::SortedSet.new([1, 2, 3])] ], [ {}, Hamster::Hash[] ], [ {"a" => 1, "b" => 2, "c" => 3}, Hamster::Hash["a" => 1, "b" => 2, "c" => 3] ], [ [], Hamster::Vector[] ], [ [1, 2, 3], Hamster::Vector[1, 2, 3] ], [ ::Set.new, Hamster::Set[] ], [ ::Set.new([1, 2, 3]), Hamster::Set[1, 2, 3] ], [ ::SortedSet.new, Hamster::SortedSet[] ], [ ::SortedSet.new([1, 2, 3]), Hamster::SortedSet[1, 2, 3] ], [ 42, 42 ], [ STDOUT, STDOUT ], # Struct conversion is one-way (from Ruby core Struct to Hamster::Hash), not back again! [ Struct::Customer.new, Hamster::Hash[name: nil, address: nil], true ], [ Struct::Customer.new('Dave', '123 Main'), Hamster::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 Hamster.from(input).should eql(expected_result) end end end context "with mixed object" do it "should return Hamster data" do input = { "a" => "b", "c" => {"d" => "e"}, "f" => Hamster::Vector["g", "h", []], "i" => Hamster::Hash["j" => {}, "k" => Hamster::Set[[], {}]] } expected_result = Hamster::Hash[ "a" => "b", "c" => Hamster::Hash["d" => "e"], "f" => Hamster::Vector["g", "h", Hamster::EmptyVector], "i" => Hamster::Hash["j" => Hamster::EmptyHash, "k" => Hamster::Set[Hamster::EmptyVector, Hamster::EmptyHash]] ] Hamster.from(input).should eql(expected_result) end end end describe ".to_ruby" do expectations.each do |expected_result, input, one_way| unless one_way context "with #{input.inspect} as input" do it "should return #{expected_result.inspect}" do Hamster.to_ruby(input).should eql(expected_result) end end end end context "with Hamster::Deque[] as input" do it "should return []" do Hamster.to_ruby(Hamster::Deque[]).should eql([]) end end context "with Hamster::Deque[Hamster::Hash[\"a\" => 1]] as input" do it "should return [{\"a\" => 1}]" do Hamster.to_ruby(Hamster::Deque[Hamster::Hash["a" => 1]]).should eql([{"a" => 1}]) end end context "with mixed object" do it "should return Ruby data structures" do input = Hamster::Hash[ "a" => "b", "c" => {"d" => "e"}, "f" => Hamster::Vector["g", "h"], "i" => {"j" => Hamster::EmptyHash, "k" => Set.new([Hamster::EmptyVector, Hamster::EmptyHash])}] expected_result = { "a" => "b", "c" => {"d" => "e"}, "f" => ["g", "h"], "i" => {"j" => {}, "k" => Set.new([[], {}])} } Hamster.to_ruby(input).should eql(expected_result) end end end end hamster-3.0.0/spec/lib/hamster/set/0000755000004100000410000000000012663306556017157 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/set/clear_spec.rb0000644000004100000410000000135112663306556021604 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::Set) instance = subclass.new([:a, :b, :c, :d]) instance.clear.class.should be(subclass) instance.clear.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/set/empty_spec.rb0000644000004100000410000000216412663306556021657 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::EmptySet) end context "from a subclass" do it "returns an empty instance of the subclass" do subclass = Class.new(Hamster::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(Hamster::Set) do def initialize @variable = 'value' end end subclass.empty.instance_variable_get(:@variable).should == 'value' end end end endhamster-3.0.0/spec/lib/hamster/set/map_spec.rb0000644000004100000410000000327012663306556021275 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::Set) instance = subclass['a', 'b'] instance.map { |item| item.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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/copying_spec.rb0000644000004100000410000000041212663306556022163 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/count_spec.rb0000644000004100000410000000144312663306556021650 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 = Hamster::Set.new(1..2000) set.count.should == 2000 set.count(&:odd?).should == 1000 end end endhamster-3.0.0/spec/lib/hamster/set/eql_spec.rb0000644000004100000410000000470412663306556021304 0ustar www-datawww-datarequire "spec_helper" require "set" require "hamster/set" describe Hamster::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 Hamster::Set" do let(:comparison) { Class.new(Hamster::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 hamster-3.0.0/spec/lib/hamster/set/to_a_spec.rb0000644000004100000410000000142412663306556021441 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/product_spec.rb0000644000004100000410000000076012663306556022201 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/difference_spec.rb0000644000004100000410000000264412663306556022616 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/intersection_spec.rb0000644000004100000410000000314112663306556023223 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/first_spec.rb0000644000004100000410000000127312663306556021650 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/minimum_spec.rb0000644000004100000410000000154712663306556022200 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/union_spec.rb0000644000004100000410000000367212663306556021656 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/none_spec.rb0000644000004100000410000000235712663306556021464 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/include_spec.rb0000644000004100000410000000332612663306556022145 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" require 'set' describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/intersect_spec.rb0000644000004100000410000000124012663306556022513 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/superset_spec.rb0000644000004100000410000000264112663306556022373 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/marshal_spec.rb0000644000004100000410000000144412663306556022150 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 hamster -e 'set = Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/to_set_spec.rb0000644000004100000410000000053112663306556022012 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/subset_spec.rb0000644000004100000410000000263512663306556022031 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/each_spec.rb0000644000004100000410000000205312663306556021416 0ustar www-datawww-datarequire "spec_helper" require "set" require "hamster/set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/set/add_spec.rb0000644000004100000410000000374712663306556021261 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/grep_v_spec.rb0000644000004100000410000000255712663306556022011 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/set/grep_spec.rb0000644000004100000410000000255512663306556021462 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 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) { ["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 describe "with a block" do let(:block) { ->(item) { item.downcase }} context "processes each matching item with the block" do let(:values) { ["A", 2, "C"] } let(:filtered) { %w[a c] } include_examples "check filtered values" end end end end hamster-3.0.0/spec/lib/hamster/set/all_spec.rb0000644000004100000410000000252712663306556021274 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/any_spec.rb0000644000004100000410000000260012663306556021303 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/sample_spec.rb0000644000004100000410000000054512663306556022003 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/set/group_by_spec.rb0000644000004100000410000000340712663306556022350 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::Set) instance = subclass.new([1, 'string', :symbol]) instance.group_by { |x| x.class }.values.each { |v| v.class.should be(subclass) } end end end end endhamster-3.0.0/spec/lib/hamster/set/flatten_spec.rb0000644000004100000410000000222312663306556022152 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster 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(Hamster::Set) subclass.new.flatten.class.should be(subclass) subclass.new([S[1], S[2]]).flatten.class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/set/reduce_spec.rb0000644000004100000410000000275712663306556022000 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/sorting_spec.rb0000644000004100000410000000312212663306556022201 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 it "only calls the passed block once 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 end end endhamster-3.0.0/spec/lib/hamster/set/inspect_spec.rb0000644000004100000410000000243312663306556022165 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::Set do describe "#inspect" do [ [[], "Hamster::Set[]"], [["A"], 'Hamster::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(/^Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/join_spec.rb0000644000004100000410000000342412663306556021460 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/sum_spec.rb0000644000004100000410000000074612663306556021331 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/maximum_spec.rb0000644000004100000410000000155012663306556022174 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/compact_spec.rb0000644000004100000410000000125112663306556022143 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/disjoint_spec.rb0000644000004100000410000000123512663306556022342 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/exclusion_spec.rb0000644000004100000410000000251312663306556022530 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/reject_spec.rb0000644000004100000410000000275312663306556022001 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/delete_spec.rb0000644000004100000410000000347612663306556021772 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/partition_spec.rb0000644000004100000410000000267512663306556022541 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/to_list_spec.rb0000644000004100000410000000142312663306556022173 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" require "hamster/list" describe Hamster::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?(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/eqeq_spec.rb0000644000004100000410000000440712663306556021456 0ustar www-datawww-datarequire "spec_helper" require "set" require "hamster/set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/set/construction_spec.rb0000644000004100000410000000070112663306556023246 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/one_spec.rb0000644000004100000410000000232512663306556021301 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/reverse_each_spec.rb0000644000004100000410000000155212663306556023154 0ustar www-datawww-datarequire "spec_helper" require "set" require "hamster/set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/set/hash_spec.rb0000644000004100000410000000131512663306556021441 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/set/select_spec.rb0000644000004100000410000000427712663306556022007 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::EmptySet) end end context "from a subclass" do it "returns an instance of the same class" do subclass = Class.new(Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/size_spec.rb0000644000004100000410000000057712663306556021501 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/immutable_spec.rb0000644000004100000410000000027412663306556022500 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" require "hamster/set" describe Hamster::Set do it "includes Immutable" do Hamster::Set.should include(Hamster::Immutable) end endhamster-3.0.0/spec/lib/hamster/set/find_spec.rb0000644000004100000410000000175212663306556021443 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/set/new_spec.rb0000644000004100000410000000253012663306556021307 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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(Hamster::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 < Hamster::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 endhamster-3.0.0/spec/lib/hamster/core_ext/0000755000004100000410000000000012663306556020174 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/core_ext/enumerable_spec.rb0000644000004100000410000000106612663306556023655 0ustar www-datawww-datarequire "spec_helper" require "hamster/core_ext/enumerable" 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 hamster-3.0.0/spec/lib/hamster/core_ext/io_spec.rb0000644000004100000410000000111712663306556022142 0ustar www-datawww-datarequire "spec_helper" require "hamster/core_ext/io" 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 hamster-3.0.0/spec/lib/hamster/core_ext/array_spec.rb0000644000004100000410000000043112663306556022647 0ustar www-datawww-datarequire "spec_helper" require "hamster/core_ext/enumerable" describe Array do let(:array) { %w[A B C] } describe "#to_list" do let(:to_list) { array.to_list } it "returns an equivalent hamster list" do expect(to_list).to eq(L["A", "B", "C"]) end end end hamster-3.0.0/spec/lib/hamster/associable/0000755000004100000410000000000012663306556020471 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/associable/associable_spec.rb0000644000004100000410000001137212663306556024141 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" require "hamster/vector" describe Hamster::Associable do describe "#update_in" do let(:hash) { Hamster::Hash[ "A" => "aye", "B" => Hamster::Hash["C" => "see", "D" => Hamster::Hash["E" => "eee"]], "F" => Hamster::Vector["G", Hamster::Hash["H" => "eitch"], "I"] ] } let(:vector) { Hamster::Vector[ 100, 101, 102, Hamster::Vector[200, 201, Hamster::Vector[300, 301, 302]], Hamster::Hash["A" => "alpha", "B" => "bravo"], [400, 401, 402] ] } context "with one level on existing key" do it "Hash passes the value to the block" do hash.update_in("A") { |value| value.should == "aye" } end it "Vector passes the value to the block" do vector.update_in(1) { |value| value.should == 101 } end it "Hash replaces the value with the result of the block" do result = hash.update_in("A") { |value| "FLIBBLE" } result.get("A").should == "FLIBBLE" end it "Vector replaces the value with the result of the block" do result = vector.update_in(1) { |value| "FLIBBLE" } result.get(1).should == "FLIBBLE" end it "Hash should preserve the original" do result = hash.update_in("A") { |value| "FLIBBLE" } hash.get("A").should == "aye" end it "Vector should preserve the original" do result = vector.update_in(1) { |value| "FLIBBLE" } vector.get(1).should == 101 end end context "with multi-level on existing keys" do it "Hash passes the value to the block" do hash.update_in("B", "D", "E") { |value| value.should == "eee" } end it "Vector passes the value to the block" do vector.update_in(3, 2, 0) { |value| value.should == 300 } end it "Hash 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 "Vector 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 "Hash should preserve the original" do result = hash.update_in("B", "D", "E") { |value| "FLIBBLE" } hash["B"]["D"]["E"].should == "eee" end it "Vector 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 "Hash passes nil to the block" do hash.update_in("B", "X", "Y") { |value| value.should be_nil } end it "Vector passes nil to the block" do vector.update_in(3, 3, "X", "Y") { |value| value.should be_nil } end it "Hash 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 it "Vector 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 "Hash 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 "Vector 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 "Hash raises ArguemntError" do expect { hash.update_in() { |v| 42 } }.to raise_error(ArgumentError) end it "Vector raises ArguemntError" do expect { vector.update_in() { |v| 42 } }.to raise_error(ArgumentError) end end end end hamster-3.0.0/spec/lib/hamster/vector/0000755000004100000410000000000012663306556017666 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/vector/fetch_spec.rb0000644000004100000410000000403712663306556022322 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/concat_spec.rb0000644000004100000410000000212512663306556022474 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/repeated_combination_spec.rb0000644000004100000410000000510612663306556025402 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/clear_spec.rb0000644000004100000410000000140412663306556022312 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) instance = subclass.new(%w{a b c}) instance.clear.class.should be(subclass) instance.clear.should be_empty end end end end endhamster-3.0.0/spec/lib/hamster/vector/values_at_spec.rb0000644000004100000410000000167112663306556023215 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) instance = subclass.new([1,2,3]) instance.values_at(1,2).class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/vector/uniq_spec.rb0000644000004100000410000000430512663306556022203 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) end end end context "from a subclass" do it "returns an instance of the subclass" do subclass = Class.new(Hamster::Vector) instance = subclass.new([1,2,3]) instance.uniq.class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/vector/bsearch_spec.rb0000644000004100000410000000367512663306556022647 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/empty_spec.rb0000644000004100000410000000206212663306556022363 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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(Hamster::Vector) do def initialize @variable = 'value' end end subclass.empty.instance_variable_get(:@variable).should == 'value' end end end endhamster-3.0.0/spec/lib/hamster/vector/combination_spec.rb0000644000004100000410000000447612663306556023542 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/drop_spec.rb0000644000004100000410000000213212663306556022167 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/map_spec.rb0000644000004100000410000000263212663306556022005 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/put_spec.rb0000644000004100000410000001251712663306556022043 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::Vector do let(:vector) { V[*values] } describe "#put" do context "when empty" do let(:vector) { V.empty } it "raises an error for index -1" do expect { vector.put(-1, :a) }.to raise_error end it "allows indexes 0 and 1 to be put" do vector.put(0, :a).should eql(V[:a]) vector.put(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.put(1) { |value| value.should == "B" } end it "replaces the value with the result of the block" do result = vector.put(1) { |value| "FLIBBLE" } result.should eql(V["A", "FLIBBLE", "C"]) end it "supports to_proc methods" do result = vector.put(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.put(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.put(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.put(-2) { |value| value.should == "B" } end it "replaces the value with the result of the block" do result = vector.put(-2) { |value| "FLIBBLE" } result.should eql(V["A", "FLIBBLE", "C"]) end it "supports to_proc methods" do result = vector.put(-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.put(-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(:put) { vector.put(1, "FLIBBLE") } it "preserves the original" do vector.should eql(V["A", "B", "C"]) end it "puts the new value at the specified index" do put.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.put(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.put(5, "FLIBBLE") result.should eql(V["A", "B", "C", nil, nil, "FLIBBLE"]) end end end context "with a negative index" do let(:put) { vector.put(-2, "FLIBBLE") } it "preserves the original" do put vector.should eql(V["A", "B", "C"]) end it "puts the new value at the specified index" do put.should eql(V["A", "FLIBBLE", "C"]) end end context "outside the absolute bounds of the vector" do it "raises an error" do expect { vector.put(-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(Hamster::Vector) instance = subclass[1,2,3] instance.put(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.put(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.put(index, index * index).should equal(vector) end end end end end end end hamster-3.0.0/spec/lib/hamster/vector/copying_spec.rb0000644000004100000410000000060712663306556022700 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/count_spec.rb0000644000004100000410000000071612663306556022361 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/eql_spec.rb0000644000004100000410000000423412663306556022011 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 Hamster::Vector" do vector.eql?(Class.new(Hamster::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 Hamster::Vector" do (vector == Class.new(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/to_a_spec.rb0000644000004100000410000000160012663306556022144 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/to_ary_spec.rb0000644000004100000410000000134612663306556022526 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/product_spec.rb0000644000004100000410000000452512663306556022713 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/first_spec.rb0000644000004100000410000000060512663306556022355 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/minimum_spec.rb0000644000004100000410000000145712663306556022707 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/rindex_spec.rb0000644000004100000410000000200112663306556022507 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/permutation_spec.rb0000644000004100000410000000570712663306556023605 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/pop_spec.rb0000644000004100000410000000106112663306556022021 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/include_spec.rb0000644000004100000410000000150312663306556022647 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/rotate_spec.rb0000644000004100000410000000403712663306556022527 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/get_spec.rb0000644000004100000410000000437212663306556022012 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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.put(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 endhamster-3.0.0/spec/lib/hamster/vector/slice_spec.rb0000644000004100000410000002575012663306556022335 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/marshal_spec.rb0000644000004100000410000000164712663306556022664 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster -e 'vector = Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/to_set_spec.rb0000644000004100000410000000066212663306556022526 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/each_spec.rb0000644000004100000410000000212612663306556022126 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/add_spec.rb0000644000004100000410000000362612663306556021764 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) instance = subclass[1,2,3] instance.add(4).class.should be(subclass) end end end end end hamster-3.0.0/spec/lib/hamster/vector/drop_while_spec.rb0000644000004100000410000000274412663306556023370 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/any_spec.rb0000644000004100000410000000274612663306556022025 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/sample_spec.rb0000644000004100000410000000056712663306556022516 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/group_by_spec.rb0000644000004100000410000000321212663306556023051 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) instance = subclass.new([1, 'string', :symbol]) instance.group_by { |x| x.class }.values.each { |v| v.class.should be(subclass) } end end end endhamster-3.0.0/spec/lib/hamster/vector/ltlt_spec.rb0000644000004100000410000000327112663306556022207 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) instance = subclass[1,2,3] (instance << 4).class.should be(subclass) end end end end hamster-3.0.0/spec/lib/hamster/vector/take_while_spec.rb0000644000004100000410000000157312663306556023347 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/flatten_spec.rb0000644000004100000410000000333212663306556022663 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/repeated_permutation_spec.rb0000644000004100000410000000654712663306556025461 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/length_spec.rb0000644000004100000410000000167012663306556022512 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/reduce_spec.rb0000644000004100000410000000302212663306556022471 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/sorting_spec.rb0000644000004100000410000000311712663306556022714 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 { |x| -x }.should == array.sort_by { |x| -x } end end end end end end endhamster-3.0.0/spec/lib/hamster/vector/set_spec.rb0000644000004100000410000000122412663306556022017 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::Vector do # Note: Vector#set will be deprecated; use Vector#put instead. See # `spec/lib/hamster/vector/put_spec.rb` for the full specs of Vector#put. describe "#set" do let(:vector) { V[5, 6, 7] } context "without block" do it "replaces the element" do result = vector.set(1, 100) result.should eql(V[5, 100, 7]) end end context "with block" do it "passes the existing element to the block and replaces the result" do result = vector.set(1) { |e| e + 100 } result.should eql(V[5, 106, 7]) end end end end hamster-3.0.0/spec/lib/hamster/vector/each_with_index_spec.rb0000644000004100000410000000216212663306556024350 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/last_spec.rb0000644000004100000410000000173412663306556022175 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/inspect_spec.rb0000644000004100000410000000231712663306556022675 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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) { "Hamster::Vector[]" } let(:values) { [] } include_examples "checking output" end context "with a single item array" do let(:output) { "Hamster::Vector[\"A\"]" } let(:values) { %w[A] } include_examples "checking output" end context "with a multi-item array" do let(:output) { "Hamster::Vector[\"A\", \"B\"]" } let(:values) { %w[A B] } include_examples "checking output" end context "from a subclass" do MyVector = Class.new(Hamster::Vector) let(:vector) { MyVector.new(values) } let(:output) { "MyVector[1, 2]" } let(:values) { [1, 2] } include_examples "checking output" end end end hamster-3.0.0/spec/lib/hamster/vector/join_spec.rb0000644000004100000410000000300012663306556022155 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/multiply_spec.rb0000644000004100000410000000246212663306556023110 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/sum_spec.rb0000644000004100000410000000055512663306556022036 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/maximum_spec.rb0000644000004100000410000000146012663306556022703 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/delete_at_spec.rb0000644000004100000410000000321412663306556023153 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 = Hamster::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 = Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/compact_spec.rb0000644000004100000410000000135512663306556022657 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/fill_spec.rb0000644000004100000410000000543012663306556022155 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/reject_spec.rb0000644000004100000410000000250312663306556022501 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/assoc_spec.rb0000644000004100000410000000241212663306556022334 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/delete_spec.rb0000644000004100000410000000171112663306556022467 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::Vector) instance = subclass.new([1,2,3]) instance.delete(1).class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/vector/shuffle_spec.rb0000644000004100000410000000227512663306556022667 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/dig_spec.rb0000644000004100000410000000126212663306556021771 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 it "returns nil when indexing deeper than possible" do expect(v.dig(0, 0)).to eq(nil) end it "returns nil if you index past the end of an array" do expect(v.dig(5)).to eq(nil) end it "raises a type error when indexing with a key arrays don't understand" do expect{ v.dig(:foo) }.to raise_error(ArgumentError) end end end hamster-3.0.0/spec/lib/hamster/vector/partition_spec.rb0000644000004100000410000000273012663306556023240 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/compare_spec.rb0000644000004100000410000000132412663306556022653 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/unshift_spec.rb0000644000004100000410000000131712663306556022707 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/zip_spec.rb0000644000004100000410000000346712663306556022041 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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(Hamster::Vector) instance = subclass.new([1,2,3]) instance.zip([4,5,6]).class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/vector/to_list_spec.rb0000644000004100000410000000130012663306556022674 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" require "hamster/list" require "hamster/core_ext" describe Hamster::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?(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/reverse_spec.rb0000644000004100000410000000104412663306556022677 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/each_index_spec.rb0000644000004100000410000000176212663306556023322 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/take_spec.rb0000644000004100000410000000212612663306556022152 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/reverse_each_spec.rb0000644000004100000410000000160212663306556023657 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/insert_spec.rb0000644000004100000410000000406612663306556022537 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" require 'pry' describe Hamster::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 = Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/select_spec.rb0000644000004100000410000000362612663306556022513 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/shift_spec.rb0000644000004100000410000000111512663306556022340 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/vector/flat_map_spec.rb0000644000004100000410000000251412663306556023012 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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 a Hamster::Vector" do expect(flat_map).to eq(flattened_vector) end it "returns a Hamster::Vector" do expect(flat_map).to be_a(Hamster::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 hamster-3.0.0/spec/lib/hamster/vector/new_spec.rb0000644000004100000410000000252312663306556022020 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::Vector do describe ".new" do it "accepts a single enumerable argument and creates a new vector" do vector = Hamster::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 = Hamster::Vector.new(array) array[0] = 'changed' vector[0].should be(1) end it "is amenable to overriding of #initialize" do class SnazzyVector < Hamster::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(Hamster::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 = Hamster::Vector['a', 'b'] vector.size.should be(2) vector[0].should == 'a' vector[1].should == 'b' end end endhamster-3.0.0/spec/lib/hamster/vector/transpose_spec.rb0000644000004100000410000000362112663306556023245 0ustar www-datawww-datarequire "spec_helper" require "hamster/vector" describe Hamster::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(Hamster::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 end endhamster-3.0.0/spec/lib/hamster/experimental/0000755000004100000410000000000012663306556021061 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/experimental/mutable_set/0000755000004100000410000000000012663306556023365 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/experimental/mutable_set/delete_qm_spec.rb0000644000004100000410000000150712663306556026666 0ustar www-datawww-datarequire "spec_helper" require "hamster/experimental/mutable_set" describe Hamster::MutableSet do let(:mutable) { Hamster::MutableSet[*values] } describe "#delete?" do let(:values) { %w[A B C] } let(:delete?) { mutable.delete?(value) } context "with an existing value" do let(:value) { "B" } it "returns true" do expect(delete?).to be(true) end it "modifies the set to remove the value" do delete? expect(mutable).to eq(Hamster::MutableSet["A", "C"]) end end context "with a non-existing value" do let(:value) { "D" } it "returns false" do expect(delete?).to be(false) end it "preserves the original values" do delete? expect(mutable).to eq(Hamster::MutableSet["A", "B", "C"]) end end end end hamster-3.0.0/spec/lib/hamster/experimental/mutable_set/add_spec.rb0000644000004100000410000000147412663306556025462 0ustar www-datawww-datarequire "spec_helper" require "hamster/experimental/mutable_set" describe Hamster::MutableSet do let(:mutable) { Hamster::MutableSet[*values] } describe "#add" do let(:values) { %w[A B C] } let(:add) { mutable.add(value) } context "with a unique value" do let(:value) { "D" } it "returns self" do expect(add).to eq(mutable) end it "modifies the original set to include new value" do add expect(mutable).to eq(Hamster::MutableSet["A", "B", "C", "D"]) end end context "with a duplicate value" do let(:value) { "C" } it "returns self" do expect(add).to eq(mutable) end it "preserves the original values" do add expect(mutable).to eq(Hamster::MutableSet["A", "B", "C"]) end end end end hamster-3.0.0/spec/lib/hamster/experimental/mutable_set/add_qm_spec.rb0000644000004100000410000000147312663306556026156 0ustar www-datawww-datarequire "spec_helper" require "hamster/experimental/mutable_set" describe Hamster::MutableSet do let(:mutable) { Hamster::MutableSet[*values] } describe "#add?" do let(:values) { %w[A B C] } let(:add?) { mutable.add?(value) } context "with a unique value" do let(:value) { "D" } it "returns true" do expect(add?).to be true end it "modifies the set to include the new value" do add? expect(mutable).to eq(Hamster::MutableSet["A", "B", "C", "D"]) end end context "with a duplicate value" do let(:value) { "C" } it "returns false" do expect(add?).to be(false) end it "preserves the original values" do add? expect(mutable).to eq(Hamster::MutableSet["A", "B", "C"]) end end end end hamster-3.0.0/spec/lib/hamster/experimental/mutable_set/delete_spec.rb0000644000004100000410000000150312663306556026165 0ustar www-datawww-datarequire "spec_helper" require "hamster/experimental/mutable_set" describe Hamster::MutableSet do let(:mutable) { Hamster::MutableSet[*values] } describe "#delete" do let(:values) { %w[A B C] } let(:delete) { mutable.delete(value) } context "with an existing value" do let(:value) { "B" } it "returns self" do expect(delete).to eq(mutable) end it "modifies the set to remove the value" do delete expect(mutable).to eq(Hamster::MutableSet["A", "C"]) end end context "with a non-existing value" do let(:value) { "D" } it "returns self" do expect(delete).to eq(mutable) end it "preserves the original values" do delete expect(mutable).to eq(Hamster::MutableSet["A", "B", "C"]) end end end end hamster-3.0.0/spec/lib/hamster/hash/0000755000004100000410000000000012663306556017307 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/hash/fetch_spec.rb0000644000004100000410000000342212663306556021740 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/min_max_spec.rb0000644000004100000410000000223412663306556022277 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/clear_spec.rb0000644000004100000410000000205712663306556021740 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::Hash) instance = subclass.new(a: 1, b: 2) instance.clear.class.should be(subclass) instance.clear.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/hash/values_at_spec.rb0000644000004100000410000000220112663306556022624 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::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(Hamster::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(Hamster::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) { Hamster::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(Hamster::Vector) hash.values_at(:x, :a, :y).should eql(V['x-VAL', 9, 'y-VAL']) end end end end hamster-3.0.0/spec/lib/hamster/hash/default_proc_spec.rb0000644000004100000410000000403312663306556023315 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/empty_spec.rb0000644000004100000410000000222012663306556022000 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::EmptyHash) end context "from a subclass" do it "returns an empty instance of the subclass" do subclass = Class.new(Hamster::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(Hamster::Hash) do def initialize @variable = 'value' end end subclass.empty.instance_variable_get(:@variable).should == 'value' end end end endhamster-3.0.0/spec/lib/hamster/hash/store_spec.rb0000644000004100000410000000435512663306556022011 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/map_spec.rb0000644000004100000410000000257512663306556021434 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::Hash) instance = subclass.new('a' => 'aye', 'b' => 'bee') instance.map { |k,v| [k, v.upcase] }.class.should be(subclass) end end end end endhamster-3.0.0/spec/lib/hamster/hash/put_spec.rb0000644000004100000410000000600112663306556021453 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::Hash do 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 endhamster-3.0.0/spec/lib/hamster/hash/copying_spec.rb0000644000004100000410000000045012663306556022315 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/sort_spec.rb0000644000004100000410000000135412663306556021640 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/key_spec.rb0000644000004100000410000000140112663306556021432 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/eql_spec.rb0000644000004100000410000000455412663306556021437 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 Hamster::Hash" do subclass = Class.new(Hamster::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 Hamster::Hash" do subclass = Class.new(Hamster::Hash) instance = subclass.new("A" => "aye", "B" => "bee", "C" => "see") (hash == instance).should == 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 endhamster-3.0.0/spec/lib/hamster/hash/to_a_spec.rb0000644000004100000410000000056412663306556021575 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/merge_spec.rb0000644000004100000410000000470312663306556021751 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 a Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/none_spec.rb0000644000004100000410000000241412663306556021606 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/get_spec.rb0000644000004100000410000000417112663306556021430 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/superset_spec.rb0000644000004100000410000000230112663306556022514 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" RSpec.describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/slice_spec.rb0000644000004100000410000000236612663306556021754 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/marshal_spec.rb0000644000004100000410000000152512663306556022300 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster -e 'dict = Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/subset_spec.rb0000644000004100000410000000230112663306556022147 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" RSpec.describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/each_spec.rb0000644000004100000410000000431512663306556021551 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 { |x| x.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 hamster-3.0.0/spec/lib/hamster/hash/all_spec.rb0000644000004100000410000000242012663306556021414 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/any_spec.rb0000644000004100000410000000263012663306556021436 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/sample_spec.rb0000644000004100000410000000062212663306556022127 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::Hash do describe "#sample" do let(:hash) { Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/pretty_print_spec.rb0000644000004100000410000000202212663306556023405 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" require "pp" require "stringio" describe Hamster::Hash do describe "#pretty_print" do let(:hash) { Hamster::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 == 'Hamster::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 == 'Hamster::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 == 'Hamster::Hash[ 1 => "tin", 2 => "earwax", 3 => "neanderthal"]' end end endhamster-3.0.0/spec/lib/hamster/hash/to_hash_spec.rb0000644000004100000410000000111012663306556022264 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::Hash do [:to_hash, :to_h].each do |method| describe "##{method}" do it "converts an empty Hamster::Hash to an empty Ruby Hash" do H.empty.send(method).should eql({}) end it "converts a non-empty Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/flatten_spec.rb0000644000004100000410000000775412663306556022320 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::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(Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/reduce_spec.rb0000644000004100000410000000203012663306556022110 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/each_with_index_spec.rb0000644000004100000410000000172212663306556023772 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/inspect_spec.rb0000644000004100000410000000163212663306556022315 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::Hash do describe "#inspect" do [ [[], 'Hamster::Hash[]'], [["A" => "aye"], 'Hamster::Hash["A" => "aye"]'], [[DeterministicHash.new("A", 1) => "aye", DeterministicHash.new("B", 2) => "bee", DeterministicHash.new("C", 3) => "see"], 'Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/values_spec.rb0000644000004100000410000000120312663306556022141 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" require "hamster/set" describe Hamster::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 Hamster::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(Hamster::Vector) result.to_a.sort.should == [15, 15, 19] end end end endhamster-3.0.0/spec/lib/hamster/hash/has_value_spec.rb0000644000004100000410000000156112663306556022620 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/reject_spec.rb0000644000004100000410000000414212663306556022123 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/assoc_spec.rb0000644000004100000410000000265512663306556021766 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/delete_spec.rb0000644000004100000410000000203012663306556022103 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::EmptyHash) end end end end endhamster-3.0.0/spec/lib/hamster/hash/dig_spec.rb0000644000004100000410000000153612663306556021416 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 it "returns nil if a value does not support dig" do expect(h.dig(:a, :foo)).to eq(nil) end it "returns the correct value when there is a default proc" do default_hash = H.new { |k| "#{k}-default" } expect(default_hash.dig(:a)).to eq("a-default") end end end hamster-3.0.0/spec/lib/hamster/hash/invert_spec.rb0000644000004100000410000000150512663306556022156 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::Hash) instance = subclass.new(a: 1, b: 2) instance.invert.class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/hash/partition_spec.rb0000644000004100000410000000214412663306556022660 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 Hamster::Hashes" do partition.each { |h| h.class.should be(Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/keys_spec.rb0000644000004100000410000000057012663306556021623 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/take_spec.rb0000644000004100000410000000252612663306556021577 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/to_proc_spec.rb0000644000004100000410000000207712663306556022321 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/has_key_spec.rb0000644000004100000410000000162612663306556022276 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/except_spec.rb0000644000004100000410000000275112663306556022143 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/construction_spec.rb0000644000004100000410000000166112663306556023404 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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(Hamster::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 a Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/reverse_each_spec.rb0000644000004100000410000000130612663306556023301 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/hash_spec.rb0000644000004100000410000000165712663306556021602 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/select_spec.rb0000644000004100000410000000362312663306556022131 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/size_spec.rb0000644000004100000410000000257312663306556021627 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/immutable_spec.rb0000644000004100000410000000030012663306556022616 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" require "hamster/hash" describe Hamster::Hash do it "includes Immutable" do Hamster::Hash.should include(Hamster::Immutable) end end hamster-3.0.0/spec/lib/hamster/hash/flat_map_spec.rb0000644000004100000410000000222612663306556022433 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/find_spec.rb0000644000004100000410000000255112663306556021571 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/hash/new_spec.rb0000644000004100000410000000366312663306556021447 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::Hash do describe ".new" do it "is amenable to overriding of #initialize" do class SnazzyHash < Hamster::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(Hamster::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(Hamster::Hash) hash.size.should == 2 hash.key?(:a).should == true hash.key?(:b).should == true end it "accepts a Hamster::Hash as initializer" do hash = H[H.new(a: 1, b: 2)] hash.class.should be(Hamster::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(Hamster::Hash) hash.size.should == 2 hash.key?(:a).should == true hash.key?(:b).should == true end it "can be used with a subclass of Hamster::Hash" do subclass = Class.new(Hamster::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 endhamster-3.0.0/spec/lib/hamster/hash/fetch_values_spec.rb0000644000004100000410000000132612663306556023320 0ustar www-datawww-datarequire "spec_helper" require "hamster/hash" describe Hamster::Hash do describe "#fetch_values" do context "when the all the requests 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(Hamster::Vector) h.fetch_values.should eql(V.empty) h.fetch_values(:a, :d, :b).should be_kind_of(Hamster::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 hamster-3.0.0/spec/lib/hamster/immutable/0000755000004100000410000000000012663306556020343 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/immutable/copying_spec.rb0000644000004100000410000000061112663306556023350 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" describe Hamster::Immutable do class Fixture include Hamster::Immutable end [:dup, :clone].each do |method| describe "##{method}" do before do @original = Fixture.new @result = @original.send(method) end it "returns self" do @result.should equal(@original) end end end endhamster-3.0.0/spec/lib/hamster/immutable/transform_spec.rb0000644000004100000410000000116512663306556023720 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" describe Hamster::Immutable do class TransformPerson < Struct.new(:first, :last) include Hamster::Immutable public :transform end let(:immutable) { TransformPerson.new("Simon", "Harris") } describe "#transform" do let(:transform) { immutable.transform { self.first = "Sampy" } } let(:original) { immutable.first } let(:modified) { transform.first } it "preserves the original" do expect(original).to eq("Simon") end it "returns a new instance with the updated values" do expect(modified).to eq("Sampy") end end end hamster-3.0.0/spec/lib/hamster/immutable/transform_unless_spec.rb0000644000004100000410000000217012663306556025306 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" describe Hamster::Immutable do class TransformUnlessPerson < Struct.new(:first, :last) include Hamster::Immutable public :transform_unless end let(:immutable) { TransformUnlessPerson.new("Simon", "Harris") } describe "#transform_unless" do let(:transform_unless) { immutable.transform_unless(condition, &block) } let(:original) { immutable.first } let(:modified) { transform_unless.first } context "when the condition is false" do let(:condition) { false } let(:block) { ->(thing) { self.first = "Sampy" } } it "preserves the original" do expect(original).to eq("Simon") end it "returns a new instance with the updated values" do expect(modified).to eq("Sampy") end end context "when the condition is true" do let(:condition) { true } let(:block) { -> { fail("Should never be called") } } it "preserves the original" do expect(original).to eq("Simon") end it "returns the original" do expect(original).to eq(modified) end end end end hamster-3.0.0/spec/lib/hamster/immutable/immutable_spec.rb0000644000004100000410000000166112663306556023665 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" describe Hamster::Immutable do describe "#immutable?" do describe "object constructed after its class becomes Immutable" do class Fixture include Hamster::Immutable end before do @fixture = Fixture.new end it "returns true" do @fixture.should be_immutable end end describe "object constructed before its class becomes Immutable" do before do @fixture = Class.new.new @fixture.class.instance_eval do include Hamster::Immutable end end describe "that are not frozen" do it "returns false" do @fixture.should_not be_immutable end end describe "that are frozen" do before do @fixture.freeze end it "returns true" do @fixture.should be_immutable end end end end endhamster-3.0.0/spec/lib/hamster/immutable/memoize_spec.rb0000644000004100000410000000171512663306556023353 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" describe Hamster::Immutable do class Fixture include Hamster::Immutable def initialize(&block) @block = block end def call @block.call end memoize :call def copy transform {} end end let(:immutable) { Fixture.new { @count += 1 } } describe "#memoize" do before(:each) do @count = 0 immutable.call end it "keeps the receiver frozen and immutable" do expect(immutable).to be_immutable end context "when called multiple times" do before(:each) do immutable.call end it "doesn't evaluate the memoized method more than once" do expect(@count).to eq(1) end end describe "when making a copy" do let(:copy) { immutable.copy } before(:each) do copy.call end it "clears all memory" do expect(@count).to eq(2) end end end end hamster-3.0.0/spec/lib/hamster/immutable/new_spec.rb0000644000004100000410000000121412663306556022471 0ustar www-datawww-datarequire "spec_helper" require "hamster/immutable" describe Hamster::Immutable do class NewPerson < Struct.new(:first, :last) include Hamster::Immutable end let(:immutable) { NewPerson.new("Simon", "Harris") } it "freezes the instance" do expect(immutable).to be_frozen end context "subclass hides all public methods" do it "freezes the instance" do my_class = Class.new do include Hamster::Immutable (public_instance_methods - Object.public_instance_methods).each do |m| protected m end end immutable = my_class.new expect(immutable).to be_frozen end end end hamster-3.0.0/spec/lib/hamster/sorted_set/0000755000004100000410000000000012663306556020537 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/sorted_set/fetch_spec.rb0000644000004100000410000000416412663306556023174 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/at_spec.rb0000644000004100000410000000113212663306556022477 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/clear_spec.rb0000644000004100000410000000215612663306556023170 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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(Hamster::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(Hamster::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]) { |x| -x } } 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 endhamster-3.0.0/spec/lib/hamster/sorted_set/values_at_spec.rb0000644000004100000410000000174012663306556024063 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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(Hamster::SortedSet) instance = subclass.new([1,2,3]) instance.values_at(1,2).class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/empty_spec.rb0000644000004100000410000000150712663306556023237 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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(Hamster::SortedSet) subclass.empty.class.should be(subclass) subclass.empty.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/drop_spec.rb0000644000004100000410000000276412663306556023053 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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]) { |x| -x }} 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(Hamster::SortedSet) subclass.new([1,2,3]).drop(1).class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/map_spec.rb0000644000004100000410000000240012663306556022647 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 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 end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/copying_spec.rb0000644000004100000410000000063312663306556023550 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/eql_spec.rb0000644000004100000410000000574512663306556022672 0ustar www-datawww-datarequire "spec_helper" require "set" require "hamster/set" describe Hamster::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 a Hamster::Set" do let(:comparison) { Hamster::Set.new(%w[A B C]) } include_examples "comparing something which is not a sorted set" end context "when comparing with a subclass of Hamster::SortedSet" do let(:comparison) { Class.new(Hamster::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]) { |n| -n }} it "returns false" do expect(eql?).to eq(false) end end end end hamster-3.0.0/spec/lib/hamster/sorted_set/difference_spec.rb0000644000004100000410000000116412663306556024172 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/intersection_spec.rb0000644000004100000410000000146412663306556024611 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/first_spec.rb0000644000004100000410000000062112663306556023224 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/minimum_spec.rb0000644000004100000410000000067512663306556023561 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/union_spec.rb0000644000004100000410000000143012663306556023224 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/include_spec.rb0000644000004100000410000000114512663306556023522 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/intersect_spec.rb0000644000004100000410000000125612663306556024102 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/superset_spec.rb0000644000004100000410000000241012663306556023745 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/slice_spec.rb0000644000004100000410000003044512663306556023203 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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"]) { |word| word.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"]) { |word| word.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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/marshal_spec.rb0000644000004100000410000000215712663306556023532 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 hamster -e 'set = Hamster::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]) { |x| -x }) }.should raise_error(TypeError) end end end hamster-3.0.0/spec/lib/hamster/sorted_set/to_set_spec.rb0000644000004100000410000000057412663306556023401 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/above_spec.rb0000644000004100000410000000312612663306556023174 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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) } set = SS.new(items) threshold = rand(1000) result = set.above(threshold) array = items.select { |x| x > threshold }.sort result.class.should be(Hamster::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) } 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(Hamster::SortedSet) result.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/subset_spec.rb0000644000004100000410000000240012663306556023377 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/each_spec.rb0000644000004100000410000000127712663306556023005 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/add_spec.rb0000644000004100000410000000332512663306556022631 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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']) { |str| str.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 endhamster-3.0.0/spec/lib/hamster/sorted_set/drop_while_spec.rb0000644000004100000410000000170112663306556024231 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/between_spec.rb0000644000004100000410000000324012663306556023526 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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) } 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(Hamster::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) } 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(Hamster::SortedSet) result.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/sample_spec.rb0000644000004100000410000000063712663306556023365 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::SortedSet do describe "#sample" do let(:sorted_set) { Hamster::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 hamster-3.0.0/spec/lib/hamster/sorted_set/group_by_spec.rb0000644000004100000410000000330012663306556023720 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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(Hamster::SortedSet) instance = subclass.new(['some', 'strings', 'here']) instance.group_by { |x| x }.values.each { |v| v.class.should be(subclass) } end end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/take_while_spec.rb0000644000004100000410000000163612663306556024220 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/sorted_set/sorting_spec.rb0000644000004100000410000000264212663306556023567 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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) { |item| item.reverse }} context "with a block" do it "preserves the original" do sorted_set.send(method, &comparator) sorted_set.to_a.should == SS.new(values) { |item| item.reverse } end it "returns #{expected.inspect}" do sorted_set.send(method, &comparator).class.should be(Hamster::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) { |item| item.reverse } end it "returns #{expected.sort.inspect}" do sorted_set.send(method).class.should be(Hamster::SortedSet) sorted_set.send(method).to_a.should == expected.sort end end end end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/last_spec.rb0000644000004100000410000000137312663306556023045 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/sorted_set/inspect_spec.rb0000644000004100000410000000206212663306556023543 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::SortedSet do describe "#inspect" do [ [[], "Hamster::SortedSet[]"], [["A"], 'Hamster::SortedSet["A"]'], [["C", "B", "A"], 'Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/up_to_spec.rb0000644000004100000410000000323112663306556023223 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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) } set = SS.new(items) threshold = rand(1000) result = set.up_to(threshold) array = items.select { |x| x <= threshold }.sort result.class.should be(Hamster::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) } 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(Hamster::SortedSet) result.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/maximum_spec.rb0000644000004100000410000000156012663306556023555 0ustar www-datawww-datarequire "spec_helper" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/delete_at_spec.rb0000644000004100000410000000106612663306556024027 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/disjoint_spec.rb0000644000004100000410000000125312663306556023722 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/exclusion_spec.rb0000644000004100000410000000114712663306556024112 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/delete_spec.rb0000644000004100000410000000504612663306556023345 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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"]) { |word| word.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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/take_spec.rb0000644000004100000410000000310112663306556023015 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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]) { |x| -x }} 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(Hamster::SortedSet) subclass.new([1,2,3]).take(1).class.should be(subclass) end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/reverse_each_spec.rb0000644000004100000410000000135012663306556024530 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/select_spec.rb0000644000004100000410000000367412663306556023367 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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(Hamster::EmptySortedSet) end end context "from a subclass" do it "returns an instance of the same class" do subclass = Class.new(Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/size_spec.rb0000644000004100000410000000061512663306556023052 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/find_index_spec.rb0000644000004100000410000000223112663306556024203 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/sorted_set/new_spec.rb0000644000004100000410000000375112663306556022675 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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 "is amenable to overriding of #initialize" do class SnazzySortedSet < Hamster::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) { |a| -a } 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 context "from a subclass" do it "returns a frozen instance of the subclass" do subclass = Class.new(Hamster::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 end endhamster-3.0.0/spec/lib/hamster/sorted_set/below_spec.rb0000644000004100000410000000312112663306556023203 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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) } set = SS.new(items) threshold = rand(1000) result = set.below(threshold) array = items.select { |x| x < threshold }.sort result.class.should be(Hamster::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) } 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(Hamster::SortedSet) result.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/sorted_set/from_spec.rb0000644000004100000410000000316012663306556023041 0ustar www-datawww-datarequire "spec_helper" require "hamster/sorted_set" describe Hamster::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) } set = SS.new(items) threshold = rand(1000) result = set.from(threshold) array = items.select { |x| x >= threshold }.sort result.class.should be(Hamster::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) } 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(Hamster::SortedSet) result.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/list/0000755000004100000410000000000012663306556017337 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/list/append_spec.rb0000644000004100000410000000201312663306556022141 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do [:append, :concat, :+].each do |method| describe "##{method}" do it "is lazy" do -> { Hamster.stream { fail }.append(Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/at_spec.rb0000644000004100000410000000134012663306556021300 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#at" do context "on a really big list" do let(:list) { Hamster.interval(0, STACK_OVERFLOW_DEPTH) } 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 endhamster-3.0.0/spec/lib/hamster/list/clear_spec.rb0000644000004100000410000000071712663306556021771 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/uniq_spec.rb0000644000004100000410000000147212663306556021656 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#uniq" do it "is lazy" do -> { Hamster.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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/empty_spec.rb0000644000004100000410000000107112663306556022033 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#empty?" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/combination_spec.rb0000644000004100000410000000200112663306556023171 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#combination" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/drop_spec.rb0000644000004100000410000000131112663306556021636 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#drop" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/map_spec.rb0000644000004100000410000000230112663306556021447 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do [:map, :collect].each do |method| describe "##{method}" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/cons_spec.rb0000644000004100000410000000116012663306556021636 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/cadr_spec.rb0000644000004100000410000000166012663306556021612 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/split_at_spec.rb0000644000004100000410000000216312663306556022517 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#split_at" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/copying_spec.rb0000644000004100000410000000055512663306556022353 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/count_spec.rb0000644000004100000410000000151112663306556022024 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#count" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/eql_spec.rb0000644000004100000410000000415112663306556021460 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#eql?" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).eql?(Hamster.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'] endhamster-3.0.0/spec/lib/hamster/list/to_a_spec.rb0000644000004100000410000000173512663306556021626 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/to_ary_spec.rb0000644000004100000410000000163512663306556022200 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 hamster-3.0.0/spec/lib/hamster/list/product_spec.rb0000644000004100000410000000105212663306556022354 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#product" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/cycle_spec.rb0000644000004100000410000000121212663306556021771 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster do describe "#cycle" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/minimum_spec.rb0000644000004100000410000000173512663306556022357 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#min" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/permutation_spec.rb0000644000004100000410000000313412663306556023246 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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(Hamster::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(Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/union_spec.rb0000644000004100000410000000150712663306556022031 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do [:union, :|].each do |method| describe "##{method}" do it "is lazy" do -> { Hamster.stream { fail }.union(Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/merge_spec.rb0000644000004100000410000000256212663306556022002 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/subsequences_spec.rb0000644000004100000410000000142512663306556023405 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/pop_spec.rb0000644000004100000410000000074612663306556021503 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/list/none_spec.rb0000644000004100000410000000236612663306556021644 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#none?" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/include_spec.rb0000644000004100000410000000174012663306556022323 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/inits_spec.rb0000644000004100000410000000120512663306556022022 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#inits" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/rotate_spec.rb0000644000004100000410000000206612663306556022200 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/slice_spec.rb0000644000004100000410000002410412663306556021776 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/each_slice_spec.rb0000644000004100000410000000265512663306556022765 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/to_set_spec.rb0000644000004100000410000000056012663306556022174 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" require "hamster/set" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/each_spec.rb0000644000004100000410000000173212663306556021601 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#each" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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) Hamster::List[*list.each].should eql(list) end end end end end end hamster-3.0.0/spec/lib/hamster/list/add_spec.rb0000644000004100000410000000115512663306556021430 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/drop_while_spec.rb0000644000004100000410000000177212663306556023041 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#drop_while" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/grep_spec.rb0000644000004100000410000000215012663306556021631 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#grep" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/all_spec.rb0000644000004100000410000000257212663306556021454 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#all?" do context "on a really big list" do let(:list) { Hamster.interval(0, STACK_OVERFLOW_DEPTH) } 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 endhamster-3.0.0/spec/lib/hamster/list/any_spec.rb0000644000004100000410000000241412663306556021466 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#any?" do context "on a really big list" do let(:list) { Hamster.interval(0, STACK_OVERFLOW_DEPTH) } 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 endhamster-3.0.0/spec/lib/hamster/list/sample_spec.rb0000644000004100000410000000055512663306556022164 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/group_by_spec.rb0000644000004100000410000000223212663306556022523 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/ltlt_spec.rb0000644000004100000410000000071312663306556021656 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/take_while_spec.rb0000644000004100000410000000223012663306556023007 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#take_while" do it "is lazy" do -> { Hamster.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 hamster-3.0.0/spec/lib/hamster/list/flatten_spec.rb0000644000004100000410000000125412663306556022335 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster do describe "#flatten" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/reduce_spec.rb0000644000004100000410000000305712663306556022152 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 hamster-3.0.0/spec/lib/hamster/list/sorting_spec.rb0000644000004100000410000000234712663306556022371 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/each_with_index_spec.rb0000644000004100000410000000144212663306556024021 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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) { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/last_spec.rb0000644000004100000410000000103712663306556021642 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#last" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/inspect_spec.rb0000644000004100000410000000141212663306556022341 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#inspect" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).inspect }.should_not raise_error end end [ [[], 'Hamster::List[]'], [["A"], 'Hamster::List["A"]'], [%w[A B C], 'Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/join_spec.rb0000644000004100000410000000275212663306556021643 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#join" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/sum_spec.rb0000644000004100000410000000103412663306556021500 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#sum" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/intersperse_spec.rb0000644000004100000410000000123112663306556023236 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#intersperse" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/maximum_spec.rb0000644000004100000410000000173612663306556022362 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#max" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/delete_at_spec.rb0000644000004100000410000000076512663306556022634 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/compact_spec.rb0000644000004100000410000000141712663306556022327 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#compact" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/multithreading_spec.rb0000644000004100000410000000240412663306556023716 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" require "concurrent/atomics" describe Hamster::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::AtomicReference.new(0) list = (1..10000).to_list.map { |x| counter.update { |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.get.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 endhamster-3.0.0/spec/lib/hamster/list/break_spec.rb0000644000004100000410000000352412663306556021766 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#break" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/fill_spec.rb0000644000004100000410000000256012663306556021627 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.stream { fail }.fill(:a, 0, 1) }.should_not raise_error end end endhamster-3.0.0/spec/lib/hamster/list/reject_spec.rb0000644000004100000410000000232612663306556022155 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do [:reject, :delete_if].each do |method| describe "##{method}" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/delete_spec.rb0000644000004100000410000000111012663306556022131 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/indices_spec.rb0000644000004100000410000000323312663306556022315 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#indices" do context "when called with a block" do it "is lazy" do count = 0 Hamster.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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/partition_spec.rb0000644000004100000410000000731712663306556022717 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" require "thread" describe Hamster::List do describe "#partition" do it "is lazy" do -> { Hamster.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 = Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/compare_spec.rb0000644000004100000410000000124512663306556022326 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/index_spec.rb0000644000004100000410000000210112663306556021777 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#index" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 if RUBY_ENGINE == 'jruby' && RUBY_VERSION <= '2.2.2' && values[0].is_a?(Fixnum) && item.is_a?(Float) skip "On JRuby, Enumerable#find_index doesn't test equality properly" else L[*values].index(item).should == expected end end end end end endhamster-3.0.0/spec/lib/hamster/list/zip_spec.rb0000644000004100000410000000123512663306556021501 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#zip" do it "is lazy" do -> { Hamster.stream { fail }.zip(Hamster.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 hamster-3.0.0/spec/lib/hamster/list/to_list_spec.rb0000644000004100000410000000053712663306556022360 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/reverse_spec.rb0000644000004100000410000000152412663306556022353 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#reverse" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).reverse }.should_not raise_error end end it "is lazy" do -> { Hamster.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 { |item| item.downcase } list.should eql(L[*values]) end it "returns #{expected.inspect}" do list.reverse { |item| item.downcase }.should == L[*expected] end end end end endhamster-3.0.0/spec/lib/hamster/list/take_spec.rb0000644000004100000410000000130112663306556021615 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#take" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/tail_spec.rb0000644000004100000410000000130112663306556021622 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#tail" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/span_spec.rb0000644000004100000410000000473512663306556021650 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe "Hamster::list#span" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/construction_spec.rb0000644000004100000410000000623712663306556023440 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster 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 Hamster.stream.should eql(L.empty) end end context "with a block" do let(:list) { count = 0; Hamster.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 Hamster.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 Hamster.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 Hamster.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 Hamster.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 Hamster.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) { Hamster.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 end end describe "[]" do it "takes a variable number of items and returns a list" do list = Hamster::List[1,2,3] list.should be_kind_of(Hamster::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(Hamster::List) L[].should be_empty end end endhamster-3.0.0/spec/lib/hamster/list/one_spec.rb0000644000004100000410000000245212663306556021462 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#one?" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/hash_spec.rb0000644000004100000410000000104712663306556021623 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#hash" do context "on a really big list" do it "doesn't run out of stack" do -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 hamster-3.0.0/spec/lib/hamster/list/insert_spec.rb0000644000004100000410000000244112663306556022203 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.stream { fail }.insert(0, :a) }.should_not raise_error end end endhamster-3.0.0/spec/lib/hamster/list/select_spec.rb0000644000004100000410000000331212663306556022154 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do let(:list) { L[*values] } let(:selected_list) { L[*selected_values] } describe "#select" do it "is lazy" do expect { Hamster.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 hamster-3.0.0/spec/lib/hamster/list/size_spec.rb0000644000004100000410000000116412663306556021652 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/tails_spec.rb0000644000004100000410000000120512663306556022010 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#tails" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/find_index_spec.rb0000644000004100000410000000176212663306556023013 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/chunk_spec.rb0000644000004100000410000000117412663306556022011 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#chunk" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/list/flat_map_spec.rb0000644000004100000410000000246312663306556022466 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 Hamster::List" do expect(flat_map).to eq(flattened_list) end it "returns a Hamster::List" do expect(flat_map).to be_a(Hamster::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 hamster-3.0.0/spec/lib/hamster/list/find_spec.rb0000644000004100000410000000230312663306556021614 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 -> { Hamster.interval(0, STACK_OVERFLOW_DEPTH).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 endhamster-3.0.0/spec/lib/hamster/list/merge_by_spec.rb0000644000004100000410000000240612663306556022471 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 { |item| -item }.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 { |item| -item }.should == L[9, 8, 7, 6, 5, 4, 3, 2, 1] end end end endhamster-3.0.0/spec/lib/hamster/list/find_all_spec.rb0000644000004100000410000000330212663306556022444 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do let(:list) { L[*values] } let(:found_list) { L[*found_values] } describe "#find_all" do it "is lazy" do expect { Hamster.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 hamster-3.0.0/spec/lib/hamster/list/head_spec.rb0000644000004100000410000000067212663306556021604 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/transpose_spec.rb0000644000004100000410000000135512663306556022720 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/list/init_spec.rb0000644000004100000410000000117712663306556021647 0ustar www-datawww-datarequire "spec_helper" require "hamster/list" describe Hamster::List do describe "#init" do it "is lazy" do -> { Hamster.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 endhamster-3.0.0/spec/lib/hamster/deque/0000755000004100000410000000000012663306556017467 5ustar www-datawww-datahamster-3.0.0/spec/lib/hamster/deque/random_modification_spec.rb0000644000004100000410000000161612663306556025037 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 = Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/clear_spec.rb0000644000004100000410000000132712663306556022117 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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(Hamster::Deque) instance = subclass.new([1,2]) instance.clear.should be_empty instance.clear.class.should be(subclass) end end endhamster-3.0.0/spec/lib/hamster/deque/empty_spec.rb0000644000004100000410000000176112663306556022171 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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(Hamster::Deque) D.empty.object_id.should be(Hamster::EmptyDeque.object_id) end context "from a subclass" do it "returns an empty instance of the subclass" do subclass = Class.new(Hamster::Deque) subclass.empty.class.should be(subclass) subclass.empty.should be_empty end end end endhamster-3.0.0/spec/lib/hamster/deque/copying_spec.rb0000644000004100000410000000056212663306556022501 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/to_a_spec.rb0000644000004100000410000000120212663306556021743 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/to_ary_spec.rb0000644000004100000410000000135412663306556022326 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 hamster-3.0.0/spec/lib/hamster/deque/first_spec.rb0000644000004100000410000000055612663306556022163 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/enqueue_spec.rb0000644000004100000410000000133512663306556022477 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/pop_spec.rb0000644000004100000410000000152612663306556021630 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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(Hamster::Deque) } let(:empty_instance) { subclass.new } it "returns emtpy object of same class" do empty_instance.pop.class.should be subclass end end end endhamster-3.0.0/spec/lib/hamster/deque/marshal_spec.rb0000644000004100000410000000205712663306556022461 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 hamster -e 'deque = Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/pretty_print_spec.rb0000644000004100000410000000112612663306556023571 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" require "pp" require "stringio" describe Hamster::Deque do describe "#pretty_print" do let(:deque) { Hamster::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 == 'Hamster::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 == 'Hamster::Deque[ "AAAA", "BBBB", "CCCC"]' end end end hamster-3.0.0/spec/lib/hamster/deque/last_spec.rb0000644000004100000410000000055612663306556021777 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/inspect_spec.rb0000644000004100000410000000113312663306556022471 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::Deque do describe "#inspect" do [ [[], 'Hamster::Deque[]'], [["A"], 'Hamster::Deque["A"]'], [%w[A B C], 'Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/push_spec.rb0000644000004100000410000000166512663306556022015 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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(Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/unshift_spec.rb0000644000004100000410000000135312663306556022510 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/to_list_spec.rb0000644000004100000410000000114512663306556022504 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" require "hamster/list" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/construction_spec.rb0000644000004100000410000000132712663306556023563 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::Deque do describe ".[]" do context "with no arguments" do it "always returns the same instance" do D[].class.should be(Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/size_spec.rb0000644000004100000410000000066712663306556022011 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/shift_spec.rb0000644000004100000410000000115612663306556022146 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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 endhamster-3.0.0/spec/lib/hamster/deque/new_spec.rb0000644000004100000410000000224512663306556021622 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::Deque do describe ".new" do it "accepts a single enumerable argument and creates a new deque" do deque = Hamster::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 < Hamster::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(Hamster::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 = Hamster::Deque['a', 'b'] deque.size.should be(2) deque.first.should == 'a' deque.dequeue.first.should == 'b' end end endhamster-3.0.0/spec/lib/hamster/deque/dequeue_spec.rb0000644000004100000410000000155312663306556022467 0ustar www-datawww-datarequire "spec_helper" require "hamster/deque" describe Hamster::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(Hamster::Deque) } let(:empty_instance) { subclass.new } it "returns emtpy object of same class" do empty_instance.send(method).class.should be subclass end end end endhamster-3.0.0/spec/fixtures/0000755000004100000410000000000012663306556016024 5ustar www-datawww-datahamster-3.0.0/spec/fixtures/io_spec.txt0000644000004100000410000000000612663306556020202 0ustar www-datawww-dataA B C hamster-3.0.0/lib/0000755000004100000410000000000012663306556013767 5ustar www-datawww-datahamster-3.0.0/lib/hamster.rb0000644000004100000410000000047012663306556015760 0ustar www-datawww-datarequire "hamster/core_ext" require "hamster/immutable" require "hamster/list" require "hamster/deque" require "hamster/hash" require "hamster/set" require "hamster/vector" require "hamster/sorted_set" require "hamster/mutable_hash" require "hamster/nested" require "hamster/version" require "hamster/associable" hamster-3.0.0/lib/hamster/0000755000004100000410000000000012663306556015432 5ustar www-datawww-datahamster-3.0.0/lib/hamster/nested.rb0000644000004100000410000000540212663306556017242 0ustar www-datawww-datarequire "set" require "hamster/hash" require "hamster/set" require "hamster/vector" require "hamster/sorted_set" require "hamster/list" require "hamster/deque" require "hamster/core_ext/struct" module Hamster class << self # Create a Hamster immutable data structure with nested Hamster data # structure from a nested Ruby object `obj`. This method recursively # "walks" the Ruby object, converting Ruby `Hash` to {Hamster::Hash}, Ruby # `Array` to {Hamster::Vector}, Ruby `Set` to {Hamster::Set}, and Ruby # `SortedSet` to {Hamster::SortedSet}. Other objects are left as-is. # # @example # h = Hamster.from({ "a" => [1, 2], "b" => "c" }) # # => Hamster::Hash["a" => Hamster::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)] } Hamster::Hash.new(res) when Hamster::Hash obj.map { |key, value| [from(key), from(value)] } when ::Struct from(obj.to_h) when ::Array res = obj.map { |element| from(element) } Hamster::Vector.new(res) when ::SortedSet # This clause must go before ::Set clause, since ::SortedSet is a ::Set. res = obj.map { |element| from(element) } Hamster::SortedSet.new(res) when ::Set res = obj.map { |element| from(element) } Hamster::Set.new(res) when Hamster::Vector, Hamster::Set, Hamster::SortedSet obj.map { |element| from(element) } else obj end end # Create a Ruby object from Hamster data. This method recursively "walks" # the Hamster object, converting {Hamster::Hash} to Ruby `Hash`, # {Hamster::Vector} and {Hamster::Deque} to Ruby `Array`, {Hamster::Set} # to Ruby `Set`, and {Hamster::SortedSet} to Ruby `SortedSet`. Other # objects are left as-is. # # @example # h = Hamster.to_ruby(Hamster.from({ "a" => [1, 2], "b" => "c" })) # # => { "a" => [1, 2], "b" => "c" } # # @return [::Hash, ::Array, ::Set, ::SortedSet, Object] def to_ruby(obj) case obj when Hamster::Hash, ::Hash obj.each_with_object({}) { |keyval, hash| hash[to_ruby(keyval[0])] = to_ruby(keyval[1]) } when Hamster::Vector, ::Array obj.each_with_object([]) { |element, arr| arr << to_ruby(element) } when Hamster::Set, ::Set obj.each_with_object(::Set.new) { |element, set| set << to_ruby(element) } when Hamster::SortedSet, ::SortedSet obj.each_with_object(::SortedSet.new) { |element, set| set << to_ruby(element) } when Hamster::Deque obj.to_a.tap { |arr| arr.map! { |element| to_ruby(element) }} else obj end end end end hamster-3.0.0/lib/hamster/core_ext.rb0000644000004100000410000000010412663306556017562 0ustar www-datawww-datarequire "hamster/core_ext/enumerable" require "hamster/core_ext/io" hamster-3.0.0/lib/hamster/core_ext/0000755000004100000410000000000012663306556017242 5ustar www-datawww-datahamster-3.0.0/lib/hamster/core_ext/struct.rb0000644000004100000410000000031512663306556021112 0ustar www-datawww-dataclass Struct # Implement Struct#to_h for Ruby interpreters which don't have it # (such as MRI 1.9.3 and lower) unless method_defined?(:to_h) def to_h Hash[each_pair.to_a] end end end hamster-3.0.0/lib/hamster/core_ext/enumerable.rb0000644000004100000410000000047312663306556021712 0ustar www-datawww-datarequire "hamster/list" # Monkey-patches to Ruby's built-in `Enumerable` module. # @see http://www.ruby-doc.org/core/Enumerable.html module Enumerable # Return a new {Hamster::List} populated with the items in this `Enumerable` object. # @return [List] def to_list Hamster::List.from_enum(self) end end hamster-3.0.0/lib/hamster/core_ext/io.rb0000644000004100000410000000104612663306556020177 0ustar www-datawww-datarequire "hamster/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 Hamster::LazyList.new do line = gets(sep) if line Hamster::Cons.new(line, to_list) else EmptyList end end end end hamster-3.0.0/lib/hamster/associable.rb0000644000004100000410000000536412663306556020074 0ustar www-datawww-datamodule Hamster # Including `Associable` in your container class gives it an `update_in` # method. # # To mix in `Associable`, your class must implement two methods: # # * `fetch(index, default = (missing_default = true))` # * `put(index, item = yield(get(index)))` # * `get(key)` # # See {Vector#fetch}, {Vector#put}, {Hash#fetch}, and {Hash#put} for examples. module Associable # Return a new container with a deeply nested value modified to the result # of the given code block. When traversing the nested containers # non-existing keys are created with empty `Hash` values. # # The code block receives the existing value of the deeply nested key/index # (or `nil` if it doesn't exist). This is useful for "transforming" the # value associated with a certain key/index. # # Naturally, the original container and sub-containers are left unmodified; # new data structure copies are created along the path as needed. # # @example # v = Hamster::Vector[123, 456, 789, Hamster::Hash["a" => Hamster::Vector[5, 6, 7]]] # v.update_in(3, "a", 1) { |value| value + 9 } # # => Hamster::Vector[123, 456, 789, Hamster::Hash["a" => Hamster::Vector[5, 15, 7]]] # hash = Hamster::Hash["a" => Hamster::Hash["b" => Hamster::Hash["c" => 42]]] # hash.update_in("a", "b", "c") { |value| value + 5 } # # => Hamster::Hash["a" => Hamster::Hash["b" => Hamster::Hash["c" => 47]]] # # @param key_path [Object(s)] List of keys/indexes which form the path to the key to be modified # @yield [value] The previously stored value # @yieldreturn [Object] The new value to store # @return [Associable] 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(fetch(key, nil)) else value = fetch(key, EmptyHash) new_value = value.update_in(*key_path[1..-1], &block) end put(key, new_value) end # Return the value of successively indexing into a collection. # If any of the keys is not present in the collection, return `nil`. # keys that the Hamster type doesn't understand, raises an argument error # # @example # h = Hamster::Hash[:a => 9, :b => Hamster::Vector['a', 'b'], :e => nil] # h.dig(:b, 0) # => "a" # h.dig(:b, 5) # => nil # h.dig(:b, 0, 0) # => nil # h.dig(:b, :a) # ArgumentError # @params keys to fetch from the collection # @return [Object] def dig(key, *rest) value = get(key) if rest.empty? || value.nil? value elsif value.respond_to?(:dig) value.dig(*rest) end end end end hamster-3.0.0/lib/hamster/hash.rb0000644000004100000410000007431412663306556016713 0ustar www-datawww-datarequire "hamster/immutable" require "hamster/undefined" require "hamster/enumerable" require "hamster/trie" require "hamster/set" require "hamster/vector" require "hamster/associable" module Hamster # A `Hamster::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 a `Hamster::Hash`. # # A `Hamster::Hash` can be created in a couple of ways: # # Hamster::Hash.new(font_size: 10, font_family: 'Arial') # Hamster::Hash[first_name: 'John', last_name: 'Smith'] # # Any `Enumerable` object which yields two-element `[key, value]` arrays # can be used to initialize a `Hamster::Hash`: # # Hamster::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 = Hamster::Hash[a: 100, b: 200] # hash.put(:c, 500) # => Hamster::Hash[:a => 100, :b => 200, :c => 500] # hash # => Hamster::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 } # => Hamster::Hash[:a => 300, :b => 200] # # Since it is immutable, all methods which you might expect to "modify" a # `Hamster::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 `Hamster::Hash`. # # Nested data structures can easily be updated using {#update_in}: # # hash = Hamster::Hash["a" => Hamster::Vector[Hamster::Hash["c" => 42]]] # hash.update_in("a", 0, "c") { |value| value + 5 } # # => Hamster::Hash["a" => Hamster::Hash["b" => Hamster::Hash["c" => 47]]] # # While a `Hamster::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, a `Hamster::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 = Hamster::Hash.new { |missing_key| missing_key * 10 } # hash[5] # => 50 class Hash include Immutable include Enumerable include Associable class << self # Create a new `Hash` populated with the given key/value pairs. # # @example # Hamster::Hash["A" => 1, "B" => 2] # => Hamster::Hash["A" => 1, "B" => 2] # Hamster::Hash[["A", 1], ["B", 2]] # => Hamster::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 ||= self.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 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 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 # Hamster::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 # Hamster::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 # Hamster::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 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h["B"] # => 2 # h.get("B") # => 2 # h.get("Elephant") # => nil # # # Hamster Hash with a default proc: # h = Hamster::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 = Hamster::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 = Hamster::Hash["A" => 1, "B" => 2] # h.put("C", 3) # # => Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h.put("B") { |value| value * 10 } # # => Hamster::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 # 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 = Hamster::Hash["a" => Hamster::Hash["b" => Hamster::Hash["c" => 42]]] # hash.update_in("a", "b", "c") { |value| value + 5 } # # => Hamster::Hash["a" => Hamster::Hash["b" => Hamster::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] # 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 # Hamster::Hash["A" => 1, "B" => 2, "C" => 3].delete("B") # # => Hamster::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 # Hamster::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 # # => Hamster::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 # Hamster::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 # # => Hamster::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 # Hamster::Hash["A" => 1, "B" => 2, "C" => 3].each_key { |k| puts "k=#{k}" } # # k=A # k=C # k=B # # => Hamster::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 # Hamster::Hash["A" => 1, "B" => 2, "C" => 3].each_value { |v| puts "v=#{v}" } # # v=1 # v=3 # v=2 # # => Hamster::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 = Hamster::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 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h.select { |k, v| v >= 2 } # # => Hamster::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 = Hamster::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 a `Hamster::Hash`, a built-in Ruby `Hash`, or any `Enumerable` # object which yields `[key, value]` pairs. # # @example # h1 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h2 = Hamster::Hash["C" => 70, "D" => 80] # h1.merge(h2) # # => Hamster::Hash["C" => 70, "A" => 1, "D" => 80, "B" => 2] # h1.merge(h2) { |key, v1, v2| v1 + v2 } # # => Hamster::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 = Hamster::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3] # h.sort { |(k1, v1), (k2, v2)| k1.size <=> k2.size } # # => Hamster::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 = Hamster::Hash["Dog" => 1, "Elephant" => 2, "Lion" => 3] # h.sort_by { |key, value| key.size } # # => Hamster::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 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h.except("A", "C") # => Hamster::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 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h.slice("B", "C") # => Hamster::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 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h.values_at("B", "A", "D") # => Hamster::Vector[2, 1, nil] # # @param wanted [Array] The keys to retrieve # @return [Vector] def values_at(*wanted) array = wanted.map { |key| get(key) } Vector.new(array.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 = Hamster::Hash["A" => 1, "B" => 2, "C" => 3] # h.fetch_values("C", "A") # => Hamster::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 a new {Set} containing the keys from this `Hash`. # # @example # Hamster::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].keys # # => Hamster::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 # Hamster::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].values # # => Hamster::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 # Hamster::Hash["A" => 1, "B" => 2, "C" => 3, "D" => 2].invert # # => Hamster::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 = Hamster::Hash["A" => 1, "B" => [2, 3, 4]] # h.flatten # # => Hamster::Vector["A", 1, "B", [2, 3, 4]] # h.flatten(2) # # => Hamster::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 # Hamster::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 # Hamster::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 # Hamster::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 # Hamster::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) self.eql?(other) || (other.respond_to?(:to_hash) && to_hash.eql?(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 the identical # values, and the two hashes are not identical. # # @param other [Hamster::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 the identical values. # # @param other [Hamster::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 [Hamster::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 [Hamster::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 # 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 `Hamster::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 # result of the default proc). # # @example # h = Hamster::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 = Hamster::Hash.empty end hamster-3.0.0/lib/hamster/set.rb0000644000004100000410000004420712663306556016561 0ustar www-datawww-datarequire "hamster/immutable" require "hamster/undefined" require "hamster/enumerable" require "hamster/hash" require "hamster/trie" require "hamster/sorted_set" require "set" module Hamster # `Hamster::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 Hamster collections. # # Like the `Set` class in Ruby's standard library, which we will call RubySet, # `Hamster::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: # # Hamster::Set.new([1, 2, 3]) # any Enumerable can be used to initialize # Hamster::Set['A', 'B', 'C', 'D'] # # The latter 2 forms of initialization can be used with your own, custom subclasses # of `Hamster::Set`. # # Unlike RubySet, all methods which you might expect to "modify" a `Hamster::Set` # actually return a new set and leave the existing one unchanged. # # @example # set1 = Hamster::Set[1, 2] # => Hamster::Set[1, 2] # set2 = Hamster::Set[1, 2] # => Hamster::Set[1, 2] # set1 == set2 # => true # set3 = set1.add("foo") # => Hamster::Set[1, 2, "foo"] # set3 - set2 # => Hamster::Set["foo"] # set3.subset?(set1) # => false # set1.subset?(set3) # => true # class Set include Immutable include 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 ||= self.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) } end end def initialize(items=[]) @trie = Trie.new(0) items.each { |item| @trie.put!(item, nil) } 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 # Hamster::Set[1, 2, 3].add(4) # => Hamster::Set[1, 2, 4, 3] # Hamster::Set[1, 2, 3].add(2) # => Hamster::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 # Hamster::Set[1, 2, 3].add?(4) # => Hamster::Set[1, 2, 4, 3] # Hamster::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 # Hamster::Set[1, 2, 3].delete(1) # => Hamster::Set[2, 3] # Hamster::Set[1, 2, 3].delete(99) # => Hamster::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 # Hamster::Set[1, 2, 3].delete?(1) # => Hamster::Set[2, 3] # Hamster::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 # Hamster::Set["Dog", "Elephant", "Lion"].each { |e| puts e } # Elephant # Dog # Lion # # => Hamster::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 # Hamster::Set["Dog", "Elephant", "Lion"].reverse_each { |e| puts e } # Lion # Dog # Elephant # # => Hamster::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 # Hamster::Set["Elephant", "Dog", "Lion"].select { |e| e.size >= 4 } # # => Hamster::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 # Hamster::Set["Cat", "Elephant", "Dog", "Lion"].map { |e| e.size } # # => Hamster::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 # Hamster::Set["A", "B", "C"].include?("B") # => true # Hamster::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 # Hamster::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 # Hamster::Set["Elephant", "Dog", "Lion"].sort # # => Hamster::SortedSet["Dog", "Elephant", "Lion"] # Hamster::Set["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } # # => Hamster::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(self.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 # Hamster::Set["Elephant", "Dog", "Lion"].sort_by { |e| e.size } # # => Hamster::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(self.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 # Hamster::Set[1, 2] | Hamster::Set[2, 3] # => Hamster::Set[1, 2, 3] # # @param other [Enumerable] The collection to merge with # @return [Set] def union(other) if other.is_a?(Hamster::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 # Hamster::Set[1, 2] & Hamster::Set[2, 3] # => Hamster::Set[2] # # @param other [Enumerable] The collection to intersect with # @return [Set] def intersection(other) if other.size < @trie.size if other.is_a?(Hamster::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 # Hamster::Set[1, 2] - Hamster::Set[2, 3] # => Hamster::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?(Hamster::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 # Hamster::Set[1, 2] ^ Hamster::Set[2, 3] # => Hamster::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 # Hamster::Set[2, 3].subset?(Hamster::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?(Hamster::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 # Hamster::Set[1, 2, 3].superset?(Hamster::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 # Hamster::Set[2, 3].proper_subset?(Hamster::Set[1, 2, 3]) # => true # Hamster::Set[1, 2, 3].proper_subset?(Hamster::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?(Hamster::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 # Hamster::Set[1, 2, 3].proper_superset?(Hamster::Set[2, 3]) # => true # Hamster::Set[1, 2, 3].proper_superset?(Hamster::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 # Hamster::Set[1, 2].disjoint?(Hamster::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?(Hamster::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 # Hamster::Set[1, 2].intersect?(Hamster::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 # Hamster::Set[Hamster::Set[1, 2], Hamster::Set[3, 4]].flatten # # => Hamster::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 # Hamster::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 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 = Hamster::Set.empty end hamster-3.0.0/lib/hamster/experimental/0000755000004100000410000000000012663306556020127 5ustar www-datawww-datahamster-3.0.0/lib/hamster/experimental/mutable_set.rb0000644000004100000410000000126112663306556022760 0ustar www-datawww-datarequire "hamster/set" require "hamster/read_copy_update" module Hamster # @api private class MutableSet include ReadCopyUpdate def self.[](*items) MutableSet.new(Set[*items]) end def add(item) transform { |set| set.add(item) } end alias :<< :add def add?(item) added = false transform do |set| added = !set.include?(item) set.add(item) end added end def delete(item) transform { |set| set.delete(item) } end def delete?(item) deleted = false transform do |set| deleted = set.include?(item) set.delete(item) end deleted end end end hamster-3.0.0/lib/hamster/experimental/mutable_queue.rb0000644000004100000410000000066612663306556023321 0ustar www-datawww-datarequire "hamster/deque" require "hamster/read_copy_update" module Hamster # @api private class MutableQueue include ReadCopyUpdate def self.[](*items) MutableQueue.new(Deque[*items]) end def enqueue(item) transform { |queue| queue.enqueue(item) } end def dequeue head = nil transform do |queue| head = queue.head queue.dequeue end head end end end hamster-3.0.0/lib/hamster/mutable_hash.rb0000644000004100000410000000110612663306556020411 0ustar www-datawww-datarequire "hamster/hash" require "hamster/read_copy_update" module Hamster # @api private class MutableHash include ReadCopyUpdate def self.[](pairs = {}) MutableHash.new(Hash[pairs]) end def put(key, value = Undefined, &block) transform { |hash| hash.put(key, value, &block) } end def store(key, value) put(key, value) value end alias :[]= :store def delete(key) old_value = nil transform do |hash| old_value = hash.get(key) hash.delete(key) end old_value end end end hamster-3.0.0/lib/hamster/immutable.rb0000644000004100000410000000321412663306556017736 0ustar www-datawww-datamodule Hamster # @private module Immutable def self.included(klass) klass.extend(ClassMethods) klass.instance_eval do include InstanceMethods end end # @private module ClassMethods def new(*args) super.__send__(:immutable!) end def memoize(*names) include MemoizeMethods unless include?(MemoizeMethods) names.each do |name| original_method = "__hamster_immutable_#{name}__" alias_method original_method, name class_eval <<-METHOD, __FILE__, __LINE__ def #{name} if @__hamster_immutable_memory__.instance_variable_defined?(:@#{name}) @__hamster_immutable_memory__.instance_variable_get(:@#{name}) else @__hamster_immutable_memory__.instance_variable_set(:@#{name}, #{original_method}) end end METHOD end end end # @private module MemoizeMethods def immutable! @__hamster_immutable_memory__ = Object.new freeze end end # @private module InstanceMethods def immutable! freeze end def immutable? frozen? end alias_method :__hamster_immutable_dup__, :dup private :__hamster_immutable_dup__ def dup self end def clone self end protected def transform_unless(condition, &block) condition ? self : transform(&block) end def transform(&block) __hamster_immutable_dup__.tap { |copy| copy.instance_eval(&block) }.immutable! end end end end hamster-3.0.0/lib/hamster/vector.rb0000644000004100000410000014134312663306556017267 0ustar www-datawww-datarequire "hamster/immutable" require "hamster/enumerable" require "hamster/hash" require "hamster/associable" module Hamster # 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 # # Hamster::Vector.new([:first, :second, :third]) # Hamster::Vector[1, 2, 3, 4, 5] # # ### Retrieving Items from Vectors # # vector = Hamster::Vector[1, 2, 3, 4, 5] # # vector[0] # => 1 # vector[-1] # => 5 # vector[0,3] # => Hamster::Vector[1, 2, 3] # vector[1..-1] # => Hamster::Vector[2, 3, 4, 5] # vector.first # => 1 # vector.last # => 5 # # ### Creating Modified Vectors # # vector.add(6) # => Hamster::Vector[1, 2, 3, 4, 5, 6] # vector.insert(1, :a, :b) # => Hamster::Vector[1, :a, :b, 2, 3, 4, 5] # vector.delete_at(2) # => Hamster::Vector[1, 2, 4, 5] # vector + [6, 7] # => Hamster::Vector[1, 2, 3, 4, 5, 6, 7] # class Vector include Immutable include Enumerable include Associable # @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 ||= self.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 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 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 # Hamster::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 # Hamster::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 # Hamster::Vector[1, 2].add(99) # => Hamster::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 put(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 # Hamster::Vector[1, 2, 3, 4].put(2, 99) # # => Hamster::Vector[1, 2, 99, 4] # Hamster::Vector[1, 2, 3, 4].put(-1, 99) # # => Hamster::Vector[1, 2, 3, 99] # Hamster::Vector[].put(2, 99) # # => Hamster::Vector[nil, nil, 99] # # @overload put(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 # Hamster::Vector[1, 2, 3, 4].put(2) { |v| v * 10 } # # => Hamster::Vector[1, 2, 30, 4] # # @param index [Integer] The index to update. May be negative. # @return [Vector] def put(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 alias :set :put # 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 = Hamster::Vector[123, 456, 789, Hamster::Hash["a" => Hamster::Vector[5, 6, 7]]] # v.update_in(3, "a", 1) { |value| value + 9 } # # => Hamster::Vector[123, 456, 789, Hamster::Hash["a" => Hamster::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] # Retrieve the item at `index`. If there is none (either the provided index # is too high or too low), return `nil`. # # @example # v = Hamster::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 = Hamster::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 = Hamster::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 = Hamster::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 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 = Hamster::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 = Hamster::Vector["A", "B", "C", "D", "E", "F"] # v[2, 3] # => Hamster::Vector["C", "D", "E"] # v[-2, 3] # => Hamster::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 = Hamster::Vector["A", "B", "C", "D", "E", "F"] # v[2..3] # => Hamster::Vector["C", "D"] # v[-2..100] # => Hamster::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 # Hamster::Vector["A", "B", "C", "D"].insert(2, "X", "Y", "Z") # # => Hamster::Vector["A", "B", "X", "Y", "Z", "C", "D"] # Hamster::Vector[].insert(2, "X", "Y", "Z") # # => Hamster::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 # Hamster::Vector["A", "B", "C", "D"].delete_at(2) # # => Hamster::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 { |a| a.shift }) end # Return a new `Vector` with the last element removed. Return `self` if # empty. # # @example # Hamster::Vector["A", "B", "C"].pop # => Hamster::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 # Hamster::Vector["A", "B"].unshift("Z") # # => Hamster::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 # Hamster::Vector["A", "B", "C"].shift # => Hamster::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 # Hamster::Vector["A", "B", "C"].each { |e| puts "Element: #{e}" } # # Element: A # Element: B # Element: C # # => Hamster::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 # Hamster::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 # Hamster::Vector["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } # # => Hamster::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 # Hamster::Vector["C", "B", "A", "B"].delete("B") # => Hamster::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 # Hamster::Vector[3, 2, 1].map { |e| e * e } # => Hamster::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 # Hamster::Vector[1, 2, 3].flat_map { |x| [x, -x] } # # => Hamster::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 # Hamster::Vector[1, 2, 3, 4].shuffle # => Hamster::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 # Hamster::Vector["A", "B", "C", "B"].uniq # => Hamster::Vector["A", "B", "C"] # Hamster::Vector["a", "A", "b"].uniq(&:upcase) # => Hamster::Vector["a", "b"] # # @return [Vector] def uniq(&block) array = self.to_a if block_given? 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 elsif array.frozen? self.class.new(array.uniq.freeze) elsif array.uniq! # 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 # Hamster::Vector["A", "B", "C"].reverse # => Hamster::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 = Hamster::Vector["A", "B", "C", "D", "E", "F"] # v.rotate(2) # => Hamster::Vector["C", "D", "E", "F", "A", "B"] # v.rotate(-1) # => Hamster::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 = Hamster::Vector["A", Hamster::Vector["B", "C", Hamster::Vector["D"]]] # v.flatten(1) # # => Hamster::Vector["A", "B", "C", Hamster::Vector["D"]] # v.flatten # # => Hamster::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 = self.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 # Hamster::Vector["A", "B", "C"] + ["D", "E"] # # => Hamster::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 = Hamster::Vector["A", "B", "C"] # v2 = Hamster::Vector[1, 2] # v1.zip(v2) # # => Hamster::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 # Hamster::Vector["Elephant", "Dog", "Lion"].sort # # => Hamster::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 # Hamster::Vector["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } # # => Hamster::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 # Hamster::Vector["Elephant", "Dog", "Lion"].sort_by { |e| e.size } # # => Hamster::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 # Hamster::Vector["A", "B", "C", "D", "E", "F"].drop(2) # # => Hamster::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 # Hamster::Vector["A", "B", "C", "D", "E", "F"].take(4) # # => Hamster::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 # Hamster::Vector[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 } # # => Hamster::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 # Hamster::Vector[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 } # # => Hamster::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 # Hamster::Vector["A", "B"] * 3 # # => Hamster::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 # Hamster::Vector["A", "B", "C", "D", "E", "F"].fill("Z") # # => Hamster::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 # Hamster::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3) # # => Hamster::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 # Hamster::Vector["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2) # # => Hamster::Vector["A", "B", "C", "Z", "Z", "F"] # Hamster::Vector["A", "B"].fill("Z", 1, 5) # # => Hamster::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 = Hamster::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] # #=> Hamster::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 self.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 = Hamster::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] # # => Hamster::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 = Hamster::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] # # => Hamster::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| 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 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 = Hamster::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] # # => Hamster::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 = Hamster::Vector[1, 2, 3] # v2 = Hamster::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 # Hamster::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? while true yield build_array[] return self if bump_counters[] end else result = [] while true 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 # Hamster::Vector[["A", 10], ["B", 20], ["C", 30]].transpose # # => Hamster::Vector[Hamster::Vector["A", "B", "C"], Hamster::Vector[10, 20, 30]] # # @return [Vector] # @raise [IndexError] if elements are not of the same size. # @raise [TypeError] if an element can not be implicitly converted to an array (using `#to_ary`) 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) 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 = Hamster::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 # Hamster::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 = Hamster::Vector["A", "B", "C", "D", "E", "F"] # v.values_at(2, 4, 5) # => Hamster::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 = Hamster::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 = Hamster::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 = Hamster::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 = Hamster::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 [::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 = Hamster::Vector.empty end hamster-3.0.0/lib/hamster/version.rb0000644000004100000410000000024712663306556017447 0ustar www-datawww-datamodule Hamster # Current released gem version. Note that master will often have the same # value as a release gem but with different code. VERSION = "3.0.0" end hamster-3.0.0/lib/hamster/read_copy_update.rb0000644000004100000410000000123412663306556021266 0ustar www-datawww-datarequire "forwardable" require "thread" module Hamster # @private module ReadCopyUpdate extend Forwardable def initialize(content) @content = content @lock = Mutex.new end def eql?(other) instance_of?(other.class) && @content.eql?(other.instance_variable_get(:@content)) end alias :== :eql? def_delegator :@content, :inspect def_delegator :@content, :to_s protected def transform @lock.synchronize do @content = yield(@content) end self end private def method_missing(name, *args, &block) @content.send(name, *args, &block) rescue super end end end hamster-3.0.0/lib/hamster/list.rb0000644000004100000410000013542112663306556016740 0ustar www-datawww-datarequire "thread" require "set" require "concurrent/atomics" require "hamster/undefined" require "hamster/enumerable" require "hamster/hash" require "hamster/set" module Hamster class << self # Create a lazy, infinite list. # # The given block is called as necessary to return successive elements of the list. # # @example # Hamster.stream { :hello }.take(3) # # => Hamster::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 # Hamster.interval(5,9) # # => Hamster::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 # Hamster.repeat(:chunky).take(4) # => Hamster::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 # Hamster.replicate(3, :hamster) # #=> Hamster::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 # Hamster.iterate(0) { |i| i.next }.take(5) # # => Hamster::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 `Hamster::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 # Hamster.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 Enumerable # @private CADR = /^c([ad]+)r$/ # Create a new `List` populated with the given items. # # @example # list = Hamster::List[:a, :b, :c] # # => Hamster::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 = Hamster::Cons.allocate items.each do |item| new_node = Hamster::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, Hamster::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 # Hamster::List[:b, :c].add(:a) # # => Hamster::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 # Hamster::List[:a, :b] << :c # # => Hamster::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 # Hamster::List[3, 2, 1].map { |e| e * e } # => Hamster::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 # Hamster::List[1, 2, 3].flat_map { |x| Hamster::List[x, 100] } # # => Hamster::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 # Hamster::List["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } # # => Hamster::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 while true 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 # Hamster::List[1, 3, 5, 7, 6, 4, 2].take_while { |e| e < 5 } # # => Hamster::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 # Hamster::List[1, 3, 5, 7, 6, 4, 2].drop_while { |e| e < 5 } # # => Hamster::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 # Hamster::List[1, 3, 5, 7, 6, 4, 2].take(3) # # => Hamster::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 # Hamster::List["A", "B", "C"].pop # => Hamster::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 # Hamster::List[1, 3, 5, 7, 6, 4, 2].drop(3) # # => Hamster::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 # Hamster::List[1, 2, 3].append(Hamster::List[4, 5]) # # => Hamster::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 # Hamster::List["A", "B", "C"].reverse # => Hamster::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 # Hamster::List["A", "B", "C"].zip(Hamster::List[1, 2, 3]) # # => Hamster::List[Hamster::List["A", 1], Hamster::List["B", 2], Hamster::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 = Hamster.iterate(1, &:next) # list2 = Hamster.iterate(2) { |n| n * 2 } # list3 = Hamster.iterate(3) { |n| n * 3 } # # # Now we transpose our 3 infinite "rows" into an infinite series of 3-element "columns" # Hamster::List[list1, list2, list3].transpose.take(4) # # => Hamster::List[ # # Hamster::List[1, 2, 3], # # Hamster::List[2, 4, 9], # # Hamster::List[3, 8, 27], # # Hamster::List[4, 16, 81]] # # @return [List] def transpose return EmptyList if empty? LazyList.new do next EmptyList if any? { |list| list.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 # Hamster::List[1, 2, 3].cycle.take(10) # # => Hamster::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 = Hamster::List["A", "B", "C", "D", "E", "F"] # l.rotate(2) # => Hamster::List["C", "D", "E", "F", "A", "B"] # l.rotate(-1) # => Hamster::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 # Hamster::List["a", "b", "c", "d"].split_at(2) # # => [Hamster::List["a", "b"], Hamster::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 # Hamster::List[4, 3, 5, 2, 1].span { |x| x > 2 } # # => [Hamster::List[4, 3, 5], Hamster::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 # Hamster::List[1, 3, 4, 2, 5].break { |x| x > 3 } # # => [Hamster::List[1, 3], Hamster::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 # Hamster::List["Elephant", "Dog", "Lion"].sort # # => Hamster::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 # Hamster::List["Elephant", "Dog", "Lion"].sort { |a,b| a.size <=> b.size } # # => Hamster::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 # Hamster::List["Elephant", "Dog", "Lion"].sort_by { |e| e.size } # # => Hamster::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 # Hamster::List["one", "two", "three"].intersperse(" ") # # => Hamster::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 # Hamster::List[:a, :b, :a, :c, :b].uniq # => Hamster::List[:a, :b, :c] # Hamster::List["a", "A", "b"].uniq(&:upcase) # => Hamster::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 # Hamster::List[1, 2].union(Hamster::List[2, 3]) # => Hamster::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 # Hamster::List["a", "b", "c"].init # => Hamster::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 # Hamster::List[1,2,3].tails # # => Hamster::List[ # # Hamster::List[1, 2, 3], # # Hamster::List[2, 3], # # Hamster::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 # Hamster::List[1,2,3].inits # # => Hamster::List[ # # Hamster::List[1], # # Hamster::List[1, 2], # # Hamster::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 # Hamster::List[1,2,3].combination(2) # # => Hamster::List[ # # Hamster::List[1, 2], # # Hamster::List[1, 3], # # Hamster::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) # # => Hamster::List[ # # Hamster::List["a", "b", "c", "d", "e"], # # Hamster::List["f", "g", "h", "i", "j"], # # Hamster::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 # Hamster::List[Hamster::List[1, 2], Hamster::List[3, 4]].flatten # # => Hamster::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 # Hamster::List["a", "b", "ab"].group_by { |e| e.size } # # Hamster::Hash[ # # 1 => Hamster::List["b", "a"], # # 2 => Hamster::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 = Hamster::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 = Hamster::List["A", "B", "C", "D", "E", "F"] # l[2, 3] # => Hamster::List["C", "D", "E"] # l[-2, 3] # => Hamster::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 = Hamster::List["A", "B", "C", "D", "E", "F"] # l[2..3] # => Hamster::List["C", "D"] # l[-2..100] # => Hamster::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 # Hamster::List[1, 2, 3, 4].indices(2) # # => Hamster::List[1] # # @overload indices # Pass each item successively to the block. Return a list of indices # where the block returns true. # # @yield [item] # @example # Hamster::List[1, 2, 3, 4].indices { |e| e.even? } # # => Hamster::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 while true 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 = Hamster::List[1, -3, -5] # list_2 = Hamster::List[-2, 4, 6] # Hamster::List[list_1, list_2].merge { |a,b| a.abs <=> b.abs } # # => Hamster::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 = Hamster::List[1, -3, -5] # list_2 = Hamster::List[-2, 4, 6] # Hamster::List[list_1, list_2].merge_by { |x| x.abs } # # => Hamster::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 # Hamster::List["A", "D", "E"].insert(1, "B", "C") # => Hamster::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 # Hamster::List[:a, :b, :a, :a, :c].delete(:a) # => Hamster::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 # Hamster::List[1, 2, 3].delete_at(1) # => Hamster::List[1, 3] # Hamster::List[1, 2, 3].delete_at(-1) # => Hamster::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 # Hamster::List["A", "B", "C", "D", "E", "F"].fill("Z") # # => Hamster::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 # Hamster::List["A", "B", "C", "D", "E", "F"].fill("Z", 3) # # => Hamster::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 # Hamster::List["A", "B", "C", "D", "E", "F"].fill("Z", 3, 2) # # => Hamster::List["A", "B", "C", "Z", "Z", "F"] # Hamster::List["A", "B"].fill("Z", 1, 5) # # => Hamster::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 # Hamster::List[1, 2, 3].permutation.to_a # # => [Hamster::List[1, 2, 3], # # Hamster::List[2, 1, 3], # # Hamster::List[2, 3, 1], # # Hamster::List[1, 3, 2], # # Hamster::List[3, 1, 2], # # Hamster::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 # Hamster::List[1, 2, 3].subsequences { |list| p list } # # prints: # # Hamster::List[1] # # Hamster::List[1, 2] # # Hamster::List[1, 2, 3] # # Hamster::List[2] # # Hamster::List[2, 3] # # Hamster::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 # Hamster::List[1, 2, 3, 4, 5, 6].partition { |x| x.even? } # # => [Hamster::List[2, 4, 6], Hamster::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 = "Hamster::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, "Hamster::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 = Hamster::List[nil, Hamster::List[1]] # l.car # => nil # l.cdr # => Hamster::List[Hamster::List[1]] # l.cadr # => Hamster::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::AtomicReference.new(0) # haven't yet run block @size = nil end def head realize if @atomic.get != 2 @head end alias :first :head def tail realize if @atomic.get != 2 @tail end def empty? realize if @atomic.get != 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 while true # try to "claim" the right to run the block which realizes target if @atomic.compare_and_swap(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.set(0) MUTEX.synchronize { QUEUE.broadcast } raise end @atomic.set(2) MUTEX.synchronize { QUEUE.broadcast } return end # we failed to "claim" it, another thread must be running it if @atomic.get == 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.get == 1 end elsif @atomic.get == 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 while true 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 while true 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 hamster-3.0.0/lib/hamster/trie.rb0000644000004100000410000002356512663306556016735 0ustar www-datawww-datamodule Hamster # @private class Trie def self.[](pairs) result = self.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(significant_bits, size = 0, entries = [], children = []) @significant_bits = significant_bits @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) # TODO: Using block.call here is slower than using yield by 5-10%, but # the latter segfaults on ruby 2.2 and above. Once that is fixed and # broken versions are sufficiently old, we should revert back to yield # with a warning that the broken versions are unsupported. # # For more context: # * https://bugs.ruby-lang.org/issues/11451 # * https://github.com/hamstergem/hamster/issues/189 @entries.each { |entry| block.call(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 wes 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(@significant_bits, @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(@significant_bits, @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(@significant_bits, new_self_size, @entries, children) end else children = @children.dup children[index] = Trie.new(@significant_bits + 5).put!(key, value) Trie.new(@significant_bits, @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(@significant_bits + 5).put!(key, value) new_size += 1 end end end if new_entries || new_children Trie.new(@significant_bits, 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(@significant_bits + 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(@significant_bits) 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(@significant_bits, 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(@significant_bits, 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(@significant_bits, @size - 1, entries, children || @children) end end private def index_for(key) (key.hash.abs >> @significant_bits) & 31 end def copy_size(copy) copy ? copy.size : 0 end end # @private EmptyTrie = Hamster::Trie.new(0) end hamster-3.0.0/lib/hamster/sorted_set.rb0000644000004100000410000012753412663306556020146 0ustar www-datawww-datarequire "hamster/immutable" require "hamster/enumerable" module Hamster # 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: # # Hamster::SortedSet.new([1, 2, 3]) # any Enumerable can be used to initialize # Hamster::SortedSet['A', 'B', 'C', 'D'] # # Or if you want to use a custom ordering: # # Hamster::SortedSet.new([1,2,3]) { |a, b| -a <=> -b } # Hamster::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 Hamster 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 include 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 ||= self.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) result = allocate result.instance_variable_set(:@node, node) result end end def initialize(items=[], &block) items = items.to_a if block if block.arity == 1 || block.arity == -1 comparator = lambda { |a,b| block.call(a) <=> block.call(b) } items = items.sort_by(&block) else comparator = block items = items.sort(&block) end @node = AVLNode.from_items(items, comparator) else @node = PlainAVLNode.from_items(items.sort) end 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 # Hamster::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 # Hamster::SortedSet["Dog", "Lion"].add("Elephant") # # => Hamster::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 # Hamster::SortedSet["Dog", "Lion"].add?("Elephant") # # => Hamster::SortedSet["Dog", "Elephant", "Lion"] # Hamster::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 # Hamster::SortedSet["A", "B", "C"].delete("B") # # => Hamster::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 # Hamster::SortedSet["A", "B", "C"].delete?("B") # # => Hamster::SortedSet["A", "C"] # Hamster::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 # Hamster::SortedSet["A", "B", "C", "D"].delete_at(2) # # => Hamster::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 = Hamster::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 # v = Hamster::SortedSet["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 = Hamster::SortedSet["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 = Hamster::SortedSet["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 >= -@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 = Hamster::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 = Hamster::SortedSet["A", "B", "C", "D", "E", "F"] # s[2, 3] # => Hamster::SortedSet["C", "D", "E"] # s[-2, 3] # => Hamster::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 = Hamster::SortedSet["A", "B", "C", "D", "E", "F"] # s[2..3] # => Hamster::SortedSet["C", "D"] # s[-2..100] # => Hamster::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 = Hamster::SortedSet["A", "B", "C", "D", "E", "F"] # s.values_at(2, 4, 5) # => Hamster::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 # Hamster::SortedSet["A", "B", "C"].each { |e| puts "Element: #{e}" } # # Element: A # Element: B # Element: C # # => Hamster::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 # Hamster::SortedSet["A", "B", "C"].reverse_each { |e| puts "Element: #{e}" } # # Element: C # Element: B # Element: A # # => Hamster::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 # Hamster::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 # Hamster::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 # Hamster::SortedSet["Bird", "Cow", "Elephant"].select { |e| e.size >= 4 } # # => Hamster::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 # Hamster::SortedSet[1, 2, 3].map { |e| -(e * e) } # # => Hamster::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 # Hamster::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 # Hamster::SortedSet["Bird", "Cow", "Elephant"].sort { |a, b| a.size <=> b.size } # # => Hamster::SortedSet["Cow", "Bird", "Elephant"] # Hamster::SortedSet["Bird", "Cow", "Elephant"].sort_by { |e| e.size } # # => Hamster::SortedSet["Cow", "Bird", "Elephant"] # # @return [SortedSet] def sort(&block) if block self.class.new(self.to_a, &block) else self.class.new(self.to_a.sort) 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 = Hamster::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 = Hamster::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 # Hamster::SortedSet["A", "B", "C", "D", "E", "F"].drop(2) # # => Hamster::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 # Hamster::SortedSet["A", "B", "C", "D", "E", "F"].take(4) # # => Hamster::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 # Hamster::SortedSet[2, 4, 6, 7, 8, 9].drop_while { |e| e.even? } # # => Hamster::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 # Hamster::SortedSet[2, 4, 6, 7, 8, 9].take_while { |e| e.even? } # # => Hamster::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 # Hamster::SortedSet[1, 2] | Hamster::SortedSet[2, 3] # # => Hamster::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 # Hamster::SortedSet[1, 2] & Hamster::SortedSet[2, 3] # # => Hamster::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 # Hamster::SortedSet[1, 2] - Hamster::SortedSet[2, 3] # # => Hamster::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 # Hamster::SortedSet[1, 2] ^ Hamster::SortedSet[2, 3] # # => Hamster::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 # Hamster::SortedSet[2, 3].subset?(Hamster::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 # Hamster::SortedSet[1, 2, 3].superset?(Hamster::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 # Hamster::SortedSet[2, 3].proper_subset?(Hamster::SortedSet[1, 2, 3]) # => true # Hamster::SortedSet[1, 2, 3].proper_subset?(Hamster::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 # Hamster::SortedSet[1, 2, 3].proper_superset?(Hamster::SortedSet[2, 3]) # => true # Hamster::SortedSet[1, 2, 3].proper_superset?(Hamster::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 # Hamster::SortedSet[1, 2].disjoint?(Hamster::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 # Hamster::SortedSet[1, 2].intersect?(Hamster::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 = Hamster::SortedSet[2, 4, 6, 8, 10] # s.above(6) # # => Hamster::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 = Hamster::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 = Hamster::SortedSet[2, 4, 6, 8, 10] # s.below(6) # # => Hamster::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 = Hamster::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 = Hamster::SortedSet[2, 4, 6, 8, 10] # s.from(6) # # => Hamster::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 = Hamster::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 = Hamster::SortedSet[2, 4, 6, 8, 10] # s.upto(6) # # => Hamster::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 = Hamster::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 = Hamster::SortedSet[2, 4, 6, 8, 10] # s.between(5, 8) # # => Hamster::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 = Hamster::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 # Hamster::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 = self.to_enum, other.to_enum while true return false if !a.next.eql?(b.next) end rescue StopIteration true end # See `Object#hash`. # @return [Integer] def hash reduce(0) { |hash, item| (hash << 5) - hash + item.hash } end # @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 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 def from_items(items) AVLNode.from_items(items.sort(&@comparator), @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) AVLNode.from_items(items.sort(&@comparator), @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 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 def from_items(items) PlainAVLNode.from_items(items.sort) 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.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 = Hamster::SortedSet.empty end hamster-3.0.0/lib/hamster/enumerable.rb0000644000004100000410000001227412663306556020104 0ustar www-datawww-datamodule Hamster # Helper module for Hamster's sequential collections # # Classes including `Hamster::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(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 self.equal?(other) enum1, enum2 = self.to_enum, other.to_enum loop do item1 = enum1.next item2 = enum2.next comp = (item1 <=> item2) return comp if comp != 0 end size1, size2 = self.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) self.eql?(other) || other.respond_to?(:to_ary) && to_ary.eql?(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 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 hamster-3.0.0/lib/hamster/undefined.rb0000644000004100000410000000007112663306556017716 0ustar www-datawww-datamodule Hamster # @private module Undefined end end hamster-3.0.0/lib/hamster/deque.rb0000644000004100000410000001563112663306556017070 0ustar www-datawww-datarequire "hamster/immutable" require "hamster/list" module Hamster # 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`: # # Hamster::Deque.new([:first, :second, :third]) # Hamster::Deque[1, 2, 3, 4, 5] # # Or you can start with an empty deque and build it up: # # Hamster::Deque.empty.push('b').push('c').unshift('a') # # Like all Hamster 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 = Hamster::Deque.empty # => Hamster::Deque[] # deque = deque.push('a').push('b').push('c') # => Hamster::Deque['a', 'b', 'c'] # deque.first # => 'a' # deque.last # => 'c' # deque = deque.shift # => Hamster::Deque['b', 'c'] # # @see http://en.wikipedia.org/wiki/Deque "Deque" on Wikipedia # class Deque include Immutable 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 ||= self.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 = Hamster::List.from_enum(items) @rear = EmptyList 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 # Hamster::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 # Hamster::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 # Hamster::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 `item` added at the end. # # @example # Hamster::Deque["A", "B", "C"].add("Z") # # => Hamster::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 # Hamster::Deque["A", "B", "C"].pop # # => Hamster::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 # Hamster::Deque["A", "B", "C"].unshift("Z") # # => Hamster::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 # Hamster::Deque["A", "B", "C"].shift # # => Hamster::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 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 { |a| a.reverse! }) end alias :entries :to_a alias :to_ary :to_a # Return a {List} with the same elements, in the same order. # @return [Hamster::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 { |a| a.reverse! }.each { |obj| result << ', ' if i > 0; result << obj.inspect; i += 1 } result << "]" end # @private def pretty_print(pp) pp.group(1, "#{self.class}[", "]") do pp.breakable '' pp.seplist(self.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 = Hamster::Deque.empty end hamster-3.0.0/metadata.yml0000644000004100000410000010072712663306556015533 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: hamster version: !ruby/object:Gem::Version version: 3.0.0 platform: ruby authors: - Simon Harris autorequire: bindir: bin cert_chain: [] date: 2016-02-20 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: concurrent-ruby requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.3' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.1' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.1' - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.8' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.8' - !ruby/object:Gem::Dependency name: pry requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.9' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.9' - !ruby/object:Gem::Dependency name: pry-doc requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.6' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.6' - !ruby/object:Gem::Dependency name: benchmark-ips requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.1' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.1' - !ruby/object:Gem::Dependency name: codeclimate-test-reporter requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.4' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.4' description: Efficient, immutable, thread-safe collection classes for Ruby email: - haruki_zaemon@mac.com executables: [] extensions: [] extra_rdoc_files: [] files: - LICENSE - lib/hamster.rb - lib/hamster/associable.rb - lib/hamster/core_ext.rb - lib/hamster/core_ext/enumerable.rb - lib/hamster/core_ext/io.rb - lib/hamster/core_ext/struct.rb - lib/hamster/deque.rb - lib/hamster/enumerable.rb - lib/hamster/experimental/mutable_queue.rb - lib/hamster/experimental/mutable_set.rb - lib/hamster/hash.rb - lib/hamster/immutable.rb - lib/hamster/list.rb - lib/hamster/mutable_hash.rb - lib/hamster/nested.rb - lib/hamster/read_copy_update.rb - lib/hamster/set.rb - lib/hamster/sorted_set.rb - lib/hamster/trie.rb - lib/hamster/undefined.rb - lib/hamster/vector.rb - lib/hamster/version.rb - spec/fixtures/io_spec.txt - spec/lib/hamster/associable/associable_spec.rb - spec/lib/hamster/core_ext/array_spec.rb - spec/lib/hamster/core_ext/enumerable_spec.rb - spec/lib/hamster/core_ext/io_spec.rb - spec/lib/hamster/deque/clear_spec.rb - spec/lib/hamster/deque/construction_spec.rb - spec/lib/hamster/deque/copying_spec.rb - spec/lib/hamster/deque/dequeue_spec.rb - spec/lib/hamster/deque/empty_spec.rb - spec/lib/hamster/deque/enqueue_spec.rb - spec/lib/hamster/deque/first_spec.rb - spec/lib/hamster/deque/inspect_spec.rb - spec/lib/hamster/deque/last_spec.rb - spec/lib/hamster/deque/marshal_spec.rb - spec/lib/hamster/deque/new_spec.rb - spec/lib/hamster/deque/pop_spec.rb - spec/lib/hamster/deque/pretty_print_spec.rb - spec/lib/hamster/deque/push_spec.rb - spec/lib/hamster/deque/random_modification_spec.rb - spec/lib/hamster/deque/shift_spec.rb - spec/lib/hamster/deque/size_spec.rb - spec/lib/hamster/deque/to_a_spec.rb - spec/lib/hamster/deque/to_ary_spec.rb - spec/lib/hamster/deque/to_list_spec.rb - spec/lib/hamster/deque/unshift_spec.rb - spec/lib/hamster/experimental/mutable_set/add_qm_spec.rb - spec/lib/hamster/experimental/mutable_set/add_spec.rb - spec/lib/hamster/experimental/mutable_set/delete_qm_spec.rb - spec/lib/hamster/experimental/mutable_set/delete_spec.rb - spec/lib/hamster/hash/all_spec.rb - spec/lib/hamster/hash/any_spec.rb - spec/lib/hamster/hash/assoc_spec.rb - spec/lib/hamster/hash/clear_spec.rb - spec/lib/hamster/hash/construction_spec.rb - spec/lib/hamster/hash/copying_spec.rb - spec/lib/hamster/hash/default_proc_spec.rb - spec/lib/hamster/hash/delete_spec.rb - spec/lib/hamster/hash/dig_spec.rb - spec/lib/hamster/hash/each_spec.rb - spec/lib/hamster/hash/each_with_index_spec.rb - spec/lib/hamster/hash/empty_spec.rb - spec/lib/hamster/hash/eql_spec.rb - spec/lib/hamster/hash/except_spec.rb - spec/lib/hamster/hash/fetch_spec.rb - spec/lib/hamster/hash/fetch_values_spec.rb - spec/lib/hamster/hash/find_spec.rb - spec/lib/hamster/hash/flat_map_spec.rb - spec/lib/hamster/hash/flatten_spec.rb - spec/lib/hamster/hash/get_spec.rb - spec/lib/hamster/hash/has_key_spec.rb - spec/lib/hamster/hash/has_value_spec.rb - spec/lib/hamster/hash/hash_spec.rb - spec/lib/hamster/hash/immutable_spec.rb - spec/lib/hamster/hash/inspect_spec.rb - spec/lib/hamster/hash/invert_spec.rb - spec/lib/hamster/hash/key_spec.rb - spec/lib/hamster/hash/keys_spec.rb - spec/lib/hamster/hash/map_spec.rb - spec/lib/hamster/hash/marshal_spec.rb - spec/lib/hamster/hash/merge_spec.rb - spec/lib/hamster/hash/min_max_spec.rb - spec/lib/hamster/hash/new_spec.rb - spec/lib/hamster/hash/none_spec.rb - spec/lib/hamster/hash/partition_spec.rb - spec/lib/hamster/hash/pretty_print_spec.rb - spec/lib/hamster/hash/put_spec.rb - spec/lib/hamster/hash/reduce_spec.rb - spec/lib/hamster/hash/reject_spec.rb - spec/lib/hamster/hash/reverse_each_spec.rb - spec/lib/hamster/hash/sample_spec.rb - spec/lib/hamster/hash/select_spec.rb - spec/lib/hamster/hash/size_spec.rb - spec/lib/hamster/hash/slice_spec.rb - spec/lib/hamster/hash/sort_spec.rb - spec/lib/hamster/hash/store_spec.rb - spec/lib/hamster/hash/subset_spec.rb - spec/lib/hamster/hash/superset_spec.rb - spec/lib/hamster/hash/take_spec.rb - spec/lib/hamster/hash/to_a_spec.rb - spec/lib/hamster/hash/to_hash_spec.rb - spec/lib/hamster/hash/to_proc_spec.rb - spec/lib/hamster/hash/values_at_spec.rb - spec/lib/hamster/hash/values_spec.rb - spec/lib/hamster/immutable/copying_spec.rb - spec/lib/hamster/immutable/immutable_spec.rb - spec/lib/hamster/immutable/memoize_spec.rb - spec/lib/hamster/immutable/new_spec.rb - spec/lib/hamster/immutable/transform_spec.rb - spec/lib/hamster/immutable/transform_unless_spec.rb - spec/lib/hamster/list/add_spec.rb - spec/lib/hamster/list/all_spec.rb - spec/lib/hamster/list/any_spec.rb - spec/lib/hamster/list/append_spec.rb - spec/lib/hamster/list/at_spec.rb - spec/lib/hamster/list/break_spec.rb - spec/lib/hamster/list/cadr_spec.rb - spec/lib/hamster/list/chunk_spec.rb - spec/lib/hamster/list/clear_spec.rb - spec/lib/hamster/list/combination_spec.rb - spec/lib/hamster/list/compact_spec.rb - spec/lib/hamster/list/compare_spec.rb - spec/lib/hamster/list/cons_spec.rb - spec/lib/hamster/list/construction_spec.rb - spec/lib/hamster/list/copying_spec.rb - spec/lib/hamster/list/count_spec.rb - spec/lib/hamster/list/cycle_spec.rb - spec/lib/hamster/list/delete_at_spec.rb - spec/lib/hamster/list/delete_spec.rb - spec/lib/hamster/list/drop_spec.rb - spec/lib/hamster/list/drop_while_spec.rb - spec/lib/hamster/list/each_slice_spec.rb - spec/lib/hamster/list/each_spec.rb - spec/lib/hamster/list/each_with_index_spec.rb - spec/lib/hamster/list/empty_spec.rb - spec/lib/hamster/list/eql_spec.rb - spec/lib/hamster/list/fill_spec.rb - spec/lib/hamster/list/find_all_spec.rb - spec/lib/hamster/list/find_index_spec.rb - spec/lib/hamster/list/find_spec.rb - spec/lib/hamster/list/flat_map_spec.rb - spec/lib/hamster/list/flatten_spec.rb - spec/lib/hamster/list/grep_spec.rb - spec/lib/hamster/list/group_by_spec.rb - spec/lib/hamster/list/hash_spec.rb - spec/lib/hamster/list/head_spec.rb - spec/lib/hamster/list/include_spec.rb - spec/lib/hamster/list/index_spec.rb - spec/lib/hamster/list/indices_spec.rb - spec/lib/hamster/list/init_spec.rb - spec/lib/hamster/list/inits_spec.rb - spec/lib/hamster/list/insert_spec.rb - spec/lib/hamster/list/inspect_spec.rb - spec/lib/hamster/list/intersperse_spec.rb - spec/lib/hamster/list/join_spec.rb - spec/lib/hamster/list/last_spec.rb - spec/lib/hamster/list/ltlt_spec.rb - spec/lib/hamster/list/map_spec.rb - spec/lib/hamster/list/maximum_spec.rb - spec/lib/hamster/list/merge_by_spec.rb - spec/lib/hamster/list/merge_spec.rb - spec/lib/hamster/list/minimum_spec.rb - spec/lib/hamster/list/multithreading_spec.rb - spec/lib/hamster/list/none_spec.rb - spec/lib/hamster/list/one_spec.rb - spec/lib/hamster/list/partition_spec.rb - spec/lib/hamster/list/permutation_spec.rb - spec/lib/hamster/list/pop_spec.rb - spec/lib/hamster/list/product_spec.rb - spec/lib/hamster/list/reduce_spec.rb - spec/lib/hamster/list/reject_spec.rb - spec/lib/hamster/list/reverse_spec.rb - spec/lib/hamster/list/rotate_spec.rb - spec/lib/hamster/list/sample_spec.rb - spec/lib/hamster/list/select_spec.rb - spec/lib/hamster/list/size_spec.rb - spec/lib/hamster/list/slice_spec.rb - spec/lib/hamster/list/sorting_spec.rb - spec/lib/hamster/list/span_spec.rb - spec/lib/hamster/list/split_at_spec.rb - spec/lib/hamster/list/subsequences_spec.rb - spec/lib/hamster/list/sum_spec.rb - spec/lib/hamster/list/tail_spec.rb - spec/lib/hamster/list/tails_spec.rb - spec/lib/hamster/list/take_spec.rb - spec/lib/hamster/list/take_while_spec.rb - spec/lib/hamster/list/to_a_spec.rb - spec/lib/hamster/list/to_ary_spec.rb - spec/lib/hamster/list/to_list_spec.rb - spec/lib/hamster/list/to_set_spec.rb - spec/lib/hamster/list/transpose_spec.rb - spec/lib/hamster/list/union_spec.rb - spec/lib/hamster/list/uniq_spec.rb - spec/lib/hamster/list/zip_spec.rb - spec/lib/hamster/nested/construction_spec.rb - spec/lib/hamster/set/add_spec.rb - spec/lib/hamster/set/all_spec.rb - spec/lib/hamster/set/any_spec.rb - spec/lib/hamster/set/clear_spec.rb - spec/lib/hamster/set/compact_spec.rb - spec/lib/hamster/set/construction_spec.rb - spec/lib/hamster/set/copying_spec.rb - spec/lib/hamster/set/count_spec.rb - spec/lib/hamster/set/delete_spec.rb - spec/lib/hamster/set/difference_spec.rb - spec/lib/hamster/set/disjoint_spec.rb - spec/lib/hamster/set/each_spec.rb - spec/lib/hamster/set/empty_spec.rb - spec/lib/hamster/set/eqeq_spec.rb - spec/lib/hamster/set/eql_spec.rb - spec/lib/hamster/set/exclusion_spec.rb - spec/lib/hamster/set/find_spec.rb - spec/lib/hamster/set/first_spec.rb - spec/lib/hamster/set/flatten_spec.rb - spec/lib/hamster/set/grep_spec.rb - spec/lib/hamster/set/grep_v_spec.rb - spec/lib/hamster/set/group_by_spec.rb - spec/lib/hamster/set/hash_spec.rb - spec/lib/hamster/set/immutable_spec.rb - spec/lib/hamster/set/include_spec.rb - spec/lib/hamster/set/inspect_spec.rb - spec/lib/hamster/set/intersect_spec.rb - spec/lib/hamster/set/intersection_spec.rb - spec/lib/hamster/set/join_spec.rb - spec/lib/hamster/set/map_spec.rb - spec/lib/hamster/set/marshal_spec.rb - spec/lib/hamster/set/maximum_spec.rb - spec/lib/hamster/set/minimum_spec.rb - spec/lib/hamster/set/new_spec.rb - spec/lib/hamster/set/none_spec.rb - spec/lib/hamster/set/one_spec.rb - spec/lib/hamster/set/partition_spec.rb - spec/lib/hamster/set/product_spec.rb - spec/lib/hamster/set/reduce_spec.rb - spec/lib/hamster/set/reject_spec.rb - spec/lib/hamster/set/reverse_each_spec.rb - spec/lib/hamster/set/sample_spec.rb - spec/lib/hamster/set/select_spec.rb - spec/lib/hamster/set/size_spec.rb - spec/lib/hamster/set/sorting_spec.rb - spec/lib/hamster/set/subset_spec.rb - spec/lib/hamster/set/sum_spec.rb - spec/lib/hamster/set/superset_spec.rb - spec/lib/hamster/set/to_a_spec.rb - spec/lib/hamster/set/to_list_spec.rb - spec/lib/hamster/set/to_set_spec.rb - spec/lib/hamster/set/union_spec.rb - spec/lib/hamster/sorted_set/above_spec.rb - spec/lib/hamster/sorted_set/add_spec.rb - spec/lib/hamster/sorted_set/at_spec.rb - spec/lib/hamster/sorted_set/below_spec.rb - spec/lib/hamster/sorted_set/between_spec.rb - spec/lib/hamster/sorted_set/clear_spec.rb - spec/lib/hamster/sorted_set/copying_spec.rb - spec/lib/hamster/sorted_set/delete_at_spec.rb - spec/lib/hamster/sorted_set/delete_spec.rb - spec/lib/hamster/sorted_set/difference_spec.rb - spec/lib/hamster/sorted_set/disjoint_spec.rb - spec/lib/hamster/sorted_set/drop_spec.rb - spec/lib/hamster/sorted_set/drop_while_spec.rb - spec/lib/hamster/sorted_set/each_spec.rb - spec/lib/hamster/sorted_set/empty_spec.rb - spec/lib/hamster/sorted_set/eql_spec.rb - spec/lib/hamster/sorted_set/exclusion_spec.rb - spec/lib/hamster/sorted_set/fetch_spec.rb - spec/lib/hamster/sorted_set/find_index_spec.rb - spec/lib/hamster/sorted_set/first_spec.rb - spec/lib/hamster/sorted_set/from_spec.rb - spec/lib/hamster/sorted_set/group_by_spec.rb - spec/lib/hamster/sorted_set/include_spec.rb - spec/lib/hamster/sorted_set/inspect_spec.rb - spec/lib/hamster/sorted_set/intersect_spec.rb - spec/lib/hamster/sorted_set/intersection_spec.rb - spec/lib/hamster/sorted_set/last_spec.rb - spec/lib/hamster/sorted_set/map_spec.rb - spec/lib/hamster/sorted_set/marshal_spec.rb - spec/lib/hamster/sorted_set/maximum_spec.rb - spec/lib/hamster/sorted_set/minimum_spec.rb - spec/lib/hamster/sorted_set/new_spec.rb - spec/lib/hamster/sorted_set/reverse_each_spec.rb - spec/lib/hamster/sorted_set/sample_spec.rb - spec/lib/hamster/sorted_set/select_spec.rb - spec/lib/hamster/sorted_set/size_spec.rb - spec/lib/hamster/sorted_set/slice_spec.rb - spec/lib/hamster/sorted_set/sorting_spec.rb - spec/lib/hamster/sorted_set/subset_spec.rb - spec/lib/hamster/sorted_set/superset_spec.rb - spec/lib/hamster/sorted_set/take_spec.rb - spec/lib/hamster/sorted_set/take_while_spec.rb - spec/lib/hamster/sorted_set/to_set_spec.rb - spec/lib/hamster/sorted_set/union_spec.rb - spec/lib/hamster/sorted_set/up_to_spec.rb - spec/lib/hamster/sorted_set/values_at_spec.rb - spec/lib/hamster/vector/add_spec.rb - spec/lib/hamster/vector/any_spec.rb - spec/lib/hamster/vector/assoc_spec.rb - spec/lib/hamster/vector/bsearch_spec.rb - spec/lib/hamster/vector/clear_spec.rb - spec/lib/hamster/vector/combination_spec.rb - spec/lib/hamster/vector/compact_spec.rb - spec/lib/hamster/vector/compare_spec.rb - spec/lib/hamster/vector/concat_spec.rb - spec/lib/hamster/vector/copying_spec.rb - spec/lib/hamster/vector/count_spec.rb - spec/lib/hamster/vector/delete_at_spec.rb - spec/lib/hamster/vector/delete_spec.rb - spec/lib/hamster/vector/dig_spec.rb - spec/lib/hamster/vector/drop_spec.rb - spec/lib/hamster/vector/drop_while_spec.rb - spec/lib/hamster/vector/each_index_spec.rb - spec/lib/hamster/vector/each_spec.rb - spec/lib/hamster/vector/each_with_index_spec.rb - spec/lib/hamster/vector/empty_spec.rb - spec/lib/hamster/vector/eql_spec.rb - spec/lib/hamster/vector/fetch_spec.rb - spec/lib/hamster/vector/fill_spec.rb - spec/lib/hamster/vector/first_spec.rb - spec/lib/hamster/vector/flat_map_spec.rb - spec/lib/hamster/vector/flatten_spec.rb - spec/lib/hamster/vector/get_spec.rb - spec/lib/hamster/vector/group_by_spec.rb - spec/lib/hamster/vector/include_spec.rb - spec/lib/hamster/vector/insert_spec.rb - spec/lib/hamster/vector/inspect_spec.rb - spec/lib/hamster/vector/join_spec.rb - spec/lib/hamster/vector/last_spec.rb - spec/lib/hamster/vector/length_spec.rb - spec/lib/hamster/vector/ltlt_spec.rb - spec/lib/hamster/vector/map_spec.rb - spec/lib/hamster/vector/marshal_spec.rb - spec/lib/hamster/vector/maximum_spec.rb - spec/lib/hamster/vector/minimum_spec.rb - spec/lib/hamster/vector/multiply_spec.rb - spec/lib/hamster/vector/new_spec.rb - spec/lib/hamster/vector/partition_spec.rb - spec/lib/hamster/vector/permutation_spec.rb - spec/lib/hamster/vector/pop_spec.rb - spec/lib/hamster/vector/product_spec.rb - spec/lib/hamster/vector/put_spec.rb - spec/lib/hamster/vector/reduce_spec.rb - spec/lib/hamster/vector/reject_spec.rb - spec/lib/hamster/vector/repeated_combination_spec.rb - spec/lib/hamster/vector/repeated_permutation_spec.rb - spec/lib/hamster/vector/reverse_each_spec.rb - spec/lib/hamster/vector/reverse_spec.rb - spec/lib/hamster/vector/rindex_spec.rb - spec/lib/hamster/vector/rotate_spec.rb - spec/lib/hamster/vector/sample_spec.rb - spec/lib/hamster/vector/select_spec.rb - spec/lib/hamster/vector/set_spec.rb - spec/lib/hamster/vector/shift_spec.rb - spec/lib/hamster/vector/shuffle_spec.rb - spec/lib/hamster/vector/slice_spec.rb - spec/lib/hamster/vector/sorting_spec.rb - spec/lib/hamster/vector/sum_spec.rb - spec/lib/hamster/vector/take_spec.rb - spec/lib/hamster/vector/take_while_spec.rb - spec/lib/hamster/vector/to_a_spec.rb - spec/lib/hamster/vector/to_ary_spec.rb - spec/lib/hamster/vector/to_list_spec.rb - spec/lib/hamster/vector/to_set_spec.rb - spec/lib/hamster/vector/transpose_spec.rb - spec/lib/hamster/vector/uniq_spec.rb - spec/lib/hamster/vector/unshift_spec.rb - spec/lib/hamster/vector/values_at_spec.rb - spec/lib/hamster/vector/zip_spec.rb - spec/lib/load_spec.rb - spec/spec_helper.rb homepage: https://github.com/hamstergem/hamster licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.9.3 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.8 signing_key: specification_version: 4 summary: Efficient, immutable, thread-safe collection classes for Ruby test_files: - spec/fixtures/io_spec.txt - spec/lib/hamster/associable/associable_spec.rb - spec/lib/hamster/core_ext/array_spec.rb - spec/lib/hamster/core_ext/enumerable_spec.rb - spec/lib/hamster/core_ext/io_spec.rb - spec/lib/hamster/deque/clear_spec.rb - spec/lib/hamster/deque/construction_spec.rb - spec/lib/hamster/deque/copying_spec.rb - spec/lib/hamster/deque/dequeue_spec.rb - spec/lib/hamster/deque/empty_spec.rb - spec/lib/hamster/deque/enqueue_spec.rb - spec/lib/hamster/deque/first_spec.rb - spec/lib/hamster/deque/inspect_spec.rb - spec/lib/hamster/deque/last_spec.rb - spec/lib/hamster/deque/marshal_spec.rb - spec/lib/hamster/deque/new_spec.rb - spec/lib/hamster/deque/pop_spec.rb - spec/lib/hamster/deque/pretty_print_spec.rb - spec/lib/hamster/deque/push_spec.rb - spec/lib/hamster/deque/random_modification_spec.rb - spec/lib/hamster/deque/shift_spec.rb - spec/lib/hamster/deque/size_spec.rb - spec/lib/hamster/deque/to_a_spec.rb - spec/lib/hamster/deque/to_ary_spec.rb - spec/lib/hamster/deque/to_list_spec.rb - spec/lib/hamster/deque/unshift_spec.rb - spec/lib/hamster/experimental/mutable_set/add_qm_spec.rb - spec/lib/hamster/experimental/mutable_set/add_spec.rb - spec/lib/hamster/experimental/mutable_set/delete_qm_spec.rb - spec/lib/hamster/experimental/mutable_set/delete_spec.rb - spec/lib/hamster/hash/all_spec.rb - spec/lib/hamster/hash/any_spec.rb - spec/lib/hamster/hash/assoc_spec.rb - spec/lib/hamster/hash/clear_spec.rb - spec/lib/hamster/hash/construction_spec.rb - spec/lib/hamster/hash/copying_spec.rb - spec/lib/hamster/hash/default_proc_spec.rb - spec/lib/hamster/hash/delete_spec.rb - spec/lib/hamster/hash/dig_spec.rb - spec/lib/hamster/hash/each_spec.rb - spec/lib/hamster/hash/each_with_index_spec.rb - spec/lib/hamster/hash/empty_spec.rb - spec/lib/hamster/hash/eql_spec.rb - spec/lib/hamster/hash/except_spec.rb - spec/lib/hamster/hash/fetch_spec.rb - spec/lib/hamster/hash/fetch_values_spec.rb - spec/lib/hamster/hash/find_spec.rb - spec/lib/hamster/hash/flat_map_spec.rb - spec/lib/hamster/hash/flatten_spec.rb - spec/lib/hamster/hash/get_spec.rb - spec/lib/hamster/hash/has_key_spec.rb - spec/lib/hamster/hash/has_value_spec.rb - spec/lib/hamster/hash/hash_spec.rb - spec/lib/hamster/hash/immutable_spec.rb - spec/lib/hamster/hash/inspect_spec.rb - spec/lib/hamster/hash/invert_spec.rb - spec/lib/hamster/hash/key_spec.rb - spec/lib/hamster/hash/keys_spec.rb - spec/lib/hamster/hash/map_spec.rb - spec/lib/hamster/hash/marshal_spec.rb - spec/lib/hamster/hash/merge_spec.rb - spec/lib/hamster/hash/min_max_spec.rb - spec/lib/hamster/hash/new_spec.rb - spec/lib/hamster/hash/none_spec.rb - spec/lib/hamster/hash/partition_spec.rb - spec/lib/hamster/hash/pretty_print_spec.rb - spec/lib/hamster/hash/put_spec.rb - spec/lib/hamster/hash/reduce_spec.rb - spec/lib/hamster/hash/reject_spec.rb - spec/lib/hamster/hash/reverse_each_spec.rb - spec/lib/hamster/hash/sample_spec.rb - spec/lib/hamster/hash/select_spec.rb - spec/lib/hamster/hash/size_spec.rb - spec/lib/hamster/hash/slice_spec.rb - spec/lib/hamster/hash/sort_spec.rb - spec/lib/hamster/hash/store_spec.rb - spec/lib/hamster/hash/subset_spec.rb - spec/lib/hamster/hash/superset_spec.rb - spec/lib/hamster/hash/take_spec.rb - spec/lib/hamster/hash/to_a_spec.rb - spec/lib/hamster/hash/to_hash_spec.rb - spec/lib/hamster/hash/to_proc_spec.rb - spec/lib/hamster/hash/values_at_spec.rb - spec/lib/hamster/hash/values_spec.rb - spec/lib/hamster/immutable/copying_spec.rb - spec/lib/hamster/immutable/immutable_spec.rb - spec/lib/hamster/immutable/memoize_spec.rb - spec/lib/hamster/immutable/new_spec.rb - spec/lib/hamster/immutable/transform_spec.rb - spec/lib/hamster/immutable/transform_unless_spec.rb - spec/lib/hamster/list/add_spec.rb - spec/lib/hamster/list/all_spec.rb - spec/lib/hamster/list/any_spec.rb - spec/lib/hamster/list/append_spec.rb - spec/lib/hamster/list/at_spec.rb - spec/lib/hamster/list/break_spec.rb - spec/lib/hamster/list/cadr_spec.rb - spec/lib/hamster/list/chunk_spec.rb - spec/lib/hamster/list/clear_spec.rb - spec/lib/hamster/list/combination_spec.rb - spec/lib/hamster/list/compact_spec.rb - spec/lib/hamster/list/compare_spec.rb - spec/lib/hamster/list/cons_spec.rb - spec/lib/hamster/list/construction_spec.rb - spec/lib/hamster/list/copying_spec.rb - spec/lib/hamster/list/count_spec.rb - spec/lib/hamster/list/cycle_spec.rb - spec/lib/hamster/list/delete_at_spec.rb - spec/lib/hamster/list/delete_spec.rb - spec/lib/hamster/list/drop_spec.rb - spec/lib/hamster/list/drop_while_spec.rb - spec/lib/hamster/list/each_slice_spec.rb - spec/lib/hamster/list/each_spec.rb - spec/lib/hamster/list/each_with_index_spec.rb - spec/lib/hamster/list/empty_spec.rb - spec/lib/hamster/list/eql_spec.rb - spec/lib/hamster/list/fill_spec.rb - spec/lib/hamster/list/find_all_spec.rb - spec/lib/hamster/list/find_index_spec.rb - spec/lib/hamster/list/find_spec.rb - spec/lib/hamster/list/flat_map_spec.rb - spec/lib/hamster/list/flatten_spec.rb - spec/lib/hamster/list/grep_spec.rb - spec/lib/hamster/list/group_by_spec.rb - spec/lib/hamster/list/hash_spec.rb - spec/lib/hamster/list/head_spec.rb - spec/lib/hamster/list/include_spec.rb - spec/lib/hamster/list/index_spec.rb - spec/lib/hamster/list/indices_spec.rb - spec/lib/hamster/list/init_spec.rb - spec/lib/hamster/list/inits_spec.rb - spec/lib/hamster/list/insert_spec.rb - spec/lib/hamster/list/inspect_spec.rb - spec/lib/hamster/list/intersperse_spec.rb - spec/lib/hamster/list/join_spec.rb - spec/lib/hamster/list/last_spec.rb - spec/lib/hamster/list/ltlt_spec.rb - spec/lib/hamster/list/map_spec.rb - spec/lib/hamster/list/maximum_spec.rb - spec/lib/hamster/list/merge_by_spec.rb - spec/lib/hamster/list/merge_spec.rb - spec/lib/hamster/list/minimum_spec.rb - spec/lib/hamster/list/multithreading_spec.rb - spec/lib/hamster/list/none_spec.rb - spec/lib/hamster/list/one_spec.rb - spec/lib/hamster/list/partition_spec.rb - spec/lib/hamster/list/permutation_spec.rb - spec/lib/hamster/list/pop_spec.rb - spec/lib/hamster/list/product_spec.rb - spec/lib/hamster/list/reduce_spec.rb - spec/lib/hamster/list/reject_spec.rb - spec/lib/hamster/list/reverse_spec.rb - spec/lib/hamster/list/rotate_spec.rb - spec/lib/hamster/list/sample_spec.rb - spec/lib/hamster/list/select_spec.rb - spec/lib/hamster/list/size_spec.rb - spec/lib/hamster/list/slice_spec.rb - spec/lib/hamster/list/sorting_spec.rb - spec/lib/hamster/list/span_spec.rb - spec/lib/hamster/list/split_at_spec.rb - spec/lib/hamster/list/subsequences_spec.rb - spec/lib/hamster/list/sum_spec.rb - spec/lib/hamster/list/tail_spec.rb - spec/lib/hamster/list/tails_spec.rb - spec/lib/hamster/list/take_spec.rb - spec/lib/hamster/list/take_while_spec.rb - spec/lib/hamster/list/to_a_spec.rb - spec/lib/hamster/list/to_ary_spec.rb - spec/lib/hamster/list/to_list_spec.rb - spec/lib/hamster/list/to_set_spec.rb - spec/lib/hamster/list/transpose_spec.rb - spec/lib/hamster/list/union_spec.rb - spec/lib/hamster/list/uniq_spec.rb - spec/lib/hamster/list/zip_spec.rb - spec/lib/hamster/nested/construction_spec.rb - spec/lib/hamster/set/add_spec.rb - spec/lib/hamster/set/all_spec.rb - spec/lib/hamster/set/any_spec.rb - spec/lib/hamster/set/clear_spec.rb - spec/lib/hamster/set/compact_spec.rb - spec/lib/hamster/set/construction_spec.rb - spec/lib/hamster/set/copying_spec.rb - spec/lib/hamster/set/count_spec.rb - spec/lib/hamster/set/delete_spec.rb - spec/lib/hamster/set/difference_spec.rb - spec/lib/hamster/set/disjoint_spec.rb - spec/lib/hamster/set/each_spec.rb - spec/lib/hamster/set/empty_spec.rb - spec/lib/hamster/set/eqeq_spec.rb - spec/lib/hamster/set/eql_spec.rb - spec/lib/hamster/set/exclusion_spec.rb - spec/lib/hamster/set/find_spec.rb - spec/lib/hamster/set/first_spec.rb - spec/lib/hamster/set/flatten_spec.rb - spec/lib/hamster/set/grep_spec.rb - spec/lib/hamster/set/grep_v_spec.rb - spec/lib/hamster/set/group_by_spec.rb - spec/lib/hamster/set/hash_spec.rb - spec/lib/hamster/set/immutable_spec.rb - spec/lib/hamster/set/include_spec.rb - spec/lib/hamster/set/inspect_spec.rb - spec/lib/hamster/set/intersect_spec.rb - spec/lib/hamster/set/intersection_spec.rb - spec/lib/hamster/set/join_spec.rb - spec/lib/hamster/set/map_spec.rb - spec/lib/hamster/set/marshal_spec.rb - spec/lib/hamster/set/maximum_spec.rb - spec/lib/hamster/set/minimum_spec.rb - spec/lib/hamster/set/new_spec.rb - spec/lib/hamster/set/none_spec.rb - spec/lib/hamster/set/one_spec.rb - spec/lib/hamster/set/partition_spec.rb - spec/lib/hamster/set/product_spec.rb - spec/lib/hamster/set/reduce_spec.rb - spec/lib/hamster/set/reject_spec.rb - spec/lib/hamster/set/reverse_each_spec.rb - spec/lib/hamster/set/sample_spec.rb - spec/lib/hamster/set/select_spec.rb - spec/lib/hamster/set/size_spec.rb - spec/lib/hamster/set/sorting_spec.rb - spec/lib/hamster/set/subset_spec.rb - spec/lib/hamster/set/sum_spec.rb - spec/lib/hamster/set/superset_spec.rb - spec/lib/hamster/set/to_a_spec.rb - spec/lib/hamster/set/to_list_spec.rb - spec/lib/hamster/set/to_set_spec.rb - spec/lib/hamster/set/union_spec.rb - spec/lib/hamster/sorted_set/above_spec.rb - spec/lib/hamster/sorted_set/add_spec.rb - spec/lib/hamster/sorted_set/at_spec.rb - spec/lib/hamster/sorted_set/below_spec.rb - spec/lib/hamster/sorted_set/between_spec.rb - spec/lib/hamster/sorted_set/clear_spec.rb - spec/lib/hamster/sorted_set/copying_spec.rb - spec/lib/hamster/sorted_set/delete_at_spec.rb - spec/lib/hamster/sorted_set/delete_spec.rb - spec/lib/hamster/sorted_set/difference_spec.rb - spec/lib/hamster/sorted_set/disjoint_spec.rb - spec/lib/hamster/sorted_set/drop_spec.rb - spec/lib/hamster/sorted_set/drop_while_spec.rb - spec/lib/hamster/sorted_set/each_spec.rb - spec/lib/hamster/sorted_set/empty_spec.rb - spec/lib/hamster/sorted_set/eql_spec.rb - spec/lib/hamster/sorted_set/exclusion_spec.rb - spec/lib/hamster/sorted_set/fetch_spec.rb - spec/lib/hamster/sorted_set/find_index_spec.rb - spec/lib/hamster/sorted_set/first_spec.rb - spec/lib/hamster/sorted_set/from_spec.rb - spec/lib/hamster/sorted_set/group_by_spec.rb - spec/lib/hamster/sorted_set/include_spec.rb - spec/lib/hamster/sorted_set/inspect_spec.rb - spec/lib/hamster/sorted_set/intersect_spec.rb - spec/lib/hamster/sorted_set/intersection_spec.rb - spec/lib/hamster/sorted_set/last_spec.rb - spec/lib/hamster/sorted_set/map_spec.rb - spec/lib/hamster/sorted_set/marshal_spec.rb - spec/lib/hamster/sorted_set/maximum_spec.rb - spec/lib/hamster/sorted_set/minimum_spec.rb - spec/lib/hamster/sorted_set/new_spec.rb - spec/lib/hamster/sorted_set/reverse_each_spec.rb - spec/lib/hamster/sorted_set/sample_spec.rb - spec/lib/hamster/sorted_set/select_spec.rb - spec/lib/hamster/sorted_set/size_spec.rb - spec/lib/hamster/sorted_set/slice_spec.rb - spec/lib/hamster/sorted_set/sorting_spec.rb - spec/lib/hamster/sorted_set/subset_spec.rb - spec/lib/hamster/sorted_set/superset_spec.rb - spec/lib/hamster/sorted_set/take_spec.rb - spec/lib/hamster/sorted_set/take_while_spec.rb - spec/lib/hamster/sorted_set/to_set_spec.rb - spec/lib/hamster/sorted_set/union_spec.rb - spec/lib/hamster/sorted_set/up_to_spec.rb - spec/lib/hamster/sorted_set/values_at_spec.rb - spec/lib/hamster/vector/add_spec.rb - spec/lib/hamster/vector/any_spec.rb - spec/lib/hamster/vector/assoc_spec.rb - spec/lib/hamster/vector/bsearch_spec.rb - spec/lib/hamster/vector/clear_spec.rb - spec/lib/hamster/vector/combination_spec.rb - spec/lib/hamster/vector/compact_spec.rb - spec/lib/hamster/vector/compare_spec.rb - spec/lib/hamster/vector/concat_spec.rb - spec/lib/hamster/vector/copying_spec.rb - spec/lib/hamster/vector/count_spec.rb - spec/lib/hamster/vector/delete_at_spec.rb - spec/lib/hamster/vector/delete_spec.rb - spec/lib/hamster/vector/dig_spec.rb - spec/lib/hamster/vector/drop_spec.rb - spec/lib/hamster/vector/drop_while_spec.rb - spec/lib/hamster/vector/each_index_spec.rb - spec/lib/hamster/vector/each_spec.rb - spec/lib/hamster/vector/each_with_index_spec.rb - spec/lib/hamster/vector/empty_spec.rb - spec/lib/hamster/vector/eql_spec.rb - spec/lib/hamster/vector/fetch_spec.rb - spec/lib/hamster/vector/fill_spec.rb - spec/lib/hamster/vector/first_spec.rb - spec/lib/hamster/vector/flat_map_spec.rb - spec/lib/hamster/vector/flatten_spec.rb - spec/lib/hamster/vector/get_spec.rb - spec/lib/hamster/vector/group_by_spec.rb - spec/lib/hamster/vector/include_spec.rb - spec/lib/hamster/vector/insert_spec.rb - spec/lib/hamster/vector/inspect_spec.rb - spec/lib/hamster/vector/join_spec.rb - spec/lib/hamster/vector/last_spec.rb - spec/lib/hamster/vector/length_spec.rb - spec/lib/hamster/vector/ltlt_spec.rb - spec/lib/hamster/vector/map_spec.rb - spec/lib/hamster/vector/marshal_spec.rb - spec/lib/hamster/vector/maximum_spec.rb - spec/lib/hamster/vector/minimum_spec.rb - spec/lib/hamster/vector/multiply_spec.rb - spec/lib/hamster/vector/new_spec.rb - spec/lib/hamster/vector/partition_spec.rb - spec/lib/hamster/vector/permutation_spec.rb - spec/lib/hamster/vector/pop_spec.rb - spec/lib/hamster/vector/product_spec.rb - spec/lib/hamster/vector/put_spec.rb - spec/lib/hamster/vector/reduce_spec.rb - spec/lib/hamster/vector/reject_spec.rb - spec/lib/hamster/vector/repeated_combination_spec.rb - spec/lib/hamster/vector/repeated_permutation_spec.rb - spec/lib/hamster/vector/reverse_each_spec.rb - spec/lib/hamster/vector/reverse_spec.rb - spec/lib/hamster/vector/rindex_spec.rb - spec/lib/hamster/vector/rotate_spec.rb - spec/lib/hamster/vector/sample_spec.rb - spec/lib/hamster/vector/select_spec.rb - spec/lib/hamster/vector/set_spec.rb - spec/lib/hamster/vector/shift_spec.rb - spec/lib/hamster/vector/shuffle_spec.rb - spec/lib/hamster/vector/slice_spec.rb - spec/lib/hamster/vector/sorting_spec.rb - spec/lib/hamster/vector/sum_spec.rb - spec/lib/hamster/vector/take_spec.rb - spec/lib/hamster/vector/take_while_spec.rb - spec/lib/hamster/vector/to_a_spec.rb - spec/lib/hamster/vector/to_ary_spec.rb - spec/lib/hamster/vector/to_list_spec.rb - spec/lib/hamster/vector/to_set_spec.rb - spec/lib/hamster/vector/transpose_spec.rb - spec/lib/hamster/vector/uniq_spec.rb - spec/lib/hamster/vector/unshift_spec.rb - spec/lib/hamster/vector/values_at_spec.rb - spec/lib/hamster/vector/zip_spec.rb - spec/lib/load_spec.rb - spec/spec_helper.rb has_rdoc: hamster-3.0.0/LICENSE0000644000004100000410000000207212663306556014227 0ustar www-datawww-dataLicensing ========= 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.