josh-multimap 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Joshua Peek
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1 @@
1
+ = Multimap
data/ext/extconf.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('nested_multimap_ext')
@@ -0,0 +1,17 @@
1
+ #include "ruby.h"
2
+
3
+ static VALUE rb_nested_multimap_aref(int argc, VALUE *argv, VALUE self)
4
+ {
5
+ int i;
6
+ VALUE r, k;
7
+
8
+ for (i = 0, r = self, k = TYPE(self); TYPE(r) == k; i++)
9
+ r = (i < argc) ? rb_hash_aref(r, argv[i]) : RHASH(r)->ifnone;
10
+
11
+ return r;
12
+ }
13
+
14
+ void Init_nested_multimap_ext() {
15
+ VALUE cNestedMultimap = rb_const_get(rb_cObject, rb_intern("NestedMultimap"));
16
+ rb_define_method(cNestedMultimap, "[]", rb_nested_multimap_aref, -1);
17
+ }
@@ -0,0 +1,46 @@
1
+ require 'nested_multimap'
2
+
3
+ # FuzzyNestedMultimap is an extension on top of NestedMultimap
4
+ # that allows fuzzy matching.
5
+ class FuzzyNestedMultimap < NestedMultimap
6
+ WILD_REGEXP = /.*/.freeze
7
+
8
+ # call-seq:
9
+ # multimap[*keys] = value => value
10
+ # multimap.store(*keys, value) => value
11
+ #
12
+ # Associates the value given by <i>value</i> with multiple key
13
+ # given by <i>keys</i>. Valid keys are restricted to strings
14
+ # and regexps. If a Regexp is used as a key, the value will be
15
+ # insert at every String key that matches that expression.
16
+ def store(*args)
17
+ keys = args.dup
18
+ value = keys.pop
19
+ key = keys.shift || WILD_REGEXP
20
+
21
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
22
+
23
+ case key
24
+ when Regexp
25
+ if keys.empty?
26
+ hash_each_pair { |k, l| l << value if key =~ k }
27
+ self.default << value
28
+ else
29
+ hash_each_pair { |k, _|
30
+ if key =~ k
31
+ args[0] = k
32
+ super(*args)
33
+ end
34
+ }
35
+
36
+ self.default = self.class.new(default) unless default.is_a?(self.class)
37
+ default[*keys.dup] = value
38
+ end
39
+ when String
40
+ super(*args)
41
+ else
42
+ raise ArgumentError, "unsupported key: #{args.first.inspect}"
43
+ end
44
+ end
45
+ alias_method :[]=, :store
46
+ end
data/lib/multimap.rb ADDED
@@ -0,0 +1,460 @@
1
+ require 'multiset'
2
+
3
+ # Multimap is a generalization of a map or associative array
4
+ # abstract data type in which more than one value may be associated
5
+ # with and returned for a given key.
6
+ class Multimap < Hash
7
+ #--
8
+ # Ignore protected aliases back to the original Hash methods
9
+ #++
10
+ module_eval %{
11
+ alias_method :hash_aref, :[]
12
+ protected :hash_aref
13
+
14
+ alias_method :hash_aset, :[]=
15
+ protected :hash_aset
16
+
17
+ alias_method :hash_each_pair, :each_pair
18
+ protected :hash_each_pair
19
+ }
20
+
21
+ # call-seq:
22
+ # Multimap[ [key =>|, value]* ] => multimap
23
+ #
24
+ # Creates a new multimap populated with the given objects.
25
+ #
26
+ # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]}
27
+ # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]}
28
+ def self.[](*args)
29
+ default = []
30
+
31
+ if args.size == 2 && args.last.is_a?(Hash)
32
+ default = args.shift
33
+ elsif !args.first.is_a?(Hash) && args.size % 2 == 1
34
+ default = args.shift
35
+ end
36
+
37
+ if args.size == 1 && args.first.is_a?(Hash)
38
+ args[0] = args.first.inject({}) { |hash, (key, value)|
39
+ unless value.is_a?(default.class)
40
+ value = (default.dup << value)
41
+ end
42
+ hash[key] = value
43
+ hash
44
+ }
45
+ else
46
+ index = 0
47
+ args.map! { |value|
48
+ unless index % 2 == 0 || value.is_a?(default.class)
49
+ value = (default.dup << value)
50
+ end
51
+ index += 1
52
+ value
53
+ }
54
+ end
55
+
56
+ map = super
57
+ map.default = default
58
+ map
59
+ end
60
+
61
+ # call-seq:
62
+ # Multimap.new => multimap
63
+ # Multimap.new(default) => multimap
64
+ #
65
+ # Returns a new, empty multimap.
66
+ #
67
+ # map = Multimap.new(Set.new)
68
+ # h["a"] = 100
69
+ # h["b"] = 200
70
+ # h["a"] #=> [100].to_set
71
+ # h["c"] #=> [].to_set
72
+ def initialize(default = [])
73
+ super
74
+ end
75
+
76
+ def initialize_copy(original) #:nodoc:
77
+ super
78
+ clear
79
+ original.each_pair { |key, container| self[key] = container }
80
+ end
81
+
82
+ # call-seq:
83
+ # map[key] = value => value
84
+ # map.store(key, value) => value
85
+ #
86
+ # Associates the value given by <i>value</i> with the key
87
+ # given by <i>key</i>. Unlike a regular hash, multiple can be
88
+ # assoicated with the same value.
89
+ #
90
+ # map = Multimap["a" => 100, "b" => 200]
91
+ # map["a"] = 9
92
+ # map["c"] = 4
93
+ # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]}
94
+ def store(key, value)
95
+ update_container(key) do |container|
96
+ container << value
97
+ container
98
+ end
99
+ end
100
+ alias_method :[]=, :store
101
+
102
+ # call-seq:
103
+ # map.delete(key, value) => value
104
+ # map.delete(key) => value
105
+ #
106
+ # Deletes and returns a key-value pair from <i>map</i>. If only
107
+ # <i>key</i> is given, all the values matching that key will be
108
+ # deleted.
109
+ #
110
+ # map = Multimap["a" => 100, "b" => [200, 300]]
111
+ # map.delete("b", 300) #=> 300
112
+ # map.delete("a") #=> [100]
113
+ def delete(key, value = nil)
114
+ if value
115
+ hash_aref(key).delete(value)
116
+ else
117
+ super(key)
118
+ end
119
+ end
120
+
121
+ # call-seq:
122
+ # map.each { |key, value| block } => map
123
+ #
124
+ # Calls <i>block</i> for each key/value pair in <i>map</i>, passing
125
+ # the key and value to the block as a two-element array.
126
+ #
127
+ # map = Multimap["a" => 100, "b" => [200, 300]]
128
+ # map.each { |key, value| puts "#{key} is #{value}" }
129
+ #
130
+ # <em>produces:</em>
131
+ #
132
+ # a is 100
133
+ # b is 200
134
+ # b is 300
135
+ def each
136
+ each_pair do |key, value|
137
+ yield [key, value]
138
+ end
139
+ end
140
+
141
+ # call-seq:
142
+ # map.each_association { |key, container| block } => map
143
+ #
144
+ # Calls <i>block</i> once for each key/container in <i>map</i>, passing
145
+ # the key and container to the block as parameters.
146
+ #
147
+ # map = Multimap["a" => 100, "b" => [200, 300]]
148
+ # map.each_association { |key, container| puts "#{key} is #{container}" }
149
+ #
150
+ # <em>produces:</em>
151
+ #
152
+ # a is [100]
153
+ # b is [200, 300]
154
+ def each_association
155
+ # each_pair
156
+ end
157
+ #--
158
+ # Ignore alias_method since the definition above serves
159
+ # as its documentation.
160
+ #++
161
+ module_eval "alias_method :each_association, :each_pair"
162
+
163
+ # call-seq:
164
+ # map.each_container { |container| block } => map
165
+ #
166
+ # Calls <i>block</i> for each container in <i>map</i>, passing the
167
+ # container as a parameter.
168
+ #
169
+ # map = Multimap["a" => 100, "b" => [200, 300]]
170
+ # map.each_container { |container| puts container }
171
+ #
172
+ # <em>produces:</em>
173
+ #
174
+ # [100]
175
+ # [200, 300]
176
+ def each_container
177
+ each_association do |_, container|
178
+ yield container
179
+ end
180
+ end
181
+
182
+ # call-seq:
183
+ # map.each_key { |key| block } => map
184
+ #
185
+ # Calls <i>block</i> for each key in <i>hsh</i>, passing the key
186
+ # as a parameter.
187
+ #
188
+ # map = Multimap["a" => 100, "b" => [200, 300]]
189
+ # map.each_key { |key| puts key }
190
+ #
191
+ # <em>produces:</em>
192
+ #
193
+ # a
194
+ # b
195
+ # b
196
+ def each_key
197
+ each_pair do |key, _|
198
+ yield key
199
+ end
200
+ end
201
+
202
+ # call-seq:
203
+ # map.each_pair { |key_value_array| block } => map
204
+ #
205
+ # Calls <i>block</i> for each key/value pair in <i>map</i>,
206
+ # passing the key and value as parameters.
207
+ #
208
+ # map = Multimap["a" => 100, "b" => [200, 300]]
209
+ # map.each_pair { |key, value| puts "#{key} is #{value}" }
210
+ #
211
+ # <em>produces:</em>
212
+ #
213
+ # a is 100
214
+ # b is 200
215
+ # b is 300
216
+ def each_pair
217
+ each_association do |key, values|
218
+ values.each do |value|
219
+ yield key, value
220
+ end
221
+ end
222
+ end
223
+
224
+ # call-seq:
225
+ # map.each_value { |value| block } => map
226
+ #
227
+ # Calls <i>block</i> for each key in <i>map</i>, passing the
228
+ # value as a parameter.
229
+ #
230
+ # map = Multimap["a" => 100, "b" => [200, 300]]
231
+ # map.each_value { |value| puts value }
232
+ #
233
+ # <em>produces:</em>
234
+ #
235
+ # 100
236
+ # 200
237
+ # 300
238
+ def each_value
239
+ each_pair do |_, value|
240
+ yield value
241
+ end
242
+ end
243
+
244
+ def freeze #:nodoc:
245
+ each_container { |container| container.freeze }
246
+ default.freeze
247
+ super
248
+ end
249
+
250
+ # call-seq:
251
+ # map.has_value?(value) => true or false
252
+ # map.value?(value) => true or false
253
+ #
254
+ # Returns <tt>true</tt> if the given value is present for any key
255
+ # in <i>map</i>.
256
+ #
257
+ # map = Multimap["a" => 100, "b" => [200, 300]]
258
+ # map.has_value?(300) #=> true
259
+ # map.has_value?(999) #=> false
260
+ def has_value?(value)
261
+ values.include?(value)
262
+ end
263
+ alias_method :value?, :has_value?
264
+
265
+ # call-seq:
266
+ # map.index(value) => key
267
+ #
268
+ # Returns the key for a given value. If not found, returns
269
+ # <tt>nil</tt>.
270
+ #
271
+ # map = Multimap["a" => 100, "b" => [200, 300]]
272
+ # map.index(100) #=> "a"
273
+ # map.index(200) #=> "b"
274
+ # map.index(999) #=> nil
275
+ def index(value)
276
+ invert[value]
277
+ end
278
+
279
+ # call-seq:
280
+ # map.replace(other_map) => map
281
+ #
282
+ # Replaces the contents of <i>map</i> with the contents of
283
+ # <i>other_map</i>.
284
+ #
285
+ # map = Multimap["a" => 100, "b" => 200]
286
+ # map.replace({ "c" => 300, "d" => 400 })
287
+ # #=> Multimap["c" => 300, "d" => 400]
288
+ def replace(other)
289
+ case other
290
+ when Array
291
+ super(self.class[self.default, *other])
292
+ when Hash
293
+ super(self.class[self.default, other])
294
+ when self.class
295
+ super
296
+ else
297
+ raise ArgumentError
298
+ end
299
+ end
300
+
301
+ # call-seq:
302
+ # map.invert => multimap
303
+ #
304
+ # Returns a new multimap created by using <i>map</i>'s values as keys,
305
+ # and the keys as values.
306
+ #
307
+ # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]]
308
+ # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"]
309
+ def invert
310
+ h = Multimap.new(default.dup)
311
+ each_pair { |key, value| h[value] = key }
312
+ h
313
+ end
314
+
315
+ # call-seq:
316
+ # map.keys => multiset
317
+ #
318
+ # Returns a new +Multiset+ populated with the keys from this hash. See also
319
+ # <tt>Multimap#values</tt> and <tt>Multimap#containers</tt>.
320
+ #
321
+ # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
322
+ # map.keys #=> Multiset.new(["a", "b", "b", "c"])
323
+ def keys
324
+ keys = Multiset.new
325
+ each_key { |key| keys << key }
326
+ keys
327
+ end
328
+
329
+ # call-seq:
330
+ # map.length => fixnum
331
+ # map.size => fixnum
332
+ #
333
+ # Returns the number of key-value pairs in the map.
334
+ #
335
+ # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
336
+ # map.length #=> 4
337
+ # map.delete("a") #=> 100
338
+ # map.length #=> 3
339
+ def size
340
+ values.size
341
+ end
342
+ alias_method :length, :size
343
+
344
+ # call-seq:
345
+ # map.merge(other_map) => multimap
346
+ #
347
+ # Returns a new multimap containing the contents of <i>other_map</i> and
348
+ # the contents of <i>map</i>.
349
+ #
350
+ # map1 = Multimap["a" => 100, "b" => 200]
351
+ # map2 = Multimap["a" => 254, "c" => 300]
352
+ # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
353
+ # map1 #=> Multimap["a" => 100, "b" => 200]
354
+ def merge(other)
355
+ dup.update(other)
356
+ end
357
+
358
+ # call-seq:
359
+ # map.merge!(other_map) => multimap
360
+ # map.update(other_map) => multimap
361
+ #
362
+ # Adds each pair from <i>other_map</i> to <i>map</i>.
363
+ #
364
+ # map1 = Multimap["a" => 100, "b" => 200]
365
+ # map2 = Multimap["b" => 254, "c" => 300]
366
+ #
367
+ # map1.merge!(map2)
368
+ # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
369
+ def update(other)
370
+ case other
371
+ when self.class
372
+ other.each_pair { |key, value| store(key, value) }
373
+ when Hash
374
+ update(self.class[self.default, other])
375
+ else
376
+ raise ArgumentError
377
+ end
378
+ self
379
+ end
380
+ alias_method :merge!, :update
381
+
382
+ # call-seq:
383
+ # map.select { |key, value| block } => multimap
384
+ #
385
+ # Returns a new Multimap consisting of the pairs for which the
386
+ # block returns true.
387
+ #
388
+ # map = Multimap["a" => 100, "b" => 200, "c" => 300]
389
+ # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300]
390
+ # map.select { |k,v| v < 200 } #=> Multimap["a" => 100]
391
+ def select
392
+ inject(self.class.new) { |map, (key, value)|
393
+ map[key] = value if yield([key, value])
394
+ map
395
+ }
396
+ end
397
+
398
+ # call-seq:
399
+ # map.to_a => array
400
+ #
401
+ # Converts <i>map</i> to a nested array of [<i>key,
402
+ # value</i>] arrays.
403
+ #
404
+ # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
405
+ # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]]
406
+ def to_a
407
+ ary = []
408
+ each_pair do |key, value|
409
+ ary << [key, value]
410
+ end
411
+ ary
412
+ end
413
+
414
+ # call-seq:
415
+ # map.to_hash => hash
416
+ #
417
+ # Converts <i>map</i> to a basic hash.
418
+ #
419
+ # map = Multimap["a" => 100, "b" => [200, 300]]
420
+ # map.to_hash #=> { "a" => [100], "b" => [200, 300] }
421
+ def to_hash
422
+ dup
423
+ end
424
+
425
+ # call-seq:
426
+ # map.containers => array
427
+ #
428
+ # Returns a new array populated with the containers from <i>map</i>. See
429
+ # also <tt>Multimap#keys</tt> and <tt>Multimap#values</tt>.
430
+ #
431
+ # map = Multimap["a" => 100, "b" => [200, 300]]
432
+ # map.containers #=> [[100], [200, 300]]
433
+ def containers
434
+ containers = []
435
+ each_container { |container| containers << container }
436
+ containers
437
+ end
438
+
439
+ # call-seq:
440
+ # map.values => array
441
+ #
442
+ # Returns a new array populated with the values from <i>map</i>. See
443
+ # also <tt>Multimap#keys</tt> and <tt>Multimap#containers</tt>.
444
+ #
445
+ # map = Multimap["a" => 100, "b" => [200, 300]]
446
+ # map.values #=> [100, 200, 300]
447
+ def values
448
+ values = []
449
+ each_value { |value| values << value }
450
+ values
451
+ end
452
+
453
+ protected
454
+ def update_container(key) #:nodoc:
455
+ container = hash_aref(key)
456
+ container = container.dup if container.equal?(default)
457
+ container = yield(container)
458
+ hash_aset(key, container)
459
+ end
460
+ end
data/lib/multiset.rb ADDED
@@ -0,0 +1,153 @@
1
+ require 'set'
2
+
3
+ # Multiset implements a collection of unordered values and
4
+ # allows duplicate values.
5
+ class Multiset < Set
6
+ def initialize(*args, &block) #:nodoc:
7
+ @hash = Hash.new(0)
8
+ super
9
+ end
10
+
11
+ # Returns the number of times an element belongs to the multiset.
12
+ def multiplicity(e)
13
+ @hash[e]
14
+ end
15
+
16
+ # Returns the total number of elements in a multiset, including
17
+ # repeated memberships
18
+ def cardinality
19
+ @hash.inject(0) { |s, (e, m)| s += m }
20
+ end
21
+ alias_method :size, :cardinality
22
+ alias_method :length, :cardinality
23
+
24
+ # Converts the set to an array. The order of elements is uncertain.
25
+ def to_a
26
+ inject([]) { |ary, (key, _)| ary << key }
27
+ end
28
+
29
+ # Returns true if the set is a superset of the given set.
30
+ def superset?(set)
31
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
32
+ return false if cardinality < set.cardinality
33
+ set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
34
+ end
35
+
36
+ # Returns true if the set is a proper superset of the given set.
37
+ def proper_superset?(set)
38
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
39
+ return false if cardinality <= set.cardinality
40
+ set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
41
+ end
42
+
43
+ # Returns true if the set is a subset of the given set.
44
+ def subset?(set)
45
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
46
+ return false if set.cardinality < cardinality
47
+ all? { |o| multiplicity(o) <= set.multiplicity(o) }
48
+ end
49
+
50
+ # Returns true if the set is a proper subset of the given set.
51
+ def proper_subset?(set)
52
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
53
+ return false if set.cardinality <= cardinality
54
+ all? { |o| multiplicity(o) <= set.multiplicity(o) }
55
+ end
56
+
57
+ # Calls the given block once for each element in the set, passing
58
+ # the element as parameter. Returns an enumerator if no block is
59
+ # given.
60
+ def each
61
+ @hash.each_pair do |key, multiplicity|
62
+ multiplicity.times do
63
+ yield(key)
64
+ end
65
+ end
66
+ self
67
+ end
68
+
69
+ # Adds the given object to the set and returns self. Use +merge+ to
70
+ # add many elements at once.
71
+ def add(o)
72
+ @hash[o] ||= 0
73
+ @hash[o] += 1
74
+ self
75
+ end
76
+ alias << add
77
+
78
+ undef :add?
79
+
80
+ # Deletes all the identical object from the set and returns self.
81
+ # If +n+ is given, it will remove that amount of identical objects
82
+ # from the set. Use +subtract+ to delete many different items at
83
+ # once.
84
+ def delete(o, n = nil)
85
+ if n
86
+ @hash[o] ||= 0
87
+ @hash[o] -= n if @hash[o] > 0
88
+ @hash.delete(o) if @hash[o] == 0
89
+ else
90
+ @hash.delete(o)
91
+ end
92
+ self
93
+ end
94
+
95
+ undef :delete?
96
+
97
+ # Deletes every element of the set for which block evaluates to
98
+ # true, and returns self.
99
+ def delete_if
100
+ each { |o| delete(o) if yield(o) }
101
+ self
102
+ end
103
+
104
+ # Merges the elements of the given enumerable object to the set and
105
+ # returns self.
106
+ def merge(enum)
107
+ enum.each { |o| add(o) }
108
+ self
109
+ end
110
+
111
+ # Deletes every element that appears in the given enumerable object
112
+ # and returns self.
113
+ def subtract(enum)
114
+ enum.each { |o| delete(o, 1) }
115
+ self
116
+ end
117
+
118
+ # Returns a new set containing elements common to the set and the
119
+ # given enumerable object.
120
+ def &(enum)
121
+ s = dup
122
+ n = self.class.new
123
+ enum.each { |o|
124
+ if s.include?(o)
125
+ s.delete(o, 1)
126
+ n.add(o)
127
+ end
128
+ }
129
+ n
130
+ end
131
+ alias intersection &
132
+
133
+ # Returns a new set containing elements exclusive between the set
134
+ # and the given enumerable object. (set ^ enum) is equivalent to
135
+ # ((set | enum) - (set & enum)).
136
+ def ^(enum)
137
+ n = self.class.new(enum)
138
+ each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) }
139
+ n
140
+ end
141
+
142
+ # Returns true if two sets are equal. Two multisets are equal if
143
+ # they have the same cardinalities and each element has the same
144
+ # multiplicity in both sets. The equality of each element inside
145
+ # the multiset is defined according to Object#eql?.
146
+ def eql?(set)
147
+ return true if equal?(set)
148
+ set = self.class.new(set) unless set.is_a?(self.class)
149
+ return false unless cardinality == set.cardinality
150
+ superset?(set) && subset?(set)
151
+ end
152
+ alias_method :==, :eql?
153
+ end
@@ -0,0 +1,169 @@
1
+ require 'multimap'
2
+
3
+ # NestedMultimap allows values to be assoicated with a nested
4
+ # set of keys.
5
+ class NestedMultimap < Multimap
6
+ # call-seq:
7
+ # multimap[*keys] = value => value
8
+ # multimap.store(*keys, value) => value
9
+ #
10
+ # Associates the value given by <i>value</i> with multiple key
11
+ # given by <i>keys</i>.
12
+ #
13
+ # map = NestedMultimap.new
14
+ # map["a"] = 100
15
+ # map["a", "b"] = 101
16
+ # map["a"] = 102
17
+ # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}}
18
+ def store(*args)
19
+ value = args.pop
20
+ key = args.shift
21
+ keys = args
22
+
23
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
24
+
25
+ if keys.length > 0
26
+ update_container(key) do |container|
27
+ container = self.class.new(container) unless container.is_a?(self.class)
28
+ container[*keys] = value
29
+ container
30
+ end
31
+ else
32
+ super(key, value)
33
+ end
34
+ end
35
+ alias_method :[]=, :store
36
+
37
+ # call-seq:
38
+ # multimap << obj => multimap
39
+ #
40
+ # Pushes the given object on to the end of all the containers.
41
+ #
42
+ # map = NestedMultimap["a" => [100], "b" => [200, 300]]
43
+ # map << 300
44
+ # map["a"] #=> [100, 300]
45
+ # map["c"] #=> [300]
46
+ def <<(value)
47
+ hash_each_pair { |_, container| container << value }
48
+ self.default << value
49
+ self
50
+ end
51
+
52
+ # call-seq:
53
+ # multimap[*keys] => value
54
+ # multimap[key1, key2, key3] => value
55
+ #
56
+ # Retrieves the <i>value</i> object corresponding to the
57
+ # <i>*keys</i> object.
58
+ def [](*keys)
59
+ i, l, r, k = 0, keys.length, self, self.class
60
+ while r.is_a?(k)
61
+ r = i < l ? r.hash_aref(keys[i]) : r.default
62
+ i += 1
63
+ end
64
+ r
65
+ end
66
+
67
+ # call-seq:
68
+ # multimap.each_association { |key, container| block } => multimap
69
+ #
70
+ # Calls <i>block</i> once for each key/container in <i>map</i>, passing
71
+ # the key and container to the block as parameters.
72
+ #
73
+ # map = NestedMultimap.new
74
+ # map["a"] = 100
75
+ # map["a", "b"] = 101
76
+ # map["a"] = 102
77
+ # map["c"] = 200
78
+ # map.each_association { |key, container| puts "#{key} is #{container}" }
79
+ #
80
+ # <em>produces:</em>
81
+ #
82
+ # ["a", "b"] is [100, 101, 102]
83
+ # "c" is [200]
84
+ def each_association
85
+ super do |key, container|
86
+ if container.respond_to?(:each_association)
87
+ container.each_association do |nested_key, value|
88
+ yield [key, nested_key].flatten, value
89
+ end
90
+ else
91
+ yield key, container
92
+ end
93
+ end
94
+ end
95
+
96
+ # call-seq:
97
+ # multimap.each_container_with_default { |container| block } => map
98
+ #
99
+ # Calls <i>block</i> for every container in <i>map</i> including
100
+ # the default, passing the container as a parameter.
101
+ #
102
+ # map = NestedMultimap.new
103
+ # map["a"] = 100
104
+ # map["a", "b"] = 101
105
+ # map["a"] = 102
106
+ # map.each_container_with_default { |container| puts container }
107
+ #
108
+ # <em>produces:</em>
109
+ #
110
+ # [100, 101, 102]
111
+ # [100, 102]
112
+ # []
113
+ def each_container_with_default
114
+ each_container = Proc.new do |container|
115
+ if container.respond_to?(:each_container_with_default)
116
+ container.each_container_with_default do |value|
117
+ yield value
118
+ end
119
+ else
120
+ yield container
121
+ end
122
+ end
123
+
124
+ hash_each_pair { |_, container| each_container.call(container) }
125
+ each_container.call(default)
126
+
127
+ self
128
+ end
129
+
130
+ # call-seq:
131
+ # multimap.containers_with_default => array
132
+ #
133
+ # Returns a new array populated with all the containers from
134
+ # <i>map</i> including the default.
135
+ #
136
+ # map = NestedMultimap.new
137
+ # map["a"] = 100
138
+ # map["a", "b"] = 101
139
+ # map["a"] = 102
140
+ # map.containers_with_default #=> [[100, 101, 102], [100, 102], []]
141
+ def containers_with_default
142
+ containers = []
143
+ each_container_with_default { |container| containers << container }
144
+ containers
145
+ end
146
+
147
+ # call-seq:
148
+ # multimap.height => fixnum
149
+ #
150
+ # Returns the deepest level of nesting.
151
+ #
152
+ # map = NestedMultimap["a" => 100, "b" => 200]
153
+ # map["a", "b"] = 101
154
+ # map.height #=> 2
155
+ # map["a", "b", "c"] = 102
156
+ # map.height #=> 3
157
+ def height
158
+ containers_with_default.max { |a, b| a.length <=> b.length }.length
159
+ end
160
+
161
+ def inspect #:nodoc:
162
+ super.gsub(/\}$/, ", default => #{default.inspect}}")
163
+ end
164
+ end
165
+
166
+ begin
167
+ require 'nested_multimap_ext'
168
+ rescue LoadError
169
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: josh-multimap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Peek
8
+ - Joshua Hull
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-05-26 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Multimap includes a standard Ruby multimap implementation as well as a nested multimap and a fuzzy nested multimap
18
+ email: josh@joshpeek.com
19
+ executables: []
20
+
21
+ extensions:
22
+ - ext/extconf.rb
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ - MIT-LICENSE
26
+ files:
27
+ - ext/nested_multimap_ext.c
28
+ - lib/fuzzy_nested_multimap.rb
29
+ - lib/multimap.rb
30
+ - lib/multiset.rb
31
+ - lib/nested_multimap.rb
32
+ - README.rdoc
33
+ - MIT-LICENSE
34
+ has_rdoc: true
35
+ homepage: http://github.com/josh/multimap
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project: multimap
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Ruby implementation of multimap
60
+ test_files: []
61
+