map 2.7.1 → 2.8.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.
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