Related
I was learning about recursion in python and solved some common problems like factorial, iterating through nested lists etc.
While solving such problems, I came up with this challenge where you have to use recusion to traverse through a heterogeneous input(an input which contains single str elements, nested lists, dictionary etc).
So the challenge involves traversing through all the values in this input and replace a specified value with another one.
The input used here looks like this:
input = ['a', 'b', {'1':{'o':'a'}}, 'c', 'd', {'T': 'b', 'K': [1, 'a', 3, {'S':{'Z':'t'},'R':{'3':'a'}}, {'key':[66,'a',88]}, ['a', 'c']]}, ['a'], 3, 'r', 'a']
The input is a list which itself contains lists and dicts, and some of these lists and dicts are nested and also have one another in themselves.
And the output that I'm expecting to get is:
# this is the output that should be got at the end, after running the code
['#', 'b', {'1':{'o':'#'}}, 'c', 'd', {'T': 'b', 'K': [1, '#', 3, {'S':{'Z':'t'},'R':{'3':'#'}}, {'key':[66,'#',88]}, ['#', 'c']]}, ['#'], 3, 'r', '#']
# exactly like input but with all 'a' replaced with '#'
# of course we can use treat change the input to string and then use replace() of string module and get the output
# but then this wont be a challenge would it?
correct = ['a', 'b', 'a', 'c', 'd', 'b', 1, 'a', 3, 't', 'a', 66, 'a', 88, 'a', 'c', 'a', 3, 'r', 'a']
The code I wrote is:
remove = 'a'
replace = 'X'
output = []
def recall(input):
for item in input:
if isinstance(item, list):
recall(item)
elif isinstance(item, dict):
for entry in item.values():
recall(entry)
else:
if isinstance(input, dict) and item in input.keys():
if input[item]==remove:
input[item]=replace
output.append(input[item])
else:
output.append(input[item])
else:
if item==remove:
item=replace
output.append(item)
else:
output.append(item)
print(item)
recall(input)
print(output)
This produces the output:
['X', 'b', 'X', 'c', 'd', 'b', 1, 'X', 3, 't', 'X', 66, 'X', 88, 'X', 'c', 'X', 3, 'r', 'X']
# a single list with all the 'a' replaced but there are no dicts with their key value pairs in it
I havn't been able to find a way to achieve the desired output.
Am I doing something wrong? Or is there any way the desired output can be achieved with recursion?
You are appending all the values to a single global output variable regardless of whether they are from a nested dict or list. Instead, you need to get the function to return a value so that the function at the next level up can deal with it appropriately - appending a dict or list in the right position in the nested hierarchy. For example, you could do it this way:
def replace_values(item, replacement):
if isinstance(item, list):
return [replace_values(v, replacement) for v in item]
elif isinstance(item, dict):
return {k: replace_values(v, replacement) for k, v in item.items()}
# Alternatively, in case you need the dict keys to be replaced too:
# return {replacement.get(k, k): replace_values(v, replacement) for k, v in item.items()}
else:
return replacement.get(item, item)
input_list = ['a', 'b', {'1':{'o':'a'}}, 'c', 'd', {'T': 'b', 'K': [1, 'a', 3, {'S':{'Z':'t'},'R':{'3':'a'}}, {'key':[66,'a',88]}, ['a', 'c']]}, ['a'], 3, 'r', 'a']
print(replace_values(input_list, {"a": "#"}))
Note that the type (dict, list, or other) and ordering of the elements are the same in the return value as in the input item. This preserves the nested structure of the input.
In Python, I need a dictionary object which looks like:
{'a': 10, 'b': 20, 'c': 10, 'd': 10, 'e': 20}
I've been able to get this successfully by combining the dict.update() and dict.fromkeys() functions like so:
myDict = {}
myDict.update(dict.fromkeys(['a', 'c', 'd'], 10))
myDict.update(dict.fromkeys(['b', 'e'], 20))
However, because the code is being written for novice users who may need to make add keys/values on occasion, I'd prefer a simple bare-bones (Perl-like) syntax such as:
myDict = {}
myDict['a', 'c', 'd'] = 10
myDict['b', 'e'] = 20
This, however, gives me:
myDict = {('a', 'c', 'd'): 10, ('b', 'e'): 20}
Is there a way I can simplify my first example (using dict.update() and dict.fromkeys()) further, and get the dict object I'm looking for?
Or, alternatively, if I have a dict with tuples as in my second example, is there an easy way for me to do a lookup such as myDict['c'] or myDict.get('c') and get the value 10?
I would say what you have is very simple, you could slightly improve it to be:
my_dict = dict.fromkeys(['a', 'c', 'd'], 10)
my_dict.update(dict.fromkeys(['b', 'e'], 20))
If your keys are tuple you could do:
>>> my_dict = {('a', 'c', 'd'): 10, ('b', 'e'): 20}
>>> next(v for k, v in my_dict.items() if 'c' in k) # use .iteritems() python-2.x
10
This is, of course, will return first encountered value, key for which contains given element.
Similar to #SilentGhost but a more declarative syntax (with Python 3.5+) I prefer:
myDict = {
**dict.fromkeys(['a', 'c', 'd'], 10),
**dict.fromkeys(['b', 'e'], 20)
}
Your first example can be simplified using a loop:
myDict = {}
for key in ['a', 'c', 'd']:
myDict[key] = 10
for key in ['b', 'e']:
myDict[key] = 20
No specialized syntax or trickery, and I can't think of anything which would be easier to understand.
Regarding your second question, there is no simple and efficient way to do the lookup as in your second example. I can only think of iterating over the keys (tuples) and checking whether the key is in any of them, which isn't what you're looking for. Stick to using a straightforward dict with the keys you want.
In general, if you are aiming for code that can be understood by novices, stick to the basics such as if conditions and for/while loops.
Dict union (3.9+)
Now with Python 3.9, you can do this:
myDict = dict.fromkeys(['a', 'c', 'd'], 10) | dict.fromkeys(['b', 'e'], 20)
Although personally, I'm not sure I would use this, since it's hard to read.
Dict comprehension
myDict = {
k: v
for keys, v in [(['a', 'c', 'd'], 10), (['b', 'e'], 20)]
for k in keys
}
This is also hard to read, but I'm mentioning it for the sake of completeness.
reference
You could inherit from dict to implement a sort of "update from keys":
class LazyDict(dict):
def keylist(self, keys, value):
for key in keys:
self[key] = value
>>> d = LazyDict()
>>> d.keylist(('a', 'b', 'c'), 10)
>>> d
{'a': 10, 'c': 10, 'b': 10}
but I prefer loop solution
Method:
def multi_key_dict_get(d, k):
for keys, v in d.items():
if k in keys:
return v
return None
Usage:
my_dict = {
('a', 'b', 'c'): 10,
('p', 'q', 'r'): 50
}
value = multi_key_dict_get(my_dict, 'a')
While #SilentGhost's answer works pretty fine with single length of keys, it won't work correctly for those looking for a "multiple character length of keys" solution, and so I've thought of the below solution [...]
let's say that we have the above dict and keys we are looking for:
my_dict = {
'key1':'KEY_1',
('tk1', 'tk2','tk3'):'TK_1_2_3',
'key2':'KEY_2'
}
my_keys = ['key2','ke', 'tk2','k','key','exception'] # key's I'm looking for
the example & SOLUTION below:
for key in my_keys:
print(next((v for k, v in my_dict.items() if (key == k) or (isinstance(k,tuple) and key in k)),None))
CORRECTLY outputs:
KEY_2
None
TK_1_2_3
None
None
None
While with (a slightly modified solution [so it won't throw StopIteration exception] of) #SilentGhost's answer
for key in my_keys:
print(next((v for k, v in my_dict.items() if key in k),None)) # added ((...),None)
the results are WRONG because [...]2 if not a StopIteration exception:
KEY_2
KEY_1
TK_1_2_3
KEY_1
KEY_1
None
While personally I wouldn't really recommend it from a perspective of speed efficiency (at least not for all use cases), it is indeed a way of solving this issue and so I decided to post it.
class MyDict(dict):
def __setitem__(self,keys,value):
if type(keys)!=tuple:keys=(keys,)
for key in keys:super().__setitem__(key,value)
myDict = MyDict()
myDict['a', 'c', 'd'] = 10
myDict['b', 'e'] = 20
print(myDict) # {'a': 10, 'c': 10, 'd': 10, 'b': 20, 'e': 20}
I am a student in a python course where we created a list of tuples (containing 2 elements) that we're trying to manipulate in various ways. In addition, we are to convert those tuple elements into a dictionary and re-create the manipulations using the dictionary and avoiding for loops. The task I'm stuck on is that given a specific id (which could be a key OR value in the dictionary) the function returns all the other keys/values that are found in that dictionary.
It doesn't seem efficient to use a dictionary for this, but that's the section we are on in the course and is specifically asked by the assignment. Also no for loops (if that is possible?). Recall that the id can be either a key or a value in the dictionary.
example_dictionary = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}
def get_interactions(example_dictionary, id):
output = ''
for j,k in example_dictionary.items():
if j == id:
output = output + k + ' '
if k == id:
output = output + j + ' '
return output
This code works just fine, however it 1) has a for loop (no good) and 2) isn't very pythonic (kind of an eyesore)! How could I use the dictionary more efficiently and condense down my lines? I am in Python 3, Thank you!
Expected result
Having one dictionary and value named wanted, you want to create another dict being copy of
original one with removed all items not having key or value equal to wanted value.
It can be expressed in form of pytest test case with couple of scenarios.
import pytest
scenarios = [
[
# dct
{'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
# wanted
"A",
# expected (result)
{'A': 'C', 'D': 'A'},
],
[
# dct
{'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
# wanted
"E",
# expected (result)
{'R': 'E'},
],
[
# dct
{'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
# wanted
"D",
# expected (result)
{'D': 'A', 'C': 'D'},
],
[
# dct
{'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
# wanted
"nothere",
# expected (result)
{},
],
[
# dct
{'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'},
# wanted
"A",
# expected (result)
{'A': 'C', 'D': 'A'},
],
]
# replace with real implementation
def get_key_or_val_itms(dct, wanted):
# something comes here
return result
#pytest.mark.parametrize("scenario", scenarios)
def test_it(scenario):
dct, wanted, expected = scenario
assert get_key_or_val_itms(dct, wanted) == expected
Do not bother with anything apart from scenarios. It lists couple of test scenarios with input
dictionary, value for wanted and expected result.
Building stones for the solution
dict.items() - dict to list of tuples
>>> dct = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}
>>> dct.items()
[('A', 'C'), ('R', 'E'), ('D', 'A'), ('L', 'R'), ('C', 'D')]
testing membership of a value in a tuple/list
>>> 'A' in ('A', 'C')
True
>>> 'A' in ('R', 'E')
False
Lambda function testing, if wanted is present in a tuple
lambda allows "in place" function definition. It is often used in places,
where some functions expects reference to a function.
First, create named function tuple_wanted
>>> wanted = "A"
>>> def tuple_wanted(tpl):
... return wanted in tpl
and test it (note, that wanted has now value "A"):
>>> tuple_wanted(('A', 'C'))
True
>>> tuple_wanted(('R', 'E'))
False
Now create the function. To play with it, we store the result of lambda in fun:
>>> fun = lambda tpl: wanted in tpl
It can be used in the same manner a tuple_wanted before:
>>> fun(('A', 'C'))
True
>>> fun(('R', 'E'))
False
Later on we will use the result of lambda directly (see filter) without
storing it into any variable.
filter removing all list items not passing some test
filter gets test function and iterable (e.g. list of items) to test by it.
Result of calling filter is list of items from the iterable, which passed the test.
In our case, we want to pass only the tuples, containing wanted value (e.g. "A")
>>> filter(tuple_wanted, dct.items())
[('A', 'C'), ('D', 'A')]
>>> filter(fun, dct.items())
[('A', 'C'), ('D', 'A')]
>>> filter(lambda tpl: wanted in tpl, dct.items())
[('A', 'C'), ('D', 'A')]
Convert list of tuples with 2 items into dictionary
>>> tpllst = [('A', 'C'), ('D', 'A')]
>>> dict(tpllst)
{'A': 'C', 'D': 'A'}
Function doing the work
Long version
This version is here to explain what is going on step by step:
def get_key_or_val_itms(dct, wanted):
# dict as [(key, val), (key2, val2), ...]
tpldct = dct.items()
# find tuples, where either key or val equals `wanted` value
# first make function, which detects, the tuple we search for
def tuple_wanted(tpl):
return wanted in tpl
# now use it to filter only what we search for
restpldct = filter(tuple_wanted, tpldct)
# finally, turn the result into dict
return dict(restpldct)
Short version
def get_key_or_val_itms(dct, wanted):
return dict(filter(lambda tpl: wanted in tpl, dct.items()))
Conclusions
It works (with either long or short version of the function):
>>> dct = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}
>>> wanted = "A"
>>> get_key_or_val_itms(dct, wanted)
{'A': 'C', 'D': 'A'}
If you put the function into file with test suite, calling $ py.test -sv the_file.py shall output:
$ py.test -sv the_file.py
py.test================================ test session starts =========================
=======
platform linux2 -- Python 2.7.9, pytest-2.8.7, py-1.4.31, pluggy-0.3.1 -- /home/javl/
.virtualenvs/stack/bin/python2
cachedir: .cache
rootdir: /home/javl/sandbox/stack/dict, inifile:
collected 5 items
countdict.py::test_it[scenario0] PASSED
countdict.py::test_it[scenario1] PASSED
countdict.py::test_it[scenario2] PASSED
countdict.py::test_it[scenario3] PASSED
countdict.py::test_it[scenario4] PASSED
============================= 5 passed in 0.01 seconds ==============================
As can be seen, all the scenarios are passing.
Explanation how py.test works is out of scope of this answer, to learn more about it, see http://pytest.org/latest/
I wouldn't know how to avoid using a for loop, other than making your own for loop, similar to the following:
i = 0
def func(tup, id) {
if i < len(dictionary_items):
output = False
if tup[0] == id or tup[1] == id:
output = id + ' '
i += 1
return output
}
dictionary_items = dictionary.items()
func(dictionary_items[0], id)
func(dictionary_items[1], id)
func(dictionary_items[2], id)
And so on. However, that would be ugly and extremely non-pythonic.
As for making your code more pythonic, you can change the lines output = output + k + ' ' to output += k + ' ' or output = k + ' ' (You're concatenating strings k and ' ' to an empty string, output, which changes nothing about the strings k and ' ').
Furthermore, you could check if j == id or k == id rather than two seperate if statements, then saying output = id + ' ',since if j or k are equal to id, it doesn't matter if you return whichever of j and k is equal to the id or if you return the id itself.
You have to check all the keys and values, so there is always going to be some type of loop. Python has many ways to iterate (ie. loop) through items without explicit use of for.
One good way to iterate through items without for is with the filter, map, and reduce built-in functions along with the lambda syntax for creating small, anonymous functions.
from itertools import chain
# Get values for matching keys and vice versa
values = map(lambda x: x[1] if x[0] == id else None, dct.items())
keys = map(lambda x: x[0] if x[1] == id else None, dct.items())
# Then you filter out the None values
# itertools.chain allows us to conveniently do this in one line
matches = filter(lambda x: x is not None, chain(keys, values))
If you can't use itertools.chain, you'll just need a few extra steps
keys = filter(lambda x: x is not None, keys)
values = filter(lambda x: x is not None, values)
matches = keys + values
If you need a space separated output of values:
output = ' '.join(matches)
You could use list comprehensions, although one could argue that it is a kind of for loop:
example_dictionary = {'A': 'C', 'R': 'E', 'D': 'A', 'L': 'R', 'C': 'D'}
def get_interactions(dic, id):
output =[v for k, v in dic.items() if k == id] + [k for k,v in dic.items() if v == id]
return output
I have a question about the un-ordered nature of sets.
This code:
#Set1 is 'a' to 'e' in alpha order
set1 = {}
set1 = {'a', 'b', 'c', 'd', 'e'}
print('\nSet1 :', set1)
#Set2 is 'f' to 'a' (missing 'e') in reverse-alpha order
set2 = {}
set2 = {'f', 'd', 'c', 'b', 'a'}
print ('Set2 :', set2)
print ('Common to both sets:', set1.intersection(set2))
...gives random ordering of the elements in set1, set2 and in the result of set.intersection:
Set : {'a', 'c', 'b', 'e', 'd'}
Set: {'a', 'c', 'b', 'd', 'f'}
Common to both sets: {'a', 'c', 'b', 'd'}
Although not a problem per se, my question is this: is there a set algorithm for this? Or could I (feasibly) use this property to generate random lists of items present in two lists (i.e. is it truly random?). BTW, I have no idea why I might want to do this - thinking out loud.
The order in which sets are printed is based on, among other things, a hash of their contents - it is not random. If you need a set to be ordered, you can always use the built-in sorted() function:
>>>> sorted(set1.intersection(set2))
{'a', 'b', 'c', 'd'}
given:
template = {'a': 'b', 'c': 'd'}
add = ['e', 'f']
k = 'z'
I want to use list comprehension to generate
[{'a': 'b', 'c': 'd', 'z': 'e'},
{'a': 'b', 'c': 'd', 'z': 'f'}]
I know I can do this:
out = []
for v in add:
t = template.copy()
t[k] = v
out.append(t)
but it is a little verbose and has no advantage over what I'm trying to replace.
This slightly more general question on merging dictionaries is somewhat related but more or less says don't.
[dict(template,z=value) for value in add]
or (to use k):
[dict(template,**{k:value}) for value in add]