1

I had some problems with the code that was given in an answer at this post: Can I use a nested for loop for an if-else statement with multiple conditions in python?

import pprint

board = {
    '1a': 'bking',
    '4e': 'bpawn',
    '2c': 'bpawn',
    '3f': 'bpawn',
    '5h': 'bbishop',
    '6d': 'wking',
    '7f': 'wrook',
    '2b': 'wqueen'
}

count = {}
for k, v in board.items():
    count[k[0]][k[1:]] = v
pprint.pprint(count)

I wanted to get the following dictionary:

count = {'b': {'king': 1, 'pawn': 3, 'bishop': 1},
         'w': {'king': 1, 'rook': 1, 'queen': 1}}

Received error:

Traceback (most recent call last):
  File "/Users/Andrea_5K/Library/Mobile Documents/com~apple~CloudDocs/automateStuff2/ch5/flatToNest2.py", line 21, in <module>
    count[k[0]][k[1:]] = v
KeyError: '1'
aurumpurum
  • 932
  • 3
  • 11
  • 27

3 Answers3

1

OP comment says output should be the count of each piece. That can be done as follows using setdefault

nested = {}
for k, v in board.items():
    nested.setdefault(v[0], {})  # default dictionary for missing key
    nested[v[0]][v[1:]] = nested[v[0]].get(v[1:], 0) + 1 # increment piece count

pprint.pprint(nested)
# Result
{'b': {'bishop': 1, 'king': 1, 'pawn': 3},
 'w': {'king': 1, 'queen': 1, 'rook': 1}}
DarrylG
  • 16,732
  • 2
  • 17
  • 23
  • The disadvantage of using `setdefault` like that is that it *always* makes a new dictionary, which mostly goes to waste. – Mad Physicist Dec 08 '20 at 12:24
  • @MadPhysicist--isn't it only making a dictionary if one doesn't exist? The next line then uses the dictionary i.e. nested[k[0]][k[1:]] – DarrylG Dec 08 '20 at 12:28
  • No, it only adds the dictionary if the key isn't present. `{}` always creates a new dictionary – Mad Physicist Dec 08 '20 at 12:53
  • @MadPhysicist--yes, I agree setdefault(...) always evaluates the default. So are you saying the evaluation of the default is an issue since it creates a default dictionary that may not be used? I agree that [defaultdict is faster](https://stackoverflow.com/questions/38625608/setdefault-vs-defaultdict-performance), but I don't think the speed difference is meaningful is this case. Agree? – DarrylG Dec 08 '20 at 13:02
  • That's exactly what I'm saying. Speed and tiny fragments of memory likely aren't a problem in this case, but it's something to be aware of. – Mad Physicist Dec 08 '20 at 13:54
1

The problem in your code is that when you access nested[k[0]], you expect nested to already have this key, and you expect the corresponding value to be a dict.

The easiest way to solve this problem is to use a defaultdict(dict) that will create it on the fly when needed:

from collections import defaultdict

board = {
    '1a': 'bking',
    '4e': 'bpawn',
    '2c': 'bpawn',
    '3f': 'bpawn',
    '5h': 'bbishop',
    '6d': 'wking',
    '7f': 'wrook',
    '2b': 'wqueen'
}

nested = defaultdict(dict)
for k, v in board.items():
    nested[k[0]][k[1:]] = v
print(nested)

# defaultdict(<class 'dict'>, {'1': {'a': 'bking'}, '4': {'e': 'bpawn'}, '2': {'c': 'bpawn', 'b': 'wqueen'}, '3': {'f': 'bpawn'}, '5': {'h': 'bbishop'}, '6': {'d': 'wking'}, '7': {'f': 'wrook'}})
Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50
  • Thanks. But I need another solution: My nested dictionary should contain the counts of every piece in the board. For example: count = {'b': {'king': 1, 'pawn': 3, 'bishop': 1}, 'w': {'king': 1, 'rook': 1, 'queen': 1}} – aurumpurum Dec 08 '20 at 12:31
1

If all you need is counts, use collections.Counter, and split the result using a collections.defaultdict afterward:

counts = defaultdict(dict)
for piece, count in Counter(board.values()).items():
    counts[piece[0]][piece[1:]] = count
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Thanks. I just need the dictionary counts = {'b': {'king': 1, 'pawn': 3, 'bishop': 1}, 'w': {'king': 1, 'rook': 1, 'queen': 1}}). Because I want to use it for different validation tests, as you mentioned in the OP. How did you mean to split the result? – aurumpurum Dec 08 '20 at 18:01
  • 1
    @aurumpurum. I'm pretty sure that that's what I'm outputting, but I'm on mobile, so can't test just now – Mad Physicist Dec 08 '20 at 18:03
  • the variable counts holds defaultdict(, {'b': {'king': 1, 'pawn': 3, 'bishop': 1}, 'w': {'king': 1, 'rook': 1, 'queen': 1}}) but I only want to have the dictionary. – aurumpurum Dec 08 '20 at 18:07
  • 1
    @aurumpurum. A defaultdict is a subclass of dict. If you want to get a plain dict you can do `counts = dict(counts)`, but that is really unnecessary. You can do all the same things to a defaultdict that you can do to a dict. The only real difference is that when you access a missing key, it creates it for your. – Mad Physicist Dec 08 '20 at 18:11