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.
I have the following:
mylist = ['A','A','A','B','B','C']
colors = ['r','g','b','w','y']
I want all the same elements in mylist to get the same color, from the beginning of the color list, so the result would be like this:
result = ['r','r','r','g','g','b']
The colors, w and y would be ignored. Can't seem to get the mapping working correctly.
I have tried:
result = [[y for y in colors if set(mylist) == x] for x in mylist]
Edit: to make it more clear, ['r','g','b','w','y'] doesn't always need to be mapped to ABCDEF... mylist could have been ['cat','cat','cat','dog','dog','bird']
You may first create the mapping as a dict, then use it to get the result
mylist = ['A', 'A', 'A', 'B', 'B', 'C']
colors = ['r', 'g', 'b', 'w', 'y']
mapping = dict(zip(
sorted(set(mylist)),
colors
))
print(mapping) # {'A': 'r', 'B': 'g', 'C': 'b'}
result = [mapping[l] for l in mylist]
print(result) # ['r', 'r', 'r', 'g', 'g', 'b']
If you don't care about the order of colours:
color_map = dict(zip(set(mylist), colors))
result = [color_map[item] for item in mylist]
If you care about the order of colours:
from collections import OrderedDict
color_map = OrderedDict(zip(OrderedDict((item, True) for item in mylist), colors))
result = [color_map[item] for item in mylist]
You could use Counter to count how many times a value appears in your list.
Then use that mapping to fill your result list.
from collections import Counter
mylist = ['A','A','A','B','B','C']
colors = ['r','g','b','w','y']
result = []
for idx, (_,v) in enumerate( Counter(mylist).items() ):
result.extend( colors[idx] * v )
print(result)
Output:
['r', 'r', 'r', 'g', 'g', 'b']
Note: Requires Python > 3.7, otherwise the order of the dict is not guaranteed - this also applies to the other answers here that rely on dict.
For me the easiest way would be:
mylist = ['A', 'A', 'A', 'B', 'B', 'C']
colors = ['r', 'g', 'b', 'w', 'y']
result = []
for i, item in enumerate(sorted(set(mylist))): # sets doesn't maintain the order, so its sorted alphabetically
result.extend(colors[i] * mylist.count(item))
print(result)
I would suggest to use the dictionary instead to keep the mapping:
result = []
color_map = {}
idx = 0
for elt in mylist:
if elt not in color_map.keys():
color_map[elt] = colors[idx]
idx += 1
result.append(color_map[elt])
This also avoids iterating over the colors list separately.
I have a problem that I tried to solve myself but I'm not sure if there is better algorithm to solve this.
Suppose I have a dict with the entire alphabet.
I can invoke the function passing a string of characters indicating the keys related to the lists that I want to use to generate the output. I can add the same key multiple times.
I would like to generate all possible combinations between them and solve it without recursion.
I tried to solve the problem myself using the brute force approach below but I would like to know if there is a better approach to that.
My attempt is as follows:
words_dict = {
2: ['a', 'b', 'c'],
3: ['d', 'e', 'f'],
4: ['g', 'h', 'i'],
5: ['j', 'k', 'l'],
6: ['m', 'n', 'o'],
7: ['p', 'q', 'r'],
8: ['t', 'u', 'v', 'w'],
9: ['x', 'y', 'z'],
}
def generate_combinations(numbers):
words_to_expand = words_dict[int(numbers[0])]
for i in range(1, len(numbers)):
letters_list = words_dict[int(numbers[i])]
aux_words_expanded = []
for incomplete_word in words_to_expand:
for letter_to_combine in letters_list:
expanded_word = incomplete_word + letter_to_combine
aux_words_expanded.append(expanded_word)
words_to_expand = aux_words_expanded
print(words_to_expand)
return words_to_expand
pass
generate_combinations('234')
#MmBaguette's answer points you in the right direction.
It suggests the use of itertools
The complete function would be something like:
def generate_combinations(numbers):
words = []
indexes = (int(val) for val in numbers)
letter_groups = [words_dict[idx] for idx in indexes]
for entry in itertools.product(*letter_groups):
words.append("".join(entry))
print(words)
print(len(words))
or even shorter:
def generate_combinations(numbers):
indexes = (int(val) for val in numbers)
letter_groups = [words_dict[idx] for idx in indexes]
words = list("".join(entry)
for entry in itertools.product(*letter_groups))
print(words)
print(len(words))
Here's a more Pythonic way to do that
import itertools
my_list = ["a", "b", "c", "d"]
itertools.combinations(my_list) #returns generator of combinations
itertools.permutations(my_list, 3) #same but with length of results specified
I have a list of lists and I want to remove duplicates within each nested list.
Input: [['c', 'p', 'p'], ['a', 'a', 'a'], ['t', 't', 'p']]
Output: [['c', 'p'], ['a'], ['t','p']]
The key here is that I cannot use the set() function or fromkeys().
Here is the code I have,
ans = []
for i in letters:
[ans.append([x]) for x in i if x not in ans]
which returns
[['c'], ['p'], ['p'], ['a'], ['a'], ['a'], ['t'], ['t'], ['p']]
which isn't what I want.
You tripped yourself up with the nested lists. A second loop is necessary to filter the elements. Although it's quite inefficient, you can write your attempt as
ans = []
for i in letters:
k = []
for j in i:
if j not in k:
k.append(j)
ans.append(k)
You can likely shorten this code, but not reduce its complexity.
To do that, you can use something sorted and itertools.groupby. this is still less efficient than a hash table, but better than linear lookup (although it likely doesn't matter much for short arrays):
ans = [[k for k, _ in groupby(sorted(i))] for i in letters]
You can iterate over the inner list and check if that character is already present or not
inputList = [['c', 'p', 'p'], ['a', 'a', 'a'], ['t', 't', 'p']]
result = []
for l in inputList:
# create a empty list to store intermediate result
tmp = []
# iterate over sublist
for ch in l:
if ch not in tmp: tmp.append(ch)
result.append(tmp)
print(result)
Since you can't use set() or fromkeys(), I would suggest a normal loop iteration, each time checking if value is already present:
lst = [['c', 'p', 'p'], ['a', 'a', 'a'], ['t', 't', 'p']]
new_lst = []
for x in lst:
res = []
for y in x:
if y not in res:
res.append(y)
new_lst.append(res)
print(new_lst)
Ideally, new_lst here should be a set.
list=[['c', 'p', 'p'], ['a', 'a', 'a'], ['t', 't', 'p']]
ans=[]
for sublist in list:
temp=[]
for ch in sublist:
if ch not in temp:
temp.append(ch)
ans.append(temp)
print(ans)
#I think it should work, very simple, it could be more complex
Just ignore every instance of a letter until it is the last one.
for every sublist of input:[[...] for sub in input]
store the letter if it isn't in the rest of the sublist:[ltr for i, ltr in enumerate(sub) if ltr not in sub[i+1:]]
Put it together and you have:
input = [['c', 'p', 'p'], ['a', 'a', 'a'], ['t', 't', 'p']]
output = [[ltr for i, ltr in enumerate(sub) if ltr not in sub[i+1:]] for sub in input]
print(output) #[['c', 'p'], ['a'], ['t', 'p']]
I am very new to python and trying to find the solution to this for a class.
I need the function missing_letters to take a list, check the letters using histogram and then loop over the letters in alphabet to determine which are missing from the input parameter. Finally I need to print the letters that are missing, in a string.
alphabet = "abcdefghijklmnopqrstuvwxyz"
test = ["one","two","three"]
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] += 1
return d
def missing_letter(s):
for i in s:
checked = (histogram(i))
As you can see I haven't gotten very far, at the moment missing_letters returns
{'o': 1, 'n': 1, 'e': 1}
{'t': 1, 'w': 1, 'o': 1}
{'t': 1, 'h': 1, 'r': 1, 'e': 2}
I now need to loop over alphabet to check which characters are missing and print. Any help and direction will be much appreciated. Many thanks!
You can use set functions in python, which is very fast and efficient:
alphabet = set('abcdefghijklmnopqrstuvwxyz')
s1 = 'one'
s2 = 'two'
s3 = 'three'
list_of_missing_letters = set(alphabet) - set(s1) - set(s2) - set(s3)
print(list_of_missing_letters)
Or like this:
from functools import reduce
alphabet = set('abcdefghijklmnopqrstuvwxyz')
list_of_strings = ['one', 'two', 'three']
list_of_missing_letters = set(alphabet) - \
reduce(lambda x, y: set(x).union(set(y)), list_of_strings)
print(list_of_missing_letters)
Or using your own histogram function:
alphabet = "abcdefghijklmnopqrstuvwxyz"
test = ["one", "two", "three"]
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] += 1
return d
def missing_letter(t):
test_string = ''.join(t)
result = []
for l in alphabet:
if l not in histogram(test_string).keys():
result.append(l)
return result
print(missing_letter(test))
Output:
['a', 'b', 'c', 'd', 'f', 'g', 'i', 'j', 'k', 'l', 'm', 'p', 'q', 's', 'u', 'v', 'x', 'y', 'z']
from string import ascii_lowercase
words = ["one","two","three"]
letters = [l.lower() for w in words for l in w]
# all letters not in alphabet
letter_str = "".join(x for x in ascii_lowercase if x not in letters)
Output:
'abcdfgijklmpqsuvxyz'
It is not the easiest question to understand, but from what I can gather you require all the letters of the alphabet not in the input to be returned in console.
So a loop as opposed to functions which have been already shown would be:
def output():
output = ""
for i in list(alphabet):
for key in checked.keys():
if i != key:
if i not in list(output):
output += i
print(output)
Sidenote: Please either make checked a global variable or put it outside of function so this function can use it