-
Bi-directional Mapping: Automatically mirrors
{k:v}
pairs as{v:k}
pairs.- Key-index lookup will return
v
fork
, and returnk
forv
.
- Key-index lookup will return
-
Standard Dictionary Operations: Supports operations like
keys
,items
,get
,pop
, ... -
Hashable and Unique: Requires both keys and values to be hashable
and unique to maintain the mirrored lookup. -
Order Preservation: Preserves the order of keys and values based on the order keys are added.
MirrorDict
is a dictionary-like object that maintains a bi-directional/mirrored mapping between keys and values. A MirrorDict
object ensures every key-value pair ({k:v}
) added is mirrored as a value-key pair ({v:k}
). This means you can look up either by key or by value, and the corresponding value or key will be returned. Since both the k
and v
are hashed, MirrorDict
requires that both be hashable and unique.
The order of the keys are preserved and the order of values are based on the keys. Methods, such as keys()
and values()
method return the items that were set initially as a k
and v
respectively (i.e. md[k]=v
stores k
in the keys and v
in the values). Changing either a key or value removes the old mirrored relationship to create the new one. If the key is changed, then the old one is removed and the new added to the end of keys()
.
For example,
md[k] = v
, makes k
the key and v
the value, and, md[k] == v
and md[v] == k
; and
md[k] = v2
, updates the key k
to the value v2
;
md[v2] = k
, removes k
in the keys and adds v2
to the end of keys and adds k
to the end of values.
from MirrorDict import MirrorDict
md = MirrorDict() # Empty MirrorDict ➣ MirrorDict({})
md['a'] = 1 # Add k='a' associated with v=1 ➣ MirrorDict({'a': 1})
md['b'] = 2 # Add k='b' associated with v=2 ➣ MirrorDict({'a': 1, 'b': 2})
md['c'] = 3 # Add k='c' associated with v=3 ➣ MirrorDict({'a': 1, 'b': 2, 'c': 3})
md['b'] # returns 2
md[2] # returns 'b'
md.keys() # ➣ dict_keys( ['a', 'b', 'c'])
md.values() # ➣ dict_values([ 1, 2, 3 ])
md['b'] = 9 # Change k='b' to have v=9 ➣ MirrorDict({'a': 1, 'b': 9, 'c': 3})
md.keys() # ➣ dict_keys( ['a', 'b', 'c'])
md.values() # ➣ dict_values([ 1, 9 , 3])
md['b'] # returns 9
md[9] # returns 'b'; note md[2] no longer exists and will raise an exception
md['c'] # returns 3
md[3] # returns 'c'
md[9] = 'b' # Replace k='b' with k=9 and v='b' ➣ MirrorDict({'a': 1, 'c': 3, 9: 'b'})
md.keys() # ➣ dict_keys( ['a', 'c', 9 ])
md.values() # ➣ dict_values([ 1 , 3, 'b'])
md['b'] # returns 9
md[9] # returns 'b'
To install the module
pip install --upgrade git+https://github.com/ScottBoyce-Python/MirrorDict.git
or you can clone the respository with
git clone https://github.com/ScottBoyce-Python/MirrorDict.git
then rename the file MirrorDict/__init__.py
to MirrorDict/MirrorDict.py
and move MirrorDict.py
to wherever you want to use it.
The MirrorDict class supports the same initialization that dict
supports. In addition to this, it can also support more than one argument to define multiple groups of k:v
pairs (dict
only allows at most one argument). The order keys are stored in the order that k:v
pairs are added, with the last duplicate k
or v
, superseding the previous ones.
from MirrorDict import MirrorDict
# initialize an empty MirrorDict
md0 = MirrorDict() # = MirrorDict({})
# initialize 3 pairs as list of tuples.
# keys = ['a','b','c']
# values = [ 1, 2, 3 ]
md1 = MirrorDict([('a', 1), ('b', 2), ('c', 3)]) # = MirrorDict({'a': 1, 'b': 2, 'c': 3})
# initialize two lists containing pairs as tuples.
md2 = MirrorDict( [('a', 1), ('b', 2), ('c', 3)], [('d', 4), ('e', 5)] )
# = MirrorDict({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
# initialize with zip to get list of tuples.
md3 = MirrorDict(zip(['d','e','f'], [4, 5, 6])) # = MirrorDict({'d': 4, 'e': 5, 'f': 6})
# initialize with a dict and/or another MirrorDict
d = {'a': 1, 'b': 2, 'c': 3}
md4 = MirrorDict(d) # = MirrorDict({'a': 1, 'b': 2, 'c': 3})
md5 = MirrorDict(md3) # = MirrorDict({'d': 4, 'e': 5, 'f': 6})
md6 = MirrorDict(d, md3) # = MirrorDict({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6})
md7 = d | md5 # = MirrorDict({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6})
All the methods and attributes that are part of dict
are also part of MirrorDict
. Internally MirrorDict uses two dict attributes to hold the key-value ({k:v}
) and value-key ({v:k}
) mirrored relationship. {k:v}
is stored in the _key
attribute and {v:k}
is stored in the _val
attribute .
Below are examples showcasing how to create and interact with a MirrorDict
.
from MirrorDict import MirrorDict
md = MirrorDict() # Empty MirrorDict
md["a"] = 1 # = MirrorDict({'a': 1})
md["b"] = 2 # = MirrorDict({'a': 1, 'b': 2})
md["c"] = 3 # = MirrorDict({'a': 1, 'b': 2, 'c': 3})
assert md["b"] == 2
assert md[2] == "b"
assert list(md.keys() ) == ["a", "b", "c"]
assert list(md.values()) == [ 1, 2, 3 ]
md.update([("d", 4), ("e", 5)]) # = MirrorDict({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5})
md["f"] = 6 # = MirrorDict({'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6})
assert list(md.keys() ) == ["a", "b", "c", "d", "e", "f"]
assert list(md.values()) == [ 1, 2, 3, 4, 5, 6 ]
del md[5]
pop3 = md.pop(3)
assert pop3 == "c"
assert list(md.keys() ) == ["a", "b", "d", "f"]
assert list(md.values()) == [ 1, 2, 4, 6 ]
md[2] = "b" # set command updates key=2 and value="b"
assert list(md.keys() ) == ["a", "d", "f", 2 ]
assert list(md.values()) == [ 1, 4, 6, "b"]
for k in md:
print(k) # prints: 'a', 'd', 'f', then 2
for k, v in md.items():
print(f"{k}:{v}") # prints: 'a':1, 'd':4, 'f':6, then 2:'b'
This project uses pytest
and pytest-xdist
for testing. Tests are located in the tests
folder. To run tests, install the required packages and execute the following command:
pip install pytest pytest-xdist
pytest # run all tests, note options are set in the pyproject.toml file
Note, that the pyproject.toml contains the flags used for pytest.
This project is licensed under the MIT License. See the LICENSE file for details.
Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.
Scott E. Boyce