0

I want to permute the elements of a list according to their index modulo 3, so for example the list:

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

should be reordered into:

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

and in general:

[A0, A1, A2, A3, A4, A5, A6, A7, A8]

should become:

[A0, A3, A6, A1, A4, A7, A2, A5, A8]

I have tried to use the following code:

def arr_sort(arr, algo):
    arrtmp = arr

    arrlen = len(arr)

    if algo == 1:
        return arr

    if algo == 2:
        count = 0
        while count < (arrlen - 1):
            for index, val in enumerate(arr):
                if index % 3 == 0:
                    arrtmp[count] = val
                    count += 1
            for index, val in enumerate(arr):
                if index % 3 == 1:
                    arrtmp[count] = val
                    count += 1
            for index, val in enumerate(arr):
                if index % 3 == 2:
                    arrtmp[count] = val
                    count += 1

    return arrtmp

It's not working properly, as arr gets changed throughout the while loop, and I can't really see why. (also I know that I could do the "f index % ... bits in a for loop as well, but it should work anyways, right?)

Is there a pre-existing function that could do that instead?

miradulo
  • 28,857
  • 6
  • 80
  • 93
  • Downvote: Please check at least the basic google results before asking here. – tripleee Jul 01 '16 at 09:19
  • 1
    Possible duplicate of [Reorder Python List](http://stackoverflow.com/questions/19736234/reorder-python-list) – Essex Jul 01 '16 at 09:19
  • 1
    `random.shuffle` seems to do the trick. Either that, or I did not understand the question. – tobias_k Jul 01 '16 at 09:20
  • Yeah, [`list.sort()`](https://wiki.python.org/moin/HowTo/Sorting) among others. But what exactly is the sorting order for the first list? – dhke Jul 01 '16 at 09:20
  • @tobias_k random.shuffle() only seems to work in the first case ;-) – dhke Jul 01 '16 at 09:21
  • 1
    The desired transformation is quite clearly described, if you close voters would care to read the question, including the title. – Magnus Hoff Jul 01 '16 at 09:24
  • I've gone ahead and reworded your whole question. I hope now it's dead clear what you meant. Note that the term *sorting* refers to ordering the elements by comparing their values. You seem to want a *permutation* (i.e. a rearrangement) of the elements **without** taking into account their values... – Bakuriu Jul 01 '16 at 09:37
  • BTW: their are ways to **hack** a solution using the built-in sort anyway: `>>> sorted(range(9), key=lambda x, y=[-1]: y.__setitem__(0,y[0]+1) or y[0]%3)[0, 3, 6, 1, 4, 7, 2, 5, 8]` – Bakuriu Jul 01 '16 at 09:41

4 Answers4

5

You are reordering on the modulo of 3:

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

l_sorted = sorted(l, key=lambda x: x%3)
print(l_sorted)
# [0, 3, 6, 1, 4, 7, 2, 5, 8]

If you're reordering on the index and not on the values, then something more detailed requiring enumerate would do that:

l_sorted = [x[1] for x in sorted(enumerate(l), key=lambda x: x[0]%3)]
print(l_sorted)
# [0, 3, 6, 1, 4, 7, 2, 5, 8]
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • I think the desired ordering is based on the index, not value. I'd post that solution, but the question is apparently on hold. – Magnus Hoff Jul 01 '16 at 09:25
  • 1
    @MagnusHoff The problem is exactly what you said in your comment. You **think** that the OP meant something, but aren't sure at all. So you should **not** post an answer until the OP clarifies exactly what he's looking for. – Bakuriu Jul 01 '16 at 09:27
3

Another one based on index is

l_sorted = sum([l[x::3] for x in range(3)],[])
percusse
  • 3,006
  • 1
  • 14
  • 28
2

Let a = [0,1,2,3,4,5,6,7,8].

a[::3] will give you every third element: [0, 3, 6]

a[n::3] will give you every third element starting at n, so a[1::3] becomes [1, 4, 7] and a[2::3] becomes [2, 5, 8].

Concatenate these lists with +: a[0::3] + a[1::3] + a[2::3] evaluates to [0, 3, 6, 1, 4, 7, 2, 5, 8], as specified.

Magnus Hoff
  • 21,529
  • 9
  • 63
  • 82
1

Here's a generalized solution to chain a list together by grouping on some step. itertools.chain.from_iterable is used to chain the resulting groups from each step together.

from itertools import chain

def step_chain(l, step_len):
    return chain.from_iterable(l[s::step_len] for s in range(step_len))

Demo:

>>> l = [0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> list(step_chain(l, 2))
[0, 2, 4, 6, 8, 1, 3, 5, 7]
>>> list(step_chain(l, 3))
[0, 3, 6, 1, 4, 7, 2, 5, 8]

If the step length does not fit nicely into the list, the additional elements are placed at the end of the new list, and if the step length is larger than the list then evidently the original list is returned.

>>> step_chain(l, 7)
[0, 7, 1, 8, 2, 3, 4, 5, 6]
>>> step_chain(l, 100)
[0, 1, 2, 3, 4, 5, 6, 7, 8]

Why your attempt doesn't work

When you set temp_arr = arr, temp_arr is simply a reference to the same underlying (mutable) list as arr. Hence you are modifying it. You can think of temp_arr and arr as two "sticky-notes" attached to the same underlying list object, which will change accordingly.

If you copy your original list arr with the list() built-in

arrtmp = list(arr)

your approach works for this particular case.

miradulo
  • 28,857
  • 6
  • 80
  • 93
  • Ah thank you for the insight! I thought temp_arr = arr will create a new list, a copy. That actually explains another hickup in another part of my script :) – Florian Thamer Jul 02 '16 at 09:50