erlang-terms 1.1.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.editorconfig +20 -0
  4. data/.gitignore +10 -18
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +15 -3
  8. data/.yardopts +6 -0
  9. data/CHANGELOG.md +9 -0
  10. data/Gemfile +21 -1
  11. data/LICENSE.txt +1 -1
  12. data/README.md +95 -17
  13. data/Rakefile +8 -3
  14. data/erlang-terms.gemspec +14 -11
  15. data/lib/erlang-terms.rb +1 -0
  16. data/lib/erlang/associable.rb +98 -0
  17. data/lib/erlang/atom.rb +257 -0
  18. data/lib/erlang/binary.rb +425 -0
  19. data/lib/erlang/bitstring.rb +464 -0
  20. data/lib/erlang/cons.rb +122 -0
  21. data/lib/erlang/enumerable.rb +160 -0
  22. data/lib/erlang/error.rb +4 -0
  23. data/lib/erlang/export.rb +110 -12
  24. data/lib/erlang/float.rb +201 -0
  25. data/lib/erlang/function.rb +259 -0
  26. data/lib/erlang/immutable.rb +101 -0
  27. data/lib/erlang/list.rb +1685 -24
  28. data/lib/erlang/map.rb +935 -21
  29. data/lib/erlang/nil.rb +73 -10
  30. data/lib/erlang/pid.rb +120 -18
  31. data/lib/erlang/port.rb +123 -0
  32. data/lib/erlang/reference.rb +161 -0
  33. data/lib/erlang/string.rb +175 -3
  34. data/lib/erlang/term.rb +24 -0
  35. data/lib/erlang/terms.rb +324 -8
  36. data/lib/erlang/terms/version.rb +1 -1
  37. data/lib/erlang/trie.rb +364 -0
  38. data/lib/erlang/tuple.rb +1582 -14
  39. data/lib/erlang/undefined.rb +32 -0
  40. metadata +49 -71
  41. data/spec/erlang/export_spec.rb +0 -17
  42. data/spec/erlang/list_spec.rb +0 -39
  43. data/spec/erlang/map_spec.rb +0 -24
  44. data/spec/erlang/nil_spec.rb +0 -18
  45. data/spec/erlang/pid_spec.rb +0 -21
  46. data/spec/erlang/string_spec.rb +0 -11
  47. data/spec/erlang/terms_spec.rb +0 -7
  48. data/spec/erlang/tuple_spec.rb +0 -20
  49. data/spec/spec_helper.rb +0 -7
@@ -1,31 +1,945 @@
1
1
  module Erlang
2
- class Map < ::Hash
3
- def inspect
4
- "#<#{self.class.name} \##{super}>"
5
- end
2
+ # A `Erlang::Map` maps a set of unique keys to corresponding values, much
3
+ # like a dictionary maps from words to definitions. Given a key, it can store
4
+ # and retrieve an associated value in constant time. If an existing key is
5
+ # stored again, the new value will replace the old. It behaves much like
6
+ # Ruby's built-in Hash, which we will call RubyHash for clarity. Like
7
+ # RubyHash, two keys that are `#eql?` to each other and have the same
8
+ # `#hash` are considered identical in a `Erlang::Map`.
9
+ #
10
+ # A `Erlang::Map` can be created in a couple of ways:
11
+ #
12
+ # Erlang::Map[first_name: 'John', last_name: 'Smith']
13
+ # Erlang::Map[:first_name, 'John', :last_name, 'Smith']
14
+ #
15
+ # Any `Enumerable` object with an even number of elements can be used to
16
+ # initialize a `Erlang::Map`:
17
+ #
18
+ # Erlang::Map[:first_name, 'John', :last_name, 'Smith']
19
+ #
20
+ # Any `Enumerable` object which yields two-element `[key, value]` arrays
21
+ # can be used to initialize a `Erlang::Map`:
22
+ #
23
+ # Erlang::Map.new([[:first_name, 'John'], [:last_name, 'Smith']])
24
+ #
25
+ # Key/value pairs can be added using {#put}. A new map is returned and the
26
+ # existing one is left unchanged:
27
+ #
28
+ # map = Erlang::Map[a: 100, b: 200]
29
+ # map.put(:c, 500) # => Erlang::Map[:a => 100, :b => 200, :c => 500]
30
+ # map # => Erlang::Map[:a => 100, :b => 200]
31
+ #
32
+ # {#put} can also take a block, which is used to calculate the value to be
33
+ # stored.
34
+ #
35
+ # map.put(:a) { |current| current + 200 } # => Erlang::Map[:a => 300, :b => 200]
36
+ #
37
+ # Since it is immutable, all methods which you might expect to "modify" a
38
+ # `Erlang::Map` actually return a new map and leave the existing one
39
+ # unchanged. This means that the `map[key] = value` syntax from RubyHash
40
+ # *cannot* be used with `Erlang::Map`.
41
+ #
42
+ # Nested data structures can easily be updated using {#update_in}:
43
+ #
44
+ # map = Erlang::Map["a" => Erlang::Tuple[Erlang::Map["c" => 42]]]
45
+ # map.update_in("a", 0, "c") { |value| value + 5 }
46
+ # # => Erlang::Map["a" => Erlang::Tuple[Erlang::Map["c" => 47]]]
47
+ #
48
+ # While a `Erlang::Map` can iterate over its keys or values, it does not
49
+ # guarantee any specific iteration order (unlike RubyHash). Methods like
50
+ # {#flatten} do not guarantee the order of returned key/value pairs.
51
+ #
52
+ # Like RubyHash, a `Erlang::Map` can have a default block which is used
53
+ # when looking up a key that does not exist. Unlike RubyHash, the default
54
+ # block will only be passed the missing key, without the hash itself:
55
+ #
56
+ # map = Erlang::Map.new { |missing_key| missing_key * 10 }
57
+ # map[5] # => 50
58
+ #
59
+ # Licensing
60
+ # =========
61
+ #
62
+ # Portions taken and modified from https://github.com/hamstergem/hamster
63
+ #
64
+ # Copyright (c) 2009-2014 Simon Harris
65
+ #
66
+ # Permission is hereby granted, free of charge, to any person obtaining
67
+ # a copy of this software and associated documentation files (the
68
+ # "Software"), to deal in the Software without restriction, including
69
+ # without limitation the rights to use, copy, modify, merge, publish,
70
+ # distribute, sublicense, and/or sell copies of the Software, and to
71
+ # permit persons to whom the Software is furnished to do so, subject to
72
+ # the following conditions:
73
+ #
74
+ # The above copyright notice and this permission notice shall be
75
+ # included in all copies or substantial portions of the Software.
76
+ #
77
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
78
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
79
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
80
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
81
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
82
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
83
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
84
+ #
85
+ class Map
86
+ include Erlang::Term
87
+ include Erlang::Immutable
88
+ include Erlang::Enumerable
89
+ include Erlang::Associable
90
+
91
+ # Return the number of pairs in this `Map`
92
+ # @return [Integer]
93
+ attr_reader :arity
94
+ alias :size :arity
95
+
96
+ class << self
97
+ # Create a new `Map` populated with the given key/value pairs.
98
+ #
99
+ # @example
100
+ # Erlang::Map["A" => 1, "B" => 2] # => Erlang::Map["A" => 1, "B" => 2]
101
+ # Erlang::Map["A", 1, "B", 2] # => Erlang::Map["A" => 1, "B" => 2]
102
+ #
103
+ # @param pairs [::Enumerable] initial content of hash. An empty hash is returned if not provided.
104
+ # @return [Map]
105
+ def [](*pairs)
106
+ return empty if pairs.nil? or pairs.empty?
107
+ if pairs.size == 1
108
+ return pairs[0] if pairs[0].is_a?(Erlang::Map)
109
+ return new(pairs[0]) if pairs[0].is_a?(::Hash)
110
+ end
111
+ raise ArgumentError, 'odd number of arguments for Erlang::Map' if pairs.size.odd?
112
+ pairs = pairs.each_slice(2).to_a
113
+ return new(pairs)
114
+ end
6
115
 
7
- def pretty_inspect
8
- "#<#{self.class.name} #{super[0..-2]}>\n"
116
+ # Return an empty `Map`. If used on a subclass, returns an empty instance
117
+ # of that class.
118
+ #
119
+ # @return [Map]
120
+ def empty
121
+ return @empty ||= self.new
122
+ end
123
+
124
+ # "Raw" allocation of a new `Map`. Used internally to create a new
125
+ # instance quickly after obtaining a modified {Trie}.
126
+ #
127
+ # @return [Map]
128
+ # @private
129
+ def alloc(trie = EmptyTrie, block = nil)
130
+ obj = allocate
131
+ obj.instance_variable_set(:@trie, trie)
132
+ obj.instance_variable_set(:@default, block)
133
+ return obj
134
+ end
135
+
136
+ def compare(a, b)
137
+ raise ArgumentError, "'a' must be of Erlang::Map type" if not a.kind_of?(Erlang::Map)
138
+ raise ArgumentError, "'b' must be of Erlang::Map type" if not b.kind_of?(Erlang::Map)
139
+ c = a.size <=> b.size
140
+ return c if c != 0
141
+ return 0 if a.eql?(b)
142
+ return Erlang.compare(a.sort, b.sort)
143
+ end
9
144
  end
10
145
 
11
- def pretty_print(q)
12
- q.group(1, '#{', '}') {
13
- q.seplist(self, nil, :each_pair) { |k, v|
14
- q.group {
15
- q.pp k
16
- q.text ' => '
17
- q.group(1) {
18
- q.breakable ''
19
- q.pp v
20
- }
21
- }
146
+ # @param pairs [::Enumerable] initial content of map. An empty map is returned if not provided.
147
+ # @yield [key] Optional _default block_ to be stored and used to calculate the default value of a missing key. It will not be yielded during this method. It will not be preserved when marshalling.
148
+ # @yieldparam key Key that was not present in the map.
149
+ def initialize(pairs = nil, &block)
150
+ if pairs
151
+ obj = ::Array.new(pairs.size)
152
+ i = 0
153
+ pairs.each do |key, val|
154
+ obj[i] = [Erlang.from(key), Erlang.from(val)]
155
+ i += 1
156
+ end
157
+ pairs = obj
158
+ end
159
+ @trie = pairs ? Trie[pairs] : EmptyTrie
160
+ @default = block
161
+ if block_given?
162
+ @default = ->(key) {
163
+ return Erlang.from(block.call(key))
22
164
  }
165
+ end
166
+ end
167
+
168
+ # Return the default block if there is one. Otherwise, return `nil`.
169
+ #
170
+ # @return [Proc]
171
+ def default_proc
172
+ return @default
173
+ end
174
+
175
+ # Return the number of key/value pairs in this `Map`.
176
+ #
177
+ # @example
178
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].size # => 3
179
+ #
180
+ # @return [Integer]
181
+ def size
182
+ return @trie.size
183
+ end
184
+ alias :arity :size
185
+ alias :length :size
186
+
187
+ # Return `true` if this `Map` contains no key/value pairs.
188
+ #
189
+ # @return [Boolean]
190
+ def empty?
191
+ return @trie.empty?
192
+ end
193
+
194
+ # Return `true` if the given key object is present in this `Map`. More precisely,
195
+ # return `true` if a key with the same `#hash` code, and which is also `#eql?`
196
+ # to the given key object is present.
197
+ #
198
+ # @example
199
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].key?("B") # => true
200
+ #
201
+ # @param key [Object] The key to check for
202
+ # @return [Boolean]
203
+ def key?(key)
204
+ key = Erlang.from(key)
205
+ return @trie.key?(key)
206
+ end
207
+ alias :has_key? :key?
208
+ alias :include? :key?
209
+ alias :member? :key?
210
+
211
+ # Return `true` if this `Map` has one or more keys which map to the provided value.
212
+ #
213
+ # @example
214
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].value?(2) # => true
215
+ #
216
+ # @param value [Object] The value to check for
217
+ # @return [Boolean]
218
+ def value?(value)
219
+ value = Erlang.from(value)
220
+ each { |k,v| return true if value == v }
221
+ return false
222
+ end
223
+ alias :has_value? :value?
224
+
225
+ # Retrieve the value corresponding to the provided key object. If not found, and
226
+ # this `Map` has a default block, the default block is called to provide the
227
+ # value. Otherwise, return `nil`.
228
+ #
229
+ # @example
230
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
231
+ # m["B"] # => 2
232
+ # m.get("B") # => 2
233
+ # m.get("Elephant") # => nil
234
+ #
235
+ # # Erlang Map with a default proc:
236
+ # m = Erlang::Map.new("A" => 1, "B" => 2, "C" => 3) { |key| key.size }
237
+ # m.get("B") # => 2
238
+ # m.get("Elephant") # => 8
239
+ #
240
+ # @param key [Object] The key to look up
241
+ # @return [Object]
242
+ def get(key)
243
+ key = Erlang.from(key)
244
+ entry = @trie.get(key)
245
+ if entry
246
+ return entry[1]
247
+ elsif @default
248
+ return @default.call(key)
249
+ end
250
+ end
251
+ alias :[] :get
252
+
253
+ # Retrieve the value corresponding to the given key object, or use the provided
254
+ # default value or block, or otherwise raise a `KeyError`.
255
+ #
256
+ # @overload fetch(key)
257
+ # Retrieve the value corresponding to the given key, or raise a `KeyError`
258
+ # if it is not found.
259
+ # @param key [Object] The key to look up
260
+ # @overload fetch(key) { |key| ... }
261
+ # Retrieve the value corresponding to the given key, or call the optional
262
+ # code block (with the missing key) and get its return value.
263
+ # @yield [key] The key which was not found
264
+ # @yieldreturn [Object] Object to return since the key was not found
265
+ # @param key [Object] The key to look up
266
+ # @overload fetch(key, default)
267
+ # Retrieve the value corresponding to the given key, or else return
268
+ # the provided `default` value.
269
+ # @param key [Object] The key to look up
270
+ # @param default [Object] Object to return if the key is not found
271
+ #
272
+ # @example
273
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
274
+ # m.fetch("B") # => 2
275
+ # m.fetch("Elephant") # => KeyError: key not found: "Elephant"
276
+ #
277
+ # # with a default value:
278
+ # m.fetch("B", 99) # => 2
279
+ # m.fetch("Elephant", 99) # => 99
280
+ #
281
+ # # with a block:
282
+ # m.fetch("B") { |key| key.size } # => 2
283
+ # m.fetch("Elephant") { |key| key.size } # => 8
284
+ #
285
+ # @return [Object]
286
+ def fetch(key, default = Undefined)
287
+ key = Erlang.from(key)
288
+ entry = @trie.get(key)
289
+ if entry
290
+ return entry[1]
291
+ elsif block_given?
292
+ return yield(key)
293
+ elsif not Undefined.equal?(default)
294
+ return Erlang.from(default)
295
+ else
296
+ raise KeyError, "key not found: #{key.inspect}"
297
+ end
298
+ end
299
+
300
+ # Return a new `Map` with the existing key/value associations, plus an association
301
+ # between the provided key and value. If an equivalent key is already present, its
302
+ # associated value will be replaced with the provided one.
303
+ #
304
+ # If the `value` argument is missing, but an optional code block is provided,
305
+ # it will be passed the existing value (or `nil` if there is none) and what it
306
+ # returns will replace the existing value. This is useful for "transforming"
307
+ # the value associated with a certain key.
308
+ #
309
+ # Avoid mutating objects which are used as keys. `String`s are an exception:
310
+ # unfrozen `String`s which are used as keys are internally duplicated and
311
+ # frozen. This matches RubyHash's behaviour.
312
+ #
313
+ # @example
314
+ # m = Erlang::Map["A" => 1, "B" => 2]
315
+ # m.put("C", 3)
316
+ # # => Erlang::Map["A" => 1, "B" => 2, "C" => 3]
317
+ # m.put("B") { |value| value * 10 }
318
+ # # => Erlang::Map["A" => 1, "B" => 20]
319
+ #
320
+ # @param key [Object] The key to store
321
+ # @param value [Object] The value to associate it with
322
+ # @yield [value] The previously stored value, or `nil` if none.
323
+ # @yieldreturn [Object] The new value to store
324
+ # @return [Map]
325
+ def put(key, value = yield(get(key)))
326
+ new_trie = @trie.put(Erlang.from(key), Erlang.from(value))
327
+ if new_trie.equal?(@trie)
328
+ return self
329
+ else
330
+ return self.class.alloc(new_trie, @default)
331
+ end
332
+ end
333
+
334
+ # @!method update_in(*key_path, &block)
335
+ # Return a new `Map` with a deeply nested value modified to the result of
336
+ # the given code block. When traversing the nested `Map`es and `Tuple`s,
337
+ # non-existing keys are created with empty `Map` values.
338
+ #
339
+ # The code block receives the existing value of the deeply nested key (or
340
+ # `nil` if it doesn't exist). This is useful for "transforming" the value
341
+ # associated with a certain key.
342
+ #
343
+ # Note that the original `Map` and sub-`Map`es and sub-`Tuple`s are left
344
+ # unmodified; new data structure copies are created along the path wherever
345
+ # needed.
346
+ #
347
+ # @example
348
+ # map = Erlang::Map["a" => Erlang::Map["b" => Erlang::Map["c" => 42]]]
349
+ # map.update_in("a", "b", "c") { |value| value + 5 }
350
+ # # => Erlang::Map["a" => Erlang::Map["b" => Erlang::Map["c" => 47]]]
351
+ #
352
+ # @param key_path [::Array<Object>] List of keys which form the path to the key to be modified
353
+ # @yield [value] The previously stored value
354
+ # @yieldreturn [Object] The new value to store
355
+ # @return [Map]
356
+ # @see Associable#update_in
357
+
358
+ # An alias for {#put} to match RubyHash's API. Does not support {#put}'s
359
+ # block form.
360
+ #
361
+ # @see #put
362
+ # @param key [Object] The key to store
363
+ # @param value [Object] The value to associate it with
364
+ # @return [Map]
365
+ def store(key, value)
366
+ return put(key, value)
367
+ end
368
+
369
+ # Return a new `Map` with `key` removed. If `key` is not present, return
370
+ # `self`.
371
+ #
372
+ # @example
373
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].delete("B")
374
+ # # => Erlang::Map["A" => 1, "C" => 3]
375
+ #
376
+ # @param key [Object] The key to remove
377
+ # @return [Map]
378
+ def delete(key)
379
+ return derive_new_map(@trie.delete(key))
380
+ end
381
+
382
+ # Call the block once for each key/value pair in this `Map`, passing the key/value
383
+ # pair as parameters. No specific iteration order is guaranteed, though the order will
384
+ # be stable for any particular `Map`.
385
+ #
386
+ # @example
387
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].each { |k, v| puts "k=#{k} v=#{v}" }
388
+ #
389
+ # k=A v=1
390
+ # k=C v=3
391
+ # k=B v=2
392
+ # # => Erlang::Map["A" => 1, "B" => 2, "C" => 3]
393
+ #
394
+ # @yield [key, value] Once for each key/value pair.
395
+ # @return [self]
396
+ def each(&block)
397
+ return to_enum if not block_given?
398
+ @trie.each(&block)
399
+ return self
400
+ end
401
+ alias :each_pair :each
402
+
403
+ # Call the block once for each key/value pair in this `Map`, passing the key/value
404
+ # pair as parameters. Iteration order will be the opposite of {#each}.
405
+ #
406
+ # @example
407
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].reverse_each { |k, v| puts "k=#{k} v=#{v}" }
408
+ #
409
+ # k=B v=2
410
+ # k=C v=3
411
+ # k=A v=1
412
+ # # => Erlang::Map["A" => 1, "B" => 2, "C" => 3]
413
+ #
414
+ # @yield [key, value] Once for each key/value pair.
415
+ # @return [self]
416
+ def reverse_each(&block)
417
+ return enum_for(:reverse_each) if not block_given?
418
+ @trie.reverse_each(&block)
419
+ return self
420
+ end
421
+
422
+ # Call the block once for each key/value pair in this `Map`, passing the key as a
423
+ # parameter. Ordering guarantees are the same as {#each}.
424
+ #
425
+ # @example
426
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].each_key { |k| puts "k=#{k}" }
427
+ #
428
+ # k=A
429
+ # k=C
430
+ # k=B
431
+ # # => Erlang::Map["A" => 1, "B" => 2, "C" => 3]
432
+ #
433
+ # @yield [key] Once for each key/value pair.
434
+ # @return [self]
435
+ def each_key
436
+ return enum_for(:each_key) if not block_given?
437
+ @trie.each { |k,v| yield k }
438
+ return self
439
+ end
440
+
441
+ # Call the block once for each key/value pair in this `Map`, passing the value as a
442
+ # parameter. Ordering guarantees are the same as {#each}.
443
+ #
444
+ # @example
445
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].each_value { |v| puts "v=#{v}" }
446
+ #
447
+ # v=1
448
+ # v=3
449
+ # v=2
450
+ # # => Erlang::Map["A" => 1, "B" => 2, "C" => 3]
451
+ #
452
+ # @yield [value] Once for each key/value pair.
453
+ # @return [self]
454
+ def each_value
455
+ return enum_for(:each_value) if not block_given?
456
+ @trie.each { |k,v| yield v }
457
+ return self
458
+ end
459
+
460
+ # Call the block once for each key/value pair in this `Map`, passing the key/value
461
+ # pair as parameters. The block should return a `[key, value]` array each time.
462
+ # All the returned `[key, value]` arrays will be gathered into a new `Map`.
463
+ #
464
+ # @example
465
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
466
+ # m.map { |k, v| ["new-#{k}", v * v] }
467
+ # # => Erlang::Map["new-C" => 9, "new-B" => 4, "new-A" => 1]
468
+ #
469
+ # @yield [key, value] Once for each key/value pair.
470
+ # @return [Map]
471
+ def map
472
+ return enum_for(:map) unless block_given?
473
+ return self if empty?
474
+ return self.class.new(super, &@default)
475
+ end
476
+ alias :collect :map
477
+
478
+ # Return a new `Map` with all the key/value pairs for which the block returns true.
479
+ #
480
+ # @example
481
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
482
+ # m.select { |k, v| v >= 2 }
483
+ # # => Erlang::Map["B" => 2, "C" => 3]
484
+ #
485
+ # @yield [key, value] Once for each key/value pair.
486
+ # @yieldreturn Truthy if this pair should be present in the new `Map`.
487
+ # @return [Map]
488
+ def select(&block)
489
+ return enum_for(:select) unless block_given?
490
+ return derive_new_map(@trie.select(&block))
491
+ end
492
+ alias :find_all :select
493
+ alias :keep_if :select
494
+
495
+ # Yield `[key, value]` pairs until one is found for which the block returns true.
496
+ # Return that `[key, value]` pair. If the block never returns true, return `nil`.
497
+ #
498
+ # @example
499
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
500
+ # m.find { |k, v| v.even? }
501
+ # # => ["B", 2]
502
+ #
503
+ # @return [Array]
504
+ # @yield [key, value] At most once for each key/value pair, until the block returns `true`.
505
+ # @yieldreturn Truthy to halt iteration and return the yielded key/value pair.
506
+ def find
507
+ return enum_for(:find) unless block_given?
508
+ each { |entry| return entry if yield entry }
509
+ return nil
510
+ end
511
+ alias :detect :find
512
+
513
+ # Return a new `Map` containing all the key/value pairs from this `Map` and
514
+ # `other`. If no block is provided, the value for entries with colliding keys
515
+ # will be that from `other`. Otherwise, the value for each duplicate key is
516
+ # determined by calling the block.
517
+ #
518
+ # `other` can be a `Erlang::Map`, a built-in Ruby `Map`, or any `Enumerable`
519
+ # object which yields `[key, value]` pairs.
520
+ #
521
+ # @example
522
+ # m1 = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
523
+ # m2 = Erlang::Map["C" => 70, "D" => 80]
524
+ # m1.merge(m2)
525
+ # # => Erlang::Map["C" => 70, "A" => 1, "D" => 80, "B" => 2]
526
+ # m1.merge(m2) { |key, v1, v2| v1 + v2 }
527
+ # # => Erlang::Map["C" => 73, "A" => 1, "D" => 80, "B" => 2]
528
+ #
529
+ # @param other [::Enumerable] The collection to merge with
530
+ # @yieldparam key [Object] The key which was present in both collections
531
+ # @yieldparam my_value [Object] The associated value from this `Map`
532
+ # @yieldparam other_value [Object] The associated value from the other collection
533
+ # @yieldreturn [Object] The value to associate this key with in the new `Map`
534
+ # @return [Map]
535
+ def merge(other)
536
+ other = Erlang.from(other)
537
+ trie = if block_given?
538
+ other.reduce(@trie) do |acc, (key, value)|
539
+ if entry = acc.get(key)
540
+ acc.put(key, yield(key, entry[1], value))
541
+ else
542
+ acc.put(key, value)
543
+ end
544
+ end
545
+ else
546
+ @trie.bulk_put(other)
547
+ end
548
+
549
+ return derive_new_map(trie)
550
+ end
551
+
552
+ # Return a sorted {List} which contains all the `[key, value]` pairs in
553
+ # this `Map` as two-element `Tuple`s.
554
+ #
555
+ # @overload sort
556
+ # Uses `#<=>` to determine sorted order.
557
+ # @overload sort { |(k1, v1), (k2, v2)| ... }
558
+ # Uses the block as a comparator to determine sorted order.
559
+ #
560
+ # @example
561
+ # m = Erlang::Map["Dog" => 1, "Elephant" => 2, "Lion" => 3]
562
+ # m.sort { |(k1, v1), (k2, v2)| k1.size <=> k2.size }
563
+ # # => Erlang::List[Erlang::Tuple["Dog", 1], Erlang::Tuple["Lion", 3], Erlang::Tuple["Elephant", 2]]
564
+ # @yield [(k1, v1), (k2, v2)] Any number of times with different pairs of key/value associations.
565
+ # @yieldreturn [Integer] Negative if the first pair should be sorted
566
+ # lower, positive if the latter pair, or 0 if equal.
567
+ #
568
+ # @see ::Enumerable#sort
569
+ #
570
+ # @return [List]
571
+ def sort(&comparator)
572
+ comparator = Erlang.method(:compare) unless block_given?
573
+ array = super(&comparator)
574
+ array.map! { |k, v| next Erlang::Tuple[k, v] }
575
+ return List.from_enum(array)
576
+ end
577
+
578
+ # Return a {List} which contains all the `[key, value]` pairs in this `Hash`
579
+ # as two-element `Tuple`s. The order which the pairs will appear in is determined by
580
+ # passing each pair to the code block to obtain a sort key object, and comparing
581
+ # the sort keys using `#<=>`.
582
+ #
583
+ # @see ::Enumerable#sort_by
584
+ #
585
+ # @example
586
+ # m = Erlang::Map["Dog" => 1, "Elephant" => 2, "Lion" => 3]
587
+ # m.sort_by { |key, value| key.size }
588
+ # # => Erlang::List[Erlang::Tuple["Dog", 1], Erlang::Tuple["Lion", 3], Erlang::Tuple["Elephant", 2]]
589
+ #
590
+ # @yield [key, value] Once for each key/value pair.
591
+ # @yieldreturn a sort key object for the yielded pair.
592
+ # @return [List]
593
+ def sort_by(&transformer)
594
+ return sort unless block_given?
595
+ block = ->(x) { Erlang.from(transformer.call(x)) }
596
+ array = super(&block)
597
+ array.map! { |k, v| next Erlang::Tuple[k, v] }
598
+ return List.from_enum(array)
599
+ end
600
+
601
+ # Return a new `Map` with the associations for all of the given `keys` removed.
602
+ #
603
+ # @example
604
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
605
+ # m.except("A", "C") # => Erlang::Map["B" => 2]
606
+ #
607
+ # @param keys [Array] The keys to remove
608
+ # @return [Map]
609
+ def except(*keys)
610
+ return keys.reduce(self) { |map, key| map.delete(key) }
611
+ end
612
+
613
+ # Return a new `Map` with only the associations for the `wanted` keys retained.
614
+ #
615
+ # @example
616
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
617
+ # m.slice("B", "C") # => Erlang::Map["B" => 2, "C" => 3]
618
+ #
619
+ # @param wanted [::Enumerable] The keys to retain
620
+ # @return [Map]
621
+ def slice(*wanted)
622
+ trie = Trie.new(0)
623
+ wanted.each { |key|
624
+ key = Erlang.from(key)
625
+ trie.put!(key, get(key)) if key?(key)
23
626
  }
627
+ return self.class.alloc(trie, @default)
628
+ end
629
+
630
+ # Return a {List} of the values which correspond to the `wanted` keys.
631
+ # If any of the `wanted` keys are not present in this `Map`, `nil` will be
632
+ # placed instead, or the result of the default proc (if one is defined),
633
+ # similar to the behavior of {#get}.
634
+ #
635
+ # @example
636
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
637
+ # m.values_at("B", "A", "D") # => Erlang::List[2, 1, nil]
638
+ #
639
+ # @param wanted [Array] The keys to retrieve
640
+ # @return [List]
641
+ def values_at(*wanted)
642
+ array = wanted.map { |key|
643
+ key = Erlang.from(key)
644
+ get(key)
645
+ }
646
+ return List.from_enum(array.freeze)
647
+ end
648
+
649
+ # Return a {List} of the values which correspond to the `wanted` keys.
650
+ # If any of the `wanted` keys are not present in this `Map`, raise `KeyError`
651
+ # exception.
652
+ #
653
+ # @example
654
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
655
+ # m.fetch_values("C", "A") # => Erlang::List[3, 1]
656
+ # m.fetch_values("C", "Z") # => KeyError: key not found: "Z"
657
+ #
658
+ # @param wanted [Array] The keys to retrieve
659
+ # @return [Tuple]
660
+ def fetch_values(*wanted)
661
+ array = wanted.map { |key|
662
+ key = Erlang.from(key)
663
+ fetch(key)
664
+ }
665
+ return List.from_enum(array.freeze)
666
+ end
667
+
668
+ # Return a new {List} containing the keys from this `Map`.
669
+ #
670
+ # @example
671
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3, "D" => 2].keys
672
+ # # => Erlang::List["D", "C", "B", "A"]
673
+ #
674
+ # @return [Set]
675
+ def keys
676
+ return Erlang::List.from_enum(each_key.to_a.freeze)
677
+ end
678
+
679
+ # Return a new {List} populated with the values from this `Map`.
680
+ #
681
+ # @example
682
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3, "D" => 2].values
683
+ # # => Erlang::List[2, 3, 2, 1]
684
+ #
685
+ # @return [List]
686
+ def values
687
+ return Erlang::List.from_enum(each_value.to_a.freeze)
688
+ end
689
+
690
+ # Return a new `Map` created by using keys as values and values as keys.
691
+ # If there are multiple values which are equivalent (as determined by `#hash` and
692
+ # `#eql?`), only one out of each group of equivalent values will be
693
+ # retained. Which one specifically is undefined.
694
+ #
695
+ # @example
696
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3, "D" => 2].invert
697
+ # # => Erlang::Map[1 => "A", 3 => "C", 2 => "B"]
698
+ #
699
+ # @return [Map]
700
+ def invert
701
+ pairs = []
702
+ each { |k,v| pairs << [v, k] }
703
+ return self.class.new(pairs, &@default)
704
+ end
705
+
706
+ # Return a new {List} which is a one-dimensional flattening of this `Map`.
707
+ # If `level` is 1, all the `[key, value]` pairs in the hash will be concatenated
708
+ # into one {List}. If `level` is greater than 1, keys or values which are
709
+ # themselves `Array`s or {List}s will be recursively flattened into the output
710
+ # {List}. The depth to which that flattening will be recursively applied is
711
+ # determined by `level`.
712
+ #
713
+ # As a special case, if `level` is 0, each `[key, value]` pair will be a
714
+ # separate element in the returned {List}.
715
+ #
716
+ # @example
717
+ # m = Erlang::Map["A" => 1, "B" => [2, 3, 4]]
718
+ # m.flatten
719
+ # # => Erlang::List["A", 1, "B", [2, 3, 4]]
720
+ # h.flatten(2)
721
+ # # => Erlang::List["A", 1, "B", 2, 3, 4]
722
+ #
723
+ # @param level [Integer] The number of times to recursively flatten the `[key, value]` pairs in this `Map`.
724
+ # @return [List]
725
+ def flatten(level = 1)
726
+ return List.from_enum(self) if level == 0
727
+ array = []
728
+ each { |k,v| array << k; array << v }
729
+ array.flatten!(level-1) if level > 1
730
+ return List.from_enum(array.freeze)
731
+ end
732
+
733
+ # Searches through the `Map`, comparing `obj` with each key (using `#==`).
734
+ # When a matching key is found, return the `[key, value]` pair as a `Tuple`.
735
+ # Return `nil` if no match is found.
736
+ #
737
+ # @example
738
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].assoc("B") # => Erlang::Tuple["B", 2]
739
+ #
740
+ # @param obj [Object] The key to search for (using #==)
741
+ # @return [Tuple]
742
+ def assoc(obj)
743
+ obj = Erlang.from(obj)
744
+ each { |entry| return Erlang::Tuple[*entry] if obj == entry[0] }
745
+ return nil
746
+ end
747
+
748
+ # Searches through the `Map`, comparing `obj` with each value (using `#==`).
749
+ # When a matching value is found, return the `[key, value]` pair as a `Tuple`.
750
+ # Return `nil` if no match is found.
751
+ #
752
+ # @example
753
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].rassoc(2) # => Erlang::Tuple["B", 2]
754
+ #
755
+ # @param obj [Object] The value to search for (using #==)
756
+ # @return [Tuple]
757
+ def rassoc(obj)
758
+ obj = Erlang.from(obj)
759
+ each { |entry| return Erlang::Tuple[*entry] if obj == entry[1] }
760
+ return nil
761
+ end
762
+
763
+ # Searches through the `Map`, comparing `value` with each value (using `#==`).
764
+ # When a matching value is found, return its associated key object.
765
+ # Return `nil` if no match is found.
766
+ #
767
+ # @example
768
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].key(2) # => "B"
769
+ #
770
+ # @param value [Object] The value to search for (using #==)
771
+ # @return [Object]
772
+ def key(value)
773
+ value = Erlang.from(value)
774
+ each { |entry| return entry[0] if value == entry[1] }
775
+ return nil
776
+ end
777
+
778
+ # Return a randomly chosen `[key, value]` pair from this `Map`. If the hash is empty,
779
+ # return `nil`.
780
+ #
781
+ # @example
782
+ # Erlang::Map["A" => 1, "B" => 2, "C" => 3].sample
783
+ # # => Erlang::Tuple["C", 3]
784
+ #
785
+ # @return [Tuple]
786
+ def sample
787
+ return Erlang::Tuple[*@trie.at(rand(size))]
24
788
  end
25
789
 
26
- alias_method :original_to_s, :to_s
27
- def to_s
28
- "\##{original_to_s}"
790
+ # Return an empty `Map` instance, of the same class as this one. Useful if you
791
+ # have multiple subclasses of `Map` and want to treat them polymorphically.
792
+ # Maintains the default block, if there is one.
793
+ #
794
+ # @return [Map]
795
+ def clear
796
+ if @default
797
+ return self.class.alloc(EmptyTrie, @default)
798
+ else
799
+ return self.class.empty
800
+ end
29
801
  end
802
+
803
+ # Return true if `other` has the same type and contents as this `Map`.
804
+ #
805
+ # @param other [Object] The collection to compare with
806
+ # @return [Boolean]
807
+ def eql?(other)
808
+ return true if other.equal?(self)
809
+ return @trie.eql?(other.instance_variable_get(:@trie)) if instance_of?(other.class)
810
+ return !!(Erlang.compare(other, self) == 0)
811
+ end
812
+ alias :== :eql?
813
+
814
+ # See `Object#hash`.
815
+ # @return [Integer]
816
+ def hash
817
+ return keys.sort.reduce(Erlang::Map.hash) do |acc, key|
818
+ (acc << 32) - acc + key.hash + get(key).hash
819
+ end
820
+ end
821
+
822
+ # Return the contents of this `Map` as a programmer-readable `String`. If all the
823
+ # keys and values are serializable as Ruby literal strings, the returned string can
824
+ # be passed to `eval` to reconstitute an equivalent `Map`. The default
825
+ # block (if there is one) will be lost when doing this, however.
826
+ #
827
+ # @return [String]
828
+ def inspect
829
+ # result = "#{self.class}["
830
+ result = "{"
831
+ i = 0
832
+ each do |key, val|
833
+ result << ', ' if i > 0
834
+ result << key.inspect << ' => ' << val.inspect
835
+ i += 1
836
+ end
837
+ return result << "}"
838
+ end
839
+
840
+ # Allows this `Map` to be printed using `Erlang.inspect()`.
841
+ #
842
+ # @return [String]
843
+ def erlang_inspect(raw = false)
844
+ result = '#{'
845
+ each_with_index do |(key, val), i|
846
+ result << ',' if i > 0
847
+ result << Erlang.inspect(key, raw: raw)
848
+ result << ' => '
849
+ result << Erlang.inspect(val, raw: raw)
850
+ end
851
+ return result << '}'
852
+ end
853
+
854
+ # Allows this `Map` to be printed at the `pry` console, or using `pp` (from the
855
+ # Ruby standard library), in a way which takes the amount of horizontal space on
856
+ # the screen into account, and which indents nested structures to make them easier
857
+ # to read.
858
+ #
859
+ # @private
860
+ def pretty_print(pp)
861
+ # return pp.group(1, "#{self.class}[", "]") do
862
+ return pp.group(1, "{", "}") do
863
+ pp.breakable ''
864
+ pp.seplist(self, nil) do |key, val|
865
+ pp.group do
866
+ key.pretty_print(pp)
867
+ pp.text ' => '
868
+ pp.group(1) do
869
+ pp.breakable ''
870
+ val.pretty_print(pp)
871
+ end
872
+ end
873
+ end
874
+ end
875
+ end
876
+
877
+ # Convert this `Erlang::Map` to an instance of Ruby's built-in `Hash`.
878
+ #
879
+ # @return [::Hash]
880
+ def to_hash
881
+ output = {}
882
+ each do |key, value|
883
+ output[key] = value
884
+ end
885
+ return output
886
+ end
887
+ alias :to_h :to_hash
888
+
889
+ # Return a Proc which accepts a key as an argument and returns the value.
890
+ # The Proc behaves like {#get} (when the key is missing, it returns nil or
891
+ # result of the default proc).
892
+ #
893
+ # @example
894
+ # m = Erlang::Map["A" => 1, "B" => 2, "C" => 3]
895
+ # m.to_proc.call("B")
896
+ # # => 2
897
+ # ["A", "C", "X"].map(&h) # The & is short for .to_proc in Ruby
898
+ # # => [1, 3, nil]
899
+ #
900
+ # @return [Proc]
901
+ def to_proc
902
+ return lambda { |key| get(key) }
903
+ end
904
+
905
+ # @return [::Hash]
906
+ # @private
907
+ def marshal_dump
908
+ return to_hash
909
+ end
910
+
911
+ # @private
912
+ def marshal_load(dictionary)
913
+ @trie = Trie[dictionary]
914
+ __send__(:immutable!)
915
+ return self
916
+ end
917
+
918
+ private
919
+
920
+ # Return a new `Map` which is derived from this one, using a modified {Trie}.
921
+ # The new `Map` will retain the existing default block, if there is one.
922
+ #
923
+ def derive_new_map(trie)
924
+ if trie.equal?(@trie)
925
+ return self
926
+ elsif trie.empty?
927
+ if @default
928
+ return self.class.alloc(EmptyTrie, @default)
929
+ else
930
+ return self.class.empty
931
+ end
932
+ else
933
+ return self.class.alloc(trie, @default)
934
+ end
935
+ end
936
+
30
937
  end
31
- end
938
+
939
+ # The canonical empty `Map`. Returned by `Map[]` when
940
+ # invoked with no arguments; also returned by `Map.empty`. Prefer using this
941
+ # one rather than creating many empty hashes using `Map.new`.
942
+ #
943
+ # @private
944
+ EmptyMap = Erlang::Map.empty
945
+ end