Reputation: 2259
I'm trying to write some code that calculates powers of 2 but stores them in a dictionary that has powers of 10 as the key, so for example, 2^9 would be stored as
{0:2, 1:1, 2:5}
where you have 5*10^2 + 1*10^1 + 2*10^0.
So currently, I have something like
def foo():
result={0:2}
for i in range(2,10):
for p in result.keys():
result[p]*=2
for p in result.keys():
if result[p] / 10 != 0:
result.setdefault(p+1,result[p]/10)
result[p] = result[p] % 10
The issue is
result.setdefault(p+1,result[p]/10)
It overwrites whatever was in result[p+1]
. I know it's possible to just check the dictionary and then "initialize" the new key if needed, but is there a more elegant way to do it on the fly? I could initialize result to be long enough, but since I don't necessarily know how long is "long enough", it seems to me to make more sense to extend it on the fly.
Basically I'd like something along the lines of
for p in result.keys():
if result[p] / 10 != 0:
if result[p+1] exists:
result[p+1]+=result[p]/10
else:
create result[p+1] and set its value to result[p]/10
Any help is much appreciated!
Upvotes: 2
Views: 4984
Reputation: 365707
So, what you're doing each time through the main loop is: double each digit, then carry extra tens to the next digit. The problem is that, for the highest digit, the next digit doesn't exist.
So here's a solution—although it's not going to work for any power other than 2, for reasons I'll get to below.
def foo():
result={0:2}
for i in range(2,10):
for p in result.keys():
result[p]*=2
for p in result.keys()[:]:
if result[p] / 10 != 0:
result[p+1] = result.get(p+1, 0) + result[p] / 10
result[p] = result[p] % 10
There are two key changes from your original code.
First, we had to add [:]
to the end of the second result.keys()
, to iterate over a copy of the dictionary's set of keys before the loop, rather than its current keys. The reason is that if the last digit is >5 we're going to be adding a new key to the dictionary, and you're not allowed to do that while iterating over it. (Why? A few reasons, but the simple one is that dictionaries are in arbitrary order, and every time you add a key, the whole order can change. This will be important later, too.)
Second, there's your original question: How can you avoid having to check if p+1 in result
to decide whether to store r_p
or add r_p
to the existing value?
When you're dealing with counts, you use collection.Counter
, which is like a dict
except that it only stores integers, and any missing key has a value of 0. Otherwise, you usually use collection.defaultdict
, which is like a dict
except that you can specify an default value that all missing keys have. With either a Counter
or defaultdict
, all you'd need is result[p+1] += result[p]/10
.
But I've used a different alternative: the get
method on the dict
, which lets you specify a default value. I'll explain why later, but keep in mind that in general, when you find yourself reaching for get
, you may want a defaultdict
or Counter
instead.
So, now it'll run, and work, for powers of 2. But it won't work for other powers, for two reasons:
First, we're doing the carries in random order. For powers of 2, the most you could carry is a 1, and there's no way a 1 could affect whether the next digit up carries. (8 won't carry and neither will 8+1, and there's now way to get a 9.) But for any other power, that isn't true.
In simple testing, you might not notice this, because it just so happens that (at least in CPython 2.7 and 3.2) when you start with an empty dict
and add a small number of small keys (actually, keys with small hash values, but small ints hash to themselves) in sorted order, and don't delete anything, the dict
will often iterate (and print) in order. But in general, the order is not predictable, and you can't rely on it.
The solution to that problem is to use collections.OrderedDict
, which iterates over the keys in the order in which they were added. And that's why I didn't want to use defaultdict
or Counter
: because if you have to switch to OrderedDict
, your only option is get
.
Second, once your exponent gets over 10, you may need to carry 100. That means you have to carry the 10, which will cause the next digit to carry even if it's 0. That means that we can't just copy the keys in advance. For example, let's say we're doing powers of 75. Start off with {0:5, 1:7}
. Multiply each digit by 75 to {0:375, 1:525}
. Now carry the 37: {0:5, 1:562}
. Now carry the 56: {0:5, 1:2, 2:56}
. Because we were only iterating over a copy of the original keys—that is, [0, 1]
—we don't get a chance to carry the 5.
I'll leave it up to you to fix that problem.
However, one thing you might want to consider is creating a new dictionary, instead of modifying the old one in place. Then you can get around all of these problems. (As a general rule, using pure functions instead of mutating state gets around a lot of problems, but of course at the cost of extra copying.)
def double(d):
result = Counter()
for p in d:
result[p] += d[p] % 10
result[p+1] += d[p] / 10
return result
digits={0:2}
for i in range(2,10):
for p in digits:
digits[p]*=2
digits = double(digits)
Upvotes: 2
Reputation: 298166
You can check for the existence of a key by using the in
syntax:
for p in result.keys()[:]:
r_p = result[p] / 10
if r_p != 0:
if p + 1 in result:
result[p + 1] += r_p
else:
result[p + 1] = r_p
Or, you can use Counter
, which automatically does this:
from collections import Counter
result = Counter()
for p in result.keys()[:]:
r_p = result[p] / 10
if r_p != 0:
result[p + 1] += r_p
Also, your condition is a little strange. result[p] / 10 == 0
only if result[p] < 10
on Python 2 or result[p] == 0
on Python 3.
Upvotes: 1