josh-multimap 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+