immutable-ruby 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a963e4f0565bc516534d1cf81f0206357b252f3a
4
- data.tar.gz: 38a237bd2bdcef74cb66971375e1414ea36c67bd
3
+ metadata.gz: f0b9336a378ad7d4e2cf98b8977c0dbac13ed74b
4
+ data.tar.gz: 2ec6d56a86049914a7c3e9807277a6134f5f4842
5
5
  SHA512:
6
- metadata.gz: '040888fc5816c86d1df3333bd1e7e922096c7775e196f5bf3c9e9dedc599dc55b888cf31c1b602bcc4bfda8c06e28b436a793d4e221fca54855e69b0b2d2e523'
7
- data.tar.gz: fb6bffbec4a70ccc00058140a563007cbf7617c55af03328c2a8385772caaf4d6e0d22e9e10664541ec04ad3f1dfe43f5b3b327268539841d6edb9c7683af1dc
6
+ metadata.gz: 02a4a9fa583aa94d5a609b8654247d486e41c51efcf0e3f3447ec47f6fd31e2f204e65dc906b6c6b82341dfdcc826b1a9ed55d9a4c8022e88508068999859d2f
7
+ data.tar.gz: 4246f4d3457284cd82c13ed20b92f1b8d00e9cad02d30a0909f8f9d4194e331deac73fbaf5ebbaa1a4f395199c68f90cfdfef68403ac1c6a81edaf5b5e5878dc
@@ -76,21 +76,43 @@ module Immutable
76
76
  def alloc(node)
77
77
  allocate.tap { |s| s.instance_variable_set(:@node, node) }.freeze
78
78
  end
79
+
80
+ # @private
81
+ # Unfortunately, Ruby's stdlib doesn't do this for us
82
+ # array must be sorted
83
+ def uniq_by_comparator!(array, comparator)
84
+ to_check, shift, sz, prev_obj = 1, 0, array.size, array[0]
85
+ while to_check < sz
86
+ next_obj = array[to_check]
87
+ if comparator.call(prev_obj, next_obj) == 0
88
+ shift += 1
89
+ else
90
+ if shift > 0
91
+ array[to_check - shift] = next_obj
92
+ end
93
+ prev_obj = next_obj
94
+ end
95
+ to_check += 1
96
+ end
97
+ array.pop(shift) if shift > 0
98
+ end
79
99
  end
80
100
 
81
101
  def initialize(items=[], &block)
82
102
  items = items.to_a
83
103
  if block
84
104
  if block.arity == 1 || block.arity == -1
105
+ items = items.uniq(&block)
106
+ items.sort_by!(&block)
85
107
  comparator = lambda { |a,b| block.call(a) <=> block.call(b) }
86
- items = items.sort_by(&block)
87
108
  else
88
- comparator = block
89
109
  items = items.sort(&block)
110
+ SortedSet.uniq_by_comparator!(items, block)
111
+ comparator = block
90
112
  end
91
113
  @node = AVLNode.from_items(items, comparator)
92
114
  else
93
- @node = PlainAVLNode.from_items(items.sort)
115
+ @node = PlainAVLNode.from_items(items.uniq.sort!)
94
116
  end
95
117
  freeze
96
118
  end
@@ -476,9 +498,11 @@ module Immutable
476
498
  def sort(&block)
477
499
  if block
478
500
  self.class.new(self.to_a, &block)
501
+ elsif @node.natural_order?
502
+ self
479
503
  else
480
- self.class.new(self.to_a.sort)
481
- end
504
+ self.class.new(self)
505
+ end
482
506
  end
483
507
  alias :sort_by :sort
484
508
 
@@ -990,7 +1014,8 @@ module Immutable
990
1014
 
991
1015
  # @private
992
1016
  class AVLNode
993
- def self.from_items(items, comparator, from = 0, to = items.size-1) # items must be sorted
1017
+ def self.from_items(items, comparator, from = 0, to = items.size-1)
1018
+ # items must be sorted, without duplicates (as determined by comparator)
994
1019
  size = to - from + 1
995
1020
  if size >= 3
996
1021
  middle = (to + from) / 2
@@ -1013,8 +1038,12 @@ module Immutable
1013
1038
  end
1014
1039
  attr_reader :item, :left, :right, :height, :size
1015
1040
 
1041
+ # Used to implement #map
1042
+ # Takes advantage of the fact that Enumerable#map allocates a new Array
1016
1043
  def from_items(items)
1017
- AVLNode.from_items(items.sort(&@comparator), @comparator)
1044
+ items.sort!(&@comparator)
1045
+ SortedSet.uniq_by_comparator!(items, @comparator)
1046
+ AVLNode.from_items(items, @comparator)
1018
1047
  end
1019
1048
 
1020
1049
  def natural_order?
@@ -1374,7 +1403,9 @@ module Immutable
1374
1403
  end
1375
1404
  def bulk_insert(items)
1376
1405
  items = items.to_a if !items.is_a?(Array)
1377
- AVLNode.from_items(items.sort(&@comparator), @comparator)
1406
+ items = items.sort(&@comparator)
1407
+ SortedSet.uniq_by_comparator!(items, @comparator)
1408
+ AVLNode.from_items(items, @comparator)
1378
1409
  end
1379
1410
  def bulk_delete(items); self; end
1380
1411
  def keep_only(items); self; end
@@ -1397,7 +1428,8 @@ module Immutable
1397
1428
  # AVL node which does not use a comparator function; it keeps items sorted
1398
1429
  # in their natural order
1399
1430
  class PlainAVLNode < AVLNode
1400
- def self.from_items(items, from = 0, to = items.size-1) # items must be sorted
1431
+ def self.from_items(items, from = 0, to = items.size-1)
1432
+ # items must be sorted, with no duplicates
1401
1433
  size = to - from + 1
1402
1434
  if size >= 3
1403
1435
  middle = (to + from) / 2
@@ -1418,8 +1450,12 @@ module Immutable
1418
1450
  end
1419
1451
  attr_reader :item, :left, :right, :height, :size
1420
1452
 
1453
+ # Used to implement #map
1454
+ # Takes advantage of the fact that Enumerable#map allocates a new Array
1421
1455
  def from_items(items)
1422
- PlainAVLNode.from_items(items.sort)
1456
+ items.uniq!
1457
+ items.sort!
1458
+ PlainAVLNode.from_items(items)
1423
1459
  end
1424
1460
 
1425
1461
  def natural_order?
@@ -1447,7 +1483,7 @@ module Immutable
1447
1483
  end
1448
1484
  def bulk_insert(items)
1449
1485
  items = items.to_a if !items.is_a?(Array)
1450
- PlainAVLNode.from_items(items.sort)
1486
+ PlainAVLNode.from_items(items.uniq.sort!)
1451
1487
  end
1452
1488
  end
1453
1489
 
@@ -1,5 +1,5 @@
1
1
  module Immutable
2
2
  # Current released gem version. Note that master will often have the same
3
3
  # value as a release gem but with different code.
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
@@ -47,13 +47,19 @@ describe Immutable::Set do
47
47
  end
48
48
 
49
49
  describe "#sort_by" do
50
- it "only calls the passed block once for each item" do
50
+ # originally this test checked that #sort_by only called the block once
51
+ # for each item
52
+ # however, when initializing a SortedSet, we need to make sure that it
53
+ # does not include any duplicates, and we use the block when checking that
54
+ # the real point here is that the block should not be called an excessive
55
+ # number of times, degrading performance
56
+ it "calls the passed block no more than twice for each item" do
51
57
  count = 0
52
58
  fn = lambda { |x| count += 1; -x }
53
59
  items = 100.times.collect { rand(10000) }.uniq
54
60
 
55
61
  S[*items].sort_by(&fn).to_a.should == items.sort.reverse
56
- count.should == items.length
62
+ count.should <= (items.length * 2)
57
63
  end
58
64
  end
59
65
  end
@@ -5,7 +5,7 @@ describe Immutable::SortedSet do
5
5
  context "when called without a block" do
6
6
  it "returns a sorted set of all items higher than the argument" do
7
7
  100.times do
8
- items = rand(100).times.collect { rand(1000) }
8
+ items = rand(100).times.collect { rand(1000) }.uniq
9
9
  set = SS.new(items)
10
10
  threshold = rand(1000)
11
11
  result = set.above(threshold)
@@ -20,7 +20,7 @@ describe Immutable::SortedSet do
20
20
  context "when called with a block" do
21
21
  it "yields all the items higher than the argument" do
22
22
  100.times do
23
- items = rand(100).times.collect { rand(1000) }
23
+ items = rand(100).times.collect { rand(1000) }.uniq
24
24
  set = SS.new(items)
25
25
  threshold = rand(1000)
26
26
  result = []
@@ -5,7 +5,7 @@ describe Immutable::SortedSet do
5
5
  context "when called without a block" do
6
6
  it "returns a sorted set of all items lower than the argument" do
7
7
  100.times do
8
- items = rand(100).times.collect { rand(1000) }
8
+ items = rand(100).times.collect { rand(1000) }.uniq
9
9
  set = SS.new(items)
10
10
  threshold = rand(1000)
11
11
  result = set.below(threshold)
@@ -20,7 +20,7 @@ describe Immutable::SortedSet do
20
20
  context "when called with a block" do
21
21
  it "yields all the items lower than the argument" do
22
22
  100.times do
23
- items = rand(100).times.collect { rand(1000) }
23
+ items = rand(100).times.collect { rand(1000) }.uniq
24
24
  set = SS.new(items)
25
25
  threshold = rand(1000)
26
26
  result = []
@@ -5,7 +5,7 @@ describe Immutable::SortedSet do
5
5
  context "when called without a block" do
6
6
  it "returns a sorted set of all items from the first argument to the second" do
7
7
  100.times do
8
- items = rand(100).times.collect { rand(1000) }
8
+ items = rand(100).times.collect { rand(1000) }.uniq
9
9
  set = SS.new(items)
10
10
  from,to = [rand(1000),rand(1000)].sort
11
11
  result = set.between(from, to)
@@ -20,7 +20,7 @@ describe Immutable::SortedSet do
20
20
  context "when called with a block" do
21
21
  it "yields all the items lower than the argument" do
22
22
  100.times do
23
- items = rand(100).times.collect { rand(1000) }
23
+ items = rand(100).times.collect { rand(1000) }.uniq
24
24
  set = SS.new(items)
25
25
  from,to = [rand(1000),rand(1000)].sort
26
26
  result = []
@@ -5,7 +5,7 @@ describe Immutable::SortedSet do
5
5
  context "when called without a block" do
6
6
  it "returns a sorted set of all items equal to or greater than the argument" do
7
7
  100.times do
8
- items = rand(100).times.collect { rand(1000) }
8
+ items = rand(100).times.collect { rand(1000) }.uniq
9
9
  set = SS.new(items)
10
10
  threshold = rand(1000)
11
11
  result = set.from(threshold)
@@ -20,7 +20,7 @@ describe Immutable::SortedSet do
20
20
  context "when called with a block" do
21
21
  it "yields all the items equal to or greater than than the argument" do
22
22
  100.times do
23
- items = rand(100).times.collect { rand(1000) }
23
+ items = rand(100).times.collect { rand(1000) }.uniq
24
24
  set = SS.new(items)
25
25
  threshold = rand(1000)
26
26
  result = []
@@ -21,6 +21,10 @@ describe Immutable::SortedSet do
21
21
  it "returns a new set with the mapped values" do
22
22
  sorted_set.send(method, &:downcase).should eql(SS["a", "b", "c"])
23
23
  end
24
+
25
+ it "filters out duplicates" do
26
+ sorted_set.send(method) { 'blah' }.should eq(SS['blah'])
27
+ end
24
28
  end
25
29
 
26
30
  context "with no block" do
@@ -37,6 +41,10 @@ describe Immutable::SortedSet do
37
41
  it "returns a new set with the mapped values" do
38
42
  sorted_set.send(method, &:downcase).should == ['c', 'b', 'a']
39
43
  end
44
+
45
+ it "filters out duplicates" do
46
+ sorted_set.send(method) { 'blah' }.should eq(SS['blah'])
47
+ end
40
48
  end
41
49
  end
42
50
  end
@@ -18,6 +18,19 @@ describe Immutable::SortedSet do
18
18
  sorted_set[2].should be(3)
19
19
  end
20
20
 
21
+ it "doesn't mutate the initializer" do
22
+ array = [3,2,1,3,2,1] # this will need to be sorted and duplicates filtered out
23
+ sorted_set = SS.new(array)
24
+ expect(array).to eq([3,2,1,3,2,1])
25
+ end
26
+
27
+ it "doesn't change if the initializer is later mutated" do
28
+ array = [3,2,1,3,2,1]
29
+ sorted_set = SS.new(array)
30
+ array.clear
31
+ expect(sorted_set.to_a).to eq([1,2,3])
32
+ end
33
+
21
34
  it "is amenable to overriding of #initialize" do
22
35
  class SnazzySortedSet < Immutable::SortedSet
23
36
  def initialize
@@ -50,6 +63,51 @@ describe Immutable::SortedSet do
50
63
  sorted_set[1].should be(Object)
51
64
  end
52
65
 
66
+ it "filters out duplicates" do
67
+ sorted_set = SS.new(['a', 'b', 'a', 'c', 'b', 'a', 'c', 'c'])
68
+ expect(sorted_set.size).to be(3)
69
+ end
70
+
71
+ context "when passed a comparator with arity 2" do
72
+ it "still filters out duplicates" do
73
+ sorted_set = SS.new([1,2,7,8,9,10]) { |x,y| (x%7) <=> (y%7) }
74
+ expect(sorted_set.to_a).to eq([7,1,2,10])
75
+ end
76
+
77
+ it "still doesn't mutate the initializer" do
78
+ array = [3,2,1,3,2,1] # this will need to be sorted and duplicates filtered out
79
+ sorted_set = SS.new(array) { |x,y| y <=> x }
80
+ expect(array).to eq([3,2,1,3,2,1])
81
+ end
82
+
83
+ it "still doesn't change if the initializer is later mutated" do
84
+ array = [3,2,1,3,2,1]
85
+ sorted_set = SS.new(array) { |x,y| y <=> x }
86
+ array.clear
87
+ expect(sorted_set.to_a).to eq([3,2,1])
88
+ end
89
+ end
90
+
91
+ context "when passed a block with arity 1" do
92
+ it "still filters out duplicates" do
93
+ sorted_set = SS.new([1,2,7,8,9,10]) { |x| x % 7 }
94
+ expect(sorted_set.to_a).to eq([7,1,2,10])
95
+ end
96
+
97
+ it "still doesn't mutate the initializer" do
98
+ array = [3,2,1,3,2,1] # this will need to be sorted and duplicates filtered out
99
+ sorted_set = SS.new(array) { |x| x % 7 }
100
+ expect(array).to eq([3,2,1,3,2,1])
101
+ end
102
+
103
+ it "still doesn't change if the initializer is later mutated" do
104
+ array = [3,2,1,3,2,1]
105
+ sorted_set = SS.new(array) { |x| x % 7 }
106
+ array.clear
107
+ expect(sorted_set.to_a).to eq([1,2,3])
108
+ end
109
+ end
110
+
53
111
  context "from a subclass" do
54
112
  it "returns a frozen instance of the subclass" do
55
113
  subclass = Class.new(Immutable::SortedSet)
@@ -67,5 +125,13 @@ describe Immutable::SortedSet do
67
125
  sorted_set[0].should == 'a'
68
126
  sorted_set[1].should == 'b'
69
127
  end
128
+
129
+ it "filters out duplicate items" do
130
+ sorted_set = SS['a', 'b', 'a', 'c', 'b', 'a', 'c', 'c']
131
+ expect(sorted_set.size).to be(3)
132
+ sorted_set[0].should == 'a'
133
+ sorted_set[1].should == 'b'
134
+ sorted_set[2].should == 'c'
135
+ end
70
136
  end
71
137
  end
@@ -41,4 +41,16 @@ describe Immutable::SortedSet do
41
41
  end
42
42
  end
43
43
  end
44
+
45
+ describe :sort do
46
+ context "on a SortedSet with custom sort order" do
47
+ let(:sorted_set) { SS.new([1,2,3,4]) { |x,y| y <=> x }}
48
+
49
+ it "returns a SortedSet with the natural sort order" do
50
+ result = sorted_set.sort
51
+ expect(sorted_set.to_a).to eq([4,3,2,1])
52
+ expect(result.to_a).to eq([1,2,3,4])
53
+ end
54
+ end
55
+ end
44
56
  end
@@ -24,4 +24,35 @@ describe Immutable::SortedSet do
24
24
  end
25
25
  end
26
26
  end
27
+
28
+ describe :union do
29
+ it "filters out duplicates when passed an Array" do
30
+ sorted_set = SS['A', 'B', 'C', 'D'].union(['A', 'A', 'A', 'C', 'A', 'B', 'E'])
31
+ expect(sorted_set.to_a).to eq(['A', 'B', 'C', 'D', 'E'])
32
+ end
33
+
34
+ it "doesn't mutate an Array which is passed in" do
35
+ array = [3,2,1,3]
36
+ sorted_set = SS[1,2,5].union(array)
37
+ expect(array).to eq([3,2,1,3])
38
+ end
39
+
40
+ context "on a set ordered by a comparator" do
41
+ # Completely different code is executed when #union is called on a SS
42
+ # with a comparator block, so we should repeat all the same tests
43
+
44
+ it "still filters out duplicates when passed an Array" do
45
+ sorted_set = SS.new([1,2,3]) { |x,y| (x%7) <=> (y%7) }
46
+ sorted_set = sorted_set.union([7,8,9])
47
+ expect(sorted_set.to_a).to eq([7,1,2,3])
48
+ end
49
+
50
+ it "still doesn't mutate an Array which is passed in" do
51
+ array = [3,2,1,3]
52
+ sorted_set = SS.new([1,2,5]) { |x,y| y <=> x }
53
+ sorted_set = sorted_set.union(array)
54
+ expect(array).to eq([3,2,1,3])
55
+ end
56
+ end
57
+ end
27
58
  end
@@ -5,7 +5,7 @@ describe Immutable::SortedSet do
5
5
  context "when called without a block" do
6
6
  it "returns a sorted set of all items equal to or less than the argument" do
7
7
  100.times do
8
- items = rand(100).times.collect { rand(1000) }
8
+ items = rand(100).times.collect { rand(1000) }.uniq
9
9
  set = SS.new(items)
10
10
  threshold = rand(1000)
11
11
  result = set.up_to(threshold)
@@ -20,7 +20,7 @@ describe Immutable::SortedSet do
20
20
  context "when called with a block" do
21
21
  it "yields all the items equal to or less than than the argument" do
22
22
  100.times do
23
- items = rand(100).times.collect { rand(1000) }
23
+ items = rand(100).times.collect { rand(1000) }.uniq
24
24
  set = SS.new(items)
25
25
  threshold = rand(1000)
26
26
  result = []
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Immutable::SortedSet do
4
+ # Utility method used for filtering out duplicate objects, with equality
5
+ # determined by comparator
6
+ describe ".uniq_by_comparator!" do
7
+ it "can handle empty arrays" do
8
+ array = []
9
+ SS.uniq_by_comparator!(array, ->(x,y) { x <=> y })
10
+ expect(array).to be_empty
11
+ end
12
+
13
+ it "can handle arrays with 1 element" do
14
+ array = [1]
15
+ SS.uniq_by_comparator!(array, ->(x,y) { x <=> y })
16
+ expect(array).to eq([1])
17
+ end
18
+
19
+ it "can handle arrays with 2 elements and no dupes" do
20
+ array = [1, 2]
21
+ SS.uniq_by_comparator!(array, ->(x,y) { x <=> y })
22
+ expect(array).to eq([1, 2])
23
+ end
24
+
25
+ it "can handle arrays with 2 elements and dupes" do
26
+ array = [1, 1]
27
+ SS.uniq_by_comparator!(array, ->(x,y) { x <=> y })
28
+ expect(array).to eq([1])
29
+ end
30
+
31
+ it "can handle arrays with lots of elements" do
32
+ 100.times do
33
+ array1 = rand(100).times.collect { rand(100) }.sort
34
+ array2 = array1.dup.uniq
35
+ SS.uniq_by_comparator!(array1, ->(x,y) { x <=> y })
36
+ expect(array1).to eq(array2)
37
+ end
38
+ end
39
+
40
+ it "works with funny comparators" do
41
+ # let's work in modulo arithmetic
42
+ comparator = ->(x,y) { (x % 7) <=> (y % 7) }
43
+ array = [21, 1, 8, 1, 9, 10, 3, 5, 6, 20] # this is "sorted" (modulo 7)
44
+ SS.uniq_by_comparator!(array, comparator)
45
+ expect(array).to eq([21, 1, 9, 10, 5, 6])
46
+ end
47
+ end
48
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immutable-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dowad
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2017-11-05 00:00:00.000000000 Z
14
+ date: 2018-05-12 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: concurrent-ruby
@@ -408,6 +408,7 @@ files:
408
408
  - spec/lib/immutable/sorted_set/to_set_spec.rb
409
409
  - spec/lib/immutable/sorted_set/union_spec.rb
410
410
  - spec/lib/immutable/sorted_set/up_to_spec.rb
411
+ - spec/lib/immutable/sorted_set/util_spec.rb
411
412
  - spec/lib/immutable/sorted_set/values_at_spec.rb
412
413
  - spec/lib/immutable/vector/add_spec.rb
413
414
  - spec/lib/immutable/vector/any_spec.rb
@@ -787,6 +788,7 @@ test_files:
787
788
  - spec/lib/immutable/sorted_set/group_by_spec.rb
788
789
  - spec/lib/immutable/sorted_set/between_spec.rb
789
790
  - spec/lib/immutable/sorted_set/sample_spec.rb
791
+ - spec/lib/immutable/sorted_set/util_spec.rb
790
792
  - spec/lib/immutable/sorted_set/values_at_spec.rb
791
793
  - spec/lib/immutable/sorted_set/marshal_spec.rb
792
794
  - spec/lib/immutable/sorted_set/intersect_spec.rb