7

This question is different to other questions on the topic of sorting a list based on the order of another list in the sense that the order list does not contain all the keys used in the list.

Say I have a list [a, b, c, d, e] and my order list [b, d, e].

Now I change my order list to [b, e, d]. Is there a relatively simple algorithm to resort the original list? Let's say it's not important whether the final ordering is [a, b, e, c, d] or [a, b, c, e, d], and the order list will always be a subset of the original list.

Edit: clearing up some questions about the final ordering, from my example: e was ordered to be between b and d, and in the sorted list it doesn't matter if e ends up adjacent to b or d. But for example, if due to this sorting a moved to after b - while a legal ordering - it's not desirable.

2
  • 3
    Is the result [b, e, d, a, c] legal? I am not 100% sure what you mean Commented Aug 5, 2015 at 15:16
  • 1
    Good question, but no, I'm thinking along the lines, in the order list, indexOf(e) < indexOf(d) so the final ordering it should reflect that. But indexOf(a) is undefined so the ordering regarding a should stay the same. Commented Aug 5, 2015 at 15:22

5 Answers 5

2

You can accomplish what you want by setting up a custom comparator as Dennis Callanan has suggested and then doing a stable sort of the array. Quicksort is not a stable sort; it will generally change the order of elements not included in the partial ordering. Merge sort is a stable sort.

If you use linear search in the comparator, the algorithm will run in time O(n^2 log n) I think. What you need to do to make it run faster is to make one run through the new array, hashing the position of each element in the array for fast lookups.

I could implement it in Java but I don't know Python, sorry.

Another perspective on this is to use a topological sort. In your original list you had a -> b -> c -> d -> e where the arrows mean a "comes before" b. Then you add the new data b -> e -> d. You've got to break any arrows in the first list that lead to contradictions, i.e., d -> e. What you then have is a bunch of arrows:

a -> b, b -> c, c -> d, b -> e, e -> d

If that is a directed acyclic graph (i.e., no contradictions), you can then toposort it in O(V + E) time. Since the number of edges is at most 2n, that's O(n) time, very efficient.

The issue is deciding what arrows to break (and maybe replace with other arrows) in the original list so that there aren't any contradictions. In general that's an NP-hard problem called minimum feedback arc set but I suspect there's something in the structure of your problem that would make it run faster.

Finally, what about just replacing the elements in the (non-contiguous) subarray [ ..., b, ..., d, e] with the permutation given by the new array? That can be accomplished in O(n) time.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for a great answer, I'll check out your suggestions. I'm not too worried about efficiency, n^2 should be okay. I'm using JavaScript btw, but I understand Java and Python.
0

EDIT Here's a slightly more efficient version; rather than looking up the index of the order elements in every iteration (arr.indexOf), just look them up once at the beginning at keep the indices updated.

Worst time if N is the length of your array and M is the length of your order is O(N * M + M^2)

function partialSort(arr, order) {
    var orderIndex = [];
    for(var i = 0; i < order.length; i++) {
        orderIndex[i] = arr.indexOf(order[i]);
    }
    for(var i = 0; i < orderIndex.length; i++) {
        var indexI = orderIndex[i];
        for(var j = i + 1; j < orderIndex.length; j++) {
            var indexJ = orderIndex[j];
            if(indexI > indexJ) {
                var temp = arr[indexI];
                arr[indexI] = arr[indexJ];
                arr[indexJ] = temp;
                orderIndex[i] = indexJ;
                orderIndex[j] = indexI;
                indexI = indexJ;
            }
        }
    }
    return arr;
}
var a = [1, 2, 3, 4, 5, 6, 7];
var o = [3,5,7];
console.log(o + "\n" + partialSort(a, o));
o = [5,3,7];
console.log(o + "\n" + partialSort(a, o));
o = [7,3,5];
console.log(o + "\n" + partialSort(a, o));

Here's the version using an MlogM sort O(N * M + M * log(M) + M)

function partialSort(arr, order) {
    var orderIndex = [];
    for(var i = 0; i < order.length; i++) {
        orderIndex[i] = arr.indexOf(order[i]);
    }
    // sort by index ~some quick sort variant O(M * log(M))
    orderIndex.sort(function(a, b) { return a - b; });
    // put the ordered elements in the correct sequence in the main array
    for(var i = 0; i < orderIndex.length; i++) {
        arr[orderIndex[i]] = order[i];
    }
    return arr;
}

If you want the absolute best efficiency you could replace orderIndex.sort with a radix sort O(N * M + M + M)

2 Comments

This probably isn't the most efficient way to do it, but it works perfectly, thanks.
@Niel - I gave you a few more efficient options
0

One naive n^2 approach is:

for each S in order-list
  if S is in other-list
     remove S from other-list
     add S to end of other-list

Which will result in the items from your sort-list being removed and appended to your other list: [a, c, b, e, d]

3 Comments

This works really well except that for some reason items there were not reordered explicitly change positions, even though the final ordering is still legal according to the order list.
Do you have a requirement that items not in the ordered list must maintain their original positions, and have only items in the ordered list change places? If this is the case, a solution may involve making a "collection view" to capture each index of an element that can be moved, and using this view to apply the sort.
Retaining their original/relative positions is definitely preferable. I'll check out this collection view.
0

I wrote a version of quicksort for fun for another question (Javascript Double sorting algorithm), which I have no idea if it is fully reliable. It was originally meant to sort just the strings or just the numbers but seems adaptable to this case (I changed the "isNumber" and "less" functions):

function isNumber(x,y) {
  return (map[x]);
}

function less(a,b,y){
  return y ? a < b : map[a] < map[b];
}

function swap(a, i, j) { var t = a[i]; a[i] = a[j]; a[j] = t; }

function partition(array, pivot, left, right, what) {
  var store = left,
      pivotValue = array[pivot];

  swap(array, pivot, right);

  for (var v = left; v < right; v++) {
    if (less(array[v],pivotValue,what) && isNumber(array[v],what)) {
      swap(array, v, store);
      store++;
    }
  }

  while(!isNumber(array[store],what))
    store++;

  swap(array, right, store);

  return store;
}

function doubleQSort(array, left, right, what) {
  while(!isNumber(array[right],what) && right > left)
    right--;
  while(!isNumber(array[left],what) && left < right)
    left++;

  var pivot = null;

  if (left < right) {
    pivot = (right + left) >> 1;

    while(!isNumber(array[pivot],what))
      pivot--;

    newPivot = partition(array, pivot, left, right, what);

    doubleQSort(array, left, newPivot - 1,what);
    doubleQSort(array, newPivot + 1, right,what);
  }
}

Output:

var things = ['a', 'b', 'c', 'd', 'e'];
var map = {'b':1, 'e':2, 'd':3}

doubleQSort(things,0,things.length - 1);
console.log(things) // [a, b, c, e, d]

Comments

0

Python approach, easily implemented in any language (Java won't require functools)

import functools

order = [5, 1, 4]

def numeric_compare(x, y):
    if x in order and y in order:
        if order.index(x) > order.index(y):
            return 1
        elif order.index(x) < order.index(y):
            return -1
    else:
        if x in order:
            return -1
        elif y in order:
            return 1
    return 0

a = [1,2,3,4,5]
a.sort(key = functools.cmp_to_key(numeric_compare))

2 Comments

I am using Javascript in fact, and I was able to get it working using the array.sort() function. However it only works 90% of the time. It seems that if the item that is in order, is in between items that are not in order, then the function returns 0 and it is not sorted.
Thanks for pointing that out, I seem to have solved the problem, but my sorting algorithm essentially appends the ordered array to the beginning of the "superset" array now, which makes me question the efficiency and purpose of using a sort and compare function at all. But at least it's something to play around with and learn from.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.