immutable-ruby 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/immutable/deque.rb +36 -1
- data/lib/immutable/enumerable.rb +1 -1
- data/lib/immutable/hash.rb +1 -1
- data/lib/immutable/trie.rb +19 -27
- data/lib/immutable/version.rb +1 -1
- data/spec/lib/immutable/deque/dequeue_spec.rb +1 -1
- data/spec/lib/immutable/deque/rotate_spec.rb +68 -0
- data/spec/lib/immutable/hash/eql_spec.rb +6 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d4dfc5d75d70e963c56837a552ac1902889f05f2fd73a3995c69959815780594
|
4
|
+
data.tar.gz: b9daefb94ad6bd5e3bf3a0bc7b74265a9d97188856ab78508f2cdf813b342636
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 306f0ef37c0ba06cf729b230f8eb6ec850cf6ff6130fea45eaf125d307078617f8688bc02b2dd6a75aae11134a45172f7ca11a12e6693409c36d5f00b7f18fc0
|
7
|
+
data.tar.gz: '0039ef718ca215f12d6826ea6803c7e404fd211536095c5a4995ca04e30b0effefa11951a640bca03b37ee9834ff94bac7acd9d1ccaf2d08ba449204850e42cb'
|
data/lib/immutable/deque.rb
CHANGED
@@ -110,10 +110,38 @@ module Immutable
|
|
110
110
|
@front.last # memoize?
|
111
111
|
end
|
112
112
|
|
113
|
+
# Return a new `Deque` with elements rotated by `n` positions.
|
114
|
+
# A positive rotation moves elements to the right, negative to the left, and 0 is a no-op.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# Immutable::Deque["A", "B", "C"].rotate(1)
|
118
|
+
# # => Immutable::Deque["C", "A", "B"]
|
119
|
+
# Immutable::Deque["A", "B", "C"].rotate(-1)
|
120
|
+
# # => Immutable::Deque["B", "C", "A"]
|
121
|
+
#
|
122
|
+
# @param n [Integer] number of positions to move elements by
|
123
|
+
# @return [Deque]
|
124
|
+
def rotate(n)
|
125
|
+
return self.class.empty if self.empty?
|
126
|
+
|
127
|
+
n %= self.size
|
128
|
+
return self if n == 0
|
129
|
+
|
130
|
+
a, b = @front, @rear
|
131
|
+
|
132
|
+
if b.size >= n
|
133
|
+
n.times { a = a.cons(b.head); b = b.tail }
|
134
|
+
else
|
135
|
+
(self.size - n).times { b = b.cons(a.head); a = a.tail }
|
136
|
+
end
|
137
|
+
|
138
|
+
self.class.alloc(a, b)
|
139
|
+
end
|
140
|
+
|
113
141
|
# Return a new `Deque` with `item` added at the end.
|
114
142
|
#
|
115
143
|
# @example
|
116
|
-
# Immutable::Deque["A", "B", "C"].
|
144
|
+
# Immutable::Deque["A", "B", "C"].push("Z")
|
117
145
|
# # => Immutable::Deque["A", "B", "C", "Z"]
|
118
146
|
#
|
119
147
|
# @param item [Object] The item to add
|
@@ -180,6 +208,13 @@ module Immutable
|
|
180
208
|
self.class.empty
|
181
209
|
end
|
182
210
|
|
211
|
+
# Return a new `Deque` with the same items, but in reverse order.
|
212
|
+
#
|
213
|
+
# @return [Deque]
|
214
|
+
def reverse
|
215
|
+
self.class.alloc(@rear, @front)
|
216
|
+
end
|
217
|
+
|
183
218
|
# Return true if `other` has the same type and contents as this `Deque`.
|
184
219
|
#
|
185
220
|
# @param other [Object] The collection to compare with
|
data/lib/immutable/enumerable.rb
CHANGED
@@ -107,7 +107,7 @@ module Immutable
|
|
107
107
|
# Return true if `other` contains the same elements, in the same order.
|
108
108
|
# @return [Boolean]
|
109
109
|
def ==(other)
|
110
|
-
self.eql?(other) || other.respond_to?(:to_ary) && to_ary
|
110
|
+
self.eql?(other) || (other.respond_to?(:to_ary) && to_ary == other.to_ary)
|
111
111
|
end
|
112
112
|
|
113
113
|
# Convert all the elements into strings and join them together, separated by
|
data/lib/immutable/hash.rb
CHANGED
@@ -771,7 +771,7 @@ module Immutable
|
|
771
771
|
# @param other [Object] The object to compare with
|
772
772
|
# @return [Boolean]
|
773
773
|
def ==(other)
|
774
|
-
self.eql?(other) || (other.respond_to?(:to_hash) && to_hash
|
774
|
+
self.eql?(other) || (other.respond_to?(:to_hash) && to_hash == other.to_hash)
|
775
775
|
end
|
776
776
|
|
777
777
|
# Return true if this `Hash` is a proper superset of `other`, which means
|
data/lib/immutable/trie.rb
CHANGED
@@ -10,8 +10,8 @@ module Immutable
|
|
10
10
|
# Returns the number of key-value pairs in the trie.
|
11
11
|
attr_reader :size
|
12
12
|
|
13
|
-
def initialize(
|
14
|
-
@
|
13
|
+
def initialize(bitshift, size = 0, entries = [], children = [])
|
14
|
+
@bitshift = bitshift
|
15
15
|
@entries = entries
|
16
16
|
@children = children
|
17
17
|
@size = size
|
@@ -19,7 +19,7 @@ module Immutable
|
|
19
19
|
|
20
20
|
# Returns <tt>true</tt> if the trie contains no key-value pairs.
|
21
21
|
def empty?
|
22
|
-
size == 0
|
22
|
+
@size == 0
|
23
23
|
end
|
24
24
|
|
25
25
|
# Returns <tt>true</tt> if the given key is present in the trie.
|
@@ -29,15 +29,7 @@ module Immutable
|
|
29
29
|
|
30
30
|
# Calls <tt>block</tt> once for each entry in the trie, passing the key-value pair as parameters.
|
31
31
|
def each(&block)
|
32
|
-
|
33
|
-
# the latter segfaults on ruby 2.2 and above. Once that is fixed and
|
34
|
-
# broken versions are sufficiently old, we should revert back to yield
|
35
|
-
# with a warning that the broken versions are unsupported.
|
36
|
-
#
|
37
|
-
# For more context:
|
38
|
-
# * https://bugs.ruby-lang.org/issues/11451
|
39
|
-
# * https://github.com/hamstergem/hamster/issues/189
|
40
|
-
@entries.each { |entry| block.call(entry) if entry }
|
32
|
+
@entries.each { |entry| yield entry if entry }
|
41
33
|
@children.each do |child|
|
42
34
|
child.each(&block) if child
|
43
35
|
end
|
@@ -65,7 +57,7 @@ module Immutable
|
|
65
57
|
|
66
58
|
# @return [Trie] A copy of `self` with the given value associated with the
|
67
59
|
# key (or `self` if no modification was needed because an identical
|
68
|
-
# key-value pair
|
60
|
+
# key-value pair was already stored
|
69
61
|
def put(key, value)
|
70
62
|
index = index_for(key)
|
71
63
|
entry = @entries[index]
|
@@ -74,7 +66,7 @@ module Immutable
|
|
74
66
|
entries = @entries.dup
|
75
67
|
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
|
76
68
|
entries[index] = [key, value].freeze
|
77
|
-
Trie.new(@
|
69
|
+
Trie.new(@bitshift, @size + 1, entries, @children)
|
78
70
|
elsif entry[0].eql?(key)
|
79
71
|
if entry[1].equal?(value)
|
80
72
|
self
|
@@ -82,7 +74,7 @@ module Immutable
|
|
82
74
|
entries = @entries.dup
|
83
75
|
key = key.dup.freeze if key.is_a?(String) && !key.frozen?
|
84
76
|
entries[index] = [key, value].freeze
|
85
|
-
Trie.new(@
|
77
|
+
Trie.new(@bitshift, @size, entries, @children)
|
86
78
|
end
|
87
79
|
else
|
88
80
|
child = @children[index]
|
@@ -94,12 +86,12 @@ module Immutable
|
|
94
86
|
children = @children.dup
|
95
87
|
children[index] = new_child
|
96
88
|
new_self_size = @size + (new_child.size - child.size)
|
97
|
-
Trie.new(@
|
89
|
+
Trie.new(@bitshift, new_self_size, @entries, children)
|
98
90
|
end
|
99
91
|
else
|
100
92
|
children = @children.dup
|
101
|
-
children[index] = Trie.new(@
|
102
|
-
Trie.new(@
|
93
|
+
children[index] = Trie.new(@bitshift + 5).put!(key, value)
|
94
|
+
Trie.new(@bitshift, @size + 1, @entries, children)
|
103
95
|
end
|
104
96
|
end
|
105
97
|
end
|
@@ -141,14 +133,14 @@ module Immutable
|
|
141
133
|
end
|
142
134
|
else
|
143
135
|
new_children ||= @children.dup
|
144
|
-
new_children[index] = Trie.new(@
|
136
|
+
new_children[index] = Trie.new(@bitshift + 5).put!(key, value)
|
145
137
|
new_size += 1
|
146
138
|
end
|
147
139
|
end
|
148
140
|
end
|
149
141
|
|
150
142
|
if new_entries || new_children
|
151
|
-
Trie.new(@
|
143
|
+
Trie.new(@bitshift, new_size, new_entries || @entries, new_children || @children)
|
152
144
|
else
|
153
145
|
self
|
154
146
|
end
|
@@ -172,7 +164,7 @@ module Immutable
|
|
172
164
|
@children[index] = child.put!(key, value)
|
173
165
|
@size += child.size - old_child_size
|
174
166
|
else
|
175
|
-
@children[index] = Trie.new(@
|
167
|
+
@children[index] = Trie.new(@bitshift + 5).put!(key, value)
|
176
168
|
@size += 1
|
177
169
|
end
|
178
170
|
end
|
@@ -193,7 +185,7 @@ module Immutable
|
|
193
185
|
|
194
186
|
# Returns a copy of <tt>self</tt> with the given key (and associated value) deleted. If not found, returns <tt>self</tt>.
|
195
187
|
def delete(key)
|
196
|
-
find_and_delete(key) || Trie.new(@
|
188
|
+
find_and_delete(key) || Trie.new(@bitshift)
|
197
189
|
end
|
198
190
|
|
199
191
|
# Delete multiple elements from a Trie. This is more efficient than
|
@@ -238,7 +230,7 @@ module Immutable
|
|
238
230
|
end
|
239
231
|
|
240
232
|
if new_entries || new_children
|
241
|
-
Trie.new(@
|
233
|
+
Trie.new(@bitshift, new_size, new_entries || @entries, new_children || @children)
|
242
234
|
else
|
243
235
|
self
|
244
236
|
end
|
@@ -297,7 +289,7 @@ module Immutable
|
|
297
289
|
children = @children.dup
|
298
290
|
children[index] = copy
|
299
291
|
new_size = @size - (child.size - copy_size(copy))
|
300
|
-
return Trie.new(@
|
292
|
+
return Trie.new(@bitshift, new_size, @entries, children)
|
301
293
|
end
|
302
294
|
end
|
303
295
|
end
|
@@ -318,14 +310,14 @@ module Immutable
|
|
318
310
|
else
|
319
311
|
entries[index] = nil
|
320
312
|
end
|
321
|
-
Trie.new(@
|
313
|
+
Trie.new(@bitshift, @size - 1, entries, children || @children)
|
322
314
|
end
|
323
315
|
end
|
324
316
|
|
325
317
|
private
|
326
318
|
|
327
319
|
def index_for(key)
|
328
|
-
(key.hash.abs >> @
|
320
|
+
(key.hash.abs >> @bitshift) & 31
|
329
321
|
end
|
330
322
|
|
331
323
|
def copy_size(copy)
|
@@ -334,5 +326,5 @@ module Immutable
|
|
334
326
|
end
|
335
327
|
|
336
328
|
# @private
|
337
|
-
EmptyTrie = Trie.new(0)
|
329
|
+
EmptyTrie = Trie.new(0).freeze
|
338
330
|
end
|
data/lib/immutable/version.rb
CHANGED
@@ -26,7 +26,7 @@ describe Immutable::Deque do
|
|
26
26
|
context "on empty subclass" do
|
27
27
|
let(:subclass) { Class.new(Immutable::Deque) }
|
28
28
|
let(:empty_instance) { subclass.new }
|
29
|
-
it "returns
|
29
|
+
it "returns empty object of same class" do
|
30
30
|
empty_instance.send(method).class.should be subclass
|
31
31
|
end
|
32
32
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Immutable::Deque do
|
4
|
+
|
5
|
+
# Deques can have items distributed differently between the 'front' and 'rear' lists
|
6
|
+
# and still be equivalent
|
7
|
+
# Since the implementation of #rotate depends on how items are distributed between the
|
8
|
+
# two lists, we need to test both the case where most items are on the 'front' and
|
9
|
+
# where most are on the 'rear'
|
10
|
+
big_front = D.alloc(L.from_enum([1, 2, 3]), L.from_enum([5, 4]))
|
11
|
+
big_rear = D.alloc(L.from_enum([1, 2]), L.from_enum([5, 4, 3]))
|
12
|
+
|
13
|
+
describe "#rotate" do
|
14
|
+
[
|
15
|
+
[[], 9999, []],
|
16
|
+
[['A'], -1, ['A']],
|
17
|
+
[['A', 'B', 'C'], -1, ['B', 'C', 'A']],
|
18
|
+
[['A', 'B', 'C', 'D'], 0, ['A', 'B', 'C', 'D']],
|
19
|
+
[%w[A B C D], 2, %w[C D A B]],
|
20
|
+
].each do |values, rotation, expected|
|
21
|
+
context "on #{values.inspect}" do
|
22
|
+
let(:deque) { D[*values] }
|
23
|
+
|
24
|
+
it "preserves the original" do
|
25
|
+
deque.rotate(rotation)
|
26
|
+
deque.should eql(D[*values])
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns #{expected.inspect}" do
|
30
|
+
deque.rotate(rotation).should eql(D[*expected])
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns a frozen instance" do
|
34
|
+
deque.rotate(rotation).should be_frozen
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "on a Deque with most items on 'front' list" do
|
40
|
+
it "works with a small rotation" do
|
41
|
+
big_front.rotate(2).should eql(D[4, 5, 1, 2, 3])
|
42
|
+
end
|
43
|
+
|
44
|
+
it "works with a larger rotation" do
|
45
|
+
big_front.rotate(4).should eql(D[2, 3, 4, 5, 1])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "on a Deque with most items on 'rear' list" do
|
50
|
+
it "works with a small rotation" do
|
51
|
+
big_rear.rotate(2).should eql(D[4, 5, 1, 2, 3])
|
52
|
+
end
|
53
|
+
|
54
|
+
it "works with a larger rotation" do
|
55
|
+
big_rear.rotate(4).should eql(D[2, 3, 4, 5, 1])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "on empty subclass" do
|
60
|
+
let(:subclass) { Class.new(Immutable::Deque) }
|
61
|
+
let(:empty_instance) { subclass.new }
|
62
|
+
it "returns an empty object of the same class" do
|
63
|
+
empty_instance.rotate(1).class.should be subclass
|
64
|
+
empty_instance.rotate(-1).class.should be subclass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "spec_helper"
|
2
|
+
require "bigdecimal"
|
2
3
|
|
3
4
|
describe Immutable::Hash do
|
4
5
|
let(:hash) { H["A" => "aye", "B" => "bee", "C" => "see"] }
|
@@ -33,6 +34,11 @@ describe Immutable::Hash do
|
|
33
34
|
instance = subclass.new("A" => "aye", "B" => "bee", "C" => "see")
|
34
35
|
(hash == instance).should == true
|
35
36
|
end
|
37
|
+
|
38
|
+
it "performs numeric conversions between floats and BigDecimals" do
|
39
|
+
expect(H[a: 0.0] == H[a: BigDecimal('0.0')]).to be true
|
40
|
+
expect(H[a: BigDecimal('0.0')] == H[a: 0.0]).to be true
|
41
|
+
end
|
36
42
|
end
|
37
43
|
|
38
44
|
[:eql?, :==].each do |method|
|
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.
|
4
|
+
version: 0.0.4
|
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:
|
14
|
+
date: 2019-05-16 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: concurrent-ruby
|
@@ -167,6 +167,7 @@ files:
|
|
167
167
|
- spec/lib/immutable/deque/pretty_print_spec.rb
|
168
168
|
- spec/lib/immutable/deque/push_spec.rb
|
169
169
|
- spec/lib/immutable/deque/random_modification_spec.rb
|
170
|
+
- spec/lib/immutable/deque/rotate_spec.rb
|
170
171
|
- spec/lib/immutable/deque/shift_spec.rb
|
171
172
|
- spec/lib/immutable/deque/size_spec.rb
|
172
173
|
- spec/lib/immutable/deque/to_a_spec.rb
|
@@ -504,8 +505,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
504
505
|
- !ruby/object:Gem::Version
|
505
506
|
version: '0'
|
506
507
|
requirements: []
|
507
|
-
|
508
|
-
rubygems_version: 2.6.13
|
508
|
+
rubygems_version: 3.0.1
|
509
509
|
signing_key:
|
510
510
|
specification_version: 4
|
511
511
|
summary: Efficient, immutable, thread-safe collection classes for Ruby
|
@@ -606,6 +606,7 @@ test_files:
|
|
606
606
|
- spec/lib/immutable/deque/copying_spec.rb
|
607
607
|
- spec/lib/immutable/deque/pop_spec.rb
|
608
608
|
- spec/lib/immutable/deque/to_a_spec.rb
|
609
|
+
- spec/lib/immutable/deque/rotate_spec.rb
|
609
610
|
- spec/lib/immutable/deque/marshal_spec.rb
|
610
611
|
- spec/lib/immutable/set/difference_spec.rb
|
611
612
|
- spec/lib/immutable/set/disjoint_spec.rb
|