map 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/TODO +3 -0
  2. data/lib/map.rb +112 -9
  3. data/map.gemspec +1 -1
  4. data/test/map_test.rb +59 -0
  5. metadata +5 -5
data/TODO CHANGED
@@ -1,4 +1,7 @@
1
1
  todo:
2
+ - #slice operator
3
+ - depth first each-ish delete operator
4
+
2
5
  - struct tests
3
6
  - map.push(object)
4
7
 
data/lib/map.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class Map < Hash
2
- Version = '2.7.1' unless defined?(Version)
2
+ Version = '2.8.0' unless defined?(Version)
3
3
  Load = Kernel.method(:load) unless defined?(Load)
4
4
 
5
5
  class << Map
@@ -145,8 +145,17 @@ class Map < Hash
145
145
 
146
146
  args
147
147
  end
148
-
149
148
  alias_method '[]', 'new'
149
+
150
+ def intersection(a, b)
151
+ a, b, i = Map.for(a), Map.for(b), Map.new
152
+ a.depth_first_each{|key, val| i.set(key, val) if b.has?(key)}
153
+ i
154
+ end
155
+
156
+ def match(haystack, needle)
157
+ intersection(haystack, needle) == needle
158
+ end
150
159
  end
151
160
 
152
161
  unless defined?(Dynamic)
@@ -443,22 +452,54 @@ class Map < Hash
443
452
  end
444
453
  end
445
454
 
446
- # misc
455
+ # equality / sorting / matching support
447
456
  #
448
- def ==(hash)
449
- return false unless(Map === hash)
450
- return false if keys != hash.keys
451
- super hash
457
+ def ==(other)
458
+ case other
459
+ when Map
460
+ return false if keys != other.keys
461
+ super(other)
462
+
463
+ when Hash
464
+ self == Map.from_hash(other, self)
465
+
466
+ else
467
+ false
468
+ end
452
469
  end
453
470
 
454
471
  def <=>(other)
455
- keys <=> klass.coerce(other).keys
472
+ cmp = keys <=> klass.coerce(other).keys
473
+ return cmp unless cmp.zero?
474
+ values <=> klass.coerce(other).values
456
475
  end
457
476
 
458
477
  def =~(hash)
459
478
  to_hash == klass.coerce(hash).to_hash
460
479
  end
461
480
 
481
+ # reordering support
482
+ #
483
+ def reorder(order = {})
484
+ order = Map.for(order)
485
+ map = Map.new
486
+ keys = order.depth_first_keys | depth_first_keys
487
+ keys.each{|key| map.set(key, get(key))}
488
+ map
489
+ end
490
+
491
+ def reorder!(order = {})
492
+ replace(reorder(order))
493
+ end
494
+
495
+ # support for building ordered hasshes from a map's own image
496
+ #
497
+ def Map.from_hash(hash, order = nil)
498
+ map = Map.for(hash)
499
+ map.reorder!(order) if order
500
+ map
501
+ end
502
+
462
503
  def invert
463
504
  inverted = klass.allocate
464
505
  inverted.default = self.default
@@ -697,6 +738,8 @@ class Map < Hash
697
738
  Map.alphanumeric_key_for(key)
698
739
  end
699
740
 
741
+ ## TODO - technically this returns only leaves so the name isn't *quite* right. re-factor for 3.0
742
+ #
700
743
  def Map.depth_first_each(enumerable, path = [], accum = [], &block)
701
744
  Map.pairs_for(enumerable) do |key, val|
702
745
  path.push(key)
@@ -710,10 +753,22 @@ class Map < Hash
710
753
  if block
711
754
  accum.each{|keys, val| block.call(keys, val)}
712
755
  else
713
- [path, accum]
756
+ accum
714
757
  end
715
758
  end
716
759
 
760
+ def Map.depth_first_keys(enumerable, path = [], accum = [], &block)
761
+ accum = Map.depth_first_each(enumerable, path = [], accum = [], &block)
762
+ accum.map!{|kv| kv.first}
763
+ accum
764
+ end
765
+
766
+ def Map.depth_first_values(enumerable, path = [], accum = [], &block)
767
+ accum = Map.depth_first_each(enumerable, path = [], accum = [], &block)
768
+ accum.map!{|kv| kv.last}
769
+ accum
770
+ end
771
+
717
772
  def Map.pairs_for(enumerable, *args, &block)
718
773
  if block.nil?
719
774
  pairs, block = [], lambda{|*pair| pairs.push(pair)}
@@ -736,9 +791,57 @@ class Map < Hash
736
791
  pairs ? pairs : result
737
792
  end
738
793
 
794
+ def Map.breadth_first_each(enumerable, accum = [], &block)
795
+ levels = []
796
+
797
+ keys = Map.depth_first_keys(enumerable)
798
+
799
+ keys.each do |key|
800
+ key.size.times do |i|
801
+ k = key.slice(0, i + 1)
802
+ level = k.size - 1
803
+ levels[level] ||= Array.new
804
+ last = levels[level].last
805
+ levels[level].push(k) unless last == k
806
+ end
807
+ end
808
+
809
+ levels.each do |level|
810
+ level.each do |key|
811
+ val = enumerable.get(key)
812
+ block ? block.call(key, val) : accum.push([key, val])
813
+ end
814
+ end
815
+
816
+ block ? enumerable : accum
817
+ end
818
+
819
+ def Map.keys_for(enumerable)
820
+ keys = enumerable.respond_to?(:keys) ? enumerable.keys : Array.new(enumerable.size){|i| i}
821
+ end
822
+
739
823
  def depth_first_each(*args, &block)
740
824
  Map.depth_first_each(enumerable=self, *args, &block)
741
825
  end
826
+
827
+ def depth_first_keys(*args, &block)
828
+ Map.depth_first_keys(enumerable=self, *args, &block)
829
+ end
830
+
831
+ def depth_first_values(*args, &block)
832
+ Map.depth_first_values(enumerable=self, *args, &block)
833
+ end
834
+
835
+ def breadth_first_each(*args, &block)
836
+ Map.breadth_first_each(enumerable=self, *args, &block)
837
+ end
838
+
839
+ def contains(other)
840
+ other = other.is_a?(Hash) ? Map.coerce(other) : other
841
+ breadth_first_each{|key, value| return true if value == other}
842
+ return false
843
+ end
844
+ alias_method 'contains?', 'contains'
742
845
  end
743
846
 
744
847
  module Kernel
@@ -3,7 +3,7 @@
3
3
 
4
4
  Gem::Specification::new do |spec|
5
5
  spec.name = "map"
6
- spec.version = "2.7.1"
6
+ spec.version = "2.8.0"
7
7
  spec.platform = Gem::Platform::RUBY
8
8
  spec.summary = "map"
9
9
  spec.description = "description: map kicks the ass"
@@ -371,6 +371,65 @@ Testing Map do
371
371
  assert{ each_pair = ['a', 'b', 'c', nil] }
372
372
  end
373
373
 
374
+ testing 'that maps support breath_first_each' do
375
+ map = Map[
376
+ 'hash' , {'x' => 'y'},
377
+ 'nested hash' , {'nested' => {'a' => 'b'}},
378
+ 'array' , [0, 1, 2],
379
+ 'nested array' , [[3], [4], [5]],
380
+ 'string' , '42'
381
+ ]
382
+
383
+ accum = []
384
+ Map.breadth_first_each(map){|k, v| accum.push([k, v])}
385
+ expected =
386
+ [[["hash"], {"x"=>"y"}],
387
+ [["nested hash"], {"nested"=>{"a"=>"b"}}],
388
+ [["array"], [0, 1, 2]],
389
+ [["nested array"], [[3], [4], [5]]],
390
+ [["string"], "42"],
391
+ [["hash", "x"], "y"],
392
+ [["nested hash", "nested"], {"a"=>"b"}],
393
+ [["array", 0], 0],
394
+ [["array", 1], 1],
395
+ [["array", 2], 2],
396
+ [["nested array", 0], [3]],
397
+ [["nested array", 1], [4]],
398
+ [["nested array", 2], [5]],
399
+ [["nested hash", "nested", "a"], "b"],
400
+ [["nested array", 0, 0], 3],
401
+ [["nested array", 1, 0], 4],
402
+ [["nested array", 2, 0], 5]]
403
+ end
404
+
405
+ testing 'that maps have a needle-in-a-haystack like #contains? method' do
406
+ haystack = Map[
407
+ 'hash' , {'x' => 'y'},
408
+ 'nested hash' , {'nested' => {'a' => 'b'}},
409
+ 'array' , [0, 1, 2],
410
+ 'nested array' , [[3], [4], [5]],
411
+ 'string' , '42'
412
+ ]
413
+
414
+ needles = [
415
+ {'x' => 'y'},
416
+ {'nested' => {'a' => 'b'}},
417
+ {'a' => 'b'},
418
+ [0,1,2],
419
+ [[3], [4], [5]],
420
+ [3],
421
+ [4],
422
+ [5],
423
+ '42',
424
+ 0,1,2,
425
+ 3,4,5
426
+ ]
427
+
428
+ needles.each do |needle|
429
+ assert{ haystack.contains?(needle) }
430
+ end
431
+ end
432
+
374
433
  testing 'that #update and #replace accept map-ish objects' do
375
434
  o = Object.new
376
435
  def o.to_map() {:k => :v} end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: map
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 47
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
- - 7
9
- - 1
10
- version: 2.7.1
8
+ - 8
9
+ - 0
10
+ version: 2.8.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ara T. Howard
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-02 00:00:00 -07:00
18
+ date: 2011-04-08 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies: []
21
21