multimap-1.1.2/0000755000175000017500000000000012305434010012341 5ustar jonasjonasmultimap-1.1.2/spec/0000755000175000017500000000000012305434010013273 5ustar jonasjonasmultimap-1.1.2/spec/set_examples.rb0000644000175000017500000002065412305434010016320 0ustar jonasjonasshared_examples_for Set do it "should create a new set containing the given objects" do Multiset[] Multiset[nil] Multiset[1, 2, 3] Multiset[].size.should eql(0) Multiset[nil].size.should eql(1) Multiset[[]].size.should eql(1) Multiset[[nil]].size.should eql(1) set = Multiset[2, 4, 6, 4] Multiset.new([2, 4, 6]).should_not eql(set) set = Multiset[2, 4, 6, 4] Multiset.new([2, 4, 6, 4]).should eql(set) end it "should create a new set containing the elements of the given enumerable object" do Multiset.new() Multiset.new(nil) Multiset.new([]) Multiset.new([1, 2]) Multiset.new('a'..'c') lambda { Multiset.new(false) }.should raise_error lambda { Multiset.new(1) }.should raise_error lambda { Multiset.new(1, 2) }.should raise_error Multiset.new().size.should eql(0) Multiset.new(nil).size.should eql(0) Multiset.new([]).size.should eql(0) Multiset.new([nil]).size.should eql(1) ary = [2, 4, 6, 4] set = Multiset.new(ary) ary.clear set.should_not be_empty set.size.should eql(4) ary = [1, 2, 3] s = Multiset.new(ary) { |o| o * 2 } [2, 4, 6].should eql(s.sort) end it "should duplicate set" do set1 = Multiset[1, 2] set2 = set1.dup set1.should_not equal(set2) set1.should eql(set2) set1.add(3) set1.should_not eql(set2) end it "should return the number of elements" do Multiset[].size.should eql(0) Multiset[1, 2].size.should eql(2) Multiset[1, 2, 1].size.should eql(3) end it "should return true if the set contains no elements" do Multiset[].should be_empty Multiset[1, 2].should_not be_empty end it "should remove all elements and returns self" do set = Multiset[1, 2] ret = set.clear set.should equal(ret) set.should be_empty end it "should replaces the contents of the set with the contents of the given enumerable object and returns self" do set = Multiset[1, 2] ret = set.replace('a'..'c') set.should equal(ret) set.should eql(Multiset['a', 'b', 'c']) end it "should convert the set to an array" do set = Multiset[1, 2, 3, 2] ary = set.to_a ary.sort.should eql([1, 2, 2, 3]) end it "should return true if the set contains the given object" do set = Multiset[1, 2, 3] set.include?(1).should be_true set.include?(2).should be_true set.include?(3).should be_true set.include?(0).should be_false set.include?(nil).should be_false set = Multiset["1", nil, "2", nil, "0", "1", false] set.include?(nil).should be_true set.include?(false).should be_true set.include?("1").should be_true set.include?(0).should be_false set.include?(true).should be_false end it "should return true if the set is a superset of the given set" do set = Multiset[1, 2, 3] lambda { set.superset?() }.should raise_error lambda { set.superset?(2) }.should raise_error lambda { set.superset?([2]) }.should raise_error set.superset?(Multiset[]).should be_true set.superset?(Multiset[1, 2]).should be_true set.superset?(Multiset[1, 2, 3]).should be_true set.superset?(Multiset[1, 2, 3, 4]).should be_false set.superset?(Multiset[1, 4]).should be_false Multiset[].superset?(Multiset[]).should be_true end it "should return true if the set is a proper superset of the given set" do set = Multiset[1, 2, 3] lambda { set.proper_superset?() }.should raise_error lambda { set.proper_superset?(2) }.should raise_error lambda { set.proper_superset?([2]) }.should raise_error set.proper_superset?(Multiset[]).should be_true set.proper_superset?(Multiset[1, 2]).should be_true set.proper_superset?(Multiset[1, 2, 3]).should be_false set.proper_superset?(Multiset[1, 2, 3, 4]).should be_false set.proper_superset?(Multiset[1, 4]).should be_false Multiset[].proper_superset?(Multiset[]).should be_false end it "should return true if the set is a subset of the given set" do set = Multiset[1, 2, 3] lambda { set.subset?() }.should raise_error lambda { set.subset?(2) }.should raise_error lambda { set.subset?([2]) }.should raise_error set.subset?(Multiset[1, 2, 3, 4]).should be_true set.subset?(Multiset[1, 2, 3]).should be_true set.subset?(Multiset[1, 2]).should be_false set.subset?(Multiset[]).should be_false Multiset[].subset?(Multiset[1]).should be_true Multiset[].subset?(Multiset[]).should be_true end it "should return true if the set is a proper subset of the given set" do set = Multiset[1, 2, 3] lambda { set.proper_subset?() }.should raise_error lambda { set.proper_subset?(2) }.should raise_error lambda { set.proper_subset?([2]) }.should raise_error set.proper_subset?(Multiset[1, 2, 3, 4]).should be_true set.proper_subset?(Multiset[1, 2, 3]).should be_false set.proper_subset?(Multiset[1, 2]).should be_false set.proper_subset?(Multiset[]).should be_false Multiset[].proper_subset?(Multiset[]).should be_false end it "should add the given object to the set and return self" do set = Multiset[1, 2, 3] ret = set.add(2) set.should equal(ret) set.should eql(Multiset[1, 2, 2, 3]) ret = set.add(4) set.should equal(ret) set.should eql(Multiset[1, 2, 2, 3, 4]) end it "should delete the given object from the set and return self" do set = Multiset[1, 2, 3] ret = set.delete(4) set.should equal(ret) set.should eql(Multiset[1, 2, 3]) ret = set.delete(2) set.should eql(ret) set.should eql(Multiset[1, 3]) end it "should delete every element of the set for which block evaluates to true, and return self" do set = Multiset.new(1..10) ret = set.delete_if { |i| i > 10 } set.should equal(ret) set.should eql(Multiset.new(1..10)) set = Multiset.new(1..10) ret = set.delete_if { |i| i % 3 == 0 } set.should equal(ret) set.should eql(Multiset[1, 2, 4, 5, 7, 8, 10]) end it "should deletes every element of the set for which block evaluates to true but return nil if no changes were made" do set = Multiset.new(1..10) ret = set.reject! { |i| i > 10 } ret.should be_nil set.should eql(Multiset.new(1..10)) ret = set.reject! { |i| i % 3 == 0 } set.should equal(ret) set.should eql(Multiset[1, 2, 4, 5, 7, 8, 10]) end it "should merge the elements of the given enumerable object to the set and return self" do set = Multiset[1, 2, 3] ret = set.merge([2, 4, 6]) set.should equal(ret) set.should eql(Multiset[1, 2, 2, 3, 4, 6]) end it "should delete every element that appears in the given enumerable object and return self" do set = Multiset[1, 2, 3] ret = set.subtract([2, 4, 6]) set.should equal(ret) set.should eql(Multiset[1, 3]) end it "should return a new set built by merging the set and the elements of the given enumerable object" do set = Multiset[1, 2, 3] ret = set + [2, 4, 6] set.should_not equal(ret) ret.should eql(Multiset[1, 2, 2, 3, 4, 6]) end it "should return a new set built by duplicating the set, removing every element that appears in the given enumerable object" do set = Multiset[1, 2, 3] ret = set - [2, 4, 6] set.should_not equal(ret) ret.should eql(Multiset[1, 3]) end it "should return a new set containing elements common to the set and the given enumerable object" do set = Multiset[1, 2, 3, 4] ret = set & [2, 4, 6] set.should_not equal(ret) ret.should eql(Multiset[2, 4]) end it "should return a new set containing elements exclusive between the set and the given enumerable object" do set = Multiset[1, 2, 3, 4] ret = set ^ [2, 4, 5] set.should_not equal(ret) ret.should eql(Multiset[1, 3, 5]) end end shared_examples_for Set, "with inital values [1, 2]" do it "should add element to set" do @set.add("foo") @set.include?(1).should be_true @set.include?(2).should be_true @set.include?("foo").should be_true end it "should merge elements into the set" do @set.merge([2, 6]) @set.include?(1).should be_true @set.include?(2).should be_true @set.include?(2).should be_true @set.include?(6).should be_true end it "should iterate over all the values in the set" do a = [] @set.each { |o| a << o } a.should eql([1, 2]) end it "should convert to an array" do @set.to_a.should eql([1, 2]) end it "should convert to a set" do @set.to_set.to_a.should eql([1, 2]) end end multimap-1.1.2/spec/multiset_spec.rb0000644000175000017500000001260412305434010016503 0ustar jonasjonasrequire 'spec_helper' describe Multiset do it_should_behave_like "Set" it "should return the multiplicity of the element" do set = Multiset.new([:a, :a, :b, :b, :b, :c]) set.multiplicity(:a).should eql(2) set.multiplicity(:b).should eql(3) set.multiplicity(:c).should eql(1) end it "should return the cardinality of the set" do set = Multiset.new([:a, :a, :b, :b, :b, :c]) set.cardinality.should eql(6) end it "should be eql" do s1 = Multiset.new([:a, :b]) s2 = Multiset.new([:b, :a]) s1.should eql(s2) s1 = Multiset.new([:a, :a]) s2 = Multiset.new([:a]) s1.should_not eql(s2) end it "should replace the contents of the set" do set = Multiset[:a, :b, :b, :c] ret = set.replace(Multiset[:a, :a, :b, :b, :b, :c]) set.should equal(ret) set.should eql(Multiset[:a, :a, :b, :b, :b, :c]) set = Multiset[:a, :b, :b, :c] ret = set.replace([:a, :a, :b, :b, :b, :c]) set.should equal(ret) set.should eql(Multiset[:a, :a, :b, :b, :b, :c]) end it "should return true if the set is a superset of the given set" do set = Multiset[1, 2, 2, 3] set.superset?(Multiset[]).should be_true set.superset?(Multiset[1, 2]).should be_true set.superset?(Multiset[1, 2, 3]).should be_true set.superset?(Multiset[1, 2, 2, 3]).should be_true set.superset?(Multiset[1, 2, 2, 2]).should be_false set.superset?(Multiset[1, 2, 3, 4]).should be_false set.superset?(Multiset[1, 4]).should be_false end it "should return true if the set is a proper superset of the given set" do set = Multiset[1, 2, 2, 3, 3] set.proper_superset?(Multiset[]).should be_true set.proper_superset?(Multiset[1, 2]).should be_true set.proper_superset?(Multiset[1, 2, 3]).should be_true set.proper_superset?(Multiset[1, 2, 2, 3, 3]).should be_false set.proper_superset?(Multiset[1, 2, 2, 2]).should be_false set.proper_superset?(Multiset[1, 2, 3, 4]).should be_false set.proper_superset?(Multiset[1, 4]).should be_false end it "should return true if the set is a subset of the given set" do set = Multiset[1, 2, 2, 3] set.subset?(Multiset[1, 2, 2, 3, 4]).should be_true set.subset?(Multiset[1, 2, 2, 3, 3]).should be_true set.subset?(Multiset[1, 2, 2, 3]).should be_true set.subset?(Multiset[1, 2, 3]).should be_false set.subset?(Multiset[1, 2, 2]).should be_false set.subset?(Multiset[1, 2, 3]).should be_false set.subset?(Multiset[]).should be_false end it "should return true if the set is a proper subset of the given set" do set = Multiset[1, 2, 2, 3, 3] set.proper_subset?(Multiset[1, 2, 2, 3, 3, 4]).should be_true set.proper_subset?(Multiset[1, 2, 2, 3, 3]).should be_false set.proper_subset?(Multiset[1, 2, 3]).should be_false set.proper_subset?(Multiset[1, 2, 2]).should be_false set.proper_subset?(Multiset[1, 2, 3]).should be_false set.proper_subset?(Multiset[]).should be_false end it "should delete the objects from the set and return self" do set = Multiset[1, 2, 2, 3] ret = set.delete(4) set.should equal(ret) set.should eql(Multiset[1, 2, 2, 3]) ret = set.delete(2) set.should eql(ret) set.should eql(Multiset[1, 3]) end it "should delete the number objects from the set and return self" do set = Multiset[1, 2, 2, 3] ret = set.delete(2, 1) set.should eql(ret) set.should eql(Multiset[1, 2, 3]) end it "should merge the elements of the given enumerable object to the set and return self" do set = Multiset[1, 2, 3] ret = set.merge([2, 4, 5]) set.should equal(ret) set.should eql(Multiset[1, 2, 2, 3, 4, 5]) set = Multiset[1, 2, 3] ret = set.merge(Multiset[2, 4, 5]) set.should equal(ret) set.should eql(Multiset[1, 2, 2, 3, 4, 5]) end it "should delete every element that appears in the given enumerable object and return self" do set = Multiset[1, 2, 2, 3] ret = set.subtract([2, 4, 6]) set.should equal(ret) set.should eql(Multiset[1, 2, 3]) end it "should return a new set containing elements common to the set and the given enumerable object" do set = Multiset[1, 2, 2, 3, 4] ret = set & [2, 2, 4, 5] set.should_not equal(ret) ret.should eql(Multiset[2, 2, 4]) set = Multiset[1, 2, 3] ret = set & [1, 2, 2, 2] set.should_not equal(ret) ret.should eql(Multiset[1, 2]) end it "should return a new set containing elements exclusive between the set and the given enumerable object" do set = Multiset[1, 2, 3, 4, 5] ret = set ^ [2, 4, 5, 5] set.should_not equal(ret) ret.should eql(Multiset[1, 3, 5]) set = Multiset[1, 2, 4, 5, 5] ret = set ^ [2, 3, 4, 5] set.should_not equal(ret) ret.should eql(Multiset[1, 3, 5]) end it "should marshal set" do set = Multiset[1, 2, 3, 4, 5] data = Marshal.dump(set) Marshal.load(data).should eql(set) end it "should dump yaml" do require 'yaml' set = Multiset[1, 2, 3, 4, 5] data = YAML.dump(set) YAML.load(data).should eql(set) end end describe Multiset, "with inital values" do it_should_behave_like "Set with inital values [1, 2]" before do @set = Multiset.new([1, 2]) end it "should return the multiplicity of the element" do @set.multiplicity(1).should eql(1) @set.multiplicity(2).should eql(1) end it "should return the cardinality of the set" do @set.cardinality.should eql(2) end end multimap-1.1.2/spec/enumerable_examples.rb0000644000175000017500000000364612305434010017646 0ustar jonasjonasshared_examples_for Enumerable, Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do it "should check all key/value pairs for condition" do @map.all? { |key, value| key =~ /\w/ }.should be_true @map.all? { |key, value| key =~ /\d/ }.should be_false @map.all? { |key, value| value > 0 }.should be_true @map.all? { |key, value| value > 200 }.should be_false end it "should check any key/value pairs for condition" do @map.any? { |key, value| key == "a" }.should be_true @map.any? { |key, value| key == "z" }.should be_false @map.any? { |key, value| value == 100 }.should be_true @map.any? { |key, value| value > 1000 }.should be_false end it "should collect key/value pairs" do @map.collect { |key, value| [key, value] }.should sorted_eql([["a", 100], ["b", 200], ["b", 300]]) @map.map { |key, value| [key, value] }.should sorted_eql([["a", 100], ["b", 200], ["b", 300]]) end it "should detect key/value pair" do @map.detect { |key, value| value > 200 }.should eql(["b", 300]) @map.find { |key, value| value > 200 }.should eql(["b", 300]) end it "should return entries" do @map.entries.should sorted_eql([["a", 100], ["b", 200], ["b", 300]]) @map.to_a.should sorted_eql([["a", 100], ["b", 200], ["b", 300]]) end it "should find all key/value pairs" do @map.find_all { |key, value| value >= 200 }.should eql([["b", 200], ["b", 300]]) @map.select { |key, value| value >= 200 }.should eql(Multimap["b", [200, 300]]) end it "should combine key/value pairs with inject" do @map.inject(0) { |sum, (key, value)| sum + value }.should eql(600) @map.inject(0) { |memo, (key, value)| memo > value ? memo : value }.should eql(300) end it "should check for key membership" do @map.member?("a").should be_true @map.include?("a").should be_true @map.member?("z").should be_false @map.include?("z").should be_false end end multimap-1.1.2/spec/multimap_spec.rb0000644000175000017500000000266412305434010016472 0ustar jonasjonasrequire 'spec_helper' describe Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}" it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}" before do @map = Multimap["a" => 100, "b" => [200, 300]] end end describe Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}" it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}" before do @map = Multimap["a", 100, "b", [200, 300]] end end describe Multimap, "with", Set do it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}" it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}" before do @container = Set @map = Multimap.new(@container.new) @map["a"] = 100 @map["b"] = 200 @map["b"] = 300 end end describe Multimap, "with", MiniArray do it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}" it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}" before do @container = MiniArray @map = Multimap.new(@container.new) @map["a"] = 100 @map["b"] = 200 @map["b"] = 300 end end multimap-1.1.2/spec/nested_multimap_spec.rb0000644000175000017500000001263112305434010020027 0ustar jonasjonasrequire 'spec_helper' describe NestedMultimap, "with inital values" do it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}" it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}" before do @map = NestedMultimap["a" => [100], "b" => [200, 300]] end it "should set value at nested key" do @map["foo", "bar", "baz"] = 100 @map["foo", "bar", "baz"].should eql([100]) end it "should allow nil keys to be set" do @map["b", nil] = 400 @map["b", "c"] = 500 @map["a"].should eql([100]) @map["b"].should eql([200, 300]) @map["b", nil].should eql([200, 300, 400]) @map["b", "c"].should eql([200, 300, 500]) end it "should treat missing keys as append to all" do @map[] = 400 @map["a"].should eql([100, 400]) @map["b"].should eql([200, 300, 400]) @map["c"].should eql([400]) @map[nil].should eql([400]) end it "should append the value to default containers" do @map << 400 @map["a"].should eql([100, 400]) @map["b"].should eql([200, 300, 400]) @map["c"].should eql([400]) @map[nil].should eql([400]) end it "should append the value to all containers" do @map << 500 @map["a"].should eql([100, 500]) @map["b"].should eql([200, 300, 500]) @map[nil].should eql([500]) end it "default values should be copied to new containers" do @map << 300 @map["x"] = 100 @map["x"].should eql([300, 100]) end it "should list all containers" do @map.containers.should sorted_eql([[100], [200, 300]]) end it "should list all values" do @map.values.should sorted_eql([100, 200, 300]) end end describe NestedMultimap, "with nested values" do before do @map = NestedMultimap.new @map["a"] = 100 @map["b"] = 200 @map["b", "c"] = 300 @map["c", "e"] = 400 @map["c"] = 500 end it "should retrieve container of values for key" do @map["a"].should eql([100]) @map["b"].should eql([200]) @map["c"].should eql([500]) @map["a", "b"].should eql([100]) @map["b", "c"].should eql([200, 300]) @map["c", "e"].should eql([400, 500]) end it "should append the value to default containers" do @map << 600 @map["a"].should eql([100, 600]) @map["b"].should eql([200, 600]) @map["c"].should eql([500, 600]) @map["a", "b"].should eql([100, 600]) @map["b", "c"].should eql([200, 300, 600]) @map["c", "e"].should eql([400, 500, 600]) @map[nil].should eql([600]) end it "should duplicate the containers" do map2 = @map.dup map2.should_not equal(@map) map2.should eql(@map) map2["a"].should eql([100]) map2["b"].should eql([200]) map2["c"].should eql([500]) map2["a", "b"].should eql([100]) map2["b", "c"].should eql([200, 300]) map2["c", "e"].should eql([400, 500]) map2["a"].should_not equal(@map["a"]) map2["b"].should_not equal(@map["b"]) map2["c"].should_not equal(@map["c"]) map2["a", "b"].should_not equal(@map["a", "b"]) map2["b", "c"].should_not equal(@map["b", "c"]) map2["c", "e"].should_not equal(@map["c", "e"]) map2.default.should_not equal(@map.default) map2.default.should eql(@map.default) end it "should iterate over each key/value pair and yield an array" do a = [] @map.each { |pair| a << pair } a.should sorted_eql([ ["a", 100], [["b", "c"], 200], [["b", "c"], 300], [["c", "e"], 400], [["c", "e"], 500] ]) end it "should iterate over each key/container" do a = [] @map.each_association { |key, container| a << [key, container] } a.should sorted_eql([ ["a", [100]], [["b", "c"], [200, 300]], [["c", "e"], [400, 500]] ]) end it "should iterate over each container plus the default" do a = [] @map.each_container_with_default { |container| a << container } a.should sorted_eql([ [100], [200, 300], [200], [400, 500], [500], [] ]) end it "should iterate over each key" do a = [] @map.each_key { |key| a << key } a.should sorted_eql(["a", ["b", "c"], ["b", "c"], ["c", "e"], ["c", "e"]]) end it "should iterate over each key/value pair and yield the pair" do h = {} @map.each_pair { |key, value| (h[key] ||= []) << value } h.should eql({ "a" => [100], ["c", "e"] => [400, 500], ["b", "c"] => [200, 300] }) end it "should iterate over each value" do a = [] @map.each_value { |value| a << value } a.should sorted_eql([100, 200, 300, 400, 500]) end it "should list all containers" do @map.containers.should sorted_eql([[100], [200, 300], [400, 500]]) end it "should list all containers plus the default" do @map.containers_with_default.should sorted_eql([[100], [200, 300], [200], [400, 500], [500], []]) end it "should return array of keys" do @map.keys.should eql(["a", ["b", "c"], ["b", "c"], ["c", "e"], ["c", "e"]]) end it "should list all values" do @map.values.should sorted_eql([100, 200, 300, 400, 500]) end end describe NestedMultimap, "with", Set do it_should_behave_like "Enumerable Multimap with inital values {'a' => [100], 'b' => [200, 300]}" it_should_behave_like "Hash Multimap with inital values {'a' => [100], 'b' => [200, 300]}" before do @container = Set @map = NestedMultimap.new(@container.new) @map["a"] = 100 @map["b"] = 200 @map["b"] = 300 end end multimap-1.1.2/spec/hash_examples.rb0000644000175000017500000001743212305434010016450 0ustar jonasjonasshared_examples_for Hash, Multimap, "with inital values {'a' => [100], 'b' => [200, 300]}" do before do @container ||= Array end it "should be equal to another Multimap if they contain the same keys and values" do map2 = Multimap.new(@container.new) map2["a"] = 100 map2["b"] = 200 map2["b"] = 300 @map.should eql(map2) end it "should not be equal to another Multimap if they contain different values" do @map.should_not == Multimap["a" => [100], "b" => [200]] end it "should retrieve container of values for key" do @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new([200, 300])) @map["z"].should eql(@container.new) end it "should append values to container at key" do @map["a"] = 400 @map.store("b", 500) @map["a"].should eql(@container.new([100, 400])) @map["b"].should eql(@container.new([200, 300, 500])) end it "should clear all key/values" do @map.clear @map.should be_empty end it "should be the class of the container" do @map.default.class.should eql(@container) end it "should delete all values at key" do @map.delete("a") @map["a"].should eql(@container.new) end it "should delete single value at key" do @map.delete("b", 200) @map["b"].should eql(@container.new([300])) end it "should delete if key condition is matched" do @map.delete_if { |key, value| key >= "b" }.should eql(@map) @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new) @map.delete_if { |key, value| key > "z" }.should eql(@map) end it "should delete if value condition is matched" do @map.delete_if { |key, value| value >= 300 }.should eql(@map) @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new([200])) end it "should duplicate the containers" do map2 = @map.dup map2.should_not equal(@map) map2.should eql(@map) map2["a"].should_not equal(@map["a"]) map2["b"].should_not equal(@map["b"]) map2.default.should_not equal(@map.default) map2.default.should eql(@map.default) end it "should freeze containers" do @map.freeze @map.should be_frozen @map["a"].should be_frozen @map["b"].should be_frozen end it "should iterate over each key/value pair and yield an array" do a = [] @map.each { |pair| a << pair } a.should sorted_eql([["a", 100], ["b", 200], ["b", 300]]) end it "should iterate over each container" do a = [] @map.each_container { |container| a << container } a.should sorted_eql([@container.new([100]), @container.new([200, 300])]) end it "should iterate over each key/container" do a = [] @map.each_association { |key, container| a << [key, container] } a.should sorted_eql([["a", @container.new([100])], ["b", @container.new([200, 300])]]) end it "should iterate over each key" do a = [] @map.each_key { |key| a << key } a.should sorted_eql(["a", "b", "b"]) end it "should iterate over each key/value pair and yield the pair" do h = {} @map.each_pair { |key, value| (h[key] ||= []) << value } h.should eql({ "a" => [100], "b" => [200, 300] }) end it "should iterate over each value" do a = [] @map.each_value { |value| a << value } a.should sorted_eql([100, 200, 300]) end it "should be empty if there are no key/value pairs" do @map.clear @map.should be_empty end it "should not be empty if there are any key/value pairs" do @map.should_not be_empty end it "should fetch container of values for key" do @map.fetch("a").should eql(@container.new([100])) @map.fetch("b").should eql(@container.new([200, 300])) lambda { @map.fetch("z") }.should raise_error(IndexError) end it "should check if key is present" do @map.has_key?("a").should be_true @map.key?("a").should be_true @map.has_key?("z").should be_false @map.key?("z").should be_false end it "should check containers when looking up by value" do @map.has_value?(100).should be_true @map.value?(100).should be_true @map.has_value?(999).should be_false @map.value?(999).should be_false end it "it should return the index for value" do if @map.respond_to?(:index) @map.index(200).should eql(@container.new(["b"])) @map.index(999).should eql(@container.new) end end it "should replace the contents of hash" do @map.replace({ "c" => @container.new([300]), "d" => @container.new([400]) }) @map["a"].should eql(@container.new) @map["c"].should eql(@container.new([300])) end it "should return an inverted Multimap" do if @map.respond_to?(:invert) map2 = Multimap.new(@container.new) map2[100] = "a" map2[200] = "b" map2[300] = "b" @map.invert.should eql(map2) end end it "should return array of keys" do @map.keys.should eql(["a", "b", "b"]) end it "should return the number of key/value pairs" do @map.length.should eql(3) @map.size.should eql(3) end it "should duplicate map and with merged values" do map = @map.merge("b" => 254, "c" => @container.new([300])) map["a"].should eql(@container.new([100])) map["b"].should eql(@container.new([200, 300, 254])) map["c"].should eql(@container.new([300])) @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new([200, 300])) @map["c"].should eql(@container.new) end it "should update map" do @map.update("b" => 254, "c" => @container.new([300])) @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new([200, 300, 254])) @map["c"].should eql(@container.new([300])) klass = @map.class @map.update(klass[@container.new, {"a" => @container.new([400, 500]), "c" => 600}]) @map["a"].should eql(@container.new([100, 400, 500])) @map["b"].should eql(@container.new([200, 300, 254])) @map["c"].should eql(@container.new([300, 600])) end it "should reject key pairs on copy of the map" do map = @map.reject { |key, value| key >= "b" } map["b"].should eql(@container.new) @map["b"].should eql(@container.new([200, 300])) end it "should reject value pairs on copy of the map" do map = @map.reject { |key, value| value >= 300 } map["b"].should eql(@container.new([200])) @map["b"].should eql(@container.new([200, 300])) end it "should reject key pairs" do @map.reject! { |key, value| key >= "b" }.should eql(@map) @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new) @map.reject! { |key, value| key >= "z" }.should eql(nil) end it "should reject value pairs" do @map.reject! { |key, value| value >= 300 }.should eql(@map) @map["a"].should eql(@container.new([100])) @map["b"].should eql(@container.new([200])) @map.reject! { |key, value| key >= "z" }.should eql(nil) end it "should select key/value pairs" do @map.select { |k, v| k > "a" }.should eql(Multimap["b", [200, 300]]) @map.select { |k, v| v < 200 }.should eql(Multimap["a", 100]) end it "should convert to hash" do @map.to_hash["a"].should eql(@container.new([100])) @map.to_hash["b"].should eql(@container.new([200, 300])) @map.to_hash.should_not equal(@map) end it "should return all containers" do @map.containers.should sorted_eql([@container.new([100]), @container.new([200, 300])]) end it "should return all values" do @map.values.should sorted_eql([100, 200, 300]) end it "should return return values at keys" do @map.values_at("a", "b").should eql([@container.new([100]), @container.new([200, 300])]) end it "should marshal hash" do data = Marshal.dump(@map) Marshal.load(data).should eql(@map) end it "should dump yaml" do require 'yaml' data = YAML.dump(@map) YAML.load(data).should eql(@map) end end multimap-1.1.2/spec/spec_helper.rb0000644000175000017500000000215112305434010016110 0ustar jonasjonasrequire 'multiset' require 'multimap' require 'nested_multimap' require 'enumerable_examples' require 'hash_examples' require 'set_examples' # Rubinius Hash isn't ordered by insert order Spec::Matchers.define :sorted_eql do |expected| if defined? Rubinius sorter = lambda { |a, b| a.hash <=> b.hash } match do |actual| actual.sort(&sorter).should eql(expected.sort(&sorter)) end else match do |actual| actual.should eql(expected) end end end require 'set' if defined? Rubinius class Set def <=>(other) to_a <=> other.to_a end end end require 'forwardable' class MiniArray extend Forwardable attr_accessor :data def initialize(data = []) @data = data end def initialize_copy(orig) @data = orig.data.dup end def_delegators :@data, :<<, :each, :delete, :delete_if def ==(other) other.is_a?(self.class) && @data == other.data end def eql?(other) other.is_a?(self.class) && @data.eql?(other.data) end if defined? Rubinius def hash @data.hash end def <=>(other) @data <=> other.data end end end multimap-1.1.2/README.rdoc0000644000175000017500000000061512305434010014151 0ustar jonasjonas= Multimap A Ruby multimap implementation that also includes multiset and nested multimap implementations. == Example require 'multimap' map = Multimap.new map["a"] = 100 map["b"] = 200 map["a"] = 300 map["a"] # -> [100, 300] map["b"] # -> [200] map.keys # -> # multimap-1.1.2/ext/0000755000175000017500000000000012305434010013141 5ustar jonasjonasmultimap-1.1.2/ext/nested_multimap_ext.c0000644000175000017500000000127412305434010017363 0ustar jonasjonas#include "ruby.h" VALUE cNestedMultimap; static VALUE rb_nested_multimap_aref(int argc, VALUE *argv, VALUE self) { int i; VALUE r, h; for (i = 0, r = self; rb_obj_is_kind_of(r, cNestedMultimap) == Qtrue; i++) { h = rb_funcall(r, rb_intern("_internal_hash"), 0); Check_Type(h, T_HASH); r = (i < argc) ? rb_hash_aref(h, argv[i]) : RHASH(h)->ifnone; } return r; } void Init_nested_multimap_ext() { cNestedMultimap = rb_const_get(rb_cObject, rb_intern("NestedMultimap")); // rb_funcall(cNestedMultimap, rb_intern("remove_method"), 1, rb_intern("[]")); rb_eval_string("NestedMultimap.send(:remove_method, :[])"); rb_define_method(cNestedMultimap, "[]", rb_nested_multimap_aref, -1); } multimap-1.1.2/ext/extconf.rb0000644000175000017500000000027412305434010015137 0ustar jonasjonasif RUBY_PLATFORM == 'java' File.open('Makefile', 'w') { |f| f.puts("install:\n\t$(echo Skipping native extensions)") } else require 'mkmf' create_makefile('nested_multimap_ext') end multimap-1.1.2/LICENSE0000644000175000017500000000203612305434010013347 0ustar jonasjonasCopyright (c) 2009 Joshua Peek 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.multimap-1.1.2/extras/0000755000175000017500000000000012305434010013647 5ustar jonasjonasmultimap-1.1.2/extras/graphing.rb0000644000175000017500000000275212305434010016001 0ustar jonasjonasrequire 'graphviz' class Object def to_graph_node "node#{object_id}" end def to_graph_label inspect.dot_escape end def add_to_graph(graph) graph.add_node(to_graph_node, :label => to_graph_label) end end class Array def to_graph_label "{#{map { |e| e.to_graph_label }.join('|')}}" end end class String DOT_ESCAPE = %w( \\ < > { } ) DOT_ESCAPE_REGEXP = Regexp.compile("(#{Regexp.union(*DOT_ESCAPE).source})") def dot_escape gsub(DOT_ESCAPE_REGEXP) {|s| "\\#{s}" } end end class Multimap def to_graph_label label = [] @hash.each_key do |key| label << "<#{key.to_graph_node}> #{key.to_graph_label}" end "#{label.join('|')}|" end def add_to_graph(graph) hash_node = super @hash.each_pair do |key, container| node = container.add_to_graph(graph) graph.add_edge("#{hash_node.name}:#{key.to_graph_node}", node) end unless default.nil? node = default.add_to_graph(graph) graph.add_edge("#{hash_node.name}:default", node) end hash_node end def to_graph g = GraphViz::new('G') g[:nodesep] = '.05' g[:rankdir] = 'LR' g.node[:shape] = 'record' g.node[:width] = '.1' g.node[:height] = '.1' add_to_graph(g) g end def open_graph! to_graph.output(:png => '/tmp/graph.png') system('open /tmp/graph.png') end end if __FILE__ == $0 $: << 'lib' require 'multimap' map = Multimap['a' => 100, 'b' => [200, 300]] map.open_graph! end multimap-1.1.2/.gitignore0000644000175000017500000000003612305434010014330 0ustar jonasjonas*.bundle coverage/ html/ tmp/ multimap-1.1.2/lib/0000755000175000017500000000000012305434010013107 5ustar jonasjonasmultimap-1.1.2/lib/nested_multimap.rb0000644000175000017500000000774412305434010016642 0ustar jonasjonasrequire 'multimap' # NestedMultimap allows values to be assoicated with a nested # set of keys. class NestedMultimap < Multimap # call-seq: # multimap[*keys] = value => value # multimap.store(*keys, value) => value # # Associates the value given by value with multiple key # given by keys. # # map = NestedMultimap.new # map["a"] = 100 # map["a", "b"] = 101 # map["a"] = 102 # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}} def store(*args) keys = args value = args.pop raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value if keys.length > 1 update_container(keys.shift) do |container| container = self.class.new(container) unless container.is_a?(self.class) container[*keys] = value container end elsif keys.length == 1 super(keys.first, value) else self << value end end alias_method :[]=, :store # call-seq: # multimap << obj => multimap # # Pushes the given object on to the end of all the containers. # # map = NestedMultimap["a" => [100], "b" => [200, 300]] # map << 300 # map["a"] #=> [100, 300] # map["c"] #=> [300] def <<(value) @hash.each_value { |container| container << value } self.default << value self end # call-seq: # multimap[*keys] => value # multimap[key1, key2, key3] => value # # Retrieves the value object corresponding to the # *keys object. def [](*keys) i, l, r, k = 0, keys.length, self, self.class while r.is_a?(k) r = i < l ? r._internal_hash[keys[i]] : r.default i += 1 end r end # call-seq: # multimap.each_association { |key, container| block } => multimap # # Calls block once for each key/container in map, passing # the key and container to the block as parameters. # # map = NestedMultimap.new # map["a"] = 100 # map["a", "b"] = 101 # map["a"] = 102 # map["c"] = 200 # map.each_association { |key, container| puts "#{key} is #{container}" } # # produces: # # ["a", "b"] is [100, 101, 102] # "c" is [200] def each_association super() do |key, container| if container.respond_to?(:each_association) container.each_association do |nested_key, value| yield [key, nested_key].flatten, value end else yield key, container end end end # call-seq: # multimap.each_container_with_default { |container| block } => map # # Calls block for every container in map including # the default, passing the container as a parameter. # # map = NestedMultimap.new # map["a"] = 100 # map["a", "b"] = 101 # map["a"] = 102 # map.each_container_with_default { |container| puts container } # # produces: # # [100, 101, 102] # [100, 102] # [] def each_container_with_default(&block) @hash.each_value do |container| iterate_over_container(container, &block) end iterate_over_container(default, &block) self end # call-seq: # multimap.containers_with_default => array # # Returns a new array populated with all the containers from # map including the default. # # map = NestedMultimap.new # map["a"] = 100 # map["a", "b"] = 101 # map["a"] = 102 # map.containers_with_default #=> [[100, 101, 102], [100, 102], []] def containers_with_default containers = [] each_container_with_default { |container| containers << container } containers end def inspect #:nodoc: super.gsub(/\}$/, ", default => #{default.inspect}}") end private def iterate_over_container(container) if container.respond_to?(:each_container_with_default) container.each_container_with_default do |value| yield value end else yield container end end end begin require 'nested_multimap_ext' rescue LoadError end multimap-1.1.2/lib/multimap.rb0000644000175000017500000003320712305434010015271 0ustar jonasjonasrequire 'forwardable' require 'multiset' # Multimap is a generalization of a map or associative array # abstract data type in which more than one value may be associated # with and returned for a given key. # # == Example # # require 'multimap' # map = Multimap.new # map["a"] = 100 # map["b"] = 200 # map["a"] = 300 # map["a"] # -> [100, 300] # map["b"] # -> [200] # map.keys # -> # class Multimap extend Forwardable include Enumerable # call-seq: # Multimap[ [key =>|, value]* ] => multimap # # Creates a new multimap populated with the given objects. # # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]} # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]} def self.[](*args) default = [] if args.size == 2 && args.last.is_a?(Hash) default = args.shift elsif !args.first.is_a?(Hash) && args.size % 2 == 1 default = args.shift end if args.size == 1 && args.first.is_a?(Hash) args[0] = args.first.inject({}) { |hash, (key, value)| unless value.is_a?(default.class) value = (default.dup << value) end hash[key] = value hash } else index = 0 args.map! { |value| unless index % 2 == 0 || value.is_a?(default.class) value = (default.dup << value) end index += 1 value } end map = new map.instance_variable_set(:@hash, Hash[*args]) map.default = default map end # call-seq: # Multimap.new => multimap # Multimap.new(default) => multimap # # Returns a new, empty multimap. # # map = Multimap.new(Set.new) # h["a"] = 100 # h["b"] = 200 # h["a"] #=> [100].to_set # h["c"] #=> [].to_set def initialize(default = []) @hash = Hash.new(default) end def initialize_copy(original) #:nodoc: @hash = Hash.new(original.default.dup) original._internal_hash.each_pair do |key, container| @hash[key] = container.dup end end def_delegators :@hash, :clear, :default, :default=, :empty?, :fetch, :has_key?, :key? # Retrieves the value object corresponding to the # *keys object. def [](key) @hash[key] end # call-seq: # map[key] = value => value # map.store(key, value) => value # # Associates the value given by value with the key # given by key. Unlike a regular hash, multiple can be # assoicated with the same value. # # map = Multimap["a" => 100, "b" => 200] # map["a"] = 9 # map["c"] = 4 # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]} def store(key, value) update_container(key) do |container| container << value container end end alias_method :[]=, :store # call-seq: # map.delete(key, value) => value # map.delete(key) => value # # Deletes and returns a key-value pair from map. If only # key is given, all the values matching that key will be # deleted. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.delete("b", 300) #=> 300 # map.delete("a") #=> [100] def delete(key, value = nil) if value @hash[key].delete(value) else @hash.delete(key) end end # call-seq: # map.each { |key, value| block } => map # # Calls block for each key/value pair in map, passing # the key and value to the block as a two-element array. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.each { |key, value| puts "#{key} is #{value}" } # # produces: # # a is 100 # b is 200 # b is 300 def each each_pair do |key, value| yield [key, value] end end # call-seq: # map.each_association { |key, container| block } => map # # Calls block once for each key/container in map, passing # the key and container to the block as parameters. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.each_association { |key, container| puts "#{key} is #{container}" } # # produces: # # a is [100] # b is [200, 300] def each_association(&block) @hash.each_pair(&block) end # call-seq: # map.each_container { |container| block } => map # # Calls block for each container in map, passing the # container as a parameter. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.each_container { |container| puts container } # # produces: # # [100] # [200, 300] def each_container each_association do |_, container| yield container end end # call-seq: # map.each_key { |key| block } => map # # Calls block for each key in hsh, passing the key # as a parameter. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.each_key { |key| puts key } # # produces: # # a # b # b def each_key each_pair do |key, _| yield key end end # call-seq: # map.each_pair { |key_value_array| block } => map # # Calls block for each key/value pair in map, # passing the key and value as parameters. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.each_pair { |key, value| puts "#{key} is #{value}" } # # produces: # # a is 100 # b is 200 # b is 300 def each_pair each_association do |key, values| values.each do |value| yield key, value end end end # call-seq: # map.each_value { |value| block } => map # # Calls block for each key in map, passing the # value as a parameter. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.each_value { |value| puts value } # # produces: # # 100 # 200 # 300 def each_value each_pair do |_, value| yield value end end def ==(other) #:nodoc: case other when Multimap @hash == other._internal_hash else @hash == other end end def eql?(other) #:nodoc: case other when Multimap @hash.eql?(other._internal_hash) else @hash.eql?(other) end end def freeze #:nodoc: each_container { |container| container.freeze } default.freeze super end # call-seq: # map.has_value?(value) => true or false # map.value?(value) => true or false # # Returns true if the given value is present for any key # in map. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.has_value?(300) #=> true # map.has_value?(999) #=> false def has_value?(value) values.include?(value) end alias_method :value?, :has_value? # call-seq: # map.index(value) => key # # Returns the key for a given value. If not found, returns # nil. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.index(100) #=> "a" # map.index(200) #=> "b" # map.index(999) #=> nil def index(value) invert[value] end # call-seq: # map.delete_if {| key, value | block } -> map # # Deletes every key-value pair from map for which block # evaluates to true. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.delete_if {|key, value| value >= 300 } # #=> Multimap["a" => 100, "b" => 200] # def delete_if each_association do |key, container| container.delete_if do |value| yield [key, value] end end self end # call-seq: # map.reject {| key, value | block } -> map # # Same as Multimap#delete_if, but works on (and returns) a # copy of the map. Equivalent to # map.dup.delete_if. # def reject(&block) dup.delete_if(&block) end # call-seq: # map.reject! {| key, value | block } -> map or nil # # Equivalent to Multimap#delete_if, but returns # nil if no changes were made. # def reject!(&block) old_size = size delete_if(&block) old_size == size ? nil : self end # call-seq: # map.replace(other_map) => map # # Replaces the contents of map with the contents of # other_map. # # map = Multimap["a" => 100, "b" => 200] # map.replace({ "c" => 300, "d" => 400 }) # #=> Multimap["c" => 300, "d" => 400] def replace(other) case other when Array @hash.replace(self.class[self.default, *other]) when Hash @hash.replace(self.class[self.default, other]) when self.class @hash.replace(other) else raise ArgumentError end end # call-seq: # map.invert => multimap # # Returns a new multimap created by using map's values as keys, # and the keys as values. # # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]] # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"] def invert h = self.class.new(default.dup) each_pair { |key, value| h[value] = key } h end # call-seq: # map.keys => multiset # # Returns a new +Multiset+ populated with the keys from this hash. See also # Multimap#values and Multimap#containers. # # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] # map.keys #=> Multiset.new(["a", "b", "b", "c"]) def keys keys = Multiset.new each_key { |key| keys << key } keys end # Returns true if the given key is present in Multimap. def include?(key) keys.include?(key) end alias_method :member?, :include? # call-seq: # map.length => fixnum # map.size => fixnum # # Returns the number of key-value pairs in the map. # # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] # map.length #=> 4 # map.delete("a") #=> 100 # map.length #=> 3 def size values.size end alias_method :length, :size # call-seq: # map.merge(other_map) => multimap # # Returns a new multimap containing the contents of other_map and # the contents of map. # # map1 = Multimap["a" => 100, "b" => 200] # map2 = Multimap["a" => 254, "c" => 300] # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] # map1 #=> Multimap["a" => 100, "b" => 200] def merge(other) dup.update(other) end # call-seq: # map.merge!(other_map) => multimap # map.update(other_map) => multimap # # Adds each pair from other_map to map. # # map1 = Multimap["a" => 100, "b" => 200] # map2 = Multimap["b" => 254, "c" => 300] # # map1.merge!(map2) # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] def update(other) case other when self.class other.each_pair { |key, value| store(key, value) } when Hash update(self.class[self.default, other]) else raise ArgumentError end self end alias_method :merge!, :update # call-seq: # map.select { |key, value| block } => multimap # # Returns a new Multimap consisting of the pairs for which the # block returns true. # # map = Multimap["a" => 100, "b" => 200, "c" => 300] # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300] # map.select { |k,v| v < 200 } #=> Multimap["a" => 100] def select inject(self.class.new) { |map, (key, value)| map[key] = value if yield([key, value]) map } end # call-seq: # map.to_a => array # # Converts map to a nested array of [key, # value] arrays. # # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]] def to_a ary = [] each_pair do |key, value| ary << [key, value] end ary end # call-seq: # map.to_hash => hash # # Converts map to a basic hash. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.to_hash #=> { "a" => [100], "b" => [200, 300] } def to_hash @hash.dup end # call-seq: # map.containers => array # # Returns a new array populated with the containers from map. See # also Multimap#keys and Multimap#values. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.containers #=> [[100], [200, 300]] def containers containers = [] each_container { |container| containers << container } containers end # call-seq: # map.values => array # # Returns a new array populated with the values from map. See # also Multimap#keys and Multimap#containers. # # map = Multimap["a" => 100, "b" => [200, 300]] # map.values #=> [100, 200, 300] def values values = [] each_value { |value| values << value } values end # Return an array containing the values associated with the given keys. def values_at(*keys) @hash.values_at(*keys) end def marshal_dump #:nodoc: @hash end def marshal_load(hash) #:nodoc: @hash = hash end def to_yaml(opts = {}) #:nodoc: YAML::quick_emit(self, opts) do |out| out.map(taguri, to_yaml_style) do |map| @hash.each do |k, v| map.add(k, v) end map.add('__default__', @hash.default) end end end def yaml_initialize(tag, val) #:nodoc: default = val.delete('__default__') @hash = val @hash.default = default self end protected def _internal_hash #:nodoc: @hash end def update_container(key) #:nodoc: container = @hash[key] container = container.dup if container.equal?(default) container = yield(container) @hash[key] = container end end multimap-1.1.2/lib/multiset.rb0000644000175000017500000001150712305434010015306 0ustar jonasjonasrequire 'set' # Multiset implements a collection of unordered values and # allows duplicates. # # == Example # # require 'multiset' # s1 = Multiset.new [1, 2] # -> # # s1.add(2) # -> # # s1.merge([2, 6]) # -> # # s1.multiplicity(2) # -> 3 # s1.multiplicity(3) # -> 1 class Multiset < Set def initialize(*args, &block) #:nodoc: @hash = Hash.new(0) super end # Returns the number of times an element belongs to the multiset. def multiplicity(e) @hash[e] end # Returns the total number of elements in a multiset, including # repeated memberships def cardinality @hash.inject(0) { |s, (e, m)| s += m } end alias_method :size, :cardinality alias_method :length, :cardinality # Converts the set to an array. The order of elements is uncertain. def to_a inject([]) { |ary, (key, _)| ary << key } end # Returns true if the set is a superset of the given set. def superset?(set) set.is_a?(self.class) or raise ArgumentError, "value must be a set" return false if cardinality < set.cardinality set.all? { |o| set.multiplicity(o) <= multiplicity(o) } end # Returns true if the set is a proper superset of the given set. def proper_superset?(set) set.is_a?(self.class) or raise ArgumentError, "value must be a set" return false if cardinality <= set.cardinality set.all? { |o| set.multiplicity(o) <= multiplicity(o) } end # Returns true if the set is a subset of the given set. def subset?(set) set.is_a?(self.class) or raise ArgumentError, "value must be a set" return false if set.cardinality < cardinality all? { |o| multiplicity(o) <= set.multiplicity(o) } end # Returns true if the set is a proper subset of the given set. def proper_subset?(set) set.is_a?(self.class) or raise ArgumentError, "value must be a set" return false if set.cardinality <= cardinality all? { |o| multiplicity(o) <= set.multiplicity(o) } end # Calls the given block once for each element in the set, passing # the element as parameter. Returns an enumerator if no block is # given. def each @hash.each_pair do |key, multiplicity| multiplicity.times do yield(key) end end self end # Adds the given object to the set and returns self. Use +merge+ to # add many elements at once. def add(o) @hash[o] ||= 0 @hash[o] += 1 self end alias << add undef :add? # Deletes all the identical object from the set and returns self. # If +n+ is given, it will remove that amount of identical objects # from the set. Use +subtract+ to delete many different items at # once. def delete(o, n = nil) if n @hash[o] ||= 0 @hash[o] -= n if @hash[o] > 0 @hash.delete(o) if @hash[o] == 0 else @hash.delete(o) end self end undef :delete? # Deletes every element of the set for which block evaluates to # true, and returns self. def delete_if each { |o| delete(o) if yield(o) } self end # Merges the elements of the given enumerable object to the set and # returns self. def merge(enum) enum.each { |o| add(o) } self end # Deletes every element that appears in the given enumerable object # and returns self. def subtract(enum) enum.each { |o| delete(o, 1) } self end # Returns a new set containing elements common to the set and the # given enumerable object. def &(enum) s = dup n = self.class.new enum.each { |o| if s.include?(o) s.delete(o, 1) n.add(o) end } n end alias intersection & # Returns a new set containing elements exclusive between the set # and the given enumerable object. (set ^ enum) is equivalent to # ((set | enum) - (set & enum)). def ^(enum) n = self.class.new(enum) each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) } n end # Returns true if two sets are equal. Two multisets are equal if # they have the same cardinalities and each element has the same # multiplicity in both sets. The equality of each element inside # the multiset is defined according to Object#eql?. def eql?(set) return true if equal?(set) set = self.class.new(set) unless set.is_a?(self.class) return false unless cardinality == set.cardinality superset?(set) && subset?(set) end alias_method :==, :eql? def marshal_dump #:nodoc: @hash end def marshal_load(hash) #:nodoc: @hash = hash end def to_yaml(opts = {}) #:nodoc: YAML::quick_emit(self, opts) do |out| out.map(taguri, to_yaml_style) do |map| @hash.each do |k, v| map.add(k, v) end end end end def yaml_initialize(tag, val) #:nodoc: @hash = val self end end multimap-1.1.2/Rakefile0000644000175000017500000000111112305434010014000 0ustar jonasjonasrequire 'rubygems/specification' spec = eval(File.read('multimap.gemspec')) if spec.has_rdoc require 'rake/rdoctask' Rake::RDocTask.new { |rdoc| rdoc.options = spec.rdoc_options rdoc.rdoc_files = spec.files } end task :default => :spec require 'spec/rake/spectask' Spec::Rake::SpecTask.new do |t| t.warning = true end begin require 'rake/extensiontask' Rake::ExtensionTask.new do |ext| ext.name = 'nested_multimap_ext' ext.gem_spec = $spec end desc "Run specs using C ext" task "spec:ext" => [:compile, :spec, :clobber] rescue LoadError end multimap-1.1.2/multimap.gemspec0000644000175000017500000000125012305434010015534 0ustar jonasjonasGem::Specification.new do |s| s.name = 'multimap' s.version = '1.1.2' s.date = '2009-12-24' s.summary = 'Ruby multimap implementation' s.description = <<-EOS Multimap includes a Ruby multimap implementation EOS s.files = [ 'ext/nested_multimap_ext.c', 'lib/multimap.rb', 'lib/multiset.rb', 'lib/nested_multimap.rb' ] s.extensions = ['ext/extconf.rb'] s.has_rdoc = true s.extra_rdoc_files = %w[README.rdoc LICENSE] s.rdoc_options << '--title' << 'Multimap' << '--main' << 'README.rdoc' s.author = 'Joshua Peek' s.email = 'josh@joshpeek.com' s.homepage = 'http://github.com/josh/multimap' end multimap-1.1.2/benchmarks/0000755000175000017500000000000012305434010014456 5ustar jonasjonasmultimap-1.1.2/benchmarks/bm_nested_multimap_lookup.rb0000644000175000017500000000206412305434010022246 0ustar jonasjonas$: << 'lib' require 'nested_multimap' hash = { "a" => true } map = NestedMultimap.new map["a"] = 100 map["a", "b", "c"] = 200 map["a", "b", "c", "d", "e", "f"] = 300 require 'benchmark' TIMES = 100_000 Benchmark.bmbm do |x| x.report("base:") { TIMES.times { hash["a"] } } x.report("best:") { TIMES.times { map["a"] } } x.report("average:") { TIMES.times { map["a", "b", "c"] } } x.report("worst:") { TIMES.times { map["a", "b", "c", "d", "e", "f"] } } end # Pure Ruby # user system total real # base: 0.050000 0.000000 0.050000 ( 0.049722) # best: 0.480000 0.010000 0.490000 ( 0.491012) # average: 0.770000 0.000000 0.770000 ( 0.773535) # worst: 1.120000 0.010000 1.130000 ( 1.139097) # C extension # user system total real # base: 0.050000 0.000000 0.050000 ( 0.050990) # best: 0.090000 0.000000 0.090000 ( 0.088981) # average: 0.130000 0.000000 0.130000 ( 0.132098) # worst: 0.150000 0.000000 0.150000 ( 0.158293) multimap-1.1.2/benchmarks/bm_nested_multimap_construction.rb0000644000175000017500000000225112305434010023465 0ustar jonasjonas$: << 'lib' require 'nested_multimap' tiny_mapping = { ["a"] => 100 } medium_mapping = { ["a"] => 100, ["a", "b", "c"] => 200, ["b"] => 300, ["b", "c"] => 400, ["c"] => 500, ["c", "d"] => 600, ["c", "d", "e"] => 700, ["c", "d", "e", "f"] => 800 } huge_mapping = {} alpha = ("a".."zz").to_a 100.times do |n| keys = ("a"..alpha[n % alpha.length]).to_a huge_mapping[keys] = n * 100 end require 'benchmark' Benchmark.bmbm do |x| x.report("base:") { NestedMultimap.new } x.report("tiny:") { map = NestedMultimap.new tiny_mapping.each_pair { |keys, value| map[*keys] = value } } x.report("medium:") { map = NestedMultimap.new medium_mapping.each_pair { |keys, value| map[*keys] = value } } x.report("huge:") { map = NestedMultimap.new huge_mapping.each_pair { |keys, value| map[*keys] = value } } end # Pure Ruby # user system total real # base: 0.000000 0.000000 0.000000 ( 0.000014) # tiny: 0.000000 0.000000 0.000000 ( 0.000054) # medium: 0.000000 0.000000 0.000000 ( 0.000186) # huge: 0.050000 0.000000 0.050000 ( 0.051302)