3

What would be the fastest way to break a list of random numbers into sets of two, alternately flipping every pair? For example:

pleatedTuple=(0, 1, 3, 2, 4, 5, 7, 6, 8, 9)

What I want in one operation:

flatPairs=[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

Items will be random single digits, I only made them sequential for readability. I need to do thousands of these in a run so speed is priority. Python 3.6.4.

Thank you for any ideas, I’m stumped by this one.

Gnarlodious
  • 304
  • 2
  • 4
  • 16
  • Take a look at [How do you split a list into evenly sized chunks?](https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks/16970117). This helps with the first part of breaking your iterable. – Aristu Mar 29 '18 at 02:42
  • 3
    "Thousands of these" is a very small number to a computer. Any reasonable implementation will take a tiny fraction of a second. So is speed _really_ a priority? – abarnert Mar 29 '18 at 02:51

6 Answers6

5

Option 1
As long as this is pairs we're talking about, let's try a list comprehension:

flatPairs = [
    [x, y] if i % 2 == 0 else [y, x] for i, (x, y) in enumerate(
        zip(pleatedTuple[::2], pleatedTuple[1::2])
    )
]

You can also build this from scratch using a loop:

flatPairs = []
for i, (x, y) in enumerate(zip(pleatedTuple[::2], pleatedTuple[1::2])):
    if i % 2 == 0:
        flatPairs.append([x, y])
    else:
        flatPairs.append([y, x])

print(flatPairs)
[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

Option 2
Use Ned Batchelder's chunking subroutine chunks and flip every alternate sublist:

# https://stackoverflow.com/a/312464/4909087
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]

Call chunks and exhaust the returned generator to get a list of pairs:

flatPairs = list(chunks(pleatedTuple, n=2))

Now, reverse every other pair with a loop.

for i in range(1, len(flatPairs), 2):
    flatPairs[i] = flatPairs[i][::-1]

print(flatPairs)
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

Note that in this case, the result is a list of tuples.


Performance
(of my answers only)
I'm interested in performance, so I've decided to time my answers:

# Setup
pleatedTuple = tuple(range(100000))    

# List comp
21.1 ms ± 1.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)    
# Loop
20.8 ms ± 1.71 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# chunks
26 ms ± 2.19 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

For more performance, you may replace the chunks generator with a more performant alternative:

flatPairs = list(zip(pleatedTuple[::2], pleatedTuple[1::2]))

And then reverse with a loop as required. This brings the time down considerably:

13.1 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

A 2x speedup, phew! Beware though, this isn't nearly as memory efficient as the generator would be...

cs95
  • 379,657
  • 97
  • 704
  • 746
  • 1
    Your option 2—breaking the problem into two pieces and writing the dead-simple code for both of them—is probably the ideal solution as far as the OP being able to understand and maintain his code. Unless the 9.4ms it takes to do 10000 of them really is too slow for him… – abarnert Mar 29 '18 at 02:55
  • @abarnert Of course, when I decided to mention performance, it wasn't to 1UP the other answers, merely for information purposes only. Though I like your numpy answer. – cs95 Mar 29 '18 at 03:03
  • The “performant alternative” is the best solution, significantly faster than all others. Commndations to all, some impressive Python skills! – Gnarlodious Apr 04 '18 at 03:11
2

You can use iter with list slicing:

pleatedTuple=(0, 1, 3, 2, 4, 5, 7, 6, 8, 9)
new_data = [list(pleatedTuple[i:i+2][::-1]) if c%2 != 0 else list(pleatedTuple[i:i+2]) for i, c in zip(range(0, len(pleatedTuple), 2), range(len(pleatedTuple)))]

Output:

[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
2

You can use the standard grouping idiom and zip it with the length:

>>> by_pairs_index = zip(range(len(pleatedTuple)), *[iter(pleatedTuple)]*2)
>>> [[b, a] if i%2 else [a,b] for i,a,b in by_pairs_index]
[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

If performance is critical, you may consider other approaches.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
2

Option 1

Using map and reversed and slice assignment.

p = list(map(list, zip(pleatedTuple[::2], pleatedTuple[1::2])))
p[1::2] = map(list, map(reversed, p[1::2]))

p

[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

Slight variation

p = list(map(list, zip(pleatedTuple[::2], pleatedTuple[1::2])))
p[1::2] = (x[::-1] for x in p[1::2])

p

[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

Option 2

def weird(p):
    return [[p[2 * i + i % 2], p[2 * i + (i + 1) % 2]] for i in range(len(p) // 2)]

weird(pleatedTuple)

[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

More generic

def weird(p, k):
    return [list(p[i*k:(i+1)*k][::(i-1)%2*2-1]) for i in range(len(p) // k)]

weird(pleatedTuple, 2)

[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

weird(pleatedTuple * 3, 3)

[[0, 1, 3],
 [5, 4, 2],
 [7, 6, 8],
 [1, 0, 9],
 [3, 2, 4],
 [6, 7, 5],
 [8, 9, 0],
 [2, 3, 1],
 [4, 5, 7],
 [9, 8, 6]]
piRSquared
  • 285,575
  • 57
  • 475
  • 624
1

You can do this in numpy:

>>> pleatedTuple=(0, 1, 3, 2, 4, 5, 7, 6, 8, 9)
>>> pleatedArray = np.array(pleatedTuple)
>>> flat2D = pleatedArray.reshape(5,2)
>>> flat2D[1::2] = np.flip(pleated2D[1::2], axis=1)

Of course this is probably going to waste as much time converting between tuples and arrays as it saves doing a tiny loop in numpy instead of Python. (From a quick test, it takes about twice as long as Coldspeed's Option 2 at the example size, and doesn't catch up until you get to much, much longer tuples, and you have a bunch of little tuples, not a few giant ones.)

But if you're concerned with speed, the obvious thing to do is put all of these thousands of pleated tuples into one giant numpy array and do them all at once, and then it will probably be a lot faster. (Still, we're probably talking about saving milliseconds for thousands of these.)

abarnert
  • 354,177
  • 51
  • 601
  • 671
-1

What i use is :

pleatedTuple=(0, 1, 3, 2, 4, 5, 7, 6, 8, 9)


for i in range(0,len(pleatedTuple),2):
      print(pleatedTuple[i:i+2])

Is this what you are looking for ?

(0, 1)
(3, 2)
(4, 5)
(7, 6)
(8, 9)
Aaditya Ura
  • 12,007
  • 7
  • 50
  • 88