Related
I trying to match keys and values in a dictionary together and put them into a new dictionary.
My initial dictionary looks like this:
d = {1: [5,6], 3: [4], 8: [2,3]}
I know that I can access keys and values using d.keys() and d.values().
My goal is to find all items which relate. I think it is best explained if I illustrate my desired output.
I wish to create a new dict that gives me:
finaldict = {1: [5,6], 2: [3,4,8], 3: [2,4,8], 4:[2,3,8] 5:[1,6], 6:[1,5], 8:[2,3,4]}
That is, I want to get keys of all numbers, and give values to what number they are related to.
My attempt:
d = {1: [5,6], 3:[4],8:[2,3]}
print(d)
keys = list(d.keys())
vals = list(d.values())
for i in range(0,len(d.keys())):
current_vals = list(vals[i])
length = len(current_vals)
for v in current_vals:
if v in d.keys(): #if the dictionary exists, then append
add = [keys[i],current_vals]
d[v].append(add)
else:
add2 = [keys[i],current_vals]
empty = []
for a in add2:
if type(a) == int:
empty.append(a)
if type(a) == list:
for b in a:
empty.append(b)
d[v] = empty
keys = list(d.keys())
vals = list(d.values())
for i in range(0,len(keys)):
if keys[i] in vals[i]:
vals[i].remove(keys[i])
finaldict = {}
for j in range(0,len(keys)):
finaldict[keys[j]] = vals[j]
print("Final dict:\n",finaldict)
My attempt gives me the output
Final dict:
{1: [5, 6], 3: [4, [8, [2, 3]]], 8: [2, 3], 5: [1, 6], 6: [1, 5], 4: [3], 2: [8, 3]}
As you can see, finaldict[3] has the values [4, [8, [2, 3,]]].
The values themselves are wrong (3 should not be there), and also I want this to be a single list, and not on the format it is now. There are also other issues, such as finaldict[2] having the values [8, 3] when it should, in fact, have the values [3, 4, 8] and finaldict[4] only having [3], when it should have [2, 3, 8].
Not the most beautiful code I've ever written, but here it goes.
I create sets with all the numbers that are related to each other, then create a dictionary of each of those numbers as key, with the other ones as values.
d = {1: [5,6], 3: [4], 8: [2,3]}
pools = []
for key,values in d.items():
for pool in pools:
if key in pool or any(val in pool for val in values):
pool.add(key)
[pool.add(val) for val in values]
break
else:
pools.append(set((key, *values)))
#pools = [{1, 5, 6}, {8, 2, 3, 4}]
finaldict = {k:set.difference(v, set((k,))) for pool in pools for k, v in zip(pool, [pool]*len(pool))}
print(finaldict)
You can use recursion:
d = {1: [5,6], 3: [4], 8: [2,3]}
def groups(v, seen = [], c = []):
if (k:=[i for a, b in d.items() for i in ({a, *b} if v in {a, *b} else []) if i not in {*seen, v}]):
for i in k:
yield from groups(i, seen=seen+[v], c = c+([v] if seen else []))
else:
yield c+[v]
r = {i:[*{j for k in groups(i) for j in k}] for a, b in d.items() for i in [a, *b]}
Output:
{1: [5, 6], 5: [1, 6], 6: [1, 5], 3: [8, 2, 4], 4: [8, 2, 3], 8: [2, 3, 4], 2: [8, 3, 4]}
You can build up the resulting dictionary by merging related groups and assigning the same merged group to every key that is part of it:
d = {1: [5,6], 3: [4], 8: [2,3]}
related = dict()
for key,group in d.items(): # merge/assign groups to keys
merged = set(group).union({key},*(related.get(k,[]) for k in group))
related.update((k,merged) for k in merged)
related = {k:list(g-{k}) for k,g in related.items()} # exclude key from group
print(related)
{1: [5, 6], 5: [1, 6], 6: [1, 5], 3: [8, 2, 4], 4: [8, 2, 3],
2: [8, 3, 4], 8: [2, 3, 4]}
I am trying to create a function that returns a dictionary that describes a pascal triangle.
For example,
pascal(3)
would give me
{1: [1], 2: [1,1], 3: [1,2,1]}
I currently know how to create a function that returns the list of elements
in a certain row for n equal to or greater than 2
def pascal(n):
if n == 0:
return {}
elif n == 1:
return {1:[1]}
else:
row = [1] + [list(pascal(n-1))[i] + list(pascal(n-1))[i+1] for i in range(n-2)] + [1]
return row
With this function,
pascal(3)
gives me
[1,2,1]
Is it possible to change my function in such a way that
pascal(3)
returns the desired result of
{1: [1], 2: [1,1], 3: [1,2,1]}
Any help would be appreciated.
You can use zip to pair the returning list from the recursive call with the same list but at one index apart, padded with 0:
def pascal(n):
if n == 1:
return {1: [1]}
p = pascal(n - 1)
p[n] = list(map(sum, zip([0] + p[n - 1], p[n - 1] + [0])))
return p
so that:
for n in range(1, 6):
print(pascal(n))
outputs:
{1: [1]}
{1: [1], 2: [1, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1], 5: [1, 4, 6, 4, 1]}
If you are open to an iterative solution, I cooked up up the following.
from itertools import chain
def pascal(n):
pad = (0,)
result = {1: [1]}
for i in range(2, n + 1):
previous = list(chain(pad, result[i - 1], pad))
result[i] = [sum(pair) for pair in zip(previous, previous[1:])]
return result
Demo:
>>> for n in range(1, 6):
...: print(pascal(n))
...:
...:
{1: [1]}
{1: [1], 2: [1, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1], 5: [1, 4, 6, 4, 1]}
With a bit more lines, but also better memory efficiency:
from itertools import chain, tee
def pascal(n):
pad = (0,)
result = {1: [1]}
for i in range(2, n + 1):
previous = chain(pad, result[i - 1], pad)
c1, c2 = tee(previous)
next(c2)
result[i] = [sum(pair) for pair in zip(c1, c2)]
return result
Lastly, having a dict with consecutive integer keys is not very useful, you could just use a list into which you index starting at 0. Final solution:
def pascal(n):
pad = (0,)
result = [[1]]
for i in range(1, n):
previous = chain(pad, result[i - 1], pad)
c1, c2 = tee(previous)
next(c2)
result.append([sum(pair) for pair in zip(c1, c2)])
return result
Demo:
>>> for n in range(1, 6):
...: print(pascal(n))
...:
[[1]]
[[1], [1, 1]]
[[1], [1, 1], [1, 2, 1]]
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]
edit: improved efficiency by not creating two tuples per iteration, instantiating pad once is enough.
I would be careful using recursion like that - it's very inefficient. You are calling the function twice in a loop in the function body. It's important to think about how many times the function will be called to evaluate certain values of n.
It's obvious that when n = 1, the function is called once.
When n = 2, the function is called once, and then the function calls itself twice for a total of 3 calls.
For n = 3 the function is called once, and then the functions calls itself twice, and then these two calls each call the function four times... So that's 11 calls.
So the number of calls is numCalls = 1 + 2 + 2*4 + 2*4*6 + ... + 2*4*6*...*2n)
This sequence grows extremely fast... When n is 20 that's 1308293051285742128434781 Calls
Recursion isn't always evil, you just have to be careful, this solution calls itself n times:
def genPascalDict(nMax):
if nMax < 2:
return {1: [1]}
else:
pascalDict = genPascalDict(nMax - 1)
lastRow = pascalDict[nMax - 1]
pascalDict[nMax] = [1] + [lastRow[n + 1] + lastRow[nMax - n - 2] for n in range(nMax - 2)] + [1]
return pascalDict
You can make it fast while building your dict as a side effect:
_cache = {}
def pascal(n):
try:
result = _cache[n]
except KeyError:
if n == 0:
result = []
elif n == 1:
result = [1]
else:
previous = pascal(n - 1)
result = [1] + [previous[i] + previous[i + 1] for i in range(n - 2)] + [1]
_cache[n] = result
return result
pascal(500)
print(_cache)
You don't need to compute pascal(n) more than once: it's not like it changes. So remember what your final answer was by storing it in a caching dictionary, said dictionary being what you actually wanted in the first place.
This takes about .08s to build the dictionary on my laptop.
You can use a closure with recursion:
def pascal(n:int) -> dict:
def _pascal(_s, _e, _last, _base={1:[1], 2:[1, 1]}):
return _last if not _e-_s else _pascal(_s+1, _e, {**_last, **{_s:_base.get(_s, [1, *[_last[_s-1][i]+_last[_s-1][i+1] for i in range(len(_last)-1)], 1])}})
return _pascal(1, n+1, {})
print(pascal(3))
Output:
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
Is it possible to do something like this:
l = [1, 2, 2, 3, 4, 4, 1, 1]
d = {num: [num] if num not in d else d[num].append(num) for num in l}
Inherently, I wouldn't think so, without declaring d = {} first; even then, it doesn't append:
Output: {1: [1], 2: [2], 3: [3], 4: [4]}
# desired: {1: [1, 1, 1], 2: [2, 2], 3: [3], 4: [4, 4]}
Could use a defaultdict, curious if the comprehension is even possible?
No, it's not possible. If you think about, it will make sense why. When Python evaluates an assignment statement, it first evaluates the right-hand side of the assignment - the expression. Since it hasn't evaluated the entire assignment yet, the variable on the left-hand hasn't been added to the current namespace yet. Thus, while the expression is being evaluated, the variable will be undefined.
As suggested, you can use collections.defaultdict to accomplish what you want:
>>> from collections import defaultdict
>>>
>>> l = [1, 2, 2, 3, 4, 4, 1, 1]
>>> d = defaultdict(list)
>>> for num in l:
d[num].append(num)
>>> d
defaultdict(<class 'list'>, {1: [1, 1, 1], 2: [2, 2], 3: [3], 4: [4, 4]})
>>>
d doesn't exist in your dictionary comprehension.
Why not:
l = [1, 2, 2, 3, 4, 4, 1, 1]
d = {num: [num] * l.count(num) for num in set(l)}
EDIT: I think, it is better to use a loop there
d = {}
for item in l:
d.setdefault(item, []).append(item)
No, you cannot refer to your list comprehension before the comprehension is assigned to a variable.
But you can use collections.Counter to limit those costly list.append calls.
from collections import Counter
l = [1, 2, 2, 3, 4, 4, 1, 1]
c = Counter(l)
d = {k: [k]*v for k, v in c.items()}
# {1: [1, 1, 1], 2: [2, 2], 3: [3], 4: [4, 4]}
Related: Create List of Single Item Repeated n Times in Python
Let's say we have a dictionary like this:
{0: [2, 8], 1: [8, 4], 3: [5]}
Then we encounter a key value pair 2 , 8 . Now, as the value 2 and 8 has already appeared for key 0, I need to merge the first two keys and create a new dictionary like the following:
{0: [2, 8, 4], 3: [5]}
I understand that it's possible to do a lot of looping and deleting. I'm really looking for a more pythonish way.
Thanks in advance.
your coworkers will hate you later but here
>>> d = {0: [2, 8], 1: [8, 4], 3: [5]}
>>> x = ((a,b) for a,b in itertools.combinations(d,2) if a in d and b in d and set(d[a]).intersection(d[b]))
>>> for a,b in x:d[min(a,b)].extend([i for i in d[max(a,b)] if i not in d[min(a,b)]]) or d.pop(max(a,b))
[8, 4]
>>> d
{0: [2, 8, 4], 3: [5]}
d = {0: [2, 8],
1: [8, 4],
3: [5]}
revmap = {}
for k,vals in d.items():
for v in vals:
revmap[k] = v
k,v = 2,8
d[revmap[k]].extend([i for i in d[revmap[v]] if i not in d[revmap[k]]])
d.pop(revmap[v])
I have dictionary like:
item_count_per_section = {1: 3, 2: 5, 3: 2, 4: 2}
And total count of items retrieved from this dictionary:
total_items = range(sum(item_count_per_section.values()))
Now I want to transform total_items by values of dictionary following way:
items_no_per_section = {1: [0,1,2], 2: [3,4,5,6,7], 3:[8,9], 4:[10,11] }
I.e. slice total_items sequencially to sublists which startrs from previous "iteration" index and finished with value from initial dictionary.
You don't need to find total_items at all. You can straightaway use itertools.count, itertools.islice and dictionary comprehension, like this
from itertools import count, islice
item_count_per_section, counter = {1: 3, 2: 5, 3: 2, 4: 2}, count()
print {k:list(islice(counter, v)) for k, v in item_count_per_section.items()}
Output
{1: [0, 1, 2], 2: [3, 4, 5, 6, 7], 3: [8, 9], 4: [10, 11]}
dict comprehension of itertools.isliced iter of total_items:
from itertools import islice
item_count_per_section = {1: 3, 2: 5, 3: 2, 4: 2}
total_items = range(sum(item_count_per_section.values()))
i = iter(total_items)
{key: list(islice(i, value)) for key, value in item_count_per_section.items()}
Outputs:
{1: [0, 1, 2], 2: [3, 4, 5, 6, 7], 3: [8, 9], 4: [10, 11]}
Note: this works for any total_items, not just range(sum(values)), assuming that was just your sample to keep the question generic. If you do just want the numbers, go with #thefourtheye's answer