0

I have a list of dictionary containing lists:

a = [{'alpha': 'a', 'val': 10, 'num': ['one', 'two']},
     {'alpha': 'b', 'val': 22, 'num': ['two']},
     {'alpha': 'c', 'val': 1, 'num': ['seven']},
     {'alpha': 'a', 'val': 10, 'num': ['three','nine']},
     {'alpha': 'b', 'val': 9, 'num': ['two', 'four']}]  

The output I want is:

[{'alpha': 'a', 'TotalVal': 20, num: ['one', 'two', 'three', 'nine'], 'numlen': 4}, 
 {'alpha': 'b', 'TotalVal': 31, num: ['two', 'four'], 'numlen': 2}, 
 {'alpha': 'c', 'val': 1, 'num': ['seven'], 'numlen': 1}]  

I have tried the following:

sumVal = collections.defaultdict(float)
for info in a:
    sumVal[info['alpha']] += info['val']

sumVal = [{'alpha': c, 'TotalVal': sumVal[c]} for c in sumVal]  

numList = collections.defaultdict(list)
for info in a:
    numList[info['alpha']].append(info['num'])
numList = [{'alpha': k, 'num': set(v), 'len': len(set(v))} for k, v in numList.items()]  

def merge_lists(l1, l2, key):
    merged = {}
    for item in l1+l2:
        if item[key] in merged:
            merged[item[key]].update(item)
        else:
            merged[item[key]] = item
    return [val for (_, val) in merged.items()]

final = merge_lists(sumVal, numList, 'alpha')  

I do not get the desired output for numList. Get the following error.

TypeError: unhashable type: 'list'

How can I get the desired output in lesser number of steps and get rid of the error?

Tonechas
  • 13,398
  • 16
  • 46
  • 80
Blabber
  • 349
  • 4
  • 19
  • 1
    Possible duplicate of [Python dictionary : TypeError: unhashable type: 'list'](http://stackoverflow.com/questions/8532146/python-dictionary-typeerror-unhashable-type-list) – Łukasz Rogalski Jul 20 '16 at 11:56
  • 1
    can you explain what your correct output should be. Also, show complete traceback in your question – joel goldstick Jul 20 '16 at 12:04
  • @joelgoldstick I have mentioned it, the second list from the top should be the final output. – Blabber Jul 20 '16 at 12:17

5 Answers5

1

Try this code:

a = [{'alpha':'a','val':10,'num':['one','two']},{'alpha':'b','val':22,'num':['two']},{'alpha':'c','val':1,'num':['seven']},{'alpha':'a','val':10,'num':['three','nine']},{'alpha':'b','val':9,'num':['two','four']}]

def merge_dicts(x, y):
    x.update(y)
    return x

def r(acc, x):
    if x['alpha'] in acc:
        acc[x['alpha']]['TotalVal'] += x['val']
        acc[x['alpha']]['num'] |= set(x['num'])
        acc[x['alpha']]['numlen'] = len(acc[x['alpha']]['num'])
    else:
        acc[x['alpha']] = {
            'TotalVal': x['val'],
            'num': set(x['num']),
            'numlen': len(set(x['num'])),
        }
    return acc

result = map(lambda (x, y): merge_dicts({'alpha': x}, y),
             reduce(r, a, {}).iteritems())
print(result)
frist
  • 1,918
  • 12
  • 25
  • The num thing should give me distinct values (set) and numlen the length of the set in the final output. The code's a good start though. Thanks :) – Blabber Jul 20 '16 at 12:27
  • @frist, this gives the output: [{'alpha': 'a', 'TotalVal': 20, 'num': set(['three', 'nine', 'two', 'one']), 'numlen': 4}, {'alpha': 'c', 'TotalVal': 1, 'num': set(['seven']), 'numlen': 1}, {'alpha': 'b', 'TotalVal': 31, 'num': set(['four', 'two']), 'numlen': 2}]. - one element (all list) in set! – Stan Zeez Jul 20 '16 at 14:38
  • @StanZeez author wanted the `num` field to be distinct list – frist Jul 20 '16 at 15:33
1

The problem is in this line:

numList[info['alpha']].append(info['num'])

appending a list onto a list, puts the list that you are appending inside the list you are appending to.

I think what you want is extend.

append vs. extend

Community
  • 1
  • 1
beauxq
  • 1,258
  • 1
  • 13
  • 22
  • Extend doesn't give the desired output either. Tried that! – Blabber Jul 20 '16 at 12:31
  • All of the correct data is there if you use extend. But it is in a different order than your expected output. Remember dictionaries are not ordered, so it will put things out of order. – beauxq Jul 20 '16 at 12:43
  • It works for this example but not for others somehow. – Blabber Jul 20 '16 at 12:45
1
#!/usr/bin/env python

a = [{'alpha':'a','val':10,'num':['one','two', 'one', 'two']},{'alpha':'b','val':22,'num':['two']},{'alpha':'c','val':1,'num':['seven']},{'alpha':'a','val':10,'num':['three','nine']},{'alpha':'b','val':9,'num':['two','four']}]


def merge_lists(src, key):
    merged = {}
    for i in src:
        _key = i[key]
        if _key in merged:
            merged[_key]['TotalVal'] += i['val']
            merged[_key]['num'].extend(i['num'])
            merged[_key]['num'] = list(set(i['num']))
            merged[_key]['numlen'] = len(merged[_key]['num'])
        else:
            merged[_key] = {'TotalVal': i['val'], 'alpha': i['alpha'], 'num': i['num'], 'numlen': 1}

    return [val for (_, val) in merged.items()]


final = merge_lists(a, 'alpha')

print(final)

Output: [{'alpha': 'a', 'TotalVal': 20, 'num': ['nine', 'three'], 'numlen': 2}, {'alpha': 'c', 'TotalVal': 1, 'num': ['seven'], 'numlen': 1}, {'alpha': 'b', 'TotalVal': 31, 'num': ['four', 'two'], 'numlen': 2}]

Stan Zeez
  • 1,138
  • 2
  • 16
  • 38
1

Not as short as other answers but simple in its implementation

a = [{'alpha':'a','val':10,'num':['one','two']},
    {'alpha':'b','val':22,'num':['two']},
    {'alpha':'c','val':1,'num':['seven']},
    {'alpha':'a','val':10,'num':['three','nine']},
    {'alpha':'b','val':9,'num':['two','four']}] 

new_list = []

# Loop through entries
for entry in a:
    # Store first entries
    if entry['alpha'] not in [i['alpha'] for i in new_list]:
        new_dict = {'alpha': entry['alpha'],
                    'TotalVal': entry['val'],
                    'num': entry['num'],
                    'numlen': len(entry['num'])}
        new_list.append(new_dict)
        continue

    # Add in additional entries
    for i, n in enumerate(new_list):
        if n['alpha'] == entry['alpha']:
            entry_vals = entry.values()
            new_list[i]['TotalVal'] = new_list[i]['TotalVal'] + entry['val']
            new_list[i]['num'] = new_list[i]['num'] + entry['num']
            new_list[i]['numlen'] = len(new_list[i]['num'])

# filter final data
for i, n in enumerate(new_list):
    # Remove duplicate entries in num
    for entry in n['num']:
        if n['num'].count(entry) > 1:
            new_list[i]['num'].remove(entry)

    # Update numlen
    new_list[i]['numlen'] = len(new_list[i]['num'])

print new_list
Wokpak
  • 573
  • 6
  • 16
1

Here's the simplest solution I came up with:

a = [{'alpha':'a','val':10,'num':['one','two']},
     {'alpha':'b','val':22,'num':['two']},
     {'alpha':'c','val':1,'num':['seven']},
     {'alpha':'a','val':10,'num':['three','nine']},
     {'alpha':'b','val':9,'num':['two','four']}] 

a2 = []
alphas = set(d['alpha'] for d in a)
for alpha in alphas:
    TotalVal, num, numlen = 0, set(), 0
    for d in a:
        if d['alpha'] == alpha:
            TotalVal += d['val']
            num = num | set(d['num'])
            numlen += 1
    new_dict = {'alpha': alpha, 'num': list(num), 'numlen': numlen}
    if numlen > 1:
        new_dict['TotalVal'] = TotalVal
    else:
        new_dict['val'] = TotalVal
    a2.append(new_dict)

Demo:

>>> for d in a2: print(d)
{'alpha': 'a', 'num': ['three', 'nine', 'two', 'one'], 'numlen': 2, 'TotalVal': 20}
{'alpha': 'c', 'num': ['seven'], 'numlen': 1, 'val': 1}
{'alpha': 'b', 'num': ['four', 'two'], 'numlen': 2, 'TotalVal': 31}
Tonechas
  • 13,398
  • 16
  • 46
  • 80