1

Quick question to get your Winters bashed!

I'm looping over a range of integers and I need to check to see that they are also contained within a list of lists (mysublist)

mysublist = [
  [4,5,6],
  [8,9],
  [19,20],
]


for i in range (1, 11):
  for sub in mysublist:
    if i in sub:
      print i

Is there a more Pythonic way than doing a loop within a loop to find said values?

Ghoul Fool
  • 6,249
  • 10
  • 67
  • 125
  • 1
    You can use `itertools.chain`, if that makes you happier ;) – zvone Dec 18 '19 at 20:05
  • 4
    May I ask why you are using Python 2? – AMC Dec 18 '19 at 20:06
  • @roganjosh It doesn’t redefine i, no? – AMC Dec 18 '19 at 20:06
  • https://stackoverflow.com/questions/952914/how-to-make-a-flat-list-out-of-list-of-lists to flatten the list and check if the stuff is in there. Might as well make it a set and check if the stuff is in there – Buckeye14Guy Dec 18 '19 at 20:08
  • 2
    For-loops are perfectly pythonic. This is fine. It would be *better* to modify your algorithm, so put all the items you are testing for in a `set` so that your algorithm becomes *efficient*. But again, for-loops are **perfectly Pythonic** – juanpa.arrivillaga Dec 18 '19 at 20:13
  • I would think a loop inside a loop using list-comprehension would be more Pythonic: and simplest i.e. print [i for sublist in mysublist for i in sublist if i in range(11)] – DarrylG Dec 18 '19 at 20:39
  • For-loop vs list comprehension is generally a matter of taste, and the key issue is readability – juanpa.arrivillaga Dec 18 '19 at 21:06
  • @AlexanderCécile Because I'm the Ghost of Christmas Past – Ghoul Fool Dec 18 '19 at 21:20
  • @GhoulFool Definitely Christmas _Past_, yes. Somehow that answer leaves me know less than before. – AMC Dec 19 '19 at 00:44
  • @AMC, I'm actually s-l-o-w-l-y updating all my scripts to Python 3.x, but I'm very aware of using external libraries instead of learning how to solve the problem at hand. Also, Autodesk Maya still uses 2.7 and *might* upgrade next year. Wahoooo! – Ghoul Fool Dec 19 '19 at 08:19

6 Answers6

2

Like others have said, you can use set.intersection or just a set comprehension and utilize itertools.chain:

subs = set(chain(*mysublist))
{i for i in subs if i in range(1, 11)}

This is likely less efficient than the other approach although may be more readable and straight forward for some.

Just FYI if you’re just using a range to filter the sub lists then why not just use a conditional check?

{i for i in chain(*mysublist) if 0 < i < 11}

This still results in the same output

Jab
  • 26,853
  • 21
  • 75
  • 114
1

You could use set, reduce, and concat:

mysublist = [
  [4,5,6],
  [8,9],
  [19,20],
]
import operator
import functools 
print({*range(1,11)}.intersection(set(functools.reduce(operator.concat, mysublist))))

# or : print(set(range(1,11))}.intersection(set(functools.reduce(operator.concat, mysublist))))

{4, 5, 6, 8, 9}

concat and reduce will flatten your nested lists. Then you can use setA.intersection(setB) to find the common elements.

*NOTE: kaya3 mentions that itertools.chain is faster for large inputs

JacobIRR
  • 8,545
  • 8
  • 39
  • 68
  • 1
    nums_to_search is not used? Also, `{*range(1, 11)}` would be mmore pythonic to geerate the set i think. – Grimmauld Dec 18 '19 at 20:11
  • 2
    Why is `{*range(1, 11)}` more Pythonic than `set(range(1, 11))`? *Explicit is better than implicit.* – kaya3 Dec 18 '19 at 20:14
  • 2
    By the way, `itertools.chain.from_iterable(...)` is more efficient than `reduce(operator.concat, ...)`. The latter is unnecessarily O(n^2). – kaya3 Dec 18 '19 at 20:17
  • @kaya3 - https://stackoverflow.com/a/39493960/4225229 seems to suggest the opposite. Does the performance vary with input size? – JacobIRR Dec 18 '19 at 20:22
  • 1
    Regarding "pythoninc" - I can never tell if people mean "following the PEPs" or "using the maximum number of magic operators" – JacobIRR Dec 18 '19 at 20:27
  • 3
    @JacobIRR That linked answer is comparing with very small inputs. For 1000 lists of length 1000, I get `list(chain.from_iterable(...))` being about 70 times faster than `reduce`. The reason is that `chain` has a linear time complexity, but `reduce(concat, ...)` is quadratic. – kaya3 Dec 18 '19 at 20:39
  • 2
    `reduce(concat, ..)` is definitely not pythonic, and is unnecessarily inefficient. Pythonic simply means following python idioms. And PEP8 is part of that, sure, but not all. It doesn't mean trying to cram everything into a single line, it can easily mean the opposite – juanpa.arrivillaga Dec 18 '19 at 21:03
1
>>> {*range(1, 11)} & set().union(*mysublist)
{4, 5, 6, 8, 9}
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
0

filter(lambda i: any(map(lambda sub: i in sub, mysublist)), range(1, 11)) would be pretty unpythonic but a one-liner to do the job. To print like you do above use print with sep:

mysublist = [
  [4,5,6],
  [8,9],
  [19,20],
]


print(*filter(lambda i: any(map(lambda sub: i in sub, mysublist)), range(1, 11)), sep="\n")

There are better and shorter solutions with sets. Use those instead.

Grimmauld
  • 312
  • 2
  • 9
  • for python 2 use `print("\n".join(...))` – Grimmauld Dec 18 '19 at 20:09
  • 2
    From the (ex-) Benevolent Dictator For Life himself: https://www.artima.com/weblogs/viewpost.jsp?thread=98196. None of this is pythonic – roganjosh Dec 18 '19 at 20:10
  • 3
    This is definitely not very pythonic. Pythonic doesn't mean "stuff everything into a single, unreadable line". As for `filter`/`map`, you should use a list-comprehension. Guido himself wanted to remove `filter` and `map` from the standard library in Python 3, but alas, they remain. – juanpa.arrivillaga Dec 18 '19 at 20:12
  • This is nowhere near pythonic, `map`, `filter` and the rest should have been killed a decade ago.... – AMC Dec 19 '19 at 00:49
0

You actually need to intersect range(11) with values taken from concatenation of mysublist. Why not to translate this simple idea into this one-liner:

result = set(range(11)) & set(itertools.chain(*mysublist))

or

result = set(range(11)) & set(itertools.chain.from_iterable(mysublist))

You can list your result with list(result) if you need it.

mathfux
  • 5,759
  • 1
  • 14
  • 34
0

Personally, I wouldn't modify it much.

Here is a small modification with the any method to combine the second for loop and the if query:

mysublist = [
  [4,5,6],
  [8,9],
  [19,20],
]

for i in range (1, 11):
    if any(i in y for y in mysublist):
        print i