3

Forgive me if this is obvious, but I'm very, very new to Python. I've found ways to get multiple keys from a dictionary, but that's not what I'm trying to do.

Basically I'm looking for something like this:

my_dict = { "1-10" : "foo",
            "11-20" : "bar",
            # ...
            "91-100" : "baz" }

... but where the keys aren't actually strings and any number in that given range maps to the value. So for example, my_dict[9] ought to return foo, just as my_dict[3] should. I thought of using an explicit array, like the following, but it didn't work:

my_dict = { [1, 2, 3, ..., 10] : "foo",

I'm unsure if this is even a valid use-case for a dictionary, or if there is another data structure I should be using. But Python has a way of always surprising me. So does anyone know Python magic to make this work?

asteri
  • 11,402
  • 13
  • 60
  • 84

6 Answers6

5

I must say I've never had any need to do anything like this, and there's certainly no built-in datastructure for it. (If you know anything about hashes, you'll understand why a dict can't work that way.)

One possibility would be not to use a dict at all, but have separate lists of keys and values, with the key list being the beginning of each "range". So:

keys = [0, 10, 20, 30]
values = ['foo', 'bar', 'baz', 'quux']

And now you can use bisect to find the relevant key:

import bisect
pos = bisect.bisect_left(keys, 12)
value = values[pos-1]
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
2

This certainly is not a common case, i recommend to use the obvious solution:

my_dict = dict((i, "foo") for i in range(1,10))
print my_dict
{1: 'foo', 2: 'foo', 3: 'foo', 4: 'foo', 5: 'foo', 6: 'foo', 7: 'foo', 8: 'foo', 9: 'foo'}

In order to append new elements you can update your dictionary with:

my_dict.update(new_elements) 
badc0re
  • 3,333
  • 6
  • 30
  • 46
  • Interesting. How would you append the other keys to this existing `my_dict` afterwards, though? – asteri Aug 28 '13 at 14:52
  • As long as you don't want accurate meanings of things like `len()`, and don't need to update the values after having initially written them, this would probably be fine. – Silas Ray Aug 28 '13 at 14:52
2

How about this:

def fancy_dict(*args):
    'Pass in a list of tuples, which will be key/value pairs'
    ret = {}
    for k,v in args:
        for i in k:
            ret[i] = v
    return ret

Then, you can:

>>> dic = fancy_dict((range(10), 'hello'), (range(100,125), 'bye'))
>>> dic[1]
'hello'
>>> dic[9]
'hello'
>>> dic[100]
'bye'
>>> 

You can also add logic inside of fancy_dict to say, check if an item is a string or if it is iterable and create the dictionary accordingly.

DJG
  • 6,413
  • 4
  • 30
  • 51
  • Nice. This works perfectly. Thank you! And you also taught me how to use varargs in Python along the way. :) – asteri Aug 28 '13 at 15:04
  • Be aware that `len(ret)` is going to give you potentially incorrect values. Also, likely more importantly, iterating over `ret` will not go in sorted index or insert order, and will repeat values for each range id inserted for a given value. Updating and adding values will also not work as you would likely want. – Silas Ray Aug 28 '13 at 15:29
1

If your "range keys" are simple mathematical transformations with unique mappings for every potential valid key, you could just subclass list and override __getitem__ and __setitem__, though there's good reasons to just use helper methods or straight calculations in your calling code (such as having index() return something particularly meaningful).

class RangeList(list):
    def __getitem__(self, index):
        return super(RangeList, self).__getitem__(index / 10 if index else 0)
    def __setitem__(self, index, value):
        super(RangeList, self).__setitem__(index / 10 if index else 0, value)
Silas Ray
  • 25,682
  • 5
  • 48
  • 63
0

I keep this for record and may others interest:

It works if you make keys tuple: my_dict = {(1, 2, 3, 10): "foo"}

Edit: I thought you want a list as key. Otherwise, you need make it:

>>> import numpy as np
>>> keys = np.arange(10,dtype=int)
>>> values = np.arange(3,13)
>>> d = dict(numpy.array([keys,values]).T)
>>> d
{0: 3, 1: 4, 2: 5, 3: 6, 4: 7, 5: 8, 6: 9, 7: 10, 8: 11, 9: 12}
Developer
  • 8,258
  • 8
  • 49
  • 58
  • You'd have to override getitem/setitem on a custom dict to look in to the keys, except then, of course, you'd loose the lookup efficiency of the dict hashmap. – Silas Ray Aug 28 '13 at 14:51
0

mabey you can do something along thise lines:

class my_dict(dict):
    def __getitem__(self, a):
        return dict.__getitem__(self, (a-1) / 10)
    def __setitem__(self, a, b):
        dict.__setitem__(self, (a-1) / 10, b)

dict_instance = my_dict()
dict_instance[1] = 'foo'
print dict_instance[9] # prints foo

dict_instance[17] = 'bar'
print dict_instance[12] # prints bar

this has the atvantage of beeing as fast as a normal dict (O(1)) but 10 times smaller

you also need to ovewrite __ str__ if you want it to print the ranges, you can also loop over unique keys very easy with this data type :)

jcr
  • 1,015
  • 6
  • 18