views:

322

answers:

4

I have an instance of dict with ints, floats, strings as keys, but the problem is when there are a as int and b as float, and float(a) == b, then their hash values are the same, and thats what I do NOT want to get because I need unique hash vales for this cases in order to get corresponding values.

Example:

d = {1:'1', 1.0:'1.0', '1':1, '1.0':1.0}
d[1] == '1.0'
d[1.0] == '1.0'
d['1'] == 1
d['1.0'] == 1.0

What I need is:

d = {1:'1', 1.0:'1.0', '1':1, '1.0':1.0}
d[1] == '1'
d[1.0] == '1.0'
d['1'] == 1
d['1.0'] == 1.0
+1  A: 

This doesn't solve your problem, but from Python 2.6's number documentation:

Implementors should be careful to make equal numbers equal and hash them to the same values.

Can you get by with making the float 1.00001, or something like that?

Mark Rushakoff
+6  A: 

Using a float as a dictionary key is 'unwise' it's impossible to guarantee that two floats will evaluate to the same value.

The best thing is to multiply the keys to a predetermined number of decimal places and use that integer as the key.

edit: Sorry it seems you don't want a dict with real number keys, you simply want to format an output based on the type of input?

Martin Beckett
Good point, but I am also aware of that I can create new class with separated dicts for all critical types plus one for all other types in case that I will need them, where __setitem__, __getitem__, and __delitem__ methods can determine which dict to use for storing keys and values.
mtasic
+5  A: 

Since 1 == 1.0, it would horribly break the semantics of hashing (and therefore dicts and sets) if it were the case that hash(1) != hash(1.0). More generally, it must ALWAYS be the case that x == y implies hash(x) == hash(y), for ALL x and y (there is of course no condition requiring the reverse implication to hold).

So your dict d has just three entries, as the second one you've written in the dict display overrides the first one. If you need to force equality to hold only between identical types (as opposed to numbers more generally), you need a wrapper such as:

class W(object):

  def __init__(self, x):
    self.x = x
    self.t = type(x)

  def __eq__(self, other):
    t = type(other)
    if t != type(self):
      return False
    return self.x == other.x and self.t == other.t

  def __hash__(self):
    return hash(self.x) ^ hash(self.t)

  def __getattr__(self, name):
    return getattr(self.x, name)

Depending on your exact needs you may also want to override other methods (other comparison methods such as __cmp__ or __le__, arithmetic ones, __repr__, etc etc). At any rate, this will allow you to build a dict similar to what you require, just use as keys W(1) instead of bare 1 and W(1.0) instead of bare 1.0 (you may not need to wrap non-numbers, although there's no harm if you choose to do so, and it may ease retrieval from your dict if all keys are equally wrapped).

Alex Martelli
Wrapping object may be considered as a solution, and it is really close to what I need. Anyway, I am wondering about following: h = hash('a'); d = {'a':1, h:2}; result is d['a'] != d[h] because hash values of keys are the same but their types are not and this means that dict method getitem checks both hash value of key and type of key. If so, why its not done same for int and float?
mtasic
@mtasic, are you asking why `1==1.0` in EVERY context, or why `dict`s are using `==` rather than other ways to check equality, or what else? If what you're asking is the former, consider: if `5.0*x==5.0*y`, how could it be `x!=y` without breaking the most fundamental expectations of arithmetic and algebra? If it's the latter, how could a `dict` be a mapping if `x==y` but `d[x]!=d[y]`? These are strong, precious, intensely expected invariant properties of equality, arithmetic, and indexing, and type fetishism's no good reason to shatter them; equality's always a philosophical problem, but...
Alex Martelli
+2  A: 

If you really just need to know the difference, perhaps do something hackish like:

x = '1'
y = 1

hash(type(x) + x) != hash(type(y) + y)
Adam Nelson