2

I have a sorted list and a range contains multiple lists:

>>>  n= [10, 20, 30, 40]
>>>  m= [[1, 20], [21, 30]]

What I am trying to do is to check if all the elements of the n list are within either of the existing ranges in m or not. For instance, from the above example, 40 is not within any of the ranges.

I tried to extend the answer to the question in the following post, but seems it is not working.

Checking if all elements of a List of Lists are in another List of Lists Python

is_there = set(tuple(x) for x in [n]).issubset(tuple(x) for x in m)
Alex Man
  • 457
  • 4
  • 19

3 Answers3

6

You should go through each element in n and check if it's in the range of each list of m. Assuming you are only working with ints:

[any(x in range(r[0], r[1]) for r in m) for x in n]

If you want to include the end of your range, just add 1:

[any(x in range(r[0], r[1]+1) for r in m) for x in n]
busybear
  • 10,194
  • 1
  • 25
  • 42
  • If the requirement is literally as stated ("check if all the elements of the `n` list are within ...") I'd use `all(any(x in range(...) for r in m) for x in n)` rather than instantiate the new list. And at least consider pre-creating the ranges, that's likely to be an improvement if n is long-ish. But basically agreed. – Peter DeGlopper Jun 10 '19 at 22:09
  • What if the int assumption is relaxed? for instansce, for this case: n=[14.0, 17.5, 29.5, 49.0, 49.5] and m= [[12, 22], [42, 52]]. I just want to return a boolean to check if all these 4 elements exist withing any of the ranges in m. – Alex Man Jun 10 '19 at 22:24
  • If either list may contain non-integers, you can't use `range()` - `range` objects only cover ints. In that case, replace the `x in range(r[0], r[1])` part with `r[0] <= x <= r[1]`. Or use `<` if that's the semantics you intend. As shown in https://stackoverflow.com/a/56533867/2337736 above. That's probably actually faster than making `range`s at all. – Peter DeGlopper Jun 10 '19 at 22:50
3

The simple approach is to check all the elements:

items = [10, 20, 30, 40]
ranges = [[1, 20], [21, 30]]

result = all(any(low <= i <= high for low, high in ranges) for i in items)

For fun, you can make the containment check a bit different by using actual range objects:

range_objects = [range(low, high + 1) for low, high in ranges]
filtered_items = all(any(i in r for r in range_objects) for i in items)

If you wanted to get the matching items:

good = [i for i in items if any(low <= i <= high for low, high in ranges)]

You could also get the bad elements instead:

bad = [i for i in items if all(i < low or i > high for low, high in ranges)]

That way, your original result is just not bad.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
1

Since you said, "a sorted list", you can use the following logic of min and max. The outside will be True if any of the elements in n will be outside the given ranges. It will be False, if none of the elements is outside the ranges

n= [10, 20, 30, 40] # < as per you, this is sorted
m= [[1,20], [21,30]]

outside = any([(min(n) < i[0] and max(n)> i[1]) for i in m])
# True

Edit Answering the test case asked by @Peter DeGlopper in the comment below

m = [[1, 20], [31, 40]]
n = [10, 20, 25, 30, 40]
outside = any([(l < i < r for i in n) for l, r in m])
# True
Sheldore
  • 37,862
  • 7
  • 57
  • 71
  • I finally got what you're saying... Sorry – Mad Physicist Jun 10 '19 at 22:09
  • Suppose `m = [[1, 20], [31, 40]]` and `n = [10, 20, 25, 30, 40]`. Won't this find that both the min and max of `n` appear in at least one range and incorrectly report all of `n` does? This only works if there are no gaps between `m`'s ranges. – Peter DeGlopper Jun 10 '19 at 22:54
  • @PeterDeGlopper : Thanks. You are write. This is one exception. I have edited my answer to address your test case – Sheldore Jun 10 '19 at 23:04
  • Unfortunately, your revised solution always returns `true` for a non-empty `m` - the inner list `[(l < i < r for i in n) for l, r in m]` returns a list of one generator expression for every range in `m`. Try testing with `n = [10, 20, 25, 30, 40]` and `m = [[1, 100]]`. To do it right, I think you need the `all(any())` pattern shown in Mad Physicist's answer. Or invert the boolean meaning as you described (`true` if any are outside the ranges) with `any(all(r >= i >= l for i in n) for l, r in m))`, that's just algebra - though I find the `all(any())` pattern more readable. – Peter DeGlopper Jun 11 '19 at 01:25
  • I will sleep now and get back to you tomorrow – Sheldore Jun 11 '19 at 01:27
  • What is the best way to reurn if an element belongs to range 1 or range 2? For instance, in above example ```m = [[1, 20], [31, 40]] and n = [10, 20, 25, 30, 40]```, ```10``` belongs to range ```[1,20]``` so we should return 1. Thanks – Alex Man Jun 12 '19 at 20:07