2

I have to write a function which gets three arguments. lower, higher an cache. Lower and higher gives a range where a new list is created with. This part is this code:

def one_range(lower, higher, cache): list1 = [] for i in range(lower,higher): list1.append(i) return list1 

If "range" is called twice with the same arguments, both times the same list should be returned. The second time the list is not generated again, but reused. How can i do that?

Edited the orinal function

4
  • 3
    don't call your function range. You won't be able to call the original range from inside it... Commented Nov 5, 2018 at 14:41
  • 4
    don't call your list list too... Commented Nov 5, 2018 at 14:41
  • Thanks, corrected the code :) Commented Nov 5, 2018 at 14:52
  • 1
    Caching a mutable object seems a bit dangerous - if any caller of your function modifies the returned list, future callers using the same parameters will get the modified version! Commented Nov 5, 2018 at 14:59

2 Answers 2

4

Assuming that cache is a dictionary, you can make a tuple from the other parameters and see whether that tuple is in the dict. If it is, return the value from the dict, otherwise calculate the value and store it in the dict before returning it. You might also provide a default value for cache so the function can also be used without it.

def one_range(lower, higher, cache=None): if cache is not None and (lower, higher) in cache: return cache[(lower, higher)] lst = [] for i in range(lower,higher): lst.append(i) if cache is not None: cache[(lower, higher)] = lst return lst 

Example:

c = {} x = one_range(2, 4, c) y = one_range(1, 4, c) z = one_range(2, 4, c) print(x is z) # True print(c) # {(2, 4): [2, 3], (1, 4): [1, 2, 3]} 

That's a lot of boiler plate code, though, cluttering the function. In practice, this can be done much easier with a function decorator. If you can not use functools.lru_cache, you can implement your own memoization decorator in just a few lines of code:

def memo(f): f.cache = {} def _f(*args, **kwargs): if args not in f.cache: f.cache[args] = f(*args, **kwargs) return f.cache[args] return _f 

Then use it on your function, without the no longer needed cache parameter:

@memo def one_range(lower, higher): lst = [] for i in range(lower,higher): lst.append(i) return lst 
Sign up to request clarification or add additional context in comments.

4 Comments

good. Only one thing concerns me: we return a list, which can be modified by the caller, changing the value for all previous callers, which can have unexpected results (oh just saw jasonharper comment which states the same)
@Jean-FrançoisFabre Good point. In the first approach, one could do return list(cache[(lower, higher)]); using a generic @memo decorator it would be a bit more involved.
one other trick would be to do def one_range(lower, higher, cache=dict()): to use a mutable default argument as storage. For once it would be a good use of that "feature". The drawback is that the cache cannot be cleared that way
@Jean-FrançoisFabre That's indeed a good idea, but given it's unintuitive behaviour can also be very very irritating, especially when new to Python, or even considered a bug by a teacher.
3

Assuming that your arguments are hashable, you can do this with functools.lru_cache() (Python 3.2+):

import functools @functools.lru_cache(maxsize=128) def mrange(lower, higher): print('mrange was called') res = [] for i in range(lower, higher): res.append(i) return res 

Smaller points:

  • A dictionary is used to cache results, hence why both positional and keyword arguments must be hashable.
  • Try to avoid naming objects with names that shadow (mask) existing built-in names, such as range and list.
  • Use mrange.cache_clear() to clear the cache.

Example:

>>> mrange(1, 10) mrange was called [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> mrange(1, 10) [1, 2, 3, 4, 5, 6, 7, 8, 9] 

2 Comments

Thank you for your answer. Unfortunately this is a university task and we can't use any additional tools.
How do you access the cached dictionary to see the elements in it?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.