erlang-terms 1.1.0 → 2.0.1

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 (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