2

so I'm trying to open a text file with a poem and see how many times I can spell the word "GOOD" with the letter in the text file in each line, but I get the following error:

Traceback (most recent call last):
  File "./soup.py", line 11, in <module>
    print( "\n".join( [("Case #%d: %d" % (i, parse(file[i]))) for i in range(1, len(file))]))
  File "./soup.py", line 7, in parse
    d['O'] /= 2
KeyError: 'O'

source:

#!/usr/bin/python

def parse(string):
    d = {'G' : 0, 'O' : 0, 'D' : 0}
    d = {s: string.count(s) for s in string if s in d } 
    d['O'] /= 2
    return min(d.values())

file = open("poem.txt").read().split('\n')
print( "\n".join( [("Case #%d: %d" % (i, parse(file[i]))) for i in range(1, len(file))]))
1
  • You should count with: {c: string.count(c) for c in d}.
    – Rik Poggi
    Commented Jan 22, 2012 at 19:55

4 Answers 4

4

FWIW, I would write this using the Counter object:

from collections import Counter

def spellcount(string, wanted):
    wanted_counts = Counter(wanted)
    have_counts = Counter(string)
    return min(have_counts[c]//wanted_counts[c] for c in wanted_counts)

wanted = "GOOD"
with open("poem.txt") as fp:
    for i, line in enumerate(fp):
        print("Case", i, ":", spellcount(line, wanted))

Counter behaves as a defaultdict, e.g.

>>> from collections import Counter
>>> Counter('GOOD')
Counter({'O': 2, 'G': 1, 'D': 1})
>>> Counter('GOOD')['i']
0
2
  • 1
    +1 I think your solution is best DSM but my point in the question was to learn how to handle the Key Error exception. thanks for your helpful post anyway
    – Neo
    Commented Jan 22, 2012 at 20:50
  • 1
    @Neo: no worries! Both how you would do this in Python and how you fix the problem given the code you have are useful things to know.
    – DSM
    Commented Jan 22, 2012 at 20:51
3

Are you sure your line 7 reads d['O']?

The error message suggests it reads d['C']

The problem is that if the line contains no 'O' characters then 'O' will not be in d and this gives an error.

The second time d is defined will create a new dictionary which will not include the 'O' key.

def parse(string):
    d = {'G' : 0, 'O' : 0, 'D' : 0}
    d = {s: string.count(s) for s in string if s in d }
    try:
        d['O'] /= 2
    except KeyError:
        return 0
    return min(d.values())

file = open("test1.py").read().split('\n')
print( "\n".join( [("Case #%d: %d" % (i, parse(file[i]))) for i in range(1, len(file))]))

(You might find it more efficient to do d = {s: string.count(s) for s in d })

(I also love the alternative suggestion of using collections.Counter. However, if you are interested in speed then my timing measurements show that for a 10 million character string it takes 3 seconds to make the Counter object, but only 0.012 seconds per call to string.count)

2
  • 1
    there were more letters and the word good was actually a sentence. sorry about that.
    – Neo
    Commented Jan 22, 2012 at 19:41
  • 1
    you are awesome, I don't do any python and I couldn't figure out how to put this exception and sometimes the file didnt contain any o. thank you my firend. I will have to wait 6 minutes before I can accept your answer. Consider it done!
    – Neo
    Commented Jan 22, 2012 at 19:44
1

Use 'GOD' instead of string in your dictionary comprehension, because not every line will include the 'O'.

def parse(string):
    d = {s: string.count(s) for s in 'GOD'}
    d['O'] /= 2
    return min(d.values())
1
  • Actually you should change it to 'GOD' ;), you're counting and setting the 'O's twice now. Besides that, it's perfect. Commented Jan 22, 2012 at 20:00
0

The problem is if you encounter a line that does not countain an 'O', that key won't exist. You can solve this using dict.get. dict.get does not raise an exception if a key doesn't exist but returns its second argument, in this case 0:

def parse(string):
    d = {s: string.count(s) for s in string if s in 'GOD'} 
    d['O'] = d.get('O', 0) / 2
    return min(d.values())

Note the above only matches uppercase letters, if you want both upper- and lowercase you can do this:

def parse(string):
    string = string.upper()
    d = {s: string.count(s) for s in string if s in 'GOD'} 
    d['O'] = d.get('O', 0) / 2
    return min(d.values())

Edit

Of course, using d = {s: string.count(s) for s in 'GOD'} avoids the issue entirely and is more concise too. I suggest using @Gandaro's answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.