Skip to content

Commit a31f180

Browse files
author
José Valim
committed
Impose === as the operator in the Dict API
1 parent e31a06d commit a31f180

File tree

5 files changed

+316
-81
lines changed

5 files changed

+316
-81
lines changed

lib/elixir/lib/access.ex

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ end
2020
defimpl Access, for: List do
2121
@doc """
2222
Access the given key in a tuple list.
23+
The key is found via the `===` operator.
2324
2425
## Examples
2526
@@ -32,14 +33,10 @@ defimpl Access, for: List do
3233
"★☆"
3334
3435
"""
36+
def access(dict, key)
37+
def access([{ key, value }|_], key), do: value
38+
def access([{ _, _ }|t], key), do: access(t, key)
3539
def access([], _key), do: nil
36-
37-
def access(list, key) do
38-
case :lists.keyfind(key, 1, list) do
39-
{ ^key, value } -> value
40-
false -> nil
41-
end
42-
end
4340
end
4441

4542
defimpl Access, for: Atom do

lib/elixir/lib/dict.ex

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ defmodule Dict do
3131
IO.puts "#{k}: #{v}"
3232
end)
3333
34+
## Match
35+
36+
Dictionaries are required to implement all operations
37+
using the match (`===`) operator. Any deviation from
38+
this behaviour should be avoided and explicitly documented.
3439
"""
3540

3641
use Behaviour
@@ -420,7 +425,7 @@ defmodule Dict do
420425

421426
@doc """
422427
Returns a new dict where only the keys in `keys` from `dict` are
423-
included.
428+
included.
424429
Any non-member keys are ignored.
425430
426431
## Examples
@@ -458,8 +463,8 @@ defmodule Dict do
458463
end
459464

460465
@doc """
461-
Check if two dicts are equal. If the dicts are of different types, they are
462-
first converted to lists.
466+
Check if two dicts are equal using `===`. If the dicts are
467+
of different types, they are first converted to lists.
463468
464469
## Examples
465470

lib/elixir/lib/keyword.ex

Lines changed: 143 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,23 @@ defmodule Keyword do
44
of the tuple is an atom and the second element can be
55
any value.
66
7-
A keyword may have duplicated keys, so it is not strictly
7+
A keyword may have duplicated keys so it is not strictly
88
a dictionary. However most of the functions in this module
9-
allows it to behave exactly as a dictionary. For example,
10-
`Keyword.get` will get the first entry matching the given
11-
key, regardless if duplicated entries exist. Similarly,
12-
`Keyword.put` and `Keyword.delete` ensure all duplicated
13-
entries for a given key are removed when invoked.
9+
behaves exactly as a dictionary and mimic the API defined
10+
by the `Dict` behaviour.
11+
12+
For example, `Keyword.get` will get the first entry matching
13+
the given key, regardless if duplicated entries exist.
14+
Similarly, `Keyword.put` and `Keyword.delete` ensure all
15+
duplicated entries for a given key are removed when invoked.
16+
A handful of functions exist to handle duplicated keys, in
17+
particular, `from_enum` allows creating a new keywords without
18+
removing duplicated keys, `get_values` returns all values for
19+
a given key and `delete_first` deletes just one of the existing
20+
entries.
21+
22+
Since a keyword list is simply a list, all the operations defined
23+
in `Enum` and `List` can also be applied.
1424
"""
1525

1626
@type key :: atom
@@ -23,16 +33,12 @@ defmodule Keyword do
2333
duplicated entries.
2434
"""
2535
@spec from_enum(Enum.t) :: t
26-
def from_enum(enum) when is_list(enum) do
27-
enum
28-
end
29-
3036
def from_enum(enum) do
31-
Enum.map(enum, fn(x) -> x end)
37+
Enum.to_list(enum)
3238
end
3339

3440
@doc """
35-
Checks if the given argument is a keywords list or not
41+
Checks if the given argument is a keywords list or not.
3642
"""
3743
@spec keyword?(term) :: boolean
3844
def keyword?([{ key, _value } | rest]) when is_atom(key) do
@@ -407,4 +413,129 @@ defmodule Keyword do
407413
def update([], key, initial, _fun) when is_atom(key) do
408414
[{key, initial}]
409415
end
416+
417+
@doc """
418+
Splits the given keywords in two given the given keys.
419+
Duplicated keys are preserved in the split keyword list.
420+
421+
## Examples
422+
423+
iex> d = [a: 1, b: 2, c: 3, d: 4]
424+
iex> Keyword.split(d, [:a, :c, :e])
425+
{ [a: 1, c: 3], [b: 2, d: 4] }
426+
427+
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
428+
iex> Keyword.split(d, [:a, :c, :e])
429+
{ [a: 1, c: 3, a: 5], [b: 2, d: 4] }
430+
431+
"""
432+
def split(dict, keys) do
433+
acc = { [], [] }
434+
435+
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
436+
if :lists.member(k, keys) do
437+
{ [{k, v}|take], drop }
438+
else
439+
{ take, [{k, v}|drop] }
440+
end
441+
end
442+
443+
{Enum.reverse(take), Enum.reverse(drop)}
444+
end
445+
446+
@doc """
447+
Takes the given keys from the dict.
448+
Duplicated keys are preserved in the new keyword list.
449+
450+
## Examples
451+
452+
iex> d = [a: 1, b: 2, c: 3, d: 4]
453+
iex> Keyword.take(d, [:a, :c, :e])
454+
[a: 1, c: 3]
455+
456+
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
457+
iex> Keyword.take(d, [:a, :c, :e])
458+
[a: 1, c: 3, a: 5]
459+
460+
"""
461+
def take(dict, keys) do
462+
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
463+
end
464+
465+
@doc """
466+
Drops the given keys from the dict.
467+
Duplicated keys are preserved in the new keyword list.
468+
469+
## Examples
470+
471+
iex> d = [a: 1, b: 2, c: 3, d: 4]
472+
iex> Keyword.drop(d, [:b, :d])
473+
[a: 1, c: 3]
474+
475+
iex> d = [a: 1, b: 2, c: 3, d: 4, a: 5]
476+
iex> Keyword.drop(d, [:b, :d])
477+
[a: 1, c: 3, a: 5]
478+
479+
"""
480+
def drop(dict, keys) do
481+
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
482+
end
483+
484+
@doc """
485+
Returns the first value associated with `key` in the keyword
486+
list as well as the keyword list without `key`.
487+
488+
All duplicated entries are removed. See `pop_first/3` for
489+
removing only the first entry.
490+
491+
## Examples
492+
493+
iex> Keyword.pop [a: 1], :a
494+
{1,[]}
495+
496+
iex> Keyword.pop [a: 1], :b
497+
{nil,[a: 1]}
498+
499+
iex> Keyword.pop [a: 1], :b, 3
500+
{3,[a: 1]}
501+
502+
iex> Keyword.pop [a: 1], :b, 3
503+
{3,[a: 1]}
504+
505+
iex> Keyword.pop [a: 1, a: 2], :a
506+
{1,[]}
507+
508+
"""
509+
def pop(dict, key, default // nil) do
510+
{ get(dict, key, default), delete(dict, key) }
511+
end
512+
513+
@doc """
514+
Returns the first value associated with `key` in the keyword
515+
list as well as the keyword list without that particular ocurrence
516+
of `key`.
517+
518+
Duplicated entries are not removed.
519+
520+
## Examples
521+
522+
iex> Keyword.pop_first [a: 1], :a
523+
{1,[]}
524+
525+
iex> Keyword.pop_first [a: 1], :b
526+
{nil,[a: 1]}
527+
528+
iex> Keyword.pop_first [a: 1], :b, 3
529+
{3,[a: 1]}
530+
531+
iex> Keyword.pop_first [a: 1], :b, 3
532+
{3,[a: 1]}
533+
534+
iex> Keyword.pop_first [a: 1, a: 2], :a
535+
{1,[a: 2]}
536+
537+
"""
538+
def pop_first(dict, key, default // nil) do
539+
{ get(dict, key, default), delete_first(dict, key) }
540+
end
410541
end

lib/elixir/lib/list_dict.ex

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ defmodule ListDict do
22
@moduledoc """
33
A Dict implementation that works on lists of two-items tuples.
44
5+
This dictionary is only recommended for keeping a small amount
6+
of values. Other dict alternatives are more viable for keeping
7+
any other amount than a handful.
8+
59
For more information about the functions and their APIs, please
610
consult the `Dict` module.
711
"""
@@ -38,28 +42,25 @@ defmodule ListDict do
3842
length(dict)
3943
end
4044

41-
def has_key?(dict, key) do
42-
:lists.keymember(key, 1, dict)
43-
end
45+
def has_key?(dict, key)
46+
def has_key?([{ key, _ }|_], key), do: true
47+
def has_key?([{ _, _ }|t], key), do: has_key?(t, key)
48+
def has_key?([], _key), do: false
4449

45-
def get(dict, key, default // nil) do
46-
case :lists.keyfind(key, 1, dict) do
47-
{ ^key, value } -> value
48-
false -> default
49-
end
50-
end
50+
def get(dict, key, default // nil)
51+
def get([{ key, value }|_], key, _default), do: value
52+
def get([{ _, _ }|t], key, default), do: get(t, key, default)
53+
def get([], _key, default), do: default
5154

52-
def fetch(dict, key) do
53-
case :lists.keyfind(key, 1, dict) do
54-
{ ^key, value } -> { :ok, value }
55-
false -> :error
56-
end
57-
end
55+
def fetch(dict, key)
56+
def fetch([{ key, value }|_], key), do: { :ok, value }
57+
def fetch([{ _, _ }|t], key), do: fetch(t, key)
58+
def fetch([], _key), do: :error
5859

5960
def fetch!(dict, key) do
60-
case :lists.keyfind(key, 1, dict) do
61-
{ ^key, value } -> value
62-
false -> raise(KeyError, key: key)
61+
case fetch(dict, key) do
62+
{ :ok, value } -> value
63+
:error -> raise(KeyError, key: key)
6364
end
6465
end
6566

@@ -72,43 +73,43 @@ defmodule ListDict do
7273
end
7374

7475
def put_new(dict, key, val) do
75-
case :lists.keyfind(key, 1, dict) do
76-
{ ^key, _ } -> dict
76+
case has_key?(dict, key) do
77+
true -> dict
7778
false -> [{key, val}|dict]
7879
end
7980
end
8081

81-
def delete(dict, key) do
82-
lc { k, _ } = tuple inlist dict, key != k, do: tuple
83-
end
84-
85-
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end)
82+
def delete(dict, key)
83+
def delete([{ key, _ }|t], key), do: t
84+
def delete([{ _, _ } = h|t], key), do: [h|delete(t, key)]
85+
def delete([], _key), do: []
8686

87-
def merge(dict1, dict2, fun) do
88-
Enum.reduce dict2, dict1, fn { k, v2 }, acc ->
89-
update(acc, k, v2, fn(v1) -> fun.(k, v1, v2) end)
87+
def merge(dict, enum, callback // fn(_k, _v1, v2) -> v2 end) do
88+
Enum.reduce enum, dict, fn { k, v2 }, acc ->
89+
update(acc, k, v2, fn(v1) -> callback.(k, v1, v2) end)
9090
end
9191
end
9292

9393
def split(dict, keys) do
94-
acc = { new(), new() }
94+
acc = { [], [] }
95+
9596
{take, drop} = Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
96-
if :lists.member(k, keys) do
97+
if any_key?(k, keys) do
9798
{ [{k, v}|take], drop }
9899
else
99100
{ take, [{k, v}|drop] }
100101
end
101102
end
102-
103+
103104
{Enum.reverse(take), Enum.reverse(drop)}
104105
end
105106

106107
def take(dict, keys) do
107-
lc { k, _ } = tuple inlist dict, :lists.member(k, keys), do: tuple
108+
lc { k, _ } = tuple inlist dict, any_key?(k, keys), do: tuple
108109
end
109110

110111
def drop(dict, keys) do
111-
lc { k, _ } = tuple inlist dict, not :lists.member(k, keys), do: tuple
112+
lc { k, _ } = tuple inlist dict, not any_key?(k, keys), do: tuple
112113
end
113114

114115
def update!([{key, value}|dict], key, fun) do
@@ -138,8 +139,12 @@ defmodule ListDict do
138139
def empty(_dict), do: []
139140

140141
def equal?(dict, other) do
141-
:lists.keysort(1, dict) == :lists.keysort(1, other)
142+
:lists.keysort(1, dict) === :lists.keysort(1, other)
142143
end
143144

144145
def to_list(dict), do: dict
146+
147+
defp any_key?(k, [k|_]), do: true
148+
defp any_key?(k, [_|t]), do: any_key?(k, t)
149+
defp any_key?(_k, []), do: false
145150
end

0 commit comments

Comments
 (0)