Paul C
Paul C

Reputation: 21

Merge arrays and add based on key in python

I want to merge 2 arrays in Python and add values based on a key

Example:

Groceries = [] 
A = [{group: 'Fruit',
      item: 'Banana',
      quantity: 10},
     {group: 'Vegetable',
      item: 'Carrot',
      quantity: 1}]
for item in A:
    Groceries.append(item)
B = [{group: 'Fruit',
      item: 'Banana',
      quantity: 3},
     {group: 'Vegetable',
      item: 'Celery',
      quantity: 1}]
for item in B:
    Groceries.append(item)

print(Groceries) will return:

 [{group: 'Fruit',
   item: 'Banana',
   quantity: 10},
  {group: 'Vegetable',
   item: 'Carrot',
   quantity: 1},
  {group: 'Fruit',
   item: 'Banana',
   quantity: 3},
  {group: 'Vegetable',
   item: 'Celery',
   quantity: 1}]

Is there any way I can merge them in a way where the result would instead be:

[{group: 'Fruit',
  item: 'Banana',
  quantity: 13},
 {group: 'Vegetable',
  item: 'Carrot',
  quantity: 1},
 {group: 'Vegetable',
  item: 'Celery',
  quantity: 1}]

(Note that it states 13 Bananas instead of 2 instances of Bananas)

Upvotes: 2

Views: 1223

Answers (4)

Open AI - Opting Out
Open AI - Opting Out

Reputation: 24133

Am inefficient way would be to search the list for an existing dictionary, matching the 'group' and 'item':

for item in B:
    existing = next((existing for existing in Groceries
                     if existing['group'] == item['group'] and
                     existing['item'] == item['item']),
                    None)
    if existing:
        existing['quantity'] += item['quantity']
    else:
        Groceries.append(item)

This is inefficient because it uses next to do a linear search through all entries in Groceries. If you had millions of items it would become very slow.

If you restructure your data to be dictionary lookups based upon groups and items it will be much quicker. You need a dictionary of dictionaries. You can also use a defaultdict to automatically have value 0 for missing items:

from collections import defaultdict

defaultitems = lambda: defaultdict(int)
quantities = defaultdict(defaultitems)

This allows you to create groups and items with quantity 0:

>>> quantities['Fruit']['Tomato']
0

First create using A:

defaultitems = lambda: defaultdict(int)
quantities = defaultdict(defaultitems)

for item in A:
    quantities[item['group']][item['item']] = item['quantity']

Then add B:

for item in B:
    quantities[item['group']][item['item']] += item['quantity']

However, because you get the default you could add A and B all in one loop, by chaining them together (using chain):

from itertool import chain

for item in chain(A, B):
    quantities[item['group']][item['item']] += item['quantity']

Upvotes: 0

Suresh Jaganathan
Suresh Jaganathan

Reputation: 537

    Groceries = []
A = [{'group': 'Fruit',
      'item': 'Banana',
      'quantity': 10},
     {'group': 'Vegetable',
      'item': 'Carrot',
      'quantity': 1}]
for item in A:
    Groceries.append(item)
B = [{'group': 'Fruit',
      'item': 'Banana',
      'quantity': 3},
     {'group': 'Vegetable',
      'item': 'Celery',
      'quantity': 1}]
for item in B:
    Groceries.append(item)
new_data = []
for i in Groceries:
    yes = False
    for s in new_data:
        if s['item'] == i['item']:
            s['quantity'] += i['quantity']
            yes = True
    if not yes:
        new_data.append(i)
print new_data

You can loop through the list and add the quantity if value exists. This may help

Upvotes: 0

Ilja Everilä
Ilja Everilä

Reputation: 52949

For counting jobs, use a Counter:

from collections import Counter

c = Counter()

The counter needs proper keys to differentiate items, so form (group, item) tuples from your original dict format as keys, quantity as value:

c.update({(d["group"], d["item"]): d["quantity"] for d in A})
c.update({(d["group"], d["item"]): d["quantity"] for d in B})

This method has a caveat though: if your list A or B contains multiple records for the same item, this will not work correctly as the dictionary comprehension will "remember" only the latest. If you know this is a possibility, you can revert to plain old for-looping and adding up:

from operator import itemgetter
from itertools import chain

keymaster = itemgetter("group", "item")
for d in chain(A, B):
    c[keymaster(d)] += d["quantity"]

To get your original format back create a list of small dictionaries from counter's items:

[{"group": k[0], "item": k[1], "quantity": v} for k, v in c.items()]

# results
[{'item': 'Carrot', 'group': 'Vegetable', 'quantity': 1},
 {'item': 'Celery', 'group': 'Vegetable', 'quantity': 1},
 {'item': 'Banana', 'group': 'Fruit', 'quantity': 13}]

Upvotes: 2

birkett
birkett

Reputation: 10101

Use a support method to do the merging and use a dictionary to store the results. This will do what you want but it will loose the ordering of the list. I am not sure if you need that.

Groceries = {}
A = [{'group': 'Fruit',
      'item': 'Banana',
      'quantity': 10},
     {'group': 'Vegetable',
      'item': 'Carrot',
      'quantity': 1}]

for item in A:
    Groceries[item['item']] = item

B = [{'group': 'Fruit',
      'item': 'Banana',
      'quantity': 3},
     {'group': 'Vegetable',
      'item': 'Celery',
      'quantity': 1}]

def add_item(d, other):
    key = other['item']
    if d[key] in d:
        d[key]['quantity'] += y['quantity']
    else:
        d[key] = y

for item in B:
    add_item(Groceries, item)

print(Groceries.values())

[{'group': 'Fruit', 'quantity': 13, 'item': 'Banana'}, {'group': 'Vegetable', 'quantity': 1, 'item': 'Celery'}, {'group': 'Vegetable', 'quantity': 1, 'item': 'Carrot'}]

Upvotes: 0

Related Questions