Apart from 2d_dict being an invalid variable name (it starts with a digit), your existing solution already works:
>>> from collections import defaultdict
>>> d2_dict = defaultdict(dict)
>>> d2_dict[('canned_food', 'food')]['spam'] = 'delicious'
>>> d2_dict
defaultdict(<type 'dict'>, {('canned_food', 'food'): {'spam': 'delicious'}})
In fact, you don't even need the parentheses - Python will still recognise your key as a tuple:
>>> d2_dict['fresh_food', 'food']['eggs'] = 'delicious'
>>> d2_dict
defaultdict(<type 'dict'>, {('canned_food', 'food'): {'spam': 'delicious'},
('fresh_food', 'food'): {'eggs': 'delicious'}})
... and, yes, it's a perfectly reasonable way to build a 2D+1D lookup table.
If you want to build a 3D lookup table using nested dicts instead of tuple keys, this works:
>>> d3_dict = defaultdict(lambda: defaultdict(dict))
>>> d3_dict['dried_food']['food']['jerky'] = 'chewy'
>>> d3_dict
defaultdict(<function <lambda> at 0x7f20af38a2a8>,
{'dried_food': defaultdict(<type 'dict'>, {'food': {'jerky': 'chewy'}})})
Answer from Zero Piraeus on Stack Overflowjson - Building a large (18m keys) lookup table in Python - Stack Overflow
python - "Multi-key" dictionary - Code Review Stack Exchange
Python lookup table that returns multiple values - Stack Overflow
python - Use dictionary as lookup table - Stack Overflow
What is a lookup table in Python?
How do I avoid a KeyError in a lookup table?
Can lookup-table keys be lists?
1. Comments on your code
No docstrings! What does your code do and how am I supposed to use it?
No test cases. This ought to be a good opportunity to use doctests.
Given that your purpose here is to implement RPG lookup tables, your design seems a bit hard to use. The printed RPG table probably says something like this:
1-8 giant spider 9-12 skeleton 13-18 brass dragon ...but you have to input it like this (adding one to the limit of each range):
range_dict((range(1, 9), "giant spider"), (range(9, 13), "skeleton"), (range(13, 18), "brass dragon"), ...)which would be easy to get wrong.
The design also seems fragile, because it doesn't ensure that the ranges are contiguous. If you made a data entry mistake:
d = range_dict((range(1, 8), "giant spider"), (range(9, 13), "skeleton"), (range(13, 18), "brass dragon"), ...)then the missing key would later result in an error:
>>> d[8] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 8
2. An alternative approach
Here's how I'd implement this lookup table:
from bisect import bisect_left
from collections.abc import Mapping
class LookupTable(Mapping):
"""A lookup table with contiguous ranges of small positive integers as
keys. Initialize a table by passing pairs (max, value) as
arguments. The first range starts at 1, and second and subsequent
ranges start at the end of the previous range.
>>> t = LookupTable((10, '1-10'), (35, '11-35'), (100, '36-100'))
>>> t[10], t[11], t[100]
('1-10', '11-35', '36-100')
>>> t[0]
Traceback (most recent call last):
...
KeyError: 0
>>> next(iter(t.items()))
(1, '1-10')
"""
def __init__(self, *table):
self.table = sorted(table)
self.max = self.table[-1][0]
def __getitem__(self, key):
key = int(key)
if not 1 <= key <= self.max:
raise KeyError(key)
return self.table[bisect_left(self.table, (key,))][1]
def __iter__(self):
return iter(range(1, self.max + 1))
def __len__(self):
return self.max
Notes on this implementation:
The constructor takes the maximum for each range (not the maximum plus one), making data entry less error-prone.
The minimum of each range is taken from the end of the previous range, thus ensuring that there are no gaps between ranges.
The
collections.abc.Mappingabstract base class provides much of the functionality of a read-only dictionary. The idea is that you supply the__getitem__,__iter__and__len__methods, and theMappingclass supplies implementations of__contains__,keys,items,values,get,__eq__, and__ne__methods for you. (Which you can override if you need to, but that's not necessary here.)I've used
bisect.bisect_leftto efficiently look up keys in the sorted table.
So, I came across this issue earlier today. Wasn't real thrilled with most of the solutions here, mostly because I wanted to be able to set normal keys, but also set a range of keys all at once when needed, so I came up with this:
class RangedDict(dict):
"""
A dictionary that supports setting items en masse by ranges, but also supports normal keys.
The core difference between this and any other dict is that by passing a tuple of 2 to 3 numeric values, an
inclusive range of keys will be set to that value. An example usage is:
>>> d = RangedDict({
... (1, 5): "foo"
... })
>>> print d[1] # prints "foo"
>>> print d[4] # also prints "foo"
>>> print d[5] # still prints "foo" because ranges are inclusive by default
>>> d['bar'] = 'baz'
>>> print d['bar'] # prints 'baz' because this also works like a normal dict
Do note, ranges are inclusive by default, so 5 is also set. You can control
inclusivity via the `exclusive` kwarg.
The third, optional, parameter that can be given to a range tuple is a step parameter (analogous to the step
parameter in xrange), like so: `(1, 5, 2)`, which would set keys 1, 3, and 5 only. For example:
>>> d[(11, 15, 2)] = "bar"
>>> print d[13] # prints "bar"
>>> print d[14] # raises KeyError because of step parameter
NOTE: ALL tuples are strictly interpreted as attempts to set a range tuple. This means that any tuple that does NOT
conform to the range tuple definition above (e.g., `("foo",)`) will raise a ValueError.
"""
def __init__(self, data=None, exclusive=False):
# we add data as a param so you can just wrap a dict literal in the class constructor and it works, instead of
# having to use kwargs
self._stop_offset = 0 if exclusive else 1
if data is None:
data = {}
for k, v in data.items():
if isinstance(k, tuple):
self._store_tuple(k, v)
else:
self[k] = v
def __setitem__(self, key, value):
if isinstance(key, tuple):
self._store_tuple(key, value)
else:
# let's go ahead and prevent that infinite recursion, mmmmmkay
dict.__setitem__(self, key, value)
def _store_tuple(self, _tuple, value):
if len(_tuple) > 3 or len(_tuple) < 2:
# eventually, it would be nice to allow people to store tuples as keys too. Make a new class like: RangeKey
# to do this
raise ValueError("Key: {} is invalid! Ranges are described like: (start, stop[, step])")
step = _tuple[2] if len(_tuple) == 3 else 1
start = _tuple[0]
stop = _tuple[1]
# +1 for inclusivity
for idx in xrange(start, stop + self._stop_offset, step):
dict.__setitem__(self, idx, value)
It uses tuples to describe ranges, which looks rather elegant, if I do say so myself. So, some sample usage is:
d = RangedDict()
d[(1, 15)] = None
d[(15, 25)] = "1d6x1000 cp"
Now you can easily use d[4] and get what you want.
The astute reader will however notice that this implementation does not allow you to use tuples as keys at all in your dict. I don't see that as a common use case, or something elegant to do, in general, so I felt it was worth the tradeoff. However, a new class could be made named along the lines of RangeKey(1, 5, step=2) that could be used to key ranges, thus letting you key tuples as well.
I hope a future reader enjoys my code!
You have two straightforward choices. One is the standard look-up list, as in Evan's comment:
{'guiseppe': 'Joseph', 'joe': 'Joseph', 'joey': 'Joseph', 'joseph': 'Joseph',
'Samuel': 'Samuel', 'sam': 'Samuel', 'samuel': 'Samuel', 'shmuel': 'Samuel'}
Now, adapt this to return the full object, rather than the name. Since you didn't specify an object, I'll assume a Person class:
joseph = Person(name = "Joseph", gender = "m", xro = "xy")
samuel = Person(name = "Samuel", gender = "m", xro = "xy")
Now, just insert the objects into your look-up table:
{'guiseppe': joseph, 'joe': joseph, 'joey': joseph, 'joseph': 'Joseph',
'Samuel': samuel, 'sam': samuel, 'samuel': samuel, 'shmuel': 'Samuel'}
The other look-up method is to remove the extra loop from your existing code; use the in operator:
for canon in name_forms:
for variant in name_forms[canon]:
if txt.lower() in variant:
return canon
You will still need a string-to-object table, but now it's shorter:
{"Joseph": jospeh, "Samuel": samuel}
Depends on the problem. What exactly do you want?
I can only come up with something like this:
def lookup(txt):
name_forms = {
'Joseph': ['joe', 'joseph', 'guiseppe', 'joey'],
'Samuel': ['sam', 'samuel', 'shmuel', 'kamuel'],
'Maria': ['mary', 'marie', 'miriam', 'mariana']}
for i, canon in enumerate(name_forms):
for variant in name_forms[canon]:
if txt.lower() == variant.lower():
if i <= 1:
gender = 'm'
xro = 'xy'
else:
gender = 'f'
xro = 'xx'
return {'name': canon, 'gender': gender, 'xro': xro}
lookup('joey')
I didn't change your look-up method, but it's definitely not effective as others already mentioned.
Hey everyone,
I need to map a large number of values to a smaller set of numbers and I'm wondering what the optimal way to do this would be.
My first intuition was to create a dictionary where the key was the output number and the values would be a list of the input numbers. It feels like the data should be structured in this manner since there are far fewer output than input numbers but this method would be like searching a real dictionary by definition to try to find a word.
Since that seems less than optimal, my plan is to create a one-to-one mapping even though the value numbers will be repeated many times.
I know my current idea will work but I can't shake the suspicion that there's a module or strategy I can use to make it even better. Any ideas if there's anything I can do?
Edit - More details:
I have an Excel report that contains 50,000+ rows, each with a unique ID number. These rows are then classified into 1 of 2197 classes.
In the future, I will be without the report. When I import the data, I will only have the unique ID number and will need to find a way to classify each row correctly.
I'm hoping to find a solution that allows me to use one variable input to produce multiple outputs. You can think of it as a lookup table that extends horizontally:
Input | Output1 | Output2 | Output3
We have 10 or so variables that key off of the single input, and right now, that means 10 separate lookup tables to maintain. I'm hoping someone here may have a more efficient solution?
Thanks!
Since we are not given any further information about what ranges should be associated with which values, I assume you will transfer my answer to your own problem.
Look-up-Tables are called dictionary in python. They are indicated by curly brackets.
Easy example:
myDict = {1: [1, 2, 3, 4, 5],
2: [2, 3, 4, 5, 6],
3: [3, 4, 5, 6, 7]}
Here you create a dictionary with three entries: 1, 2, 3. Each of these entries has a range associated with it. In the example it is of logic range(i, i+5).
You inquire your "Look-Up-Table" just like a list:
print(myDict[2])
>>> [2, 3, 4, 5, 6]
(Note how [2] is not index #2, but actually the value 2 you were looking for)
Often you do not want to create a dictionary by hand, but rather want to construct it automatically. You can e.g. combine two lists of the same length to a dictionary, by using dict with zip:
indices = range(15, 76) # your values from 15 to 75
i_ranges = [range(i, i+5) for i in indices] # this constructs your ranges
myDict = dict(zip(indices, i_ranges)) # zip together the lists and make a dict from it
print(myDict[20])
>>> [20, 21, 22, 23, 24]
By the way, you are not restricted to integers and lists. You can also go like:
myFruits = {'apples': 20, 'cherries': 50, 'bananas': 23}
print(myFruits['cherries'])
>>> 50
Following is a linear-interpolating lookup implementation:
from bisect import bisect_left
def lookup(x, xs, ys):
if x <= xs[0]: return ys[0]
if x >= xs[-1]: return ys[-1]
i = bisect_left(xs, x)
k = (x - xs[i-1])/(xs[i] - xs[i-1])
y = k*(ys[i]-ys[i-1]) + ys[i-1]
return y
For testing:
xs = [1, 2, 4, 8, 16, 32, 64, 128, 256]
ys = [0, 1, 2, 3, 4, 5, 6, 7, 8]
i_xs = [i/1000-500 for i in range(1000000)]
start_time = time.time()
ys = [lookup(x, xs, ys) for x in i_xs]
print("%s secs" % (time.time() - start_time))
I get around 1.8secs.
Let's go through it line by line.
ops1 = {
"1": print("1"), # this value is actually the value returned by print
"2": print("2"),
"4": print("4")
} # when the dictionary is created, everything in it is evaluated,
# so it executes those print calls
# that's why you see all three printed
lis = [1, 2, 3]
for x in range(0, len(lis)-1): # this includes 0 and 1
if (lis[x] in ops1.keys()): # lis[0] is 1 and lis[1] is 2
ops1.get(x) # look for 0 and then 1 in the dictionary
Now let's fix it.
ops1 = {
"1": lambda: print("1"), # this value is now a FUNCTION, which you can call later
"2": lambda: print("2"),
"4": lambda: print("4")
} # when the dictionary is created, everything in it is evaluated, but the functions are not called
lis = [1, 2, 3]
for x in range(len(lis)): # goes through 0, 1, and 2
if lis[x] in ops1: # lis[0] is 1, lis[1] is 2, lis[2] is 3
ops1.get(str(lis[x]), print)() # look for '1', '2', and '3',
# then call their values
You could improve that last loop like this:
for x in lis:
ops1.get(str(x), print)()
So, first we look up the key '1'. That corresponds to a function. Then we call the function. This prints the string '1\n'. Then we look up the key '2'. That corresponds to a function. Then we call the function. This prints the string '2\n'. Then we look up the key '3'. This is not present in the dictionary, so it returns a default function. Then we call this default function. This prints the string '\n'.
Try this(roughly)
Problem is you are assigning the results of calling print i.e. None, to your lookup table. You want to assign the function definition instead, wo calling it. Then call the lookedup function in your loop.
def print1():
print(1)
Ops = {
"1" : print1,
"2" : another_func,
...
}
for key in ["1","2","3"]:
opskey