from bisect import bisect_left
from collections import defaultdict
class Cache(defaultdict):
def __init__(self, method):
self.method = method
def __missing__(self, key):
return self.method(key)
def memoized(f):
cache = Cache(lambda args: f(*args))
def ret(*args):
return cache[args]
return ret
class MappedList(object):
def __init__(self, method, input):
self.method = memoized(method)
self.input = input
def __iter__(self):
return iter((self.method(x) for x in self.input))
def __len__(self):
return len(self.input)
def __getitem__(self, i):
return self.method(i)
def find_closest(data, target, key = lambda x:x):
s = sorted(data)
evaluated = MappedList(key, s)
index = bisect_left(evaluated, target)
if index == 0:
return data[0]
if index == len(data):
return data[index-1]
if target - evaluated[index-1] <= evaluated[index] - target:
return data[index-1]
else:
return data[index]
count = 0
def calc(x):
global count
count += 1
return x
count = 0
data = [0, 2, 6, 10, 15, 17]
print(list(MappedList(lambda x: x+1, data)))
count = 0
result = find_closest(data, 10, calc)
assert result == 10
print(count)
count = 0
result = find_closest(data, 9, calc)
assert result == 10
print(count)
count = 0
result = find_closest(data, 7, calc)
assert result == 6
print(count)
count = 0
result = find_closest(data, -1, calc)
assert result == 0
print(count)
count = 0
result = find_closest(data, 0, calc)
assert result == 0
print(count)
count = 0
result = find_closest(data, 17, calc)
assert result == 17
print(count)
count = 0
result = find_closest(data, 19, calc)
assert result == 17
print(count)
ZnJvbSBiaXNlY3QgaW1wb3J0IGJpc2VjdF9sZWZ0CmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IGRlZmF1bHRkaWN0CgpjbGFzcyBDYWNoZShkZWZhdWx0ZGljdCk6CglkZWYgX19pbml0X18oc2VsZiwgbWV0aG9kKToKCQlzZWxmLm1ldGhvZCA9IG1ldGhvZAoJZGVmIF9fbWlzc2luZ19fKHNlbGYsIGtleSk6CgkJcmV0dXJuIHNlbGYubWV0aG9kKGtleSkKCQkKZGVmIG1lbW9pemVkKGYpOgoJY2FjaGUgPSBDYWNoZShsYW1iZGEgYXJnczogZigqYXJncykpCglkZWYgcmV0KCphcmdzKToKCQlyZXR1cm4gY2FjaGVbYXJnc10KCXJldHVybiByZXQKCQpjbGFzcyBNYXBwZWRMaXN0KG9iamVjdCk6CglkZWYgX19pbml0X18oc2VsZiwgbWV0aG9kLCBpbnB1dCk6CgkJc2VsZi5tZXRob2QgPSBtZW1vaXplZChtZXRob2QpCgkJc2VsZi5pbnB1dCA9IGlucHV0CglkZWYgX19pdGVyX18oc2VsZik6CgkJcmV0dXJuIGl0ZXIoKHNlbGYubWV0aG9kKHgpIGZvciB4IGluIHNlbGYuaW5wdXQpKQoJZGVmIF9fbGVuX18oc2VsZik6CgkJcmV0dXJuIGxlbihzZWxmLmlucHV0KQoJZGVmIF9fZ2V0aXRlbV9fKHNlbGYsIGkpOgoJCXJldHVybiBzZWxmLm1ldGhvZChpKQoKZGVmIGZpbmRfY2xvc2VzdChkYXRhLCB0YXJnZXQsIGtleSA9IGxhbWJkYSB4OngpOgoJcyA9IHNvcnRlZChkYXRhKQoJZXZhbHVhdGVkID0gTWFwcGVkTGlzdChrZXksIHMpCglpbmRleCA9IGJpc2VjdF9sZWZ0KGV2YWx1YXRlZCwgdGFyZ2V0KQoJaWYgaW5kZXggPT0gMDoKCQlyZXR1cm4gZGF0YVswXQoJaWYgaW5kZXggPT0gbGVuKGRhdGEpOgoJCXJldHVybiBkYXRhW2luZGV4LTFdCglpZiB0YXJnZXQgLSBldmFsdWF0ZWRbaW5kZXgtMV0gPD0gZXZhbHVhdGVkW2luZGV4XSAtIHRhcmdldDoKCQlyZXR1cm4gZGF0YVtpbmRleC0xXQoJZWxzZToKCQlyZXR1cm4gZGF0YVtpbmRleF0KCmNvdW50ID0gMAoKZGVmIGNhbGMoeCk6CglnbG9iYWwgY291bnQKCWNvdW50ICs9IDEKCXJldHVybiB4Cgpjb3VudCA9IDAKCmRhdGEgPSBbMCwgMiwgNiwgMTAsIDE1LCAxN10KCnByaW50KGxpc3QoTWFwcGVkTGlzdChsYW1iZGEgeDogeCsxLCBkYXRhKSkpCmNvdW50ID0gMApyZXN1bHQgPSBmaW5kX2Nsb3Nlc3QoZGF0YSwgMTAsIGNhbGMpCmFzc2VydCByZXN1bHQgPT0gMTAKcHJpbnQoY291bnQpCmNvdW50ID0gMApyZXN1bHQgPSBmaW5kX2Nsb3Nlc3QoZGF0YSwgOSwgY2FsYykKYXNzZXJ0IHJlc3VsdCA9PSAxMApwcmludChjb3VudCkKY291bnQgPSAwCnJlc3VsdCA9IGZpbmRfY2xvc2VzdChkYXRhLCA3LCBjYWxjKQphc3NlcnQgcmVzdWx0ID09IDYKcHJpbnQoY291bnQpCmNvdW50ID0gMApyZXN1bHQgPSBmaW5kX2Nsb3Nlc3QoZGF0YSwgLTEsIGNhbGMpCmFzc2VydCByZXN1bHQgPT0gMApwcmludChjb3VudCkKY291bnQgPSAwCnJlc3VsdCA9IGZpbmRfY2xvc2VzdChkYXRhLCAwLCBjYWxjKQphc3NlcnQgcmVzdWx0ID09IDAKcHJpbnQoY291bnQpCmNvdW50ID0gMApyZXN1bHQgPSBmaW5kX2Nsb3Nlc3QoZGF0YSwgMTcsIGNhbGMpCmFzc2VydCByZXN1bHQgPT0gMTcKcHJpbnQoY291bnQpCmNvdW50ID0gMApyZXN1bHQgPSBmaW5kX2Nsb3Nlc3QoZGF0YSwgMTksIGNhbGMpCmFzc2VydCByZXN1bHQgPT0gMTcKcHJpbnQoY291bnQpCgoK