I have the list containing strings and numbers. I am trying to convert into dictionary based on the type if it is string otherwise it would become a key.
Input:
lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
Output:
[{'A': [1, 3, 4]}, {'B': [5]}, {'C': [2]}, {'D': 4}]
This is my working code so far, I it is definitely not as optimized as it could be:
main_array = []
small_array = []
se = {}
key = None
for i in range(len(lst)-1):
print(i)
if i == len(lst)-2:
if type(lst[i]) == str and type(lst[i+1]) == str:
main_array.append(lst[i])
main_array.append(lst[i+1])
elif type(lst[i]) == str and type(lst[i+1]) != str:
main_array.append({lst[i]: lst[i+1]})
elif type(lst[i]) != str and type(lst[i+1]) == str:
small_array.append(lst[i])
se.update({key: small_array})
main_array.append(se)
se = {}
small_array = []
main_array.append(lst[i+1])
elif lst[i] != type(str) and lst[i + 1] != type(str):
small_array.append(lst[i])
small_array.append(lst[i+1])
se.update({key: small_array})
main_array.append(se)
se = {}
small_array = []
else:
if type(lst[i]) == str and i != len(lst)-1:
if type(lst[i+1]) == str:
main_array.append(lst[i])
elif type(lst[i+1]) != str:
key = lst[i]
elif type(lst[i]) != str and i != len(lst)-1:
if type(lst[i+1]) == str:
small_array.append(lst[i])
se.update({key: small_array})
main_array.append(se)
se = {}
small_array = []
elif type(lst[i+1]) != str:
small_array.append(lst[i])
print(main_array)
Is there any way to optimize this code as I am intending to avoid nested loops?
It is much better to create an intermediate dictionary, then convert that dictionary to your desired result. Like this:
import collections
lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
dct = collections.defaultdict(lambda: [])
key = None
for el in lst:
if type(el) == str:
key = el
continue
dct[key].append(el)
result = [{key: dct[key]} for key in dct]
print(result)
Try using a temporary list variable to build up the list until you encounter a str value in the list:
L = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
res = {}
curr: list
for e in L:
if type(e) == str:
res[e] = curr = []
else:
curr.append(e)
print(res)
Print:
{'A': [1, 3, 4], 'B': [5], 'C': [2], 'D': [4]}
In case you really want a list of single-value dict elements:
L = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
res = []
add = list.append
curr: list
for e in L:
if type(e) == str:
curr = []
add(res, {e: curr})
else:
add(curr, e)
print(res)
Output:
[{'A': [1, 3, 4]}, {'B': [5]}, {'C': [2]}, {'D': [4]}]
I was actually curious, so I timed it. It seems close to 40% faster than using an approach with defaultdict.
from timeit import timeit
from collections import defaultdict
from itertools import groupby
L = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
# 1.385
print('dict: ', timeit("""
res = []
add = list.append
curr: list
for e in L:
if type(e) == str:
curr = []
add(res, {e: curr})
else:
add(curr, e)
""", globals=globals()))
# 1.619
print('no temp list: ', timeit("""
res = []
key = None
add = list.append
for el in L:
if type(el) == str:
key = el
add(res, {el: []})
continue
add(res[-1][key], el)
""", globals=globals()))
# 2.150
print('defaultdict: ', timeit("""
dct = defaultdict(list)
key = None
for el in L:
if type(el) == str:
key = el
else:
dct[key].append(el)
result = [{key: dct[key]} for key in dct]
""", globals=globals()))
# 2.578
print('groupby: ', timeit("""
groups = groupby(L, type)
result = []
try:
while groups:
result.append({ list(next(groups)[1])[0] : list(next(groups)[1]) })
except StopIteration:
pass
""", globals=globals()))
One way to do this is to simply add a new dictionary every time a string comes up, then append to that list until the next string. Like this:
lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
res = []
key = None
for el in lst:
if type(el) == str:
key = el
res.append({el: []})
continue
res[-1][key].append(el)
print(res)
I believe that this would be much faster than my other answer. All you need is one result array and one variable to keep track of the key.
When I tested it, this method is slightly slower than rv.kvetch's answer, however there is no need to store a temporary list. This method is cleaner (no need to add the last list) and uses less memory in theory.
Just for fun, you could use itertools.groupby to group your list by the type of value, then iterate the result to get the key and value pairs for each dict to be pushed to the result list:
import itertools
lst = ['A', 1, 3, 4, 'B', 5, 'C', 2, 'D', 4]
groups = itertools.groupby(lst, type)
result = []
try:
while groups:
result.append({ list(next(groups)[1])[0] : list(next(groups)[1]) })
except StopIteration:
pass
Output:
[
{'A': [1, 3, 4]},
{'B': [5]},
{'C': [2]},
{'D': [4]}
]
Related
I want to map the dictionary d = {'R': ['a', 'g'], 'Y': ['c', 't']} to the string s = '----YY----RR----' to get the following output:
----cc----aa----
----cc----ag----
----cc----ga----
----cc----gg----
----ct----aa----
----ct----ag----
----ct----ga----
----ct----gg----
----tc----aa----
----tc----ag----
----tc----ga----
----tc----gg----
----tt----aa----
----tt----ag----
----tt----ga----
----tt----gg----
My (very) inefficient code is as below:
seqs = set()
for k,v in d.items():
for i in v:
i_seq = seq.replace(k,i,1)
for n in v:
n_seq = i_seq.replace(k,n,1)
for k2,v2 in d.items():
for i2 in v2:
i2_seq = n_seq.replace(k2,i2,1)
for n2 in v2:
n2_seq = i2_seq.replace(k2,n2,1)
if not 'Y' in n2_seq and not 'R' in n2_seq:
seqs.add(n2_seq)
What is a smarter way to do that?
A general approach without itertools:
def replaceCombinations(s, d):
string_set = set([s])
for i in range(len(s)):
if s[i] in d.keys():
new_set = set()
for c in string_set:
new_set.update(set(c.replace(s[i], new_char, 1) for new_char in d[s[i]]))
string_set = new_set
return string_set
string = "----YY----RR----"
d = {'R': ['a', 'g'], 'Y': ['c', 't']}
for c in sorted(replaceCombinations(string, d)):
print(c)
Use itertools.product:
from itertools import product
for p in product(product(d['R'],repeat=2),product(d['Y'],repeat=2)):
print(f'----{p[0][0]}{p[0][1]}-----{p[1][0]}{p[1][1]}-----')
----aa-----cc-----
----aa-----ct-----
----aa-----tc-----
----aa-----tt-----
----ag-----cc-----
----ag-----ct-----
----ag-----tc-----
----ag-----tt-----
----ga-----cc-----
----ga-----ct-----
----ga-----tc-----
----ga-----tt-----
----gg-----cc-----
----gg-----ct-----
----gg-----tc-----
----gg-----tt-----
I have a dict with two strings "Candy-one" and "Candy-two" as the key and Capacity as the key pair. I wish to replace the strings "Candy-one" and "Candy-two" with the Brand-Name which has the same candies in the same spots "Candy-one" and "Candy-two"
This is what I tried
p = [['Brand-Name', 'Swap', ' Candy-one ', ' Candy-two ', 'Capacity'],
['Willywonker', 'Yes', 'bubblegum', 'mints', '7'],
['Mars-CO', 'Yes', 'chocolate', 'bubblegum', '1'],
['Nestle', 'Yes', 'bears', 'bubblegum', '2'],
['Uncle Jims', 'Yes', 'chocolate', 'bears', '5']]
f = {('bubblegum', 'mints'): 4,
('chocolate', 'bubblegum'): 1,
('bears', 'bubblegum'): 2,
('chocolate', 'bears'): 2}
def Brand(f,p):
i = 0
while i < len(p)-1:
i = i + 1
for key in f:
print(key[0])
print(key[1])
if key[0] == p[i][2] and key[1] == p[i][3]:
f[p[i][0]] = key.pop(key)
return f
print(brand(f,p))
this is my output
{('bubblegum', 'mints'): 4,
('chocolate', 'bubblegum'): 1,
('bears', 'bubblegum'): 2,
('chocolate', 'bears'): 2}
Its as if nothing is happening
This is the output I want
{'Willywonker': 4,
'Mars-CO': 1,
'Nestle': 2,
'Uncle Jims': 2}
Using a double loop is not efficient (quadratic complexity).
Here is how I would solve it:
def Brand(f,p):
# create a mapping dictionary
d = {tuple(x[2:4]): x[0] for x in p[1:]}
# output a new dictionary with replaced keys
# or old key if a new one is not found
return {d.get(k, k): v for k,v in f.items()}
# or if you want to drop in case of no match
# return {d[k]: v for k,v in f.items() if k in d}
Brand(f,p)
output:
{'Willywonker': 4,
'Mars-CO': 1,
'Nestle': 2,
'Uncle Jims': 2}
def Brand(f,p):
res={}
i = 0
while i < len(p)-1:
for key in f:
if key[0] == p[i+1][2] and key[1] == p[i+1][3]:
res[p[i+1][0]] = f[key]
i += 1
return res
new_dict = Brand(f,p)
print(new_dict)
{'Willywonker': 4, 'Mars-CO': 1, 'Nestle': 2, 'Uncle Jims': 2}
Or your corrected attempt:
def Brand(f,p):
res=f.copy()
i = 0
while i < len(p)-1:
for key in f:
if key[0] == p[i+1][2] and key[1] == p[i+1][3]:
res[p[i+1][0]] = res.pop(key)
i += 1
return res
updated_dict = Brand(f,p)
print(updated_dict)
{'Willywonker': 4, 'Mars-CO': 1, 'Nestle': 2, 'Uncle Jims': 2}
key, being a tuple, does not have a pop method, suggesting you never actually try to change f (maybe because you never call Brand).
Plus the stuff #quamrana indicated.
How would I turn this into dictionary comprehension?
dict_ = defaultdict(int)
for sequence in set_sequence:
for ele in sequence:
dict_[ele] += 1
You can use nested comprehension, with Counter:
from collections import Counter
set_sequence = [['a','b','b'], ['b', 'c', 'c']]
dict_ = Counter(ele for sequence in set_sequence for ele in sequence)
print(dict_) # Counter({'b': 3, 'c': 2, 'a': 1})
implement in one line, using lambda expressions(map/filter/reduce),
function that gets list of different types and returns a dictionary which has these keys:
{‘c’: , ‘i’: , ‘f’: , ‘o’: }
'c' will present list of characters
'i' list of the integers
'f' list of the floats
'o' list of any other types
for exaple for the list:
myList = ['a', 2, 3, 's', 2.23]
the output will be:
{'c': ['a', 's'], 'i': [2, 3], 'f': [2.23], 'o': []}
So far is I made a method of it that works but I'll need somehow change it one line of code:
def q1a(myList):
myDict = dict.fromkeys(('c', 'i', 'f', 'o'))
myDict['c'] = list(filter(lambda x: type(x) is str, myList))
myDict['i'] = list(filter(lambda x: type(x) is int, myList))
myDict['f'] = list(filter(lambda x: type(x) is float, myList))
myDict['o'] = list(filter(lambda x: type(x) is not float and type(x) is not int and type(x) is not str, myList))
return myDict
You can use the below ugly one-liner
out = dict(zip(['c','i','f','o'], map(list, (filter(lambda x:isinstance(x,str), lst), filter(lambda x:isinstance(x,int), lst), filter(lambda x:isinstance(x,float), lst), filter(lambda x: not isinstance(x,(str,float,int)), lst)))))
You can also use functools.reduce with a helper function (not exactly one liner but doesn't need multiple filters so saves time):
def add(d, x):
d[x[0]].append(x[1])
return d
from functools import reduce
out = reduce(add,
map(lambda x: (('c',x) if isinstance(x,str)
else (('i',x) if isinstance(x,int)
else (('f',x) if isinstance(x,float)
else ('o',x)))), lst),
{'c':[],'i':[],'f':[],'o':[]})
Output:
{'c': ['a', 's'], 'i': [2, 3], 'f': [2.23], 'o': []}
This gets around the need to assign to the keys one at a time:
def q1a(myList):
return {
'c': list(filter(lambda x: type(x) is str, myList)),
'i': list(filter(lambda x: type(x) is int, myList)),
'f': list(filter(lambda x: type(x) is float, myList)),
'o': list(filter(lambda x: type(x) is not float and type(x) is not int and type(x) is not str, myList))
}
You could use list comprehensions and create a dictionary literal:
{
'c': [e for e in myList if type(e) is str],
'i': [e for e in myList if type(e) is int],
'f': [e for e in myList if type(e) is float],
'o': [e for e in myList if type(e) not in {float, int, str}]
}
The assignment seems to be asking you to take a functional approach to this and create a single operation.
One option would be a single reduce that manipulates a dict. This isn't as natural in python as in other languages because most dict operations return None. But you can still do it in a functional way if you try (it's a one-liner broken up for a (minor) improvement in readability):
from functools import reduce
l = ['a', 2, 3, 's', 2.23]
res = reduce(
lambda d, t: dict(d, **{t[1]: d[t[1]] + [t[0]]}),
map(lambda el: (el, {str: 'c', int: 'i', float: 'f'}.get(type(el), 'o')) , l),
dict.fromkeys('cifo', [])
)
print(res)
# {'c': ['a', 's'], 'i': [2, 3], 'f': [2.23], 'o': []}
This works by creating a list of tuples with the map():
list(map(lambda el: (el, {str: 'c', int: 'i', float: 'f'}.get(type(el), 'o')) , l),)
# [('a', 'c'), (2, 'i'), (3, 'i'), ('s', 'c'), (2.23, 'f')]
and then updating a dict inside the reduce that is initialized with empty lists created with dict.fromkeys('cifo', []).
If I'm writing this in Python, my first choice would be to skip the functional approach and use a boring for loop and a default dict.
from collections import defaultdict
def q1a(myList):
d = defaultdict(list)
for v in myList:
if isinstance(v, str):
type_code = 'c'
elif isinstance(v, int):
type_code = 'i'
elif isinstnace(v, float):
type_code = 'f'
else:
type_code = 'o'
d[tc].append(v)
return d
However, this suggests a solution using itertools.groupby. The big if statement makes for a simple function like
def type_code(v):
if isinstance(v, str):
type_code = 'c'
elif isinstance(v, int):
type_code = 'i'
elif isinstnace(v, float):
type_code = 'f'
else:
type_code = 'o'
return type_code
which could be written as a single conditional expression suitable for use in a lambda expression:
lambda v: ('c' if isinstance(v, str) else
'i' if isinstance(v, int) else
'f' if isinstance(v, float) else
'o')
In the following, I'll abbreviate the above lambda expression as tc for readability.
With itertools.groupby, you can partition a (sorted) list using a function that specifies which partition each element belongs to. The partitions so produced are suitable for use in a dict comprehension.
from itertools import groupby
def q1a(myList):
return {k: list(vs) for k, vs in groupby(sorted(myList, key=tc), tc)}
One slight glitch:
>>> q1a(['a', 2, 3, 's', 2.23])
{'c': ['a', 's'], 'f': [2.23], 'i': [2, 3]}
The result doesn't include a key that isn't produced by a value in the list. You'll have to merge the result with pre-initialized dict containing all the keys.
def q1a(myList):
return ({'c': [], 'i': [], 'f': [], 'o': []}
| {k: list(vs) for k, vs in groupby(sorted(myList, key=tc), tc)})
I don't use dict.fromKeys("cifo", []) because then each key defaults to a reference to the same empty list, rather than a distinct empty list for each.
You could use a double indirection to convert types into letters and setdefault to update the resulting dictionary:
def q1a(myList):
types = {str:'c', int:'i', float:'f'}
myDict = dict()
for x in myList:
myDict.setdefault(types.get(type(x),'o'),[]).append(x)
return myDict
myList = ['a', 2, 3, 's', 2.23]
print(q1a(myList))
{'c': ['a', 's'], 'i': [2, 3], 'f': [2.23]}
Using a defaultdict could simplify it a bit:
from collections import defaultdict
def q1a(myList):
types = defaultdict(lambda:'o',{str:'c', int:'i', float:'f'})
myDict = defaultdict(list)
for x in myList:
myDict[types[type(x)]].append(x)
return dict(myDict)
I have this one long list and want to convert it to a nested list and a dictionary.
L= ["a","abc","de","efg","", "b","ijk","lm","op","qr","", "c","123","45","6789"]
output:
nested list:
[["a","abc","de","efg"], ["b","ijk","lm","op","qr"], ["c","123","45","6789"]]
dictionary:
{"a":["abc","de","efg"],
"b":["ijk","lm","op","qr"], "c":["123","45","6789] }
Can anyone tell me how to do that in python?
And I can't import anything
I assume the groups are separated by the empty strings. For this you can use itertools.groupby:
from itertools import groupby
data = ["a","abc","de","efg","", "b","ijk","lm","op","qr","", "c","123","45","6789"]
nl = [list(g) for k, g in groupby(data, ''.__ne__) if k]
d = {next(g): list(g) for k, g in groupby(data, ''.__ne__) if k}
print(nl)
print(d)
Results:
[['a', 'abc', 'de', 'efg'], ['b', 'ijk', 'lm', 'op', 'qr'], ['c', '123', '45', '6789']]
{'a': ['abc', 'de', 'efg'], 'b': ['ijk', 'lm', 'op', 'qr'], 'c': ['123', '45', '6789']}
In the groupby I'm using ''.__ne__ which is the function for "not equal" of an empty string. This way it's only capturing groups of non-empty strings.
EDIT
I just read that you cannot import. Here's a solution just using a loop:
nl = [[]]
for s in data:
if s:
nl[-1].append(s)
else:
nl.append([])
And for the dict:
itr = iter(data)
key = next(itr)
d = {key: []}
while True:
try: val = next(itr)
except StopIteration: break
if val:
d[key].append(val)
else:
key = next(itr)
d[key] = []
Here's how to convert L to a nested list:
L= ["a","abc","de","efg","","b","ijk","lm","op","qr","","c","123","45","6789"]
nested_list_L = []
temp = []
for item in L:
if item != "":
temp.append(item)
else:
nested_list_L.append(temp)
temp = []
nested_list_L.append(temp)
And here's how to convert L to a dictionary:
L= ["a","abc","de","efg","","b","ijk","lm","op","qr","","c","123","45","6789"]
dict_L = {}
temp = []
key = ""
for item in L:
if len(item) == 1:
key = item
elif len(item) > 1:
temp.append(item)
else:
dict_L[key] = temp
temp = []
key = ""
dict_L[key] = temp
From my understanding, you are trying to:
Split a list by empty string, then
Convert the resulting nested list into a dictionary, using first element of each sub-list as the key and the rest as value.
You can certainly accomplish the task without any imports. To split a list, just iterate over it and build the nested list along the way:
def split(data, on):
nested = []
curr = []
for x in data:
if x == on:
nested.append(curr)
curr = []
else:
curr.append(x)
if curr != [] or data[-1:] == [on]:
nested.append(curr)
return nested
Then, again, iterate over this nested list to build your desired dictionary:
def build_dict(key_valss):
d = {}
for key_vals in key_valss:
if key_vals != []:
key = key_vals[0]
vals = key_vals[1:]
d[key] = vals
return d
Compose the two functions to get what you want:
>>> build_dict( split(data = ["a","abc","de","efg","", "b","ijk","lm","op","qr","", "c","123","45","6789"] , on = '') )
{'a': ['abc', 'de', 'efg'], 'b': ['ijk', 'lm', 'op', 'qr'], 'c': ['123', '45', '6789']}