/
map_set.ex
449 lines (335 loc) · 12.3 KB
/
map_set.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
defmodule MapSet do
@moduledoc """
Functions that work on sets.
A set is a data structure that can contain unique elements of any kind,
without any particular order. `MapSet` is the "go to" set data structure in Elixir.
A set can be constructed using `MapSet.new/0`:
iex> MapSet.new()
MapSet.new([])
Elements in a set don't have to be of the same type and they can be
populated from an [enumerable](`t:Enumerable.t/0`) using `MapSet.new/1`:
iex> MapSet.new([1, :two, {"three"}])
MapSet.new([1, :two, {"three"}])
Elements can be inserted using `MapSet.put/2`:
iex> MapSet.new([2]) |> MapSet.put(4) |> MapSet.put(0)
MapSet.new([0, 2, 4])
By definition, sets can't contain duplicate elements: when
inserting an element in a set where it's already present, the insertion is
simply a no-op.
iex> map_set = MapSet.new()
iex> MapSet.put(map_set, "foo")
MapSet.new(["foo"])
iex> map_set |> MapSet.put("foo") |> MapSet.put("foo")
MapSet.new(["foo"])
A `MapSet` is represented internally using the `%MapSet{}` struct. This struct
can be used whenever there's a need to pattern match on something being a `MapSet`:
iex> match?(%MapSet{}, MapSet.new())
true
Note that, however, the struct fields are private and must not be accessed
directly; use the functions in this module to perform operations on sets.
`MapSet`s can also be constructed starting from other collection-type data
structures: for example, see `MapSet.new/1` or `Enum.into/2`.
`MapSet` is built on top of Erlang's
[`:sets`](https://www.erlang.org/doc/man/sets.html) (version 2). This means
that they share many properties, including logarithmic time complexity. Erlang
`:sets` (version 2) are implemented on top of maps, so see the documentation
for `Map` for more information on its execution time complexity.
"""
@type value :: term
@opaque internal(value) :: :sets.set(value)
@type t(value) :: %__MODULE__{map: internal(value)}
@type t :: t(term)
# The key name is :map because the MapSet implementation used to be based on top of maps before
# Elixir 1.15 (and Erlang/OTP 24, which introduced :sets version 2). :sets v2's internal
# representation is, anyways, exactly the same as MapSet's previous implementation. We cannot
# change the :map key name here because we'd break backwards compatibility with code compiled
# with Elixir 1.14 and earlier and executed on Elixir 1.15+.
defstruct map: :sets.new(version: 2)
@doc """
Returns a new set.
## Examples
iex> MapSet.new()
MapSet.new([])
"""
@spec new :: t
def new(), do: %MapSet{}
@doc """
Creates a set from an enumerable.
## Examples
iex> MapSet.new([:b, :a, 3])
MapSet.new([3, :a, :b])
iex> MapSet.new([3, 3, 3, 2, 2, 1])
MapSet.new([1, 2, 3])
"""
@spec new(Enumerable.t()) :: t
def new(enumerable)
def new(%__MODULE__{} = map_set), do: map_set
def new(enumerable) do
set =
enumerable
|> Enum.to_list()
|> :sets.from_list(version: 2)
%MapSet{map: set}
end
@doc """
Creates a set from an enumerable via the transformation function.
## Examples
iex> MapSet.new([1, 2, 1], fn x -> 2 * x end)
MapSet.new([2, 4])
"""
@spec new(Enumerable.t(), (term -> val)) :: t(val) when val: value
def new(enumerable, transform) when is_function(transform, 1) do
set =
enumerable
|> Enum.map(transform)
|> :sets.from_list(version: 2)
%MapSet{map: set}
end
@doc """
Deletes `value` from `map_set`.
Returns a new set which is a copy of `map_set` but without `value`.
## Examples
iex> map_set = MapSet.new([1, 2, 3])
iex> MapSet.delete(map_set, 4)
MapSet.new([1, 2, 3])
iex> MapSet.delete(map_set, 2)
MapSet.new([1, 3])
"""
@spec delete(t(val1), val2) :: t(val1) when val1: value, val2: value
def delete(%MapSet{map: set} = map_set, value) do
%{map_set | map: :sets.del_element(value, set)}
end
@doc """
Returns a set that is `map_set1` without the members of `map_set2`.
## Examples
iex> MapSet.difference(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
MapSet.new([1])
"""
@spec difference(t(val1), t(val2)) :: t(val1) when val1: value, val2: value
def difference(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
%{map_set1 | map: :sets.subtract(set1, set2)}
end
@doc """
Returns a set with elements that are present in only one but not both sets.
## Examples
iex> MapSet.symmetric_difference(MapSet.new([1, 2, 3]), MapSet.new([2, 3, 4]))
MapSet.new([1, 4])
"""
@doc since: "1.14.0"
@spec symmetric_difference(t(val1), t(val2)) :: t(val1 | val2) when val1: value, val2: value
def symmetric_difference(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
{small, large} = if :sets.size(set1) <= :sets.size(set2), do: {set1, set2}, else: {set2, set1}
disjointer_fun = fn elem, {small, acc} ->
if :sets.is_element(elem, small) do
{:sets.del_element(elem, small), acc}
else
{small, [elem | acc]}
end
end
{new_small, list} = :sets.fold(disjointer_fun, {small, []}, large)
%{map_set1 | map: :sets.union(new_small, :sets.from_list(list, version: 2))}
end
@doc """
Checks if `map_set1` and `map_set2` have no members in common.
## Examples
iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([3, 4]))
true
iex> MapSet.disjoint?(MapSet.new([1, 2]), MapSet.new([2, 3]))
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(%MapSet{map: set1}, %MapSet{map: set2}) do
:sets.is_disjoint(set1, set2)
end
@doc """
Checks if two sets are equal.
The comparison between elements is done using `===/2`,
which a set with `1` is not equivalent to a set with
`1.0`.
## Examples
iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([2, 1, 1]))
true
iex> MapSet.equal?(MapSet.new([1, 2]), MapSet.new([3, 4]))
false
iex> MapSet.equal?(MapSet.new([1]), MapSet.new([1.0]))
false
"""
@spec equal?(t, t) :: boolean
def equal?(%MapSet{map: set1}, %MapSet{map: set2}) do
set1 === set2
end
@doc """
Returns a set containing only members that `map_set1` and `map_set2` have in common.
## Examples
iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
MapSet.new([2])
iex> MapSet.intersection(MapSet.new([1, 2]), MapSet.new([3, 4]))
MapSet.new([])
"""
@spec intersection(t(val), t(val)) :: t(val) when val: value
def intersection(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
%{map_set1 | map: :sets.intersection(set1, set2)}
end
@doc """
Checks if `map_set` contains `value`.
## Examples
iex> MapSet.member?(MapSet.new([1, 2, 3]), 2)
true
iex> MapSet.member?(MapSet.new([1, 2, 3]), 4)
false
"""
@spec member?(t, value) :: boolean
def member?(%MapSet{map: set}, value) do
:sets.is_element(value, set)
end
@doc """
Inserts `value` into `map_set` if `map_set` doesn't already contain it.
## Examples
iex> MapSet.put(MapSet.new([1, 2, 3]), 3)
MapSet.new([1, 2, 3])
iex> MapSet.put(MapSet.new([1, 2, 3]), 4)
MapSet.new([1, 2, 3, 4])
"""
@spec put(t(val), new_val) :: t(val | new_val) when val: value, new_val: value
def put(%MapSet{map: set} = map_set, value) do
%{map_set | map: :sets.add_element(value, set)}
end
@doc """
Returns the number of elements in `map_set`.
## Examples
iex> MapSet.size(MapSet.new([1, 2, 3]))
3
"""
@spec size(t) :: non_neg_integer
def size(%MapSet{map: set}) do
:sets.size(set)
end
@doc """
Checks if `map_set1`'s members are all contained in `map_set2`.
This function checks if `map_set1` is a subset of `map_set2`.
## Examples
iex> MapSet.subset?(MapSet.new([1, 2]), MapSet.new([1, 2, 3]))
true
iex> MapSet.subset?(MapSet.new([1, 2, 3]), MapSet.new([1, 2]))
false
"""
@spec subset?(t, t) :: boolean
def subset?(%MapSet{map: set1}, %MapSet{map: set2}) do
:sets.is_subset(set1, set2)
end
@doc """
Converts `map_set` to a list.
## Examples
iex> MapSet.to_list(MapSet.new([1, 2, 3]))
[1, 2, 3]
"""
@spec to_list(t(val)) :: [val] when val: value
def to_list(%MapSet{map: set}) do
:sets.to_list(set)
end
@doc """
Returns a set containing all members of `map_set1` and `map_set2`.
## Examples
iex> MapSet.union(MapSet.new([1, 2]), MapSet.new([2, 3, 4]))
MapSet.new([1, 2, 3, 4])
"""
@spec union(t(val1), t(val2)) :: t(val1 | val2) when val1: value, val2: value
def union(%MapSet{map: set1} = map_set1, %MapSet{map: set2} = _map_set2) do
%{map_set1 | map: :sets.union(set1, set2)}
end
@doc """
Filters the set by returning only the elements from `map_set` for which invoking
`fun` returns a truthy value.
Also see `reject/2` which discards all elements where the function returns
a truthy value.
> #### Performance considerations {: .tip}
>
> If you find yourself doing multiple calls to `MapSet.filter/2`
> and `MapSet.reject/2` in a pipeline, it is likely more efficient
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
> a map at the end using `MapSet.new/1`.
## Examples
iex> MapSet.filter(MapSet.new(1..5), fn x -> x > 3 end)
MapSet.new([4, 5])
iex> MapSet.filter(MapSet.new(["a", :b, "c"]), &is_atom/1)
MapSet.new([:b])
"""
@doc since: "1.14.0"
@spec filter(t(a), (a -> as_boolean(term))) :: t(a) when a: value
def filter(%MapSet{map: set} = map_set, fun) when is_function(fun) do
pred = fn element -> !!fun.(element) end
%{map_set | map: :sets.filter(pred, set)}
end
@doc """
Returns a set by excluding the elements from `map_set` for which invoking `fun`
returns a truthy value.
See also `filter/2`.
## Examples
iex> MapSet.reject(MapSet.new(1..5), fn x -> rem(x, 2) != 0 end)
MapSet.new([2, 4])
iex> MapSet.reject(MapSet.new(["a", :b, "c"]), &is_atom/1)
MapSet.new(["a", "c"])
"""
@doc since: "1.14.0"
@spec reject(t(a), (a -> as_boolean(term))) :: t(a) when a: value
def reject(%MapSet{map: set} = map_set, fun) when is_function(fun) do
pred = fn element -> !fun.(element) end
%{map_set | map: :sets.filter(pred, set)}
end
@doc """
Splits the `map_set` into two `MapSet`s according to the given function `fun`.
`fun` receives each element in the `map_set` as its only argument. Returns
a tuple with the first `MapSet` containing all the elements in `map_set` for which
applying `fun` returned a truthy value, and a second `MapSet` with all the elements
for which applying `fun` returned a falsy value (`false` or `nil`).
## Examples
iex> {while_true, while_false} = MapSet.split_with(MapSet.new([1, 2, 3, 4]), fn v -> rem(v, 2) == 0 end)
iex> while_true
MapSet.new([2, 4])
iex> while_false
MapSet.new([1, 3])
iex> {while_true, while_false} = MapSet.split_with(MapSet.new(), fn {_k, v} -> v > 50 end)
iex> while_true
MapSet.new([])
iex> while_false
MapSet.new([])
"""
@doc since: "1.15.0"
@spec split_with(MapSet.t(), (any() -> as_boolean(term))) :: {MapSet.t(), MapSet.t()}
def split_with(%MapSet{map: map}, fun) when is_function(fun, 1) do
{while_true, while_false} = Map.split_with(map, fn {key, _} -> fun.(key) end)
{%MapSet{map: while_true}, %MapSet{map: while_false}}
end
defimpl Enumerable do
def count(map_set) do
{:ok, MapSet.size(map_set)}
end
def member?(map_set, val) do
{:ok, MapSet.member?(map_set, val)}
end
def slice(map_set) do
size = MapSet.size(map_set)
{:ok, size, &MapSet.to_list/1}
end
def reduce(map_set, acc, fun) do
Enumerable.List.reduce(MapSet.to_list(map_set), acc, fun)
end
end
defimpl Collectable do
def into(%@for{map: set} = map_set) do
fun = fn
list, {:cont, x} -> [x | list]
list, :done -> %{map_set | map: :sets.union(set, :sets.from_list(list, version: 2))}
_, :halt -> :ok
end
{[], fun}
end
end
defimpl Inspect do
import Inspect.Algebra
def inspect(map_set, opts) do
opts = %Inspect.Opts{opts | charlists: :as_lists}
concat(["MapSet.new(", Inspect.List.inspect(MapSet.to_list(map_set), opts), ")"])
end
end
end