10

Is it possible to do something like:

r = {range(0, 100): 'foo', range(100, 200): 'bar'}

print r[42]

> 'foo'

So I would like to use a numeric range as part of a dictionary index. To make things more complicated I would also like to use multi-indexes like ('a', range(0,100)). So the concept should be ideally expandable to that. Any suggestions?

A similar question was asked as here, but I am interested in a comprehensive implementation as opposed to the different approaches of that question.

Community
  • 1
  • 1
n1000
  • 5,058
  • 10
  • 37
  • 65
  • 1
    Are the ranges fixed? say all are `range(x*100, (x+1)*100)` for some integer `x`? – pseudoDust Feb 09 '16 at 08:39
  • @pseudoDust: Yes, fixed ranges – n1000 Feb 09 '16 at 08:52
  • I'll post an answer with a simpler and more efficient solution given that relaxation of the problem – pseudoDust Feb 09 '16 at 08:54
  • 1
    have a look at [this QA](http://stackoverflow.com/questions/3387691/python-how-to-perfectly-override-a-dict). It works with: `class RangeDictionary(TransformedDict): def __keytransform__(self, key): dk = sorted(self.keys()) return dk[bisect.bisect(dk, key)]` then `r = RangeDictionary({100: 'foo', 200: 'bar', 250: 'xyz'}); print(r[42]); print(r[142]); print(r[242])` produces `foo bar xyz`. Override `def __getitem__(self, key): return self.store[self.__keytransform__(key)]` only – Pynchia Feb 09 '16 at 09:44

7 Answers7

6

As an alternative approach, if you are trying to look up values associated with certain ranges you could use the built in Python bisect library as follows:

import bisect

def boundaries(num, breakpoints=[100, 200], result=['foo', 'bar']):
    i = bisect.bisect(breakpoints, num-1)
    return result[i]

print boundaries(42)

This would display:

foo
Martin Evans
  • 45,791
  • 17
  • 81
  • 97
5

If you are on Python 3.x you can use a range object as key but to retrieve a value you will do something like this:

In [33]: r = {range(0, 100): 'foo', range(100, 200): 'bar'}

In [34]: { r[key] for key in r if 42 in key}
Out[34]: {'foo'}

The reason you can't do this in Python2.x is because the range function in version 2.7 onwards returns a list and lists cannot be used as dictionary keys because they do not provide a valid __hash__ method.

styvane
  • 59,869
  • 19
  • 150
  • 156
  • Nice, thanks. It is really time to move to Python 3. Unfortunately this project is on 2.7. – n1000 Feb 09 '16 at 09:03
3

You can only use immutable datatypes as key. So no Lists.

But you could use a tupel to define the upper and lower bound.

r = {(0,100): 'foo', (100,200): 'bar'}

I would get the value for 42 this way:

res = ""
for (k1,k2) in r:
    if (k1 < 42 and k2 > 42):
         res = r[(k1,k2)]
print(res)

But I admit that you don't need a dictionary for that.

B5-NDDT
  • 177
  • 2
  • 14
2

If you have a simple way to calculate the range from a point, for example if all the ranges are of fixed size you can use:

def getIndex(p):
   start = floor(p/100)*100
   return (start, start+100)

Then have the dict defined:

r = {(0, 100): 'foo', (100, 200): 'bar'}

and access:

r[getIndex(42)]

This method is efficient as:

  1. You are not holding an item in the dict for each number in the range (this also allows you to have real value "keys")
  2. you are not going though the entire dict to find a value, getting a value is O(1)

Also, your getIndex function can be more complicated, for example, if your ranges are irregular in there length, your getIndex function can binary search a sorted list of range borders and return the range tuple, no longer O(1) but O(log(n)) is not bad...

pseudoDust
  • 1,336
  • 1
  • 10
  • 18
0

You can do it in one line

r = dict(zip(range(200),["foo"]*100+["bar"]*100))
-1

You could use this :

r=dict(zip(range(100),["foo"]*100))
r2=dict(zip(range(100,200),["bar"]*100))
r.update(r2)
CoMartel
  • 3,521
  • 4
  • 25
  • 48
-1

You can use list comprehension to get desired dictionary:

d = dict([(i,'foo') for i in range(0, 100)] + [(i,'bar') for i in range(100, 200)])
print d
Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82