简体   繁体   中英

Most Pythonic way to sort a list from middle?

I have a list of integers. Now I want to sort from the middle. By this I mean:

  1. starts from the number in the middle if the length of the list is an odd number;
  2. starts from the larger of the two numbers in the middle if the length of the list is an even number;
  3. alternatingly take the numbers on the left and right (or right and left, depending on their magnitudes). Always first take the larger of the two.

I managed to write the following code, but only works for odd-size lists. It will not be hard to write an even version for this, but I feel like there has to be more general and "Pythonic" way to code this. I'm actually surprised that this trivial question has never been asked before.

# Odd length, works
lst = [1, 3, 5, 7, 9, 10, 8, 6, 4, 2, 0] # Expected output [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# Even length, doesn't work
#lst = [1, 3, 5, 7, 9, 8, 6, 4, 2, 0] # Expected output [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

mid = len(lst) // 2
dist = 1
res = [lst[mid]]
while dist <= mid:
    res += sorted([lst[mid - dist], lst[mid + dist]], reverse=True)
    dist += 1
assert len(res) == len(lst)
print(res)

Note that the sequence after the first number must follows the distance +-1, +-2, +-3, ... For example, the expected output of [5, 4, 3, 2, 1] is [3, 4, 2, 5, 1] ; the expected output of [5, 3, 1, 0, 2, 4] is [1, 0, 3, 2, 5, 4]

Also, it would be nice if someone could provide the function with the option of reverse=True or False , so that user can decide whether to pick larger or smaller number first.

Try the following (although I prefer the recursive version below):

lst_odd = [1, 3, 5, 7, 9, 10, 8, 6, 4, 2, 0]
lst_even = [1, 3, 5, 7, 9, 8, 6, 4, 2, 0]

def sorted_from_middle(lst, reverse=False):
    left = lst[len(lst)//2-1::-1]
    right = lst[len(lst)//2:]
    output = [right.pop(0)] if len(lst) % 2 else []
    for t in zip(left, right):
        output += sorted(t, reverse=reverse)
    return output

print(sorted_from_middle(lst_odd)) # [10, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1]
print(sorted_from_middle(lst_even)) # [8, 9, 6, 7, 4, 5, 2, 3, 0, 1]
print(sorted_from_middle(lst_odd, True)) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(sorted_from_middle(lst_even, True)) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
print(sorted_from_middle([5, 4, 3, 2, 1], True)) # [3, 4, 2, 5, 1]
print(sorted_from_middle([5, 3, 1, 0, 2, 4], True)) # [1, 0, 3, 2, 5, 4]

The idea is to cut the input list into two parts, left and right ; make them have the equal length by first appending the center-most item, if any, to the output list (in the code this is done by stealing an item from right ). Let left be reversed, so that in both left and right the first element is the most closest to the center. Then use zip() to simultaneously take two elements with the distance 1 from the center, compare them, and append them accordingly to the output list. Then move to the next pair of items and so on.


As a side note, you can do this also with a recursive function, which handles even/odd cases more cleanly in my opinion.

def sorted_from_middle(lst, reverse=False):
    if len(lst) <= 1:
        return lst
    tail = sorted([lst[-1], lst[0]], reverse=reverse)
    return sorted_from_middle(lst[1:-1], reverse) + tail

The idea is basically just 1) excluding the middle element if it's odd and saving it, then 2) sort each pair made by one element of the first half of the remaining list organized backwards and another from the second half of the remaining list; and 3) flatten the list of pairs adding the former middle element as the first one (if any)

import numpy


lst1 = [1, 3, 5, 7, 9, 10, 8, 6, 4, 2, 0]
lst1_out = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
lst2 = [1, 3, 5, 7, 9, 8, 6, 4, 2, 0]
lst2_out = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
lst3 = [5, 4, 3, 2, 1]
lst3_out = [3, 4, 2, 5, 1]
lst4 = [5, 3, 1, 0, 2, 4]
lst4_out = [1, 0, 3, 2, 5, 4]

def sort_from_middle(lst,reverse=True):
    lst = lst.copy()
    N = len(lst)
    if N == 1:
        return lst
    elif N == 2:
        return sorted(lst, reverse=reverse)
    else:
        flatten_func = lambda t: [item for sublist in t for item in sublist]
        n = int(numpy.floor(N/2.0))
        x0 = [ lst.pop(n) ] if ((N%2) == 1) else []
        N = len(lst)
        return x0 + flatten_func([ sorted([x1,x2],reverse=reverse) for x1,x2 in zip(lst[int(N/2)-1::-1],lst[int(N/2):]) ])
    

print(lst1)
print(sort_from_middle(lst1))
print(lst1_out)

print(' \n')
print(lst2)
print(sort_from_middle(lst2))
print(lst2_out)

print(' \n')
print(lst3)
print(sort_from_middle(lst3))
print(lst3_out)

print(' \n')
print(lst4)
print(sort_from_middle(lst4))
print(lst4_out)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM