0

I am trying to create a list with tuples as elements. Each tuple has 4 integers. The 2 first integers are a result of zipping 2 ranges, while the other 2 from 2 different ones.

I am using this code to create the tuples and the final list, which is derived from the cartesian product, as seen here: Get the cartesian product of a series of lists?

import itertools
first_range = list(zip((10**exp for exp in range(0,7)),(10**exp for exp in range(1,8))))
second_range = list(zip((5*10**exp if exp != 1 else 10**2 for exp in range(1,8)),(5*10**exp for exp in range(2,9))))
final_list = list(itertools.product(first_range,second_range))

The issue with this code is that the final results looks like this:

[((1, 10), (100, 500)), ((1, 10), (500, 5000)), ((1, 10), (5000, 50000)), ((1, 10), (50000, 500000)), ((1, 10), (500000, 5000000)), ((1, 10), (5000000, 50000000)), ...

Where each list element is a tuple containing 2 other tuples, while what I want is this:

[(1, 10, 100, 500), (1, 10, 500, 5000), (1, 10, 5000, 50000), (1, 10, 50000, 500000), (1, 10, 500000, 5000000), (1, 10, 5000000, 50000000), ...

i.e. each list element is a tuple containing 4 integers.

Any ideas would be appreciated. Must be working on python3. EDIT: Updated the non-working parts of the code thanks to ShadowRanger's comments

CnewbieWannabePro
  • 124
  • 1
  • 1
  • 14
  • 1
    Side-note: `exp is not 1` is wrong. It may work 99% of the time on CPython thanks to the small `int` cache, but that's an implementation detail you shouldn't rely on. `is`/`is not` is only for language guaranteed singletons (e.g. `None`, `NotImplemented`), not `int`s. You want `exp != 1`. – ShadowRanger Mar 04 '20 at 14:14
  • @ShadowRanger Thanks for the info. – CnewbieWannabePro Mar 04 '20 at 14:16

2 Answers2

1

So, I was certain I was close to the answer once I posted this question, but I did not realize I was this close. The way to fix the issue with the extra tuples is:

import itertools
first_range = zip((10**exp for exp in range(7)),(10**exp for exp in range(1,8)))
second_range = zip((5*10**exp if exp != 1 else 10**2 for exp in range(1,8)),(5*10**exp for exp in range(2,9)))
iterator_of_tuples = itertools.product(first_range,second_range)

# the next line solves my issue
final_list = [x + y for x, y in iterator_of_tuples]

What I did was a simple merging of tuples: How to merge two tuples in Python? . Not sure why I didnt think of it earlier

Edit: Updated the answer based on ShadowRanger's input

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
CnewbieWannabePro
  • 124
  • 1
  • 1
  • 14
  • 1
    This code doesn't work; `itertools.product` doesn't return a `list`, so indexing it doesn't work (`list_with_tuples` is a misnomer; it's actually `iterator_of_tuples`). You could make it work, and run more efficiently, with just: `final_list = [x + y for x, y in list_with_tuples]` (or `final_list = list(itertools.starmap(operator.concat, list_with_tuples))` to push all the work to the C layer). – ShadowRanger Mar 04 '20 at 14:12
  • @ShadowRanger you are correct I forgot to add something in my question. It is fixed now. Also yes, I always forget to get 2 variables in the for loop – CnewbieWannabePro Mar 04 '20 at 14:15
  • 1
    K. Just so you know, `for i in range(len(seq))` is an anti-pattern in Python; people coming from C and other languages without native iteration use it, but it's slow and ugly in Python. There is almost always a better way of doing it, with direct iteration (as in my example), `zip`, `enumerate`, or if none of those are enough, one of the `itertools` APIs. The only real exception I know of is iterating over slices of a sequence, where the non-`range` based solution is significantly uglier, and only worth it if you need to handle arbitrary iterables (ones where slicing isn't an option). – ShadowRanger Mar 04 '20 at 14:18
  • It is basically my answer with 1 more useless line of code – Marco Zamboni Mar 05 '20 at 12:52
  • @MarcoZamboni your answer produces a list with 7 elements, which is incorrect. – CnewbieWannabePro Mar 06 '20 at 15:39
0

Your expected output is not the cartesian product of the two ranges.

If you want your expected output something like this will work:

final_list = [(*x, *y) for x, y in zip(first_range, second_range)]
Marco Zamboni
  • 224
  • 1
  • 9
  • it is not the cartesian product in terms that it does not distinguish between (a,b) and (b,a), correct? If so, that is ok, I get the `itertools` library is doing a set() in the end of the product. – CnewbieWannabePro Mar 04 '20 at 13:59
  • @CnewbieWannabePro: `itertools` doesn't do a thing with `set`s, I have no idea why you think it does. – ShadowRanger Mar 04 '20 at 14:10
  • @CnewbieWannabePro Nope, it is not the cartesian product because you want to merge the tuples – Marco Zamboni Mar 05 '20 at 12:51