Hanna T.
Hanna T.

Reputation: 13

Find the smallest number that is not in a list of intervals

I have to find the smallest number that is not in a list of intervals. For example, i have a list: [(2, 6), (0, 3), (9, 10)]. I have to find the smallest number by checking all numbers from 0 to 10 until i find the smallest one that is not in any of these ranges. So, the number can't be between 2 and 6 (including both), 0 and 3, 9 and 10.

I've written a function and i can't get the right answer. I always get all first numbers that the program checks, but i just need the last one. And i don't understand why it doesn't work with return.

intervals = [(2, 6), (0, 3), (9, 10)]
def smallest_number(intervals):
   i = 0
   while True:
      for first, second in intervals:
         if i in range(first, second + 1):
            i += 1
            print(i)

print(smallest_number(intervals))

I expect the output 7, but i get a loop of 7's or numbers from 1 to 7, depending on where i put print(i).

Upvotes: 0

Views: 991

Answers (6)

Param veer
Param veer

Reputation: 1

Sort the intervals by their start (end points if the start points are equal). Then start with the first interval and keep expanding the right bound till the next interval's start point is greater than our current coverage end point.

Take (2,6),(0,3),(9,10)
Sorting: (0,3),(2,6),(9,10)
current coverage = [0,3]

since 3 < 6 and 2<3, we can increase our right point to 6 so coverage becomes [0,6] but 6 < 9, so 7 is the smallest number which isn't in any of the intervals. Also you need to check if the start point of the very first interval is > 0. In that case the answer will be 0.

Upvotes: 0

Kim
Kim

Reputation: 1686

This might be a good choice if you want a list of permitted numbers, not just the smallest.

import itertools


def flatten(list_of_sublists):
    return itertools.chain.from_iterable(list_of_sublists)


def excluded_numbers(intervals):
    excluded_ranges = [range(lower, upper + 1) for lower, upper in intervals]
    excluded_numbers = set(flatten(excluded_ranges))
    return excluded_numbers


def permitted_numbers(intervals):
    excluded = excluded_numbers(intervals)
    max_excluded = max(excluded)
    candidates = set(range(max_excluded + 2))
    return candidates - excluded


def smallest_number(intervals):
    return min(permitted_numbers(intervals))

Upvotes: 0

Mikhail
Mikhail

Reputation: 1

My solution with protection from case when there is no such minimal number.

checking = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
intervals = [(2, 6), (0, 3), (9, 10)]
min_number = None

unique = set()
for left, right in intervals:
    unique.update(range(left, right + 1))

subset = set(checking) - unique
if subset:
    min_number = min(subset)

print(min_number)

Upvotes: 0

yatu
yatu

Reputation: 88226

Here's one approach:

from itertools import chain
intervals = [(2, 6), (0, 3), (9, 10)]

ranges = chain.from_iterable(range(i, j+1) for i, j in l)
min(set(range(10)).difference(ranges))
# 7

Details

The first step creates a set from the ranges contained in the tuples using a generator comprehension and calling range with both elements from each tuple. The result is flattened using itertools.chain:

ranges = chain.from_iterable(range(i, j+1) for i, j in l)
print(list(ranges))
# {0, 1, 2, 3, 4, 5, 6, 9, 10}

Now we only need to look for the min of the difference between this set and a range up to n

diff = set(range(10)).difference(ranges)
# {7, 8}

Upvotes: 2

tobias_k
tobias_k

Reputation: 82899

Your code will print all the numbers that are in the intervals, not those that are not, before it ends in an infinite loop. Try this:

def smallest_number(intervals):
    i = 0
    while True:
        for first, second in intervals:
            if i in range(first, second + 1):
                i += 1
                break # break from inner loop
        else:
            break # break from outer loop
    return i # return number

Also, while if i in range(first, second + 1): is okay (and fast) in Python 3, I'd rather suggest using if first <= i <= second:

Or shorter, using next and any:

def smallest_number(intervals, max_ = 10):
    return next((i for i in range(max_ + 1)
                 if not any(first <= i <= second for first, second in intervals)),
                None) # None is default if none is found

Upvotes: 0

MKaras
MKaras

Reputation: 727

I don't have Python here to check, but it seems your function does not return anything, so print(smallest_number(intervals)) prints None.

def smallest_number(intervals):
   i = 0
   while True:
      for first, second in intervals:
      if i in range(first, second + 1):
          i += 1
      else:
          return i

Now print(smallest_number(intervals)) should work.

Upvotes: 0

Related Questions