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.
Related
I declare letters which one has many sublists, and I declare max.
What I am trying is to fill a new list stringWithMax with sublist of letters, but in a way that sublists just take the value from 1 to de index that max has in position i. For example, I want to fill stringWithMax[0] with letters[0] but I want to take the range from 1 to max[0], so that when I print stringWithMax it displays in console [['a', 'c', 'b']], then fill stringWithMax[1] with values of letters[1] but just to take values from 1 to max[1], so that when I print again stringWithMax it displays in console [['a', 'c', 'b'], ['F', 'P', 'Z', 'W']] and consecutively get the same with the last line. So in a final way when I print stringWithMax the result in console shows [['a', 'c', 'b'], ['F', 'P', 'Z', 'W'], ['R', 'X', 'N']] and that is exactly as I want.
I have programmed this
letters = [['letters1', 'a', 'c', 'b', 'BUILD'], ['letter2','F', 'P', 'Z', 'W', 'SHOW', 'BUILD'], ['leters3','R', 'X', 'N', 'BUILD', 'SHOW']]
max = [4, 5, 4]
stringWithMax = []
def reGet():
for i in range(len(letters)):
for x in range(1, max[i]):
stringWithMax.append(letters[i][x])
print(stringWithMax)
reGet()
With this code I just get in console ['a', 'c', 'b', 'F', 'P', 'Z', 'W', 'R', 'X', 'N'] which is almost similar for what I want, but it is not a list with sublist as I want.
I hope someone can help me, thanks!
The best for your code is to make use of list comprehension
So your code would be:
letters2 = [[subitem for subitem in item if len(subitem) == 1] for item in letters]
You can use a slice:
def reGet():
for letter, upper in zip(letters, max):
stringWithMax.append(letter[1:upper])
As you can see, I've also improved your code by using zip instead of indices. You can improve it further with a list comprehension:
stringWithMax = [letter[1:upper] for letter, upper in zip(letters, max)]
Alternatively, another case of list comprehensions.
Item is a nested list in list of letters, subitem is each word/letter inside the item. It checks if the length == 1 so you know it's just a letter.
letters2 = [[subitem for subitem in item if len(subitem) == 1] for item in letters]
I have the dictionary as below:
tf ={1: ['a', 'b', 'c'], 2: ['d', 'x', 'y']}
I want to form the list of values as:
wrd_list = ['a', 'b', 'c', 'd', 'x', 'y']
I tried to do this by using the following code:
wrd_list = []
for k, v in tf.items():
for i in v:
word_list.append(i)
is there any efficient way to do this?
Using extend here is faster than append when you want to concatenate lists:
word_list = []
for v in tf.values():
word_list.extend(v)
I have a dataset along the lines of:
data.append(['a', 'b', 'c'], ['a', 'x', 'y', z'], ['a', 'x', 'e', 'f'], ['a'])
I've searched SO and found ways to return duplicates across all lists using intersection_update() (so, in this example, 'a'), but I actually want to return duplicates from any lists, i.e.,:
retVal = ['a', 'x']
Since 'a' and 'x' are duplicated at least once among all lists. Is there a built-in for Python 2.7 that can do this?
Use a Counter to determine the number of each item and chain.from_iterable to pass the items from the sublists to the Counter.
from itertools import chain
from collections import Counter
data=[['a', 'b', 'c'], ['a', 'x', 'y', 'z'], ['a', 'x', 'e', 'f'], ['a']]
c = Counter(chain.from_iterable(data))
retVal = [k for k, count in c.items() if count >= 2]
print(retVal)
#['x', 'a']
This question already has answers here:
In what order does python display dictionary keys? [duplicate]
(4 answers)
Closed 8 years ago.
I want to retrieve only the fourth item in the dictionary "e" (below).
I tried using the OrderedDict() method, but it didn't work. Here are my results:
from collections import OrderedDict
e = OrderedDict()
e = {'a': 'A',
'b': 'B',
'c': 'C',
'd': 'D',
'e': 'E'
}
for k, v in e.items():
print k, v
print e.items()[3]
The last line returns: ('e', 'E')
So I turned the keys and values into lists, but here's how the lists appeared when I printed them:
['a', 'c', 'b', 'e', 'd']
['A', 'C', 'B', 'E', 'D']
For me, this explained why it happened, but not how it happened.
So, next I sorted them. That gave me the results I was looking for -- but it seemed unnecessarily complicated:
e = {'a': 'A',
'b': 'B',
'c': 'C',
'd': 'D',
'e': 'E'
}
k, v = sorted(e.keys()), sorted(e.values())
print "{}: {}".format(k[3], v[3])
Result:
d: D
OrderedDict() wasn't necessary.
Is there an easier way to do this? And can someone explain why the elements in the dictionary are ordered like this:
keys: 'a', 'c', 'b', 'e', 'd'
values: 'A', 'C', 'B', 'E', 'D'
... which defies the structure of my original dictionary?
You're not using an ordered dict.
e = OrderedDict()
e = {'a': 'A',
'b': 'B',
'c': 'C',
'd': 'D',
'e': 'E'
}
The first line creates an OrderedDict. The second line throws it away and replaces it with a regular dict, which is unordered. (Python variables don't have types.)
But you can't just do this:
e = OrderedDict({'a': 'A', ...})
...because that's still a regular dict, which is still unordered, and OrderedDict can't magically recreate your original source ordering.
Try this:
e = OrderedDict([('a', 'A'), ('b', 'B'), ...])
Now you should have a dict-like object with the ordering you want.
And can someone explain why the elements in the dictionary are ordered like this ... which defies the structure of my original dictionary?
Because dictionaries are unordered. They're just hash maps, and hash maps have no inherent ordering.
Note that you could also do this, which will preserve the pairing of keys and values (whereas your separate sortings will not):
print sorted(e.items())[3]
I've been having issues trying to create a dictionary by using the values from a list.
alphabetList = list(string.ascii_lowercase)
alphabetList.append(list(string.ascii_lowercase))
alphabetDict = {}
def makeAlphabetDict (Dict, x):
count = 0
while count <= len(alphabetList):
item1 = x[(count + (len(alphabetList) / 2))]
item2 = item1
Dict[item1] = item2
count += 1
makeAlphabetDict(alphabetDict , alphabetList)
Which returns:
TypeError: unhashable type: 'list'
I tried here and other similar questions yet I still can't see why Python thinks I'm trying to use the list, rather than just a slice from a list.
Your list contains a nested list:
alphabetList.append(list(string.ascii_lowercase))
You now have a list with ['a', 'b', ..., 'z', ['a', 'b', ..., 'z']]. It is that last element in the outer list that causes your problem.
You'd normally would use list.extend() to add additional elements:
alphabetList.extend(string.ascii_lowercase)
You are using string.ascii_lowercase twice there; perhaps you meant to use ascii_uppercase for one of those strings instead? Even so, your code always uses the same character for both key and value so it wouldn't really matter here.
If you are trying to map lowercase to uppercase or vice-versa, just use zip() and dict():
alphabetDict = dict(zip(string.ascii_lowercase, string.ascii_uppercase))
where zip() produces pairs of characters, and dict() takes those pairs as key-value pairs. The above produces a dictionary mapping lowercase ASCII characters to uppercase:
>>> import string
>>> dict(zip(string.ascii_lowercase, string.ascii_uppercase))
{'u': 'U', 'v': 'V', 'o': 'O', 'k': 'K', 'n': 'N', 'm': 'M', 't': 'T', 'l': 'L', 'h': 'H', 'e': 'E', 'p': 'P', 'i': 'I', 'b': 'B', 'x': 'X', 'q': 'Q', 'g': 'G', 'd': 'D', 'r': 'R', 'z': 'Z', 'c': 'C', 'w': 'W', 'a': 'A', 'y': 'Y', 'j': 'J', 'f': 'F', 's': 'S'}
As Martijn Pieters noted, you have problem with the list append that adds a list within your other list. You can add two list in any of the following ways for simplicity:
alphabetList = list(string.ascii_lowercase)
alphabetList += list(string.ascii_lowercase)
# Adds two lists; same as that of alphabetList.extend(alphabetList)
alphabetList = list(string.ascii_lowercase) * 2
# Just for your use case to iterate twice over the alphabets
In either case, your alphabetDict will have only 26 alphabets and not 52 as you cannot have repeated keys within the dict.