views:

129

answers:

3

Is there any well-known algorithm (or obvious solution) for transforming a list from order A to order B using only push and remove operations? For instance, from a b c d to b c a d could be done using the following operations:

remove(a)
remove(d)
push(a)
push(d)

Or, from c b a to a b would be

remove(c)
remove(b)
push(b)

Or, from a c b to a c b d would be

push(d)

Here, push appends an element to the end of the list, and remove removes the element and shifts the subsequent elements so that the list is always in continuous state (no "empty slots"). Additionally there's a condition that at any given moment the list may contain the same element only once. Therefore it seems sound to first do all removes in one bunch, and all pushes after that. The order of removes obviously doesn't matter, but the order of pushes does.

A trivial solution would be to first remove all elements and then push the desired elements in the desired order. But since I know that most of the time the transforms will be quite small, equivalent to single pushes orremoves, I want to "reuse" any existing correct order in the list (for instance, transforming abcdef to abcde would require just one remove operation - quite a difference to the alternative (6+5 operations)). So, how to come up with the right (minimum) set of removes and list of pushes?

+5  A: 

From what I can tell, you are going to be removing from anywhere and pushing to the back.

Because you can't insert into the middle of the list, the only way you can minimize the number of operations is to go through the list linearly and remove any element that is not correct.

i.e. 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> 2, 4, 6, 7, 8, 13, 14

  1. compare 1
    1. remove 1
  2. compare 2
  3. compare 3
    1. remove 3
  4. compare 4
  5. compare 5
    1. remove 5
  6. compare 6
  7. compare 7
  8. compare 8
  9. compare 9
    1. remove 9
  10. compare 10
    1. remove 10 (reached end)
  11. push 13
  12. push 14

If your push wasn't so limited, there would be more efficient ways of doing this.

Benjamin Manns
+1: This looks about right. It seems we need to find the largest _prefix_ of B, which is a subsequence of A (not LCS), which is what you seem to be doing.
Moron
+1 This looks more efficient and less error-prone than what I wrote. =)
Benny Jobigan
Thanks. I'll be calling this The Manns Algorithm ;-)
Joonas Pulakka
+1  A: 

This isn't "well known", but rather something I just thought up.

  1. Remove all elements from the original that are not in the desired list; add each remove operation to the list of operations.
  2. Add any elements to working that are missing; add each add operation to the list of operations.
  3. Assign to each element of the original list a "order number" which is the position of its analog in the desired list. (see snippet below.)
  4. Find the first element (0 at original1) and the last element in order from there (1 at original2).
  5. Remove all elements at positions outside of this range and put them in some other temporary list; add each remove operation to the list of operations.
  6. Sort the temporary list based on the "order number".
  7. Add the elements in order from the temporary list; add each add operation to the list of operations.
  8. Verify that this newly transformed list matches the desired list.
  9. Return the list of operations.

Ex. from step 2:

2013 1234

abcd bcad

Because you can only add and remove--not insert elements or add/remove a range--I don't think you can keep the existing order of any in-order sub-segments other than the in-order segment from 0. That's why all other elements are removed in step 4. Since you don't have to add those elements immediately after removing (based on your example), I assume it's ok to store them somewhere. So, why not store them in a sortable list and sort based on the assigned "order number" to determine the order of adds?

Unless I've misunderstood your question, you're interested in the order of add/remove operations, so you don't care about actually transforming the list (since you already have the transformed list); you want the steps to create the transformed list. Therefore, each time you add or remove in the algorithm, add the "operation" into a list (queue) of operations (e.g. operations.Add("remove(a)")). The algorithm then returns this list of operations.

I wrote a possible implementation in C#. I tested it a little (screen shots below) and it seemed to work. It might also not be the best implementation that someone could write, however.

public static IEnumerable<string> DetermineTransformOrder<T>(
    IEnumerable<T> original, IEnumerable<T> desired)
{
    // handle the easy case immediately
    if (original.SequenceEqual(desired))
    {
        return new List<string>();
    }

    List<KeyValuePair<int,T>> workingOriginal = new List<KeyValuePair<int,T>>();
    List<T> workingDesired = desired.ToList();
    List<string> operations = new List<string>(); //using Queue<> is ok too

    // load workingOriginal
    foreach(T element in original)
        workingOriginal.Add(new KeyValuePair<int, T>(workingDesired.IndexOf(element), element));

    //1. Remove all elements from the original that are not in the desired list; 
    //   add each remove operation to the list of operations.
    var tempWorking = new List<KeyValuePair<int,T>>(workingOriginal);
    foreach (var element in tempWorking)
    {
        if (!workingDesired.Contains(element.Value))
        {
            workingOriginal.Remove(element);
            operations.Add("remove(" + element.Value.ToString() + ")");
        }
    }

    //2. Add any elements to working that are missing;
    //   add each add operation to the list of operations.
    tempWorking = new List<KeyValuePair<int,T>>(workingOriginal);
    foreach(T element in workingDesired)
    {
        if(!workingOriginal.Exists(x=>x.Value.Equals(element)))
        {
            workingOriginal.Add(new KeyValuePair<int, T>(workingDesired.IndexOf(element), element));
            operations.Add("add("+element+")");
        }
    }

    //3. Assign to each element of the original list a "order number" 
    //   which is the position of its analog in the desired list.

    // note: already done above. would have done it here, but
    //       KeyValuePair.Key is read-only.

    //4. Find the first element (0 at original[1]) and the 
    //   last element in order from there (1 at original[2]).
    int indexOfFirstElement = workingOriginal.FindIndex(x => x.Key == 0);
    int indexOfLastElement = indexOfFirstElement;

    // move to index of last in-order element
    for (int i = indexOfLastElement + 1;
         i < workingOriginal.Count &&
         workingOriginal[i - 1].Key + 1 == workingOriginal[i].Key;
         i++, indexOfLastElement++) ;

    //5. Remove all elements at positions outside of this range and put them in some
    //   other temporary list; add each remove operation to the list of operations.
    List<KeyValuePair<int, T>> temporary = new List<KeyValuePair<int, T>>();
    var inOrderElements = workingOriginal.GetRange(
                            indexOfFirstElement, 
                            indexOfLastElement - indexOfFirstElement + 1);
    var outOfOrderElements = new List<KeyValuePair<int, T>>(
                                workingOriginal.Except(inOrderElements));

    foreach (var element in outOfOrderElements)
    {
        workingOriginal.Remove(element);
        temporary.Add(element);
        operations.Add("remove(" + element.Value.ToString() + ")");
    }

    //6. Sort the temporary list based on the "order number".
    temporary.Sort((x, y) => { return x.Key.CompareTo(y.Key); });

    //7. Add the elements in order from the temporary list; 
    //   add each add operation to the list of operations.
    foreach (var element in temporary)
    {
        workingOriginal.Add(element);
        operations.Add("add(" + element.Value.ToString() + ")");
    }

    //8. Verify that this newly transformed list matches the desired list.
    var newlyTransformed = workingOriginal.ConvertAll<T>(x => { return x.Value; });
    if (!newlyTransformed.SequenceEqual(desired))
    {
        // this should never happen
        throw new StackOverflowException("FAILED");
    }

    //9. Return the list of operations.
    return operations;
}

alt text alt text alt text

Benny Jobigan
Thanks for your efforts, good thoughts!:)
Joonas Pulakka
No problem. It was interesting to work on (though I stayed up too late =P ).
Benny Jobigan
+1  A: 

Brainstorming:

  • An arbitrary ordering can be thought of as a sort given the proper comparison function.
  • A Cartesian Tree has the interesting property that it models both the original order of a list (by in-place traversal) and the sorted order of a list (as a heap).

Perhaps you can use a Cartesian tree to determine the order of operations as follows (edited to fix bugs discovered when working through the example below):

  • Call the original list L.
  • Construct a Cartesian tree T from L. This takes O(n) time.
  • Construct an empty priority queue Q.
  • Assign node N = root of T (this is the smallest item in the list not yet processed).
  • Repeat the following steps until Q is empty and N is null.
    • Remove all left children of N from L and put them on Q. Item N is now in position on L.
    • Assign N = right child of N.
    • If N < head of Q: remove N from L and put it on Q.
    • While head of Q <= N or N==null: pop Q and push the result on L.

Let's try an example:

I'll use the example from the Wikipedia page for Cartesian trees:

Cartesian Tree for list 9,3,7,1,8,12,10,20,15,18,5

At start:

  • L = 9,3,7,1,8,12,10,20,15,18,5
  • Q = empty
  • N = 1

First loop:

  • Move left children of N to Q:
    • L = 1,8,12,10,20,15,5
    • Q = 3,7,9
  • Reassign N to right child: N = 5
  • Move N to Q
    • L = 1,8,12,10,20,15
    • Q = 3,5,7,9
  • Push items <= N from Q onto L:
    • L = 1,8,12,10,20,15,3,5
    • Q = 7,9

Second loop:

  • Move left children of N to Q:
    • L = 1,3,5
    • Q = 7,8,9,10,12,15,20
  • Reassign N to right child: N = null
  • Push remaining items from Q onto L:
    • L = 1,3,5,7,8,9,10,12,15,20

Done.

ChrisH
Thanks for your efforts, good thoughts!:)
Joonas Pulakka