Skip to content

Commit 08211b7

Browse files
author
José Valim
committed
Merge pull request #1085 from TanYewWei/issue_1069
Issue #1069: Add Dict.drop, Dict.take and Dict.split
2 parents 16555c9 + 216019c commit 08211b7

File tree

4 files changed

+218
-0
lines changed

4 files changed

+218
-0
lines changed

lib/elixir/lib/dict.ex

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ defmodule Dict do
4141

4242
@type key :: any
4343
@type value :: any
44+
@type keys :: [ key ]
4445
@type t :: tuple | list
4546

4647
defcallback delete(t, key) :: t
48+
defcallback drop(t, keys) :: t
4749
defcallback empty(t) :: t
4850
defcallback equal?(t, t) :: boolean
4951
defcallback get(t, key) :: value
@@ -58,6 +60,8 @@ defmodule Dict do
5860
defcallback put(t, key, value) :: t
5961
defcallback put_new(t, key, value) :: t
6062
defcallback size(t) :: non_neg_integer()
63+
defcallback split(t, keys) :: {t, t}
64+
defcallback take(t, keys) :: t
6165
defcallback to_list(t) :: list()
6266
defcallback update(t, key, (value -> value)) :: t | no_return
6367
defcallback update(t, key, value, (value -> value)) :: t
@@ -362,6 +366,83 @@ defmodule Dict do
362366
target(dict).update(dict, key, initial, fun)
363367
end
364368

369+
@doc """
370+
371+
Returns a tuple of two dicts, where the first dict contains only
372+
entries from `dict` with keys in `keys`, and the second dict
373+
contains only entries from `dict` with keys not in `keys`
374+
375+
Any non-member keys are ignored.
376+
377+
## Examples
378+
379+
iex> d = HashDict.new([a: 1, b: 2])
380+
...> { d1, d2 } = Dict.split(d, [:a, :c])
381+
...> { Dict.to_list(d1), Dict.to_list(d2) }
382+
{ [a: 1], [b: 2] }
383+
384+
iex> d = HashDict.new([])
385+
...> { d1, d2 } = Dict.split(d, [:a, :c])
386+
...> { Dict.to_list(d1), Dict.to_list(d2) }
387+
{ [], [] }
388+
389+
iex> d = HashDict.new([a: 1, b: 2])
390+
...> { d1, d2 } = Dict.split(d, [:a, :b, :c])
391+
...> { Dict.to_list(d1), Dict.to_list(d2) }
392+
{ [a: 1, b: 2], [] }
393+
394+
"""
395+
@spec split(t, keys) :: {t, t}
396+
def split(dict, keys) do
397+
target(dict).split(dict, keys)
398+
end
399+
400+
@doc """
401+
402+
Returns a new dict where the the given `keys` a removed from `dict`.
403+
Any non-member keys are ignored.
404+
405+
## Examples
406+
407+
iex> d = HashDict.new([a: 1, b: 2])
408+
...> d = Dict.drop(d, [:a, :c, :d])
409+
...> Dict.to_list(d)
410+
[b: 2]
411+
412+
iex> d = HashDict.new([a: 1, b: 2])
413+
...> d = Dict.drop(d, [:c, :d])
414+
...> Dict.to_list(d)
415+
[a: 1, b: 2]
416+
417+
"""
418+
@spec drop(t, keys) :: t
419+
def drop(dict, keys) do
420+
target(dict).drop(dict, keys)
421+
end
422+
423+
@doc """
424+
425+
Returns a new dict where only the keys in `keys` from `dict` are
426+
included. Any non-member keys are ignored.
427+
428+
## Examples
429+
430+
iex> d = HashDict.new([a: 1, b: 2])
431+
...> d = Dict.take(d, [:a, :c, :d])
432+
...> Dict.to_list(d)
433+
[a: 1]
434+
435+
iex> d = HashDict.new([a: 1, b: 2])
436+
...> d = Dict.take(d, [:c, :d])
437+
...> Dict.to_list(d)
438+
[]
439+
440+
"""
441+
@spec take(t, keys) :: t
442+
def take(dict, keys) do
443+
target(dict).take(dict, keys)
444+
end
445+
365446
@doc """
366447
Returns an empty dict of the same type as `dict`.
367448
"""

lib/elixir/lib/hash_dict.ex

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,53 @@ defmodule HashDict do
286286
end
287287
end
288288

289+
@doc """
290+
Splits a dict into two dicts,
291+
one containing entries with key in the keys list,
292+
and another containing entries with key not in keys.
293+
Returns a 2-tuple of the new dicts.
294+
"""
295+
def split(dict, keys) when keys == [] do
296+
{ new(), dict }
297+
end
298+
299+
def split(dict, keys) do
300+
acc = { new(), new() }
301+
dict_fold dict, acc, fn({ k, v }, { take, drop }) ->
302+
if Enum.member?(keys, k) do
303+
{ put(take, k, v), drop }
304+
else
305+
{ take, put(drop, k, v) }
306+
end
307+
end
308+
end
309+
310+
@doc """
311+
Returns a new dict with only the entries
312+
which key is in keys
313+
"""
314+
def take(_, keys) when keys == [] do
315+
new()
316+
end
317+
318+
def take(dict, keys) do
319+
{ members, _ } = split(dict, keys)
320+
members
321+
end
322+
323+
@doc """
324+
Returns a new dict with only the entries
325+
which key is not in keys
326+
"""
327+
def drop(dict, keys) when keys == [] do
328+
dict
329+
end
330+
331+
def drop(dict, keys) do
332+
{ _, non_members } = split(dict, keys)
333+
non_members
334+
end
335+
289336
## Dict-wide functions
290337

291338
defp dict_get(ordered(bucket: bucket), key) do

lib/elixir/lib/list_dict.ex

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,53 @@ defmodule ListDict do
140140
end
141141
end
142142

143+
@doc """
144+
Splits a dict into two dicts,
145+
one containing entries with key in the keys list,
146+
and another containing entries with key not in keys.
147+
Returns a 2-tuple of the new dicts.
148+
"""
149+
def split(dict, keys) when keys == [] do
150+
{ new(), dict }
151+
end
152+
153+
def split(dict, keys) do
154+
acc = { new(), new() }
155+
Enum.reduce dict, acc, fn({ k, v }, { take, drop }) ->
156+
if Enum.member?(keys, k) do
157+
{ [{k,v}|take], drop }
158+
else
159+
{ take, [{k,v}|drop] }
160+
end
161+
end
162+
end
163+
164+
@doc """
165+
Returns a new dict with only the entries
166+
which key is in keys
167+
"""
168+
def take(_, keys) when keys == [] do
169+
new()
170+
end
171+
172+
def take(dict, keys) do
173+
{ members, _ } = split(dict, keys)
174+
members
175+
end
176+
177+
@doc """
178+
Returns a new dict with only the entries
179+
which key is not in keys
180+
"""
181+
def drop(dict, keys) when keys == [] do
182+
dict
183+
end
184+
185+
def drop(dict, keys) do
186+
{ _, non_members } = split(dict, keys)
187+
non_members
188+
end
189+
143190
@doc """
144191
Updates the key in the dict according to the given function.
145192
"""

lib/elixir/test/elixir/dict_test.exs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,49 @@ defmodule DictTest.Common do
160160
assert dicts_equal actual, new_dict
161161
end
162162

163+
test :split do
164+
split_keys = []
165+
{take, drop} = Dict.split(new_dict, split_keys)
166+
assert dicts_equal take, Dict.empty(new_dict)
167+
assert dicts_equal drop, new_dict
168+
169+
split_keys = ["unknown_key"]
170+
{take, drop} = Dict.split(new_dict, split_keys)
171+
assert dicts_equal take, Dict.empty(new_dict)
172+
assert dicts_equal drop, new_dict
173+
174+
split_keys = ["first_key", "second_key", "unknown_key"]
175+
{take, drop} = Dict.split(new_dict, split_keys)
176+
first_val = Dict.get(new_dict, "first_key")
177+
second_val = Dict.get(new_dict, "second_key")
178+
take_expected = Dict.empty(new_dict)
179+
take_expected = Dict.put(take_expected, "first_key", first_val)
180+
take_expected = Dict.put(take_expected, "second_key", second_val)
181+
drop_expected = new_dict
182+
drop_expected = Dict.delete(drop_expected, "first_key")
183+
drop_expected = Dict.delete(drop_expected, "second_key")
184+
assert dicts_equal take, take_expected
185+
assert dicts_equal drop, drop_expected
186+
end
187+
188+
test :take do
189+
result = Dict.take(new_dict, ["unknown_key"])
190+
assert dicts_equal result, Dict.empty(new_dict)
191+
192+
result = Dict.take(new_dict, ["first_key"])
193+
first_val = Dict.get(new_dict, "first_key")
194+
expected = Dict.put(Dict.empty(new_dict), "first_key", first_val)
195+
assert dicts_equal result, expected
196+
end
197+
198+
test :drop do
199+
result = Dict.drop(new_dict, ["unknown_key"])
200+
assert dicts_equal result, new_dict
201+
202+
result = Dict.drop(new_dict, ["first_key"])
203+
assert dicts_equal result, Dict.delete(new_dict, "first_key")
204+
end
205+
163206
test :empty do
164207
assert empty_dict == Dict.empty new_dict
165208
end

0 commit comments

Comments
 (0)