Skip to content

Commit 84488a8

Browse files
committed
Added bidirectional dict function & appropriate tests
1 parent 61c0743 commit 84488a8

File tree

3 files changed

+166
-56
lines changed

3 files changed

+166
-56
lines changed

domdf_python_tools/utils.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def list2string(the_list, sep=","):
115115
Convert a list to a comma separated string
116116
117117
:param the_list: The list to convert to a string
118-
:type the_list: list
118+
:type the_list: list, tuple
119119
:param sep: Separator to use for the string, default ","
120120
:type sep: str
121121
@@ -131,7 +131,7 @@ def list2str(the_list, sep=","):
131131
Convert a list to a comma separated string
132132
133133
:param the_list: The list to convert to a string
134-
:type the_list: list
134+
:type the_list: list, tuple
135135
:param sep: Separator to use for the string, default ","
136136
:type sep: str
137137
@@ -172,3 +172,52 @@ def permutations(data, n=2):
172172
if i[::-1] not in perms:
173173
perms.append(i)
174174
return perms
175+
176+
177+
class bdict(dict):
178+
"""
179+
Returns a new dictionary initialized from an optional positional argument
180+
and a possibly empty set of keyword arguments.
181+
182+
Each key:value pair is entered into the dictionary in both directions,
183+
so you can perform lookups with either the key or the value.
184+
185+
If no positional argument is given, an empty dictionary is created.
186+
If a positional argument is given and it is a mapping object, a dictionary
187+
is created with the same key-value pairs as the mapping object.
188+
Otherwise, the positional argument must be an iterable object.
189+
Each item in the iterable must itself be an iterable with exactly two
190+
objects. The first object of each item becomes a key in the new
191+
dictionary, and the second object the corresponding value.
192+
193+
If keyword arguments are given, the keyword arguments and their values are
194+
added to the dictionary created from the positional argument.
195+
196+
If an attempt is made to add a key or value that already exists in the
197+
dictionary a ValueError will be raised
198+
199+
Based on https://stackoverflow.com/a/1063393 by https://stackoverflow.com/users/9493/brian
200+
"""
201+
202+
def __init__(self, *args, **kwargs):
203+
super().__init__(self)
204+
if len(args) == 1:
205+
for key, value in dict(*args).items():
206+
self.__setitem__(key, value)
207+
if len(args) == 0:
208+
for key, value in kwargs.items():
209+
self.__setitem__(key, value)
210+
211+
def __setitem__(self, key, val):
212+
if key in self or val in self:
213+
if key in self and self[key] != val:
214+
raise ValueError(f"The key '{key}' is already present in the dictionary")
215+
if val in self and self[val] != key:
216+
raise ValueError(f"The key '{val}' is already present in the dictionary")
217+
218+
dict.__setitem__(self, key, val)
219+
dict.__setitem__(self, val, key)
220+
221+
def __delitem__(self, key):
222+
dict.__delitem__(self, self[key])
223+
dict.__delitem__(self, key)

tests/test_init.py

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,60 +13,6 @@
1313

1414
def test_pyversion():
1515
assert isinstance(pyversion, int)
16-
17-
def test_str2tuple():
18-
assert isinstance(str2tuple("1,2,3"), tuple) # tests without spaces
19-
assert isinstance(str2tuple("1, 2, 3"), tuple) # tests with spaces
20-
assert isinstance(str2tuple("1; 2; 3", sep=";"), tuple) # tests with semicolon
21-
if str2tuple("1,2,3") == (1, 2, 3):
22-
assert 1
23-
else:
24-
assert 0
25-
26-
def test_tuple2str():
27-
assert isinstance(tuple2str(("1","2","3",)), str)
28-
if tuple2str((1, 2, 3)) == "1,2,3":
29-
assert 1
30-
else:
31-
assert 0
32-
33-
assert isinstance(tuple2str((1,2,3,), sep=";"), str) # tests with semicolon
34-
if tuple2str((1, 2, 3), sep=";") == "1;2;3":
35-
assert 1
36-
else:
37-
assert 0
38-
39-
def test_chunks():
40-
assert isinstance(chunks(list(range(100)), 5), types.GeneratorType)
41-
if list(chunks(list(range(100)), 5))[0] == [0,1,2,3,4]:
42-
assert 1
43-
else:
44-
assert 0
45-
46-
47-
def test_list2str():
48-
assert isinstance(list2str([1, 2, 3,]), str)
49-
if list2str([1, 2, 3]) == "1,2,3":
50-
assert 1
51-
else:
52-
assert 0
53-
54-
assert isinstance(list2str([1, 2, 3], sep=";"), str) # tests with semicolon
55-
if list2str((1, 2, 3), sep=";") == "1;2;3":
56-
assert 1
57-
else:
58-
assert 0
5916

60-
assert isinstance(list2string([1, 2, 3, ]), str)
61-
if list2string([1, 2, 3]) == "1,2,3":
62-
assert 1
63-
else:
64-
assert 0
6517

66-
assert isinstance(list2string([1, 2, 3], sep=";"), str) # tests with semicolon
67-
if list2string((1, 2, 3), sep=";") == "1;2;3":
68-
assert 1
69-
else:
70-
assert 0
71-
7218

tests/test_utils.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
test_utils
4+
~~~~~~~~~~~~~~~
5+
6+
Test functions in utils.py
7+
8+
"""
9+
10+
import types
11+
12+
import pytest
13+
14+
from domdf_python_tools.utils import str2tuple, tuple2str, chunks, list2str, list2string, bdict
15+
16+
17+
def test_str2tuple():
18+
assert isinstance(str2tuple("1,2,3"), tuple) # tests without spaces
19+
assert isinstance(str2tuple("1, 2, 3"), tuple) # tests with spaces
20+
assert isinstance(str2tuple("1; 2; 3", sep=";"), tuple) # tests with semicolon
21+
if str2tuple("1,2,3") == (1, 2, 3):
22+
assert 1
23+
else:
24+
assert 0
25+
26+
27+
def test_tuple2str():
28+
assert isinstance(tuple2str(("1","2","3",)), str)
29+
if tuple2str((1, 2, 3)) == "1,2,3":
30+
assert 1
31+
else:
32+
assert 0
33+
34+
assert isinstance(tuple2str((1,2,3,), sep=";"), str) # tests with semicolon
35+
if tuple2str((1, 2, 3), sep=";") == "1;2;3":
36+
assert 1
37+
else:
38+
assert 0
39+
40+
41+
def test_chunks():
42+
assert isinstance(chunks(list(range(100)), 5), types.GeneratorType)
43+
if list(chunks(list(range(100)), 5))[0] == [0,1,2,3,4]:
44+
assert 1
45+
else:
46+
assert 0
47+
48+
49+
def test_list2str():
50+
assert isinstance(list2str([1, 2, 3,]), str)
51+
if list2str([1, 2, 3]) == "1,2,3":
52+
assert 1
53+
else:
54+
assert 0
55+
56+
assert isinstance(list2str([1, 2, 3], sep=";"), str) # tests with semicolon
57+
if list2str((1, 2, 3), sep=";") == "1;2;3":
58+
assert 1
59+
else:
60+
assert 0
61+
62+
assert isinstance(list2string([1, 2, 3, ]), str)
63+
if list2string([1, 2, 3]) == "1,2,3":
64+
assert 1
65+
else:
66+
assert 0
67+
68+
assert isinstance(list2string([1, 2, 3], sep=";"), str) # tests with semicolon
69+
if list2string((1, 2, 3), sep=";") == "1;2;3":
70+
assert 1
71+
else:
72+
assert 0
73+
74+
75+
def test_bdict():
76+
new_dict = bdict(Alice=27, Bob=30, Dom=23)
77+
78+
assert new_dict[23] == "Dom"
79+
assert new_dict["Alice"] == 27
80+
81+
82+
def test_bdict_from_dict():
83+
84+
original_dict = {"Alice": 27, "Bob": 30, "Dom": 23}
85+
86+
new_dict = bdict(original_dict)
87+
88+
assert new_dict[23] == "Dom"
89+
assert new_dict["Alice"] == 27
90+
91+
92+
def test_bdict_from_zip():
93+
94+
new_dict = bdict(zip(["Alice", "Bob", "Dom"], [27, 30, 23]))
95+
96+
assert new_dict[23] == "Dom"
97+
assert new_dict["Alice"] == 27
98+
99+
100+
def test_bdict_from_list():
101+
new_dict = bdict([("Alice", 27), ("Bob", 30), ("Dom", 23)])
102+
103+
assert new_dict[23] == "Dom"
104+
assert new_dict["Alice"] == 27
105+
106+
107+
def test_bdict_errors():
108+
new_dict = bdict([("Key1", "Value1"), ("Key2", "Value2"), ("Key3", "Value3")])
109+
110+
with pytest.raises(ValueError):
111+
new_dict["Key1"] = 1234
112+
with pytest.raises(ValueError):
113+
new_dict["Value1"] = 1234
114+
new_dict["Key1"] = "Value1"
115+
new_dict["Value1"] = "Key1"

0 commit comments

Comments
 (0)