-2

What's the best way of sorting a python list in an unordered fashion according to the median of value positions in the list?

Suppose:

a = [1, 3 ,6, 7, 10, 12, 17]

I'm looking for this:

a = [1, 17, 7, 3, 12, 6, 10]

Meaning, the list now looks like [start, end, mid, first_half_mid, second_half_mid, ...]

edit: To clarify further, I'm looking for a way to keep bisecting the list until it covers the whole range!

edit2: Another example to illustrate the problem

input:

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

desired output:

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

Yasin
  • 609
  • 1
  • 10
  • 22
  • 1
    When you say "accurate" you mean "best" right? Also, please provide what you have tried. – Moon Cheesez Jun 04 '16 at 08:25
  • 1
    Yeah, meant "best"! Perhaps I couldn't convey my meaning due to number of down votes, but in fact, it's a problem that have many optimization use cases. So far, I've looked into bisection and median functions, however, those are suited for searching rather than sorting. I believe what I need to figure out is how to do it recursively so that I can get the right result. – Yasin Jun 04 '16 at 08:57
  • Indeed, it's an interesting question (+1). But i don't understand your output list, shouldn't it be: `[1,17,7, 3,12,10]` - as `12` is the `end` on the second iteration? – MaxU - stand with Ukraine Jun 04 '16 at 09:17
  • Thanks Max. That's correct too. The idea is keep doing the bisection until it covers the whole range. Perhaps it should it's even more correct like this `[7, 3, 12, 1, 10, 6, 17]` which means `[result of first bisection, result of second bisection, ...]` It's also necessary both sides go in parallel. Does it make sense? – Yasin Jun 04 '16 at 09:27
  • @Yasin, yes, it makes lots of sense! I would suggest you to open a new question and add a `numpy` tag to it (if using numpy is an option for you) - this would attract numpy specialists. I'm pretty sure there is a very effective numpy/scipy solution for this task, but i don't know it yet. PS would you please ping me from this question with the link to a new one (in case you decide to open it)? – MaxU - stand with Ukraine Jun 04 '16 at 09:32
  • Sure, I'll do that soon if this doesn't get answered. What do you suggest for the title of new question? "Sorting lists in bisection/median order"? – Yasin Jun 04 '16 at 09:37
  • I thought your input list is already sorted, isn't it? It might be important... – MaxU - stand with Ukraine Jun 04 '16 at 09:40
  • Perhaps "sorting" is not the right word for it. How about "ordering"? What the input list contains (whether sorted or not) does not really matter. This is more about ordering the elements index in a bisection fashion. – Yasin Jun 04 '16 at 09:44
  • `Numpy: ordering the elements in a bisection fashion` - sounds good to me – MaxU - stand with Ukraine Jun 04 '16 at 09:45
  • "best" by what criterion? – das-g Jun 04 '16 at 10:02
  • @Yasin I think to help clarify what you would want to do here, you could provide a longer example (perhaps 3 bisections). – Moon Cheesez Jun 04 '16 at 10:05

2 Answers2

2

This looks like a breadth-first task, so I used a Queue:

# from queue import Queue, Empty # python 3
from Queue import Queue, Empty   # python 2

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
accu = []
q = Queue()
# a.sort()     # if it isn't already sorted.

def do_it(l):  # whatever a precise name might be...

    global accu
    accu = [l[0], l[-1]]
    q.put(l[1:-1])             # Add first and last element, start with rest of list.
    try:
        while True:

            l = q.get_nowait()

            if not l:
                continue

            print("working on {}".format(l))
            middle = l[len(l)//2]
            left = l[:len(l)//2]
            right = l[len(l)//2+1:]

            accu.append(middle)
            q.put(left)
            q.put(right)

            print("added {}, todo: {} // {}".format(middle, left, right))
    except Empty:
        pass



print(a)
do_it(a)
print(accu)

Result:

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

I don't quite get why 10 is before 6 in your comment.

Jasper
  • 3,939
  • 1
  • 18
  • 35
  • I think OP wants something different. The resulting list should be either `[ 1, 17, 7, 3, 12, 10, 6]` or `[ 1, 17, 7, 3, 12, 6, 10]`, depending on definition of the `middle` element – MaxU - stand with Ukraine Jun 04 '16 at 10:33
  • 1
    OP seems to differ :) (I implemented it according to the comment, always taking the "middle" element...) – Jasper Jun 04 '16 at 10:36
  • Brilliant! In fact both methods are correct according to the nature of the problem. It was hard deciding between which one to choose! I need to fix the example though to reflect the issue better. Regarding this solution, I had to do `from multiprocessing import Queue` instead of `from queue import Queue` since this one was giving me an import error. – Yasin Jun 04 '16 at 10:39
  • The module is called `Queue` in Python 2 – Jasper Jun 04 '16 at 10:46
  • @Jasper, you're right! I didn't notice that the desired output was changed in the comments – MaxU - stand with Ukraine Jun 04 '16 at 11:14
  • `from six.moves.queue import Queue` would be compatible with both Python versions at the cost of additional module - [six](https://pythonhosted.org/six/) – MaxU - stand with Ukraine Jun 04 '16 at 11:49
  • @Jasper, there seems to be an issue somewhere in the snippet. When I run it on `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`, I get `[6, 3, 9, 2, 5, 8, 10, 1]` which is missing two of the elements. Any idea why? – Yasin Jun 04 '16 at 11:50
  • You should be able to track it down with the print statements. I guess some rounding problems? – Jasper Jun 04 '16 at 11:53
  • The print shows that 4 and 7 are on the to-do list but they don't really get processed. `added 5, todo: [4] // []` and `added 8, todo: [7] // []` – Yasin Jun 04 '16 at 12:00
  • 1
    Yep, they were added to the queue but never processed. Forgot to loop. – Jasper Jun 04 '16 at 12:06
  • @Yasin, can you update your question with `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]` as input list and also post an expected/desired output list? – MaxU - stand with Ukraine Jun 04 '16 at 12:07
  • Sweet! Thanks @Jasper. MaxU, updated the post with the better example. Jasper's solution is very close to the desired output, it just misses the first and last elements on the original list showing up at the beginning of the output list. – Yasin Jun 04 '16 at 12:19
  • That's a very special case, right (putting first and last elements of source list as first and second element in result?)? – Jasper Jun 04 '16 at 12:21
  • Yes, it is. I'd just do a post-process on it and it should be good. Thanks again :) – Yasin Jun 04 '16 at 12:22
  • Added a "pre-processing" step – Jasper Jun 04 '16 at 12:23
  • Hmm, the update not quite working now! I'm getting this `[1, [1, 2, 3, 4, 5, 6, 7, 8, 9], 6, 4, 8, 3, 5, 7, 9, 2]` – Yasin Jun 04 '16 at 12:28
  • Remove the `:` in the initialization of the accumulator: `accu = [l[0], l[-1]]` – Jasper Jun 04 '16 at 12:32
  • The thing is, now the output is farther away from the desired one `[1, 10, 6, 4, 8, 3, 5, 7, 9, 2]`! previously I was getting `[6, 3, 9, 2, 5, 8, 10, 1, 4, 7]` which is a lot closer but just missing the first and last elements in the beginning of the list! – Yasin Jun 04 '16 at 12:42
1

UPDATE: consider left element as a middle if the length of the list is an even numer

def f(a):
    # take left middle element for even-length lists
    mid = len(a)//2 if len(a)%2 else len(a)//2-1
    # take len(a)//2 as a middle element 
    #mid = len(a)//2 
    if len(a) <= 2:
        return a
    elif(len(a) == 3):
        return a[[0,-1,mid]]
    else:
        return np.append(a[[0,-1,mid]], f(np.delete(a, [0,len(a)-1,mid])))

Output:

In [153]: f(a)
Out[153]: array([ 1, 17,  7,  3, 12,  6, 10])

OLD answer:

here is one of many possible solutions:

import numpy as np

def f(a):
    if len(a) <= 2:
        return a
    elif(len(a) == 3):
        return a[[0,-1,len(a)//2]]
    else:
        return np.append(a[[0,-1,len(a)//2]], f(np.delete(a, [0,len(a)-1,len(a)//2])))

a = np.array([1, 3 ,6, 7, 10, 12, 17])

In [114]: a
Out[114]: array([ 1,  3,  6,  7, 10, 12, 17])

In [115]: f(a)
Out[115]: array([ 1, 17,  7,  3, 12, 10,  6])

PS about last two numbers in the result list/array - the question is what is the middle index for the 4-elements list?

What is the middle element for [3,6,10,12] list? In my solution it would be 10 (index: 4//2 == 2)

MaxU - stand with Ukraine
  • 205,989
  • 36
  • 386
  • 419
  • Thanks Max :) This one works great too! In the case of having such even lists `[3, 6, 10, 12]` either 6 or 10 would be chosen, but often it would be the left element. Which one does your solution choose? – Yasin Jun 04 '16 at 10:43
  • you are welcome! :) My solution would chose `10` as `4//2 == 2` – MaxU - stand with Ukraine Jun 04 '16 at 10:48
  • Great! Do you think there is a way to only consider `start` and `end` on the first iteration and on the next iterations it only results the bisected result? It will be really similar to @Jasper solution with the exception that the start and end indices would now come at the beginning of the resulted list. – Yasin Jun 04 '16 at 11:05
  • @Yasin, can you post a desired output list? i'm not quite sure that i got your idea – MaxU - stand with Ukraine Jun 04 '16 at 11:08
  • Sure. As an example, try running `[1, 3 ,6, 7, 10, 12, 17]` with Jasper's method. It will result `[7, 3, 12, 1, 6, 10, 17]` which is fine and correctly bisecting. The only thing I'm missing here is that resulted list does not start with first and last elements in the original list. So I would only need to relocate those two elements to the beginning of the list and it will be `[1, 17, 7, 3, 12, 6, 10]` which is what I'm after. Your method takes care of this first and last elements, however it would be great if it only does that in the first iteration. – Yasin Jun 04 '16 at 11:17