[SOLVED] Pyhonic use of if statements for inequalities applied to chunked list

Given the following lists in Python:

l1 = [0,1000,5000,10000,20000,30000,40000,50000] #8 values, 7 intervals
v1 = [1,2,3,4,5,6,7] #7 values
v2 = [a,b,c,d,e,f,g] #7 letters

I want to check whether a number is contained in any interval, and return another value based on which interval this is.

Example:

1. My test value is 1111
2. It belongs to the second interval: 1000 < 1111 < 5000
3. Hence I need to return b

I would address the problem by:

1. Creating chunks of l1
2. Iterating over each chunk
3. Writing one if statement for each chunk
4. Return the letter corresponding to the correct chunk

I can create chunks of it by looking at every consecutive pair of numbers:

def chunker(seq, size):
return (seq[pos:pos + size] for pos in range(0, len(seq)))

for group in chunker(l1, 2):
print(group)

This returns:

[0, 1000]
[1000, 5000]
[5000, 10000]
[10000, 20000]
[20000, 30000]
[30000, 40000]
[40000, 50000]


My questions:

1. Is there a Pythonic way of writing those if statements instead of having one for each chunk? What if I have 1,000 chunks?
2. If there is, how to deal with the last chunk without creating a specific case for it? @r.ook 2019-10-09 14:49:50

You can just use a generator to get the result:

l1 = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']

value = 1111

result = next((v for v, i, j in zip(v2, l1[:-1], l1[1:]) if value in range(i, j)), None)

Output:

>>> result
'b'

By adding a default of None as second argument of next(), you can handle cases when not found as well:

value = -50

result = next((v for v, i, j in zip(v2, l1[:-1], l1[1:]) if value in range(i, j)), None)

>>> result
# None

If you just wanted the index, you can use enumerate instead:

result = next((v for v, (i, j) in enumerate(zip(l1[:-1], l1[1:])) if value in range(i, j)), None)

>>> result
# 1

Explanation:

The result line consists of a few parts:

next(iterator [, default])

This is a function to retrieve the next item in the iterator passed in the argument. The default serves to return a default value if a StopIteration is encountered. The iterator in question is the generator here (broken down for clarity):

(
v                                  # point 4
for v, i, j                      # point 2
in zip(v2, l1[:-1], l1[1:])    # point 1
if value in range(i, j)          # point 3
)
1. The zip function collates the lists that were passed, so that v2, l1[:-1] and l1[1:] forms into a tuple ('a', 0, 1000), and so on for each index.

2. The for v, i, j serves to extract the elements within the tuple.

3. The if value in range(i, j) serves to check that 1111 is within the range between range(0, 1000).

4. If it's a match, v is returned. If not, continue to the next iteration. @Zizzipupp 2019-10-09 14:51:32

Rather than b, it should return its index, e.g. 1. Also, it would be great if you could explain the result line :) @r.ook 2019-10-09 14:54:49

That's even simpler then, you don't even need v2 at all? next((v for v, (i, j) in enumerate(zip(l1[:-1], l1[1:])) if value in range(i, j)), None) @Albin Paul 2019-10-09 14:15:17

You can zip list l1 and l1[1:] which will make the intervals in a fashion that you need. And for some extra pointers you can incorporate binary search into this since the intervals are sorted, that I'll leave for you to optimise. The current runtime of this algorithm is O(n) you can reduce the runtime to O(log(n)).

from __future__ import print_function
l1 = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']
element = 1111
interval = []
letter = None
for index,(left,right) in enumerate(zip(l1,l1[1:])):
if left <= element <= right:
interval = [left,right]
letter = index
break
if letter:
print("interval is" ,interval)
else:
print("No interval found")

EDIT

I have added a algorithm using the bisect module in python it produces the same output but uses binary search hence it should be faster and runs in O(log(n))

from __future__ import print_function
import bisect
li = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']
element = 1111
index = bisect.bisect(li, element)
if index == 0 or index == len(li):
print("No interval found")
else:
print("answer is",v2[index - 1])
print("interval is",li[index - 1], li[index])

OUTPUT

interval is [1000, 5000] @Frank 2019-10-09 14:20:50

Fast but hard to read :-) @Albin Paul 2019-10-09 14:22:53

@Frank please tell which portion did you find hard to read. I`ll try my best to improve @Frank 2019-10-09 14:44:35

mostly "index,(left,right) in enumerate(zip(l1,l1[1:]))". enumerate a zip to integer and tuple of two integers makes me need a brain specialist :-D I'm also not a big friend of break inside a for loop. Don't guess me wrong. That is only my opinion. Code that works... works. @Albin Paul 2019-10-09 14:56:27

@Frank I have added a second algorithm an improved one if I may say so myself. @Frank 2019-10-09 14:12:43

This works only with chunks of len 2.

l1 = [0,1000,5000,10000,20000,30000,40000,50000] #8 values, 7 intervals
# v1 = [1,2,3,4,5,6,7] #7 values
v2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']  # 7 letters

def chunker(seq, size):
return (seq[pos:pos + size] for pos in range(0, len(seq)))

def chunk_with_value(list_of_chunks, value):
"""returns the chunk if the value is inside the range"""
for chunk in list_of_chunks:
if chunk < value < chunk:
return chunk

def chunk_to_letter(value_list, letter_list, chunk):
"""returns the letter based on the index of the first chunk element"""
for i, value in enumerate(l1):
if value == chunk:
return v2[i]

chunks = chunker(l1, 2)
chunk = chunk_with_value(chunks, 1111)
print(chunk)  # [1000, 5000]
print(chunk_to_letter(l1, v2, chunk))  # b @beer44 2019-10-09 14:26:52

Though other answer is useful but almost all of them is finding interval not desire output from v2

l1 = [0,1000,5000,10000,20000,30000,40000,50000]
v2 = ['a','b','c','d','e','f','g']
element = 1111
def get_interval(l1):
for index, left, right in zip(range(len(l1)), l1, l1[1:]):
if left <= element <= right:
return v2[index]

>>> print("answer is:" ,get_interval(element))
>>> answer is: b @user10987432 2019-10-09 14:17:10

Straight from the itertools recipe book:

def get_thing(value):

def pairwise(iterable):
from itertools import tee
a, b = tee(iterable)
next(b, None)
return zip(a, b)

interval_ranges = [
0,
100,
500,
1000
]

# There are four interval ranges, so three intervals.
things = [
"A", # 0-100
"B", # 100-500
"C" # 500-1000
]

for (begin, end), thing in zip(pairwise(interval_ranges), things):
if begin <= value < end: # modify this to suit your needs. Is the range inclusive/exclusive?
return thing
return None

def main():

thing = get_thing(400)
print(thing)

return 0

if __name__ == "__main__":
import sys
sys.exit(main())

Output:

B @rdas 2019-10-09 14:20:24

I believe you need to get the corresponding interval index and use that to query v2. This should do it:

l1 = [0,1000,5000,10000,20000,30000,40000,50000] #8 values, 7 intervals
v2 = ['a','b','c','d','e','f','g'] #7 letters

def intervals(l):
for i in range(len(l)-1):
yield i, l[i:i+2]

def interval_value(val, interval_list, value_list):
for i, interval in intervals(interval_list):
if interval <= val <= interval:
return value_list[i]

print(interval_value(1111, l1, v2))
print(interval_value(0, l1, v2))
print(interval_value(51000, l1, v2))
print(interval_value(40000, l1, v2))

Output:

b
a
None
f

You don't need the v1 values - you can work with the index on v2 directly

[SOLVED] How do I list all files of a directory?

• 2010-07-08 19:31:22
• duhhunjonn
• 3631710 View
• 3474 Score
• Tags:   python directory

[SOLVED] How do I check if a list is empty?

• 2008-09-10 06:20:11
• Ray Vega
• 2469256 View
• 3235 Score
• Tags:   python list

[SOLVED] Getting the last element of a list

• 2009-05-30 19:28:53
• Janusz
• 1795168 View
• 1816 Score
• Tags:   python list indexing

[SOLVED] Finding the index of an item given a list containing it in Python

• 2008-10-07 01:39:38
• Eugene M
• 3455403 View
• 2844 Score
• Tags:   python list indexing