Subtract dictionary value from key - python

I have a dictionary like this:
a = {(8, 9): [[0, 0], [4, 5]], (3, 4): [[1, 2], [6, 7]]}
I would like to subtract the sum of the corresponding elements of the nested lists in the values from each element of each key, and replace the key with the result.
For example:
new_key[0] = 8 - 0+4 = 4, new_key[1] = 9 - (0+5) = 4
Hence the new key becomes (4, 4) and it replaces (8, 9)
I am not able to understand how to access a list of lists which is the value to the key!
Any ideas as to how to do this?

See Access item in a list of lists for indexing list of list.
For your specific case, this should work
b = {(k[0]-v[0][0]-v[1][0], k[1]-v[0][1]-v[1][1]):v for k, v in a.items()}

for key in list(a.keys()):
new_key = []
new_key.append(key[0] - (a[key][0][0] + a[key][1][0]))
new_key.append(key[1] - (a[key][0][1] + a[key][1][1]))
a[new_key] = a.pop(key) # pop() returns the value

Iterate through the dictionary to get the keys and values and create a new one b. Then just point a to the new dictionary b
a = {(8, 9): [[0, 0], [4, 5]], (3, 4): [[1, 2], [6, 7]]}
b = {}
for key, val in a.items():
new_key = (key[0]-(val[0][0]+val[1][0]), key[1]-(val[0][1]+val[1][1]))
b[new_key] = val
a = b
del b

Try This:
b = {}
for key, value in a.items():
new_key = key[0]-(value[0][0]+value[1][0])
new_key_1 = key[1]-(value[0][1]+value[1][1])
u_key = (new_key, new_key_1)
b[u_key]=value
print(b)

The following code will work just as well with any size of key tuple (2, 5, 87, whatever.)
There is no simple way to rename a dictionary key, but you can insert a new key and delete the old one. This isn't recommended for a couple of reasons:
to be safe, you'll need to check to make sure you're not doing something weird like creating a new key only to delete that same key
be careful when you iterate over a dictionary while changing it
If you need a dictionary result, the safest thing is to generate an entirely new dictionary based on a, as has been done here.
The problem you're trying to solve is easier if you transpose the dictionary values.
After calculating the new key, (8, 9): [[0, 0], [4, 5]] should become:
(8 - sum([0, 4]), 9 - sum([0, 5])): [[0, 0], [4, 5]]
Now see how transposing helps:
transposed([[0, 0], [4, 5]]) == [[0, 4], [0, 5]]
then the new key[0] calculation is:
key[0] - sum(transposed(values)[0])
and the new key[1] calculation is:
key[1] - sum(transposed(values)[1])
So transposing makes the calculation easier.
Python dictionaries can't have lists as keys (lists are not hashable) so I've built the key as a list, then converted it to a tuple at the end.
a = {
(8, 9): [[0, 0], [4, 5]],
(3, 4): [[1, 2], [6, 7]]
}
def transpose(m):
return list(zip(*m))
results = {}
for source_keys, source_values in a.items():
transposed_values = transpose(source_values)
key = []
for n, key_item in enumerate(source_keys):
subtractables = sum(transposed_values[n])
key.append(key_item - subtractables)
results[tuple(key)] = source_values
print(results)
>>> python transpose.py
{(4, 4): [[0, 0], [4, 5]], (-4, -5): [[1, 2], [6, 7]]}

The following solution works with three assumptions:
The keys are iterables of integers
The values are iterables of iterables of integers
The inner iterables of each value are the same length as the corresponding key
Practically, this means that the length of the value can be anything, as long as it's a 2D list, and that you can have keys of different lengths, even in the same dictionary, as long as the values match along the inner dimension.
You would want to transpose the values to make the sum easier to compute. The idiom zip(*value) lets you do this quite easily. Then you map sum onto that and subtract the result from the elements of the key.
Another thing to keep in mind is that replacing keys during iteration is a very bad idea. You're better off creating an entirely new dictionary to hold the updated mapping.
from operator import sub
from itertools import starmap
a = {(8, 9): [[0, 0], [4, 5]], (3, 4): [[1, 2], [6, 7]]}
b = {
tuple(starmap(sub, zip(k, map(sum, zip(*v))))): v
for k, v in a.items()
}
The result is
{(4, 4): [[0, 0], [4, 5]], (-4, -5): [[1, 2], [6, 7]]}
Here is an IDEOne Link to play with.
The full version of the loop would look like this:
b = {}
for k, v in a.items():
vt = zip(*v) # transposed values
sums = map(sum, vt) # sum of the 1st elements, 2nd elements, etc
subs = zip(k, sums) # match elements of key with sums to subtract
diffs = starmap(sub, subs) # subtract sums from key
new_key = tuple(diffs) # evaluate the generators
b[new_key] = value

Related

Get partitioned indices of sorted 2D list

I have "2D" list and I want to make partitions/groups of the list indices based on the first value of the nested list, and then return the sorted index of the partitions/groups based on the second value in the nested list. For example
test = [[1, 2], [1, 1], [1, 5], [2, 3], [2, 1], [1, 10]]
sorted_partitions(test)
>>> [[1, 0, 2, 5], [4, 3]]
# because the groupings are [(1, [1, 1]), (0, [1, 2]), (2, [1, 5]), (5, [1, 10]), (4, [2, 1]), (3, [2, 3])]
My quick solution is to use itertools and groupby. I'm sure there's some alternate approaches with other libraries, or even without libraries.
# masochist one-liner
sorted_partitions = lambda z: return [[val[0] for val in l] for l in [list(g) for k, g in itertools.groupby(sorted(enumerate(z), key=lambda x:x[1]), key=lambda x: x[1][0])]] # not PEP compliant
# cleaner version
def sorted_partitions(x):
sorted_inds = sorted(enumerate(x), key=lambda x:x[1])
grouped_tuples = [list(g) for k, g in itertools.groupby(sorted_inds, key=lambda x: x[1][0])]
partioned_inds = [[val[0] for val in l] for l in grouped_tuples]
return partioned_inds
After coming up with what I thought would be an improvement to my original attempt, I decided to do some runtime tests. To my surprise, the bisect didn't actually improve. So the best implementation is currently:
from collections import defaultdict
def my_sorted_partitions(l):
groups = defaultdict(list)
for i, (group, val) in enumerate(l):
groups[group].append((val, i))
res = []
for group, vals_i in sorted(groups.items()):
res.append([i for val, i in sorted(vals_i)])
return res
It is very similar to the original, but uses a defaultdict instead of groupby. This means there is no need to sort the input list (which is required to use groupby). It is now necessary to sort the groups dict (by keys) but assuming num_groups << num_elements it is efficient. Lastly, we need to sort each group (by values) but since they are smaller it might be more efficient.
The attempted improvement using bisect (which removes the need to sort the values, but apparently the "repeated sorting" costs more):
def bisect_sorted_partitions(l):
groups = defaultdict(list)
for i, (group, val) in enumerate(l):
bisect.insort(groups[group], (val, i))
res = []
for group, vals_i in sorted(groups.items()):
res.append([i for val, i in vals_i])
return res
And the timing done in this REPL.
The input is randomly generated, but the results from an example run are:
My: 28.024
Bisect: 60.325
Orig: 200.61
Where Orig is the answer by the OP.

Breaking nested dictionary into list

I have been handling data from graph theory lately. I need to reiterate the shortest path in a form of nested dictionary to a tuple of lists.
For example:
What I received:
{0:{2:{4:{},3:{}}},1:{2:{2:{},7:{}}}}
What I want:
[[4,2,0],[3,2,0],[2,2,1],[7,2,1]]
My first thought was to define a function to append the list. However, I have been struggling from that for several hours already. I have no clue to get into the deepest keys of the dictionary…
Look forward to hearing advice from you guys, really appreciate of your help!!!
(P.s I have the number of layers of the nest, such as 3 for the above example)
A recursive approach works for this. Define a function that will return the path to the nodes in your dictionary like so:
For each item in the dictionary
if the item is not a dictionary, or an empty dictionary, we yield its key.
if the item is a non-empty dictionary, we get the path to the children of this item, and append the item's key to this path before yielding.
def dict_keys_to_list(d):
for k, v in d.items():
if isinstance(v, dict) and v:
for c in dict_keys_to_list(v):
yield c + [k]
else:
yield [k]
To use this:
d = {0:{2:{4:{},3:{}}},1:{2:{2:{},7:{}}}}
x = list(dict_keys_to_list(d))
print(x)
gives:
[[4, 2, 0], [3, 2, 0], [2, 2, 1], [7, 2, 1]]
You can use a recursive generator:
def unnest(d, val=[]):
if d == {}:
yield val
else:
for k,v in d.items():
yield from unnest(v, [k]+val)
list(unnest(d))
Output:
[[4, 2, 0], [3, 2, 0], [2, 2, 1], [7, 2, 1]]
How it works:
for each key, value pair, run a recursive iteration on the sub dictionaries passing the key as parameter.
at each step the new key is added on front of the list
when an empty dictionary is found the path is over, yield the list

How to efficiently get shared key subsets of multiple dicts as multiple arrays?

Is there an efficient way to get the intersection of (the keys of) multiple dictionaries?
Similar to iterating over shared keys in two dictionaries , except the idea is not to iterate but rather get the set so it can be used to get the subset of dicts.
d1 = {'a':[1,2], 'b':[2,2]}
d2 = {'e':[3,2], 'b':[5,1], 'a':[5,5]}
d3 = {'b':[8,2], 'a':[3,3], 'c': [1,2]}
So intersection manually is simple
d1.keys() & d2.keys() & d3.keys()
but what about n-dimensional list? I feel like there is a better way than this:
d_list = [d1, d2, d3]
inter_keys = {}
for i in range(len(d_list)):
if i == 0:
inter_keys = d_list[i]
inter_keys = inter_keys & d_list[i].keys()
Then getting a subset
subsets = []
for n in d_list:
subsets.append( {k: n[k] for k in inter_keys} )
and finally use it to get the value subset
v = [ x.values() for x in subsets ]
really the last part is formatted as v = np.array([ np.array(list(x.values())) for x in subsets ]) to get the ndarray as:
[[[2 2] [1 2]]
[[5 1] [5 5]]
[[8 2] [3 3]]]
I was thinking there may be an approach using something like the numpy where to more efficiently get the subset, but not sure.
I think your code can be simplified to:
In [383]: d_list=[d1,d2,d3]
In [388]: inter_keys = d_list[0].keys()
In [389]: for n in d_list[1:]:
...: inter_keys &= n.keys()
...:
In [390]: inter_keys
Out[390]: {'a', 'b'}
In [391]: np.array([[n[k] for k in inter_keys] for n in d_list])
Out[391]:
array([[[1, 2],
[2, 2]],
[[5, 5],
[5, 1]],
[[3, 3],
[8, 2]]])
That is, iteratively get the intersection of keys, followed by extraction of the values into a list of lists, which can be made into an array.
inter_keys starts as a dict.keys object, but becomes a set; both work with &=.
I don't think there's a way around the double loop with dict indexing, n[k] as the core. Unless you can use the values or items lists, there isn't a way around accessing dict items one by one.
The sub_sets list of dict is an unnecessary intermediate step.
All the keys and values can be extracted into a list of lists, but that doesn't help with selecting a common subset:
In [406]: big_list = [list(d.items()) for d in d_list]
In [407]: big_list
Out[407]:
[[('a', [1, 2]), ('b', [2, 2])],
[('e', [3, 2]), ('b', [5, 1]), ('a', [5, 5])],
[('b', [8, 2]), ('a', [3, 3]), ('c', [1, 2])]]
Assuming that the lists of values in your dictionaries are of the same length, you can use this approach:
import numpy as np
d1 = {'a':[1,2], 'b':[2,2]}
d2 = {'e':[3,2], 'b':[5,1], 'a':[5,5]}
d3 = {'b':[8,2], 'a':[3,3], 'c':[1,2]}
d_list = [d1, d2, d3]
inter_map = {} if len(d_list) == 0 else d_list[0]
for d_it in d_list[1:]:
# combine element lists based on the current intersection. keys that do not match once are removed from inter_map
inter_map = {k: inter_map[k] + d_it[k] for k in d_it.keys() & inter_map.keys()}
# inter_map holds a key->value list mapping at this point
values = np.array([item for sublist in inter_map.values() for item in sublist]).reshape([len(inter_map.keys()),
2 * len(d_list)])
# real_values restructures the values into the order used in your program, assumes you always have 2 values per sublist
real_values = np.zeros(shape=[len(d_list), 2 * len(inter_map.keys())])
for i, k in enumerate(inter_map.keys()):
real_values[:, 2*i:2*(i+1)] = values[i].reshape([len(d_list), 2])
Please note that this code is not deterministic, since the order of keys in your map is not guaranteed to be the same for different runs of the program.

how to combine same matching items in a 1d list and make it as 2d list [duplicate]

From this list:
N = [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
I'm trying to create:
L = [[1],[2,2],[3,3,3],[4,4,4,4],[5,5,5,5,5]]
Any value which is found to be the same is grouped into it's own sublist.
Here is my attempt so far, I'm thinking I should use a while loop?
global n
n = [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5] #Sorted list
l = [] #Empty list to append values to
def compare(val):
""" This function receives index values
from the n list (n[0] etc) """
global valin
valin = val
global count
count = 0
for i in xrange(len(n)):
if valin == n[count]: # If the input value i.e. n[x] == n[iteration]
temp = valin, n[count]
l.append(temp) #append the values to a new list
count +=1
else:
count +=1
for x in xrange (len(n)):
compare(n[x]) #pass the n[x] to compare function
Use itertools.groupby:
from itertools import groupby
N = [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
print([list(j) for i, j in groupby(N)])
Output:
[[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5]]
Side note: Prevent from using global variable when you don't need to.
Someone mentions for N=[1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 1] it will get [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5], [1]]
In other words, when numbers of the list isn't in order or it is a mess list, it's not available.
So I have better answer to solve this problem.
from collections import Counter
N = [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
C = Counter(N)
print [ [k,]*v for k,v in C.items()]
You can use itertools.groupby along with a list comprehension
>>> l = [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
>>> [list(v) for k,v in itertools.groupby(l)]
[[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5]]
This can be assigned to the variable L as in
L = [list(v) for k,v in itertools.groupby(l)]
You're overcomplicating this.
What you want to do is: for each value, if it's the same as the last value, just append it to the list of last values; otherwise, create a new list. You can translate that English directly to Python:
new_list = []
for value in old_list:
if new_list and new_list[-1][0] == value:
new_list[-1].append(value)
else:
new_list.append([value])
There are even simpler ways to do this if you're willing to get a bit more abstract, e.g., by using the grouping functions in itertools. But this should be easy to understand.
If you really need to do this with a while loop, you can translate any for loop into a while loop like this:
for value in iterable:
do_stuff(value)
iterator = iter(iterable)
while True:
try:
value = next(iterator)
except StopIteration:
break
do_stuff(value)
Or, if you know the iterable is a sequence, you can use a slightly simpler while loop:
index = 0
while index < len(sequence):
value = sequence[index]
do_stuff(value)
index += 1
But both of these make your code less readable, less Pythonic, more complicated, less efficient, easier to get wrong, etc.
You can do that using numpy too:
import numpy as np
N = np.array([1,2,2,3,3,3,4,4,4,4,5,5,5,5,5])
counter = np.arange(1, np.alen(N))
L = np.split(N, counter[N[1:]!=N[:-1]])
The advantage of this method is when you have another list which is related to N and you want to split it in the same way.
Another slightly different solution that doesn't rely on itertools:
#!/usr/bin/env python
def group(items):
"""
groups a sorted list of integers into sublists based on the integer key
"""
if len(items) == 0:
return []
grouped_items = []
prev_item, rest_items = items[0], items[1:]
subgroup = [prev_item]
for item in rest_items:
if item != prev_item:
grouped_items.append(subgroup)
subgroup = []
subgroup.append(item)
prev_item = item
grouped_items.append(subgroup)
return grouped_items
print group([1,2,2,3,3,3,4,4,4,4,5,5,5,5,5])
# [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5]]

Grouping numbers in a list of floats in ascending order [duplicate]

Assume no consecutive integers are in the list.
I've tried using NumPy (np.diff) for the difference between each element, but haven't been able to use that to achieve the answer. Two examples of the input (first line) and expected output (second line) are below.
[6, 0, 4, 8, 7, 6]
[[6], [0, 4, 8], [7], [6]]
[1, 4, 1, 2, 4, 3, 5, 4, 0]
[[1, 4], [1, 2, 4], [3, 5], [4], [0]]
You could use itertools.zip_longest to enable iteration over sequential element pairs in your list along with enumerate to keep track of index values where the sequences are not increasing in order to append corresponding slices to your output list.
from itertools import zip_longest
nums = [1, 4, 1, 2, 4, 3, 5, 4, 0]
results = []
start = 0
for i, (a, b) in enumerate(zip_longest(nums, nums[1:])):
if b is None or b <= a:
results.append(nums[start:i+1])
start = i + 1
print(results)
# [[1, 4], [1, 2, 4], [3, 5], [4], [0]]
Here's a simple way to do what you're asking without any extra libraries:
result_list = []
sublist = []
previous_number = None
for current_number in inp:
if previous_number is None or current_number > previous_number:
# still ascending, add to the current sublist
sublist.append(current_number)
else:
# no longer ascending, add the current sublist
result_list.append(sublist)
# start a new sublist
sublist = [current_number]
previous_number = current_number
if sublist:
# add the last sublist, if there's anything there
result_list.append(sublist)
Just cause I feel kind, this will also work with negative numbers.
seq = [6, 0, 4, 8, 7, 6]
seq_by_incr_groups = [] # Will hold the result
incr_seq = [] # Needed to create groups of increasing values.
previous_value = 0 # Needed to assert whether or not it's an increasing value.
for curr_value in seq: # Iterate over the list
if curr_value > previous_value: # It's an increasing value and belongs to the group of increasing values.
incr_seq.append(curr_value)
else: # It was lower, lets append the previous group of increasing values to the result and reset the group so that we can create a new one.
if incr_seq: # It could be that it's empty, in the case that the first number in the input list is a negative.
seq_by_incr_groups.append(incr_seq)
incr_seq = []
incr_seq.append(curr_value)
previous_value = curr_value # Needed so that we in the next iteration can assert that the value is increasing compared to the prior one.
if incr_seq: # Check if we have to add any more increasing number groups.
seq_by_incr_groups.append(incr_seq) # Add them.
print(seq_by_incr_groups)
Below code should help you. However I would recommend that you use proper nomenclature and consider handling corner cases:
li1 = [6, 0, 4, 8, 7, 6]
li2 = [1, 4, 1, 2, 4, 3, 5, 4, 0]
def inc_seq(li1):
lix = []
li_t = []
for i in range(len(li1)):
#print (i)
if i < (len(li1) - 1) and li1[i] >= li1[i + 1]:
li_t.append(li1[i])
lix.append(li_t)
li_t = []
else:
li_t.append(li1[i])
print (lix)
inc_seq(li1)
inc_seq(li2)
You can write a simple script and you don't need numpy as far as I have understood your problem statement. Try the script below. I have tested it using Python 3.6.7 and Python 2.7.15+ on my Ubuntu machine.
def breakIntoList(inp):
if not inp:
return []
sublist = [inp[0]]
output = []
for a in inp[1:]:
if a > sublist[-1]:
sublist.append(a)
else:
output.append(sublist);
sublist = [a]
output.append(sublist)
return output
list = [1, 4, 1, 2, 4, 3, 5, 4, 0]
print(list)
print(breakIntoList(list))
Explanation:
The script first checks if input List passed to it has one or more elements.
It then initialise a sublist (variable name) to hold elements in increasing order. After that, we append input List's first element into our sublist.
We iterate through the input List beginning from it's second element (Index: 1). We keep on checking if the current element in Input List is greater than last element of sublist (by sublist[-1]). If yes, we append the current element to our sublist (at the end). If not, it means we can't hold that current element in sub-List. We append the sublist to output List and clear the sublist (for holding other increasing order sublists) and add the current element to our sublist.
At the end, we append the remaining sublist to the output List.
Here's an alternative using dict, list comprehensions, and zip:
seq = [1, 4, 1, 2, 4, 3, 5, 4, 0]
dict_seq = {i:j for i,j in enumerate(seq)}
# Get the index where numbers start to decrease
idx = [0] # Adding a zero seems counter-intuitive now; we'll see the benefit later.
for k, v in dict_seq.items():
if k>0:
if dict_seq[k]<dict_seq[k-1]:
idx.append(k)
# Using zip, slice and handling the last entry
inc_seq = [seq[i:j] for i, j in zip(idx, idx[1:])] + [seq[idx[-1:]]]
Output
print(inc_seq)
>>> [[1, 4], [1, 2, 4], [3, 5], [4], [0]]
By initiating idx = [0] and creating 2 sublists idx, idx[1:], we can zip these sublists to form [0:2], [2:5], [5:7] and [7:8] with the list comprehension.
>>> print(idx)
>>> [0, 2, 5, 7, 8]
>>> for i, j in zip(idx, idx[1:]):
print('[{}:{}]'.format(i,j))
[0:2]
[2:5]
[5:7]
[7:8] # <-- need to add the last slide [8:]

Categories