By Zizzipupp


2019-10-09 14:04:21 8 Comments

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]
[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?

6 comments

@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[0], l1[:-1][0] and l1[1:][0] 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("answer is ",v2[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

answer is  b
interval is [1000, 5000]

@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[0] < value < chunk[1]:
            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[0]:
            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

@Saleem Ali 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

@Paul M. 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[0] <= val <= interval[1]:
            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

Related Questions

Sponsored Content

27 Answered Questions

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

  • 2008-09-10 06:20:11
  • Ray
  • 2846825 View
  • 3234 Score
  • 27 Answer
  • Tags:   python list

60 Answered Questions

[SOLVED] How do you split a list into evenly sized chunks?

27 Answered Questions

[SOLVED] How do I concatenate two lists in Python?

7 Answered Questions

[SOLVED] How do I get the number of elements in a list?

  • 2009-11-11 00:30:54
  • y2k
  • 3258758 View
  • 1934 Score
  • 7 Answer
  • Tags:   python list

20 Answered Questions

18 Answered Questions

[SOLVED] How to clone or copy a list?

40 Answered Questions

[SOLVED] How to make a flat list out of list of lists?

30 Answered Questions

[SOLVED] Finding the index of an item in a list

  • 2008-10-07 01:39:38
  • Eugene M
  • 3867341 View
  • 3163 Score
  • 30 Answer
  • Tags:   python list indexing

11 Answered Questions

[SOLVED] Getting the last element of a list

  • 2009-05-30 19:28:53
  • Janusz
  • 2103531 View
  • 2058 Score
  • 11 Answer
  • Tags:   python list indexing

21 Answered Questions

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

  • 2010-07-08 19:31:22
  • duhhunjonn
  • 4316827 View
  • 3473 Score
  • 21 Answer
  • Tags:   python directory

Sponsored Content