Tower of Hanoi Sort

21

1

Write a function/subroutine to sort a list of integers, Tower of Hanoi style.

You will be given a stack of integers. This is the main stack.

You are also given two more helper stacks. These helper stacks have a unique property though: every element must be smaller than or the same size as the element underneath it. The main stack has no such restriction.

You are tasked with sorting the main stack in place, putting the largest integers underneath. Your function/subroutine will return (or equivalent) the number of moves it made in sorting the stack.
Note: you must sort the main stack in place, no sorting onto another stack and calling that the answer. However, if you for some reason cannot do so, you may simulate the mutable stacks, but remember that this is Tower of Hanoi sort; there are only 3 pegs and only 1 peg may be unordered.

Your function/subroutine can inspect any stack at any time, but it can only make a move by popping and pushing. A single move is a pop from one stack which is pushed to another.

Test your function/subroutine for each permutation of the first 6 natural numbers. In other words, test your function/subroutine for {1},{2},...,{6},{1,1},{1,2},...,{1,6},{2,1},... (this should be a total of 61+62+...+66 or 55986 possibilities (thanks to Howard for correcting my math)). The function/subroutine that moves elements the least number of times wins.

Justin

Posted 2014-04-01T05:26:15.113

Reputation: 19 757

@JanDvorak That was sort of the idea on the tests. If the programmer needs to run the function 46656 times, why would he/she want to wait so long for the output? Or is there another good way of restricting this sort of thing? – Justin – 2014-04-01T05:53:03.943

Somehow I like the blind-zap challenge: you can only say "move from stack x to stack y" and see if your move succeeds, and if it does, you get charged for it; bonus points is move failure is indicated by throwing an exception / returning correctly. – John Dvorak – 2014-04-01T05:55:13.470

@JanDvorak Doesn't that encourage the simplistic method of simply finding a valid move and doing it? And I actually intended this as a sorting method (a rather fun but slow one). – Justin – 2014-04-01T05:56:29.103

so, full-view, and the askers must actually run their solutions? I can go with that. – John Dvorak – 2014-04-01T06:01:34.320

What about languages without mutable data structures (haskell, golfscript, J)? Can we get away with having to discard the old stack once its new version is obtained? – John Dvorak – 2014-04-01T06:02:55.667

@JanDvorak Yes. Just simulate a mutable stack to the best possible ability, and it should be good. But the moves need to act as if they are to the main stack (think Tower of Hanoi, there are only 3 pegs. In this challenge, only one peg has a misordering problem). – Justin – 2014-04-01T06:04:09.183

I would love to see a solution in Befunge 98 using { and }. TOSS the SOSS ftw! – Justin – 2014-04-01T06:19:38.507

That would be cool indeed. I'm not fluent in B98, unfortunately. – John Dvorak – 2014-04-01T06:21:33.950

3The list of "permutations" you gave contains 6**1+6**2+...+6**6=55986 elements. – Howard – 2014-04-01T06:39:12.243

"The function/subroutine that moves elements the least number of times wins." Does this refer to the sum over all those 55986 test cases? – Martin Ender – 2014-04-01T16:06:07.300

Hmm too bad this has received so little attention. There seem to be nice ways to solve it, like this one: http://en.wikipedia.org/wiki/Tower_of_Hanoi#Binary_solution. Maybe I'll give it a try!

– CompuChip – 2014-04-01T17:07:52.537

Please reword "permutations" as "cartesian product" – user80551 – 2014-04-01T17:19:29.953

@user80551 This is not "cartesian product". It is the set of permutations of the 1st 6 natural numbers. I use the {} to separate the lists that would be inputted. The list of lists: {1},{2},...,{6},{1,1},{1,2},...,{2,1},...,{1,1,1},{1,1,2},...,{1,1,1,1,1,1},...,{6,6,6,6,6,6} – Justin – 2014-04-01T17:37:01.763

@m.buettner Yes. Call your function/subroutine for those 55986 cases and find the sum of the outputs. This is the score for your answer. – Justin – 2014-04-01T17:38:14.317

@Quincunx I'm pretty sure these are in fact the elements of cartesian products of {1,2,3,4,5,6} with itself 1 to 6 times. Permutations would be {1,2,3,4,5,6}, {4,1,3,2,6,5}, {2,3,6,1,4,5} and so on. – Martin Ender – 2014-04-01T20:17:32.240

(In fact, the combinatorics hint at a cartesian product as well. For counting permutations factorials are used. For counting elements in a cartesian product you multiply the sizes of the things multiplied together - i.e. raise something to some power if you take the cartesian product with itself.) – Martin Ender – 2014-04-01T20:26:55.330

1

@m.buettner The distinction is that this is the elements of cartesian products 1 to 6 times. I'd probably call this "the set of permutations of each element of the power set of the first 6 natural numbers except the null set."

– Justin – 2014-04-02T17:45:18.120

1@Quincunx except the power set doesn't contain sets with repeated numbers. ;) ... but I don't think we should take this too seriously anyway, as long as we're all clear about the elements in the set. – Martin Ender – 2014-04-02T17:49:52.010

It's not permutations but combinations (up to length 6). The permutations of the numbers 1 to 6 are {1,2,3,4,5,6}, {1,2,3,4,6,5}, {1,2,3,5,4,6}, {1,2,3,5,6,4}, {1,2,3,6,4,5}, {1,2,3,6,5,4}, …, {6,5,4,3,1,2}, {6,5,4,3,2,1}. – celtschk – 2014-04-02T19:41:44.703

Similar? http://stackoverflow.com/questions/4347718/code-golf-towers-of-hanoi

– Aurel Bílý – 2014-04-02T21:04:15.807

@Quincunx: I've answered with a lower number of moves than the currently accepted one :) – justhalf – 2014-05-14T08:42:22.657

New best submission (1080544 moves) posted :) – MrBackend – 2014-06-16T15:28:12.070

Answers

4

Java - optimal solution (1080544 moves)

This solution builds a shortest path tree from the target and backwards, then traverses the path from the initial state to the target. Lots of room for speed improvement, but it still completes all 55986 problems in about a minute.

Assuming the algorithm is correctly implemented, this should be the theoretically best solution.

import java.util.*;

public class HanoiSort {

    public static void main(String[] args) {
        int sumNumMoves = 0;
        for (int size = 1; size <= 6; ++size) {
            Collection<List<Integer>> initMainStacks = generateInitMainStacks(Collections.<Integer>emptyList(), size);
            for (List<Integer> initMainStack : initMainStacks) {
                sumNumMoves += solve(initMainStack);
            }
        }
        System.out.println(sumNumMoves);
    }

    /*
     * Recursively create initial main stacks
     */
    private static Collection<List<Integer>> generateInitMainStacks(List<Integer> mainStack, int remainingSize) {
        Collection<List<Integer>> initMainStacks;
        if (remainingSize > 0) {
            initMainStacks = new ArrayList<>();
            for (int number = 1; number <= 6; ++number) {
                List<Integer> nextMainStack = new ArrayList<>(mainStack);
                nextMainStack.add(number);
                initMainStacks.addAll(generateInitMainStacks(nextMainStack, remainingSize - 1));
            }
        } else {
            List<Integer> initMainStack = new ArrayList<>(mainStack);
            initMainStacks = Collections.singleton(initMainStack);
        }
        return initMainStacks;
    }

    private static final List<Integer> EMPTY_STACK = Collections.emptyList();

    /*
     * Create a shortest path tree, starting from the target state (sorted main stack). Break when the initial state
     * is found, since there can be no shorter path. This is akin to building a chess endgame tablebase.
     *
     * Traverse the path from initial state to the target state to count the number of moves.
     */
    private static int solve(List<Integer> initMainStack) {
        List<List<Integer>> initState = Arrays.asList(new ArrayList<>(initMainStack), EMPTY_STACK, EMPTY_STACK);
        List<Integer> targetMainStack = new ArrayList<>(initMainStack);
        Collections.sort(targetMainStack);
        List<List<Integer>> targetState = Arrays.asList(new ArrayList<>(targetMainStack), EMPTY_STACK, EMPTY_STACK);
        Map<List<List<Integer>>,List<List<Integer>>> tablebase = new HashMap<>();
        Deque<List<List<Integer>>> workQueue = new ArrayDeque<>();
        tablebase.put(targetState, null);
        workQueue.add(targetState);
        while (!tablebase.containsKey(initState)) {
            assert !workQueue.isEmpty() : initState.toString();
            List<List<Integer>> state = workQueue.removeFirst();
            Collection<List<List<Integer>>> prevStates = calcPrevStates(state);
            for (List<List<Integer>> prevState : prevStates) {
                if (!tablebase.containsKey(prevState)) {
                    tablebase.put(prevState, state);
                    workQueue.add(prevState);
                }
            }
        }

        int numMoves = 0;
        List<List<Integer>> state = tablebase.get(initState);
        while (state != null) {
            ++numMoves;
            state = tablebase.get(state);
        }
        return numMoves;
    }

    /*
     * Given a state, calculate all possible previous states
     */
    private static Collection<List<List<Integer>>> calcPrevStates(List<List<Integer>> state) {
        Collection<List<List<Integer>>> prevStates = new ArrayList<>();
        for (int fromStackNo = 0; fromStackNo < 3; ++fromStackNo) {
            List<Integer> fromStack = state.get(fromStackNo);
            if (!fromStack.isEmpty()) {
                int number = fromStack.get(0);
                for (int toStackNo = 0; toStackNo < 3; ++toStackNo) {
                    if (toStackNo != fromStackNo) {
                        List<Integer> toStack = state.get(toStackNo);
                        if ((toStackNo == 0) || toStack.isEmpty() || (toStack.get(0) >= number)) {
                            List<List<Integer>> prevState = new ArrayList<>(state);
                            List<Integer> prevFromStack = new ArrayList<>(fromStack);
                            prevFromStack.remove(0);
                            prevState.set(fromStackNo, prevFromStack);
                            List<Integer> prevToStack = new ArrayList<>(toStack);
                            prevToStack.add(0, number);
                            prevState.set(toStackNo, prevToStack);
                            prevStates.add(prevState);
                        }
                    }
                }
            }
        }
        return prevStates;
    }

}

MrBackend

Posted 2014-04-01T05:26:15.113

Reputation: 386

Care to explain more on what you mean by "shortest path tree"? – justhalf – 2014-06-17T02:43:25.023

Anyway thanks for your answer, it makes me glad that I can achieve near optimal solution using heuristics only :) – justhalf – 2014-06-17T02:49:47.860

A shortest path tree is a tree in which each node/state has one "next" edge, leading to the node/state that, in turn, has the shortest distanced to the root/target state (= sorted main stack). There might be other candidate next nodes that have the same distance or more, but none that are closer to the root. For this problem, all edges in the shortest path tree has a distance of one, since it takes one move to get from one state to another. Basically, a complete shortest path tree contains the solution for all states that have the same target state. – MrBackend – 2014-06-17T08:09:53.377

@justhalf Forgot to mention you in my comment. BTW, see also http://en.wikipedia.org/wiki/Retrograde_analysis

– MrBackend – 2014-06-17T08:20:11.477

5

C - 2547172 for 55986 inputs

There is a lot of room for improvement here. For my own sanity, I simplified this so that it was only possible to inspect the top element of each stack. Lifting this self-imposed restriction would allow optimizations like determining the final order in advance and trying to minimize the number of moves required to achieve it. A compelling example is that my implementation has worst case behavior if the main stack is already sorted.

Algorithm:

  1. Fill both auxiliary stacks (room for optimization here, possibly assigning to which stack based on some kind of pivot).
  2. Merge sort the auxiliary stacks back onto the main stack.
  3. Repeat 1-2 until the main stack is sorted (but in reverse).
  4. Reverse the main stack (more room for optimization, shuffling a lot of the same elements repeatedly).

Analysis:

  • Additional space complexity is O(n) (for the two auxiliary stacks), which is good, since that was a requirement of the problem.
  • Time complexity is O(n^2) by my count. Corrections are welcome.

#include <assert.h>
#include <stdio.h>

#define SIZE 6

int s0[SIZE + 1];
int s1[SIZE + 1];
int s2[SIZE + 1];

int
count(int *stack)
{
    return stack[0];
}

int
top(int *stack)
{
    return stack[stack[0]];
}

void
push(int *stack, int value)
{
    assert(count(stack) < SIZE && "stack overflow");
    assert((stack == s0 || count(stack) == 0 || value <= top(stack)) && "stack order violated");
    stack[++stack[0]] = value;
}

int
pop(int *stack)
{
    int result = stack[stack[0]];
    assert(count(stack) > 0 && "stack underflow");
    stack[stack[0]] = 0;
    stack[0]--;
    return result;
}

int permutations;

void
permute(int len, int range, void (*cb)(void))
{
    int i;
    if(len == 0)
    {
        permutations++;
        cb();
        return;
    }
    for(i = 1; i <= range; i++)
    {
        push(s0, i);
        permute(len - 1, range, cb);
        pop(s0);
    }
}

void
print(void)
{
    int i;
    for(i = 1; i <= count(s0); i++)
    {
        printf("%d ", s0[i]);
    }
    printf("\n");
}

int save[SIZE + 1];

void
copy(int *src, int *dst)
{
    int i;
    for(i = 0; i <= SIZE; i++)
    {
        dst[i] = src[i];
    }
}

int total;

void
move(int *src, int *dst)
{
    total++;
    push(dst, pop(src));
}

void
merge(void)
{
    while(1)
    {
        if(count(s1) == 0 && count(s2) == 0)
        {
            break;
        }
        else if(count(s1) == 0 || (count(s2) > 0 && top(s2) < top(s1)))
        {
            move(s2, s0);
        }
        else
        {
            move(s1, s0);
        }
    }
}

void
reverse(void)
{
    while(1)
    {
        while(count(s2) == 0 || top(s0) == top(s2))
        {
            move(s0, s2);
        }
        if(count(s0) == 0 || top(s2) < top(s0))
        {
            while(count(s2) > 0)
            {
                move(s2, s0);
            }
            break;
        }
        while(count(s0) > 0 && (count(s1) == 0 || top(s0) <= top(s1)))
        {
            move(s0, s1);
        }
        while(count(s2) > 0)
        {
            move(s2, s0);
        }
        merge();
    }
}

void
sort(void)
{
    while(1)
    {
        if(count(s0) == 0)
        {
            merge();
            reverse();
            break;
        }
        else if(count(s1) == 0 || top(s1) >= top(s0))
        {
            move(s0, s1);
        }
        else if(count(s2) == 0 || top(s2) >= top(s0))
        {
            move(s0, s2);
        }
        else
        {
            merge();
        }
    }
}

void
helper(void)
{
    copy(s0, save);
    sort();
    copy(save, s0);
}

int
main(void)
{
    permute(1, 6, helper);
    permute(2, 6, helper);
    permute(3, 6, helper);
    permute(4, 6, helper);
    permute(5, 6, helper);
    permute(6, 6, helper);
    printf("%d\n", permutations);
    printf("%d\n", total);
    return 0;
}

laindir

Posted 2014-04-01T05:26:15.113

Reputation: 341

4

Python, 3983838 3912258 moves over 55986 inputs

This is very inefficient.

I'll add the total number of moves after the OP clarifies whether they are for all of those cases or for specific other cases.


from itertools import product       # Required for testing
import time                         # Required if you want to see it in action.

from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

def main( l ):
    ################### Data ###################
    global a , b , c , d , copy , total_moves
    total_moves = 0

    a = [ x for x in l ]  # Input stack, order doesn't matter, we'll try to empty this one
    b = []                # Usable stack, restricted by order, we'll try to get the final sorted order here
    c = []                # Usable stack, restricted by order, but we'll try to keep it as empty as possible

    d = { 'a':a , 'b':b , 'c':c }  # Passing the stacks to the nested functions by their names as a string
    copy = [ x for x in a ]        # reference copy, read-only


    ################### Functions ###################
    def is_correct( stack ):
        if d[ stack ] == sorted( copy , reverse = True ):
            return True
        else:
            return False

    def reverse_exchange( source , destination , keep = 0 ):
        #
        # keep is the number of elements to keep at the bottom of the source stack
        # The remaining top elements are moved to the destination stack
        # We first move the top elements to stack a
        # and from a to c so that the order is preserved
        # effectively splitting the source stack into two
        #

        i = 0
        while len( d[ source ] ) > keep :
            move( source , 'a' )
            i += 1
        else:
            while i > 0:
                move( 'a' , destination )
                i -= 1

    # def validate( source , destination ):
    #   # 
    #   # Validates the give move
    #   #
    #   if len( d[ source ] ) == 0:
    #       return False
    #   if destination == 'a' or len( d[ destination ] ) == 0:
    #       return True
    #   else:
    #       if d[ destination ][ len( d[ destination ] ) - 1 ] >= d[ source ][ len( d[ source ] ) - 1 ]:
    #           return True
    #       else:
    #           return False

    def move( source , destination ):
        global total_moves
        # if validate( source , destination ):
        d[ destination ].append( d[ source ].pop() )

        total_moves += 1

            # Uncomment the following to view the workings step-by-step
            # print '\n'
            # print a
            # print b
            # print c
            # print '\n'
            # time.sleep(0.1)

        return True
        # else:
        #   return False


    ################### Actual logic ###################
    while ( not is_correct( 'a' ) ):

        copy_b   = [x for x in b ]                         # Checking where the topmost element of a
        top_of_a = a[ len(a) - 1 ]                         # should be inserted
        copy_b.append( top_of_a )                          #
        sorted_copy_b = sorted( copy_b , reverse = True )  #

        reverse_exchange( 'b' , 'c' , sorted_copy_b.index( top_of_a ) )                                                  # Sandwiching the top-most element
        move( 'a' , 'b' )                                                                                                # to proper position in b
        while (len(b) > 0 and len(c) > 0 and len(a) > 0) and (sorted (b , reverse = True)[0] <= a[len(a) - 1] <= c[0]):  #  # Optimization
            move( 'a' , 'b' )                                                                                            #  #
        reverse_exchange( 'c' , 'b' )                                                                                    # b is always sorted, c is always empty

        if is_correct( 'b' ):                     # Just moving b to a
            while ( not is_correct( 'a' ) ):      # The entire program focuses on "insertion sorting"
                reverse_exchange( 'b' , 'c' , 1 ) # elements of a onto b while keeping c empty
                move( 'b' , 'a' )                 # 
                if len(c) > 0 :                       #
                    reverse_exchange( 'c' , 'b' , 1 ) # Slightly more efficient
                    move('c' , 'a' )                  #



    return total_moves


# with PyCallGraph( output= GraphvizOutput() ):


    ################### Test cases #############
i = 0
for elements in xrange( 1 , 7 ):
    for cartesian_product in product( [ 1 , 2 , 3 , 4 , 5 , 6 ] , repeat = elements ):
        input_list = [ int( y ) for y in cartesian_product ]
        i += main(input_list)
        print i
print i

Explanation

What, comments not good enough for you?


Note to OP: Thanks for not making this code-golf.

user80551

Posted 2014-04-01T05:26:15.113

Reputation: 2 520

I believe that if there is a more interesting way of doing the challenge other than [code-golf], it should be done that way. – Justin – 2014-04-01T17:39:03.700

Ok, this fails for [1,1,2]. In python, considering a list [2,1,1] is there a way to get [2,1,1].index(1) as 2 i.e. starting from higher end? – user80551 – 2014-04-01T17:42:36.663

@Quincunx No, [2,1,1,3,5].index(1)==2 instead of 1 – user80551 – 2014-04-01T17:54:25.163

Er, list.index(data) returns the index of the item data in list. I don't know the index of data i.e. 1 – user80551 – 2014-04-01T17:59:47.487

The hack would be len(list)-(list[::-1].index(1)) – user80551 – 2014-04-01T18:00:36.120

Oh. I didn't get it. Oops. You could do list[list.index(1):].index(1) to get what you want. To get a list of the indexes of every element, perhaps [x for x in range(len(list)) and list[x] == elementIAmLookingFor] – Justin – 2014-04-01T18:12:25.827

@Quincunx Thanks, but nvm the bug was in the validate method. – user80551 – 2014-04-01T18:15:27.523

You average ~99 moves per stack. Hmm. Interesting that this is so since 2**6 = 64 and I think that it is possible to get close to that value. I'm working on an algorithm that should get less than 2**n + n when n is the number of elements, if my math is perfect (which I proved it isn't in asking this challenge). – Justin – 2014-04-01T18:33:25.650

@Quincunx I made it slightly better so that it alternates between b and c when putting the final output back into a. – user80551 – 2014-04-01T18:37:17.657

Slightly better? That's a lot better :-) – Justin – 2014-04-01T18:40:05.570

2

Python: 1,688,293 1,579,182 1,524,054 1,450,842 1,093,195 moves

The main method is main_to_help_best, which is to move some selected elements from main stack to helper stack. It has a flag everything which defines whether we want it to move everything into the specified destination, or whether we want to keep only the largest in destination while the rest in the other helper.

Supposing we are moving to dst using helper helper, the function can roughly be described as follows:

  1. Find the positions of largest elements
  2. Move everything on top of the top-most largest element to helper recursively
  3. Move the largest element to dst
  4. Push back from helper to main
  5. Repeat 2-4 until the largest elements are in dst
  6. a. If everything is set, recursively move elements in main to dst
    b. Otherwise, recursively move elements in main to helper

The main sort algorithm (sort2 in my code) will then call main_to_help_best with everything set to False, and then move the largest element back to main, then move everything from the helper back to main, keeping it sorted.

More explanation embedded as comments in the code.

Basically the principles that I used are:

  1. Keep one helper to contain the maximum element(s)
  2. Keep another helper to contain any other elements
  3. Don't do unnecessary moves as much as possible

Principle 3 is implemented by not counting the move if the source is the previous destination (i.e., we just moved main to help1, then we want to move from help1 to help2), and further, we reduce the number of movement by 1 if we are moving it back to the original position (i.e. main to help1 then help1 to main). Also, if the previous n moves are all moving the same integer, we can actually reorder those n moves. So we also take advantage of that to reduce the number of moves further.

This is valid since we know all the elements in the main stack, so this can be interpreted as seeing in the future that we're going to move the element back, we should not make this move.

Sample run (stacks are displayed bottom to top - so the first element is the bottom):

Length 1
Moves: 0
Tasks: 6
Max: 0 ([1])
Average: 0.000

Length 2
Moves: 60
Tasks: 36
Max: 4 ([1, 2])
Average: 1.667

Length 3
Moves: 1030
Tasks: 216
Max: 9 ([2, 3, 1])
Average: 4.769

Length 4
Moves: 11765
Tasks: 1296
Max: 19 ([3, 4, 2, 1])
Average: 9.078

Length 5
Moves: 112325
Tasks: 7776
Max: 33 ([4, 5, 3, 2, 1])
Average: 14.445

Length 6
Moves: 968015
Tasks: 46656
Max: 51 ([5, 6, 4, 3, 2, 1])
Average: 20.748

--------------
Overall
Moves: 1093195
Tasks: 55986
Average: 19.526

We can see that the worst case is when the largest element is placed on the second bottom, while the rest are sorted. From the worst case we can see that the algorithm is O(n^2).

The number of moves is obviously minimum for n=1 and n=2 as we can see from the result, and I believe this is also minimum for larger values of n, although I can't prove it.

More explanations are in the code.

from itertools import product

DEBUG = False

def sort_better(main, help1, help2):
    # Offset denotes the bottom-most position which is incorrect
    offset = len(main)
    ref = list(reversed(sorted(main)))
    for idx, ref_el, real_el in zip(range(len(main)), ref, main):
        if ref_el != real_el:
            offset = idx
            break

    num_moves = 0
    # Move the largest to help1, the rest to help2
    num_moves += main_to_help_best(main, help1, help2, offset, False)
    # Move the largest back to main
    num_moves += push_to_main(help1, main)
    # Move everything (sorted in help2) back to main, keep it sorted
    num_moves += move_to_main(help2, main, help1)
    return num_moves

def main_to_help_best(main, dst, helper, offset, everything=True):
    """
    Moves everything to dst if everything is true,
    otherwise move only the largest to dst, and the rest to helper
    """
    if offset >= len(main):
        return 0
    max_el = -10**10
    max_idx = -1
    # Find the location of the top-most largest element
    for idx, el in enumerate(main[offset:]):
        if el >= max_el:
            max_idx = idx+offset
            max_el = el
    num_moves = 0
    # Loop from that position downwards
    for max_idx in range(max_idx, offset-1, -1):
        # Processing only at positions with largest element
        if main[max_idx] < max_el:
            continue
        # The number of elements above this largest element
        top_count = len(main)-max_idx-1
        # Move everything above this largest element to helper
        num_moves += main_to_help_best(main, helper, dst, max_idx+1)
        # Move the largest to dst
        num_moves += move(main, dst)
        # Move back the top elements
        num_moves += push_to_main(helper, main, top_count)
    # Here, the largest elements are in dst, the rest are in main, not sorted
    if everything:
        # Move everything to dst on top of the largest
        num_moves += main_to_help_best(main, dst, helper, offset)
    else:
        # Move everything to helper, not with the largest
        num_moves += main_to_help_best(main, helper, dst, offset)
    return num_moves

def verify(lst, moves):
    if len(moves) == 1:
        return True
    moves[1][0][:] = lst
    for src, dst, el in moves[1:]:
        move(src, dst)
    return True

def equal(*args):
    return len(set(str(arg.__init__) for arg in args))==1

def move(src, dst):
    dst.append(src.pop())
    el = dst[-1]
    if not equal(dst, sort.lst) and list(reversed(sorted(dst))) != dst:
        raise Exception('HELPER NOT SORTED: %s, %s' % (src, dst))

    cur_len = len(move.history)
    check_idx = -1
    matched = False
    prev_src, prev_dst, prev_el = move.history[check_idx]
    # As long as the element is the same as previous elements,
    # we can reorder the moves
    while el == prev_el:
        if equal(src, prev_dst) and equal(dst, prev_src):
            del(move.history[check_idx])
            matched = True
            break
        elif equal(src, prev_dst):
            move.history[check_idx][1] = dst
            matched = True
            break
        elif equal(dst, prev_src):
            move.history[check_idx][0] = src
            matched = True
            break
        check_idx -= 1
        prev_src, prev_dst, prev_el = move.history[check_idx]
    if not matched:
        move.history.append([src, dst, el])
    return len(move.history)-cur_len

def push_to_main(src, main, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(src)
    if amount == 0:
        return 0
    for i in range(amount):
        num_moves += move(src, main)
    return num_moves

def push_to_help(main, dst, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(main)
    if amount == 0:
        return 0
    for i in range(amount):
        num_moves += move(main, dst)
    return num_moves

def help_to_help(src, dst, main, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(src)
    if amount == 0:
        return 0
    # Count the number of largest elements
    src_len = len(src)
    base_el = src[src_len-amount]
    base_idx = src_len-amount+1
    while base_idx < src_len and base_el == src[base_idx]:
        base_idx += 1

    # Move elements which are not the largest to main
    num_moves += push_to_main(src, main, src_len-base_idx)
    # Move the largest to destination
    num_moves += push_to_help(src, dst, base_idx+amount-src_len)
    # Move back from main
    num_moves += push_to_help(main, dst, src_len-base_idx)
    return num_moves

def move_to_main(src, main, helper, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(src)
    if amount == 0:
        return 0
    # Count the number of largest elements
    src_len = len(src)
    base_el = src[src_len-amount]
    base_idx = src_len-amount+1
    while base_idx < src_len and base_el == src[base_idx]:
        base_idx += 1

    # Move elements which are not the largest to helper
    num_moves += help_to_help(src, helper, main, src_len-base_idx)
    # Move the largest to main
    num_moves += push_to_main(src, main, base_idx+amount-src_len)
    # Repeat for the rest of the elements now in the other helper
    num_moves += move_to_main(helper, main, src, src_len-base_idx)
    return num_moves

def main():
    num_tasks = 0
    num_moves = 0
    for n in range(1, 7):
        start_moves = num_moves
        start_tasks = num_tasks
        max_move = -1
        max_main = []
        for lst in map(list,product(*[[1,2,3,4,5,6]]*n)):
            num_tasks += 1
            if DEBUG: print lst, [], []
            sort.lst = lst
            cur_lst = lst[:]
            move.history = [(None, None, None)]
            help1 = []
            help2 = []
            moves = sort_better(lst, help1, help2)
            if moves > max_move:
                max_move = moves
                max_main = cur_lst
            num_moves += moves

            if DEBUG: print '%s, %s, %s (moves: %d)' % (cur_lst, [], [], moves)
            if list(reversed(sorted(lst))) != lst:
                print 'NOT SORTED: %s' % lst
                return
            if DEBUG: print

            # Verify that the modified list of moves is still valid
            verify(cur_lst, move.history)
        end_moves = num_moves - start_moves
        end_tasks = num_tasks - start_tasks
        print 'Length %d\nMoves: %d\nTasks: %d\nMax: %d (%s)\nAverage: %.3f\n' % (n, end_moves, end_tasks, max_move, max_main, 1.0*end_moves/end_tasks)
    print '--------------'
    print 'Overall\nMoves: %d\nTasks: %d\nAverage: %.3f' % (num_moves, num_tasks, 1.0*num_moves/num_tasks)

# Old sort method, which assumes we can only see the top of the stack
def sort(main, max_stack, a_stack):
    height = len(main)
    largest = -1
    num_moves = 0
    a_stack_second_el = 10**10
    for i in range(height):
        if len(main)==0:
            break
        el = main[-1]
        if el > largest: # We found a new maximum element
            if i < height-1: # Process only if it is not at the bottom of main stack
                largest = el
                if len(a_stack)>0 and a_stack[-1] < max_stack[-1] < a_stack_second_el:
                    a_stack_second_el = max_stack[-1]
                # Move aux stack to max stack then reverse the role
                num_moves += help_to_help(a_stack, max_stack, main)
                max_stack, a_stack = a_stack, max_stack
                if DEBUG: print 'Moved max_stack to a_stack: %s, %s, %s (moves: %d)' % (main, max_stack, a_stack, num_moves)
                num_moves += move(main, max_stack)
                if DEBUG: print 'Moved el to max_stack: %s, %s, %s (moves: %d)' % (main, max_stack, a_stack, num_moves)
        elif el == largest:
            # The maximum element is the same as in max stack, append
            if i < height-1: # Only if the maximum element is not at the bottom
                num_moves += move(main, max_stack)
        elif len(a_stack)==0 or el <= a_stack[-1]:
            # Current element is the same as in aux stack, append
            if len(a_stack)>0 and el < a_stack[-1]:
                a_stack_second_el = a_stack[-1]
            num_moves += move(main, a_stack)
        elif a_stack[-1] < el <= a_stack_second_el:
            # Current element is larger, but smaller than the next largest element
            # Step 1
            # Move the smallest element(s) in aux stack into max stack
            amount = 0
            while len(a_stack)>0 and a_stack[-1] != a_stack_second_el:
                num_moves += move(a_stack, max_stack)
                amount += 1

            # Step 2
            # Move all elements in main stack that is between the smallest
            # element in aux stack and current element
            while len(main)>0 and max_stack[-1] <= main[-1] <= el:
                if max_stack[-1] < main[-1] < a_stack_second_el:
                    a_stack_second_el = main[-1]
                num_moves += move(main, a_stack)
                el = a_stack[-1]

            # Step 3
            # Put the smallest element(s) back
            for i in range(amount):
                num_moves += move(max_stack, a_stack)
        else: # Find a location in aux stack to put current element
            # Step 1
            # Move all elements into max stack as long as it will still
            # fulfill the Hanoi condition on max stack, AND
            # it should be greater than the smallest element in aux stack
            # So that we won't duplicate work, because in Step 2 we want
            # the main stack to contain the minimum element
            while len(main)>0 and a_stack[-1] < main[-1] <= max_stack[-1]:
                num_moves += move(main, max_stack)

            # Step 2
            # Pick the minimum between max stack and aux stack, move to main
            # This will essentially sort (in reverse) the elements into main
            # Don't move to main the element(s) found before Step 1, because
            # we want to move them to aux stack
            while True:
                if len(a_stack)>0 and a_stack[-1] < max_stack[-1]:
                    num_moves += move(a_stack, main)
                elif max_stack[-1] < el:
                    num_moves += move(max_stack, main)
                else:
                    break

            # Step 3
            # Move all elements in main into aux stack, as long as it
            # satisfies the Hanoi condition on aux stack
            while max_stack[-1] == el:
                num_moves += move(max_stack, a_stack)
            while len(main)>0 and main[-1] <= a_stack[-1]:
                if main[-1] < a_stack[-1] < a_stack_second_el:
                    a_stack_second_el = a_stack[-1]
                num_moves += move(main, a_stack)
        if DEBUG: print main, max_stack, a_stack
    # Now max stack contains largest element(s), aux stack the rest
    num_moves += push_to_main(max_stack, main)
    num_moves += move_to_main(a_stack, main, max_stack)
    return num_moves

if __name__ == '__main__':
    main()

justhalf

Posted 2014-04-01T05:26:15.113

Reputation: 2 070

I don't get your question on 4. What is this storing the second largest element on the second helper? What does that mean? – Justin – 2014-05-14T15:21:00.607

Basically just keep track of the second largest element in a variable. I guess I was overthinking it. I think it's perfectly fine, haha – justhalf – 2014-05-15T00:49:01.153

"Your function/subroutine can inspect any stack at any time". So if what you are doing can be easily done by running through the stacks and finding the location of the second largest element, it is fine. – Justin – 2014-05-15T04:39:33.603

Perhaps you can also add a more detailed out-of-code explanation. I still don't understand how this works. – Justin – 2014-05-15T04:46:29.917

1By "inspect any stack at any time" does it mean I can read the stack like an array without consuming any move? That changes everything. Regarding the explanation, I'm still in the process of updating the algorithm (I got it even lower), so I'll update once I'm done. – justhalf – 2014-05-15T05:14:11.010

Read. Only read. It's like the game of Towers of Hanoi, you get to see each element. – Justin – 2014-05-15T05:23:24.043

1I see. That opens up a whole new possibilities and definitely we can reduce the number of moves further. It will make the problem harder though, haha, since the task is then essentially "given an array of integers, find the minimal number of moves to sort it a la Tower of Hanoi". If we're only allowed to see the top of the stack, then my algorithm is close to optimal (if not the optimal) – justhalf – 2014-05-15T08:15:36.633

1

Java -- 2129090 2083142 moves on 55986 arrays

The ideone link.

The framework to ensure the algorithm is correct:

private final Deque<Integer> main = new ArrayDeque<Integer>();
private final Deque<Integer> help1 = new ArrayDeque<Integer>();
private final Deque<Integer> help2 = new ArrayDeque<Integer>();

private int taskCount = 0;
private int opCount = 0;

private void sort(List<Integer> input) {
    taskCount++;
    main.addAll(input);
    sortMain();
    verify();
    main.clear();
}

private void verify() {
    if (!help1.isEmpty()) {
        throw new IllegalStateException("non-empty help1\n" + state());
    }
    if (!help2.isEmpty()) {
        throw new IllegalStateException("non-empty help2\n" + state());
    }
    int last = 0;
    for (int i: main) {
        if (last > i) {
            throw new IllegalStateException("unsorted main: " + main);
        }
        last = i;
    }
}

private void done() {
    System.out.println();
    System.out.print(opCount + "/" + taskCount);
}

private void move(Deque<Integer> from, Deque<Integer> to) {
    if (from == to) throw new IllegalArgumentException("moving from/to " + from);
    Integer i = from.pop();
    if (to != main && !to.isEmpty() && i > to.peek()) {
        throw new IllegalStateException(
                from + " " + i + " -> " + to);
    }
    to.push(i);
    opCount++;
}

private String name(Deque<Integer> stack) {
    return stack == help1 ? "help1" :
           stack == help2 ? "help2" :
           "main";
}

private String state() {
    return "main:  " + main + 
            "\nhelp1: " + help1 +
            "\nhelp2: " + help2;
}

The actual algorithm:

private void ensureMain(Deque<Integer> stack) {
    if (stack != main) {
        throw new IllegalArgumentException("Expected main, got " + name(stack) + "\n" + state());
    }
}

private void ensureHelp(Deque<Integer> stack) {
    if (stack == main) {
        throw new IllegalArgumentException("Expected help, got main\n" + state());
    }
}

private void ensureHelpers(Deque<Integer> stack1, Deque<Integer> stack2) {
    ensureHelp(stack1);
    ensureHelp(stack2);
}

private void sortMain() {
    int height = main.size();
    int topIndex = height;
    while (topIndex == height && height > 1) {
        topIndex = lastIndexOfLargest(height, main);
        height--;
    }
    if (topIndex == height) { 
        // is already sorted
        return;
    }
    // split stack at largest element
    int part1Count = topIndex;
    int part2Count = height - topIndex;
    // move largest and first part to help 1
    moveFromMain(part1Count+1, help1, help2);
    // merge both parts to help 2, leaving largest on 1
    mergeToHelp(part2Count, main, part1Count, help1, help2);
    // move largest to main
    move(help1, main);
    // move remaining to main
    moveToMain(height, help2, help1);
}

/** Moves elements from main to helper, sorting them */
private void moveFromMain(int amount, Deque<Integer> target, Deque<Integer> help) {
    if (amount < 1) return;
    ensureHelpers(target, help);
    int topIndex = lastIndexOfLargest(amount, main);
    int part1Count = topIndex;
    int part2Count = amount - topIndex - 1;
    // move first part to help
    moveFromMain(part1Count, help, target);
    // move largest to target
    move(main, target);
    // merge both parts to target
    mergeToHelp(part2Count, main, part1Count, help, target);
}

/** Moves elements from helper to main, keeping them sorted */
private void moveToMain(int amount, Deque<Integer> source, Deque<Integer> help) {
    if (amount < 1) return;
    ensureHelpers(source, help);
    moveHelper(amount-1, source, help);
    move(source, main);
    moveToMain(amount-1, help, source);
}

/** Moves elements between helpers */
private void moveHelper(int amount, Deque<Integer> source, Deque<Integer> target) {
    pushToMain(amount, source);
    popFromMain(amount, target);
}

/** Merges top of main and helper to other helper */
private void mergeToHelp(int mainAmount, Deque<Integer> main, int helpAmount, Deque<Integer> help, Deque<Integer> target) {
    ensureMain(main);
    ensureHelpers(help, target);
    if (mainAmount < 0) mainAmount = 0;
    if (helpAmount < 0) helpAmount = 0;
    while (mainAmount > 0 || helpAmount > 0) {
        // while there is something to merge
        int largestMain = valueOfLargest(mainAmount, main);
        int largestHelp = valueOfLargest(helpAmount, help);
        if (largestMain > largestHelp) {
            // largest is in main
            int index = firstIndexOfLargest(mainAmount, main);
            if (index > 0) {
                // move excess to help:
                int mainTop = index;
                int helpTop = elementsSmallerThan(help, largestMain);
                if (helpTop > 0) {
                    // 1. move top of help to target
                    moveHelper(helpTop, help, target);
                    // 2. merge old top with excess from main
                    mergeToHelp(mainTop, main, helpTop, target, help);
                } else {
                    moveFromMain(mainTop, help, target);
                }
                mainAmount -= mainTop;
                helpAmount += mainTop;
            }
            move(main, target);
            mainAmount--;
        } else {
            // largest is at bottom of help
            int helpTop = helpAmount - 1; // largest is at bottom
            // move top to main
            pushToMain(helpTop, help);
            mainAmount += helpTop;
            // move largest to target
            move(help, target);
            helpAmount = 0;
        }
    }
}

private void pushToMain(int amount, Deque<Integer> from) {
    for (; amount > 0; amount--) move(from, main);
}

private void popFromMain(int amount, Deque<Integer> to) {
    for (; amount > 0; amount--) move(main, to);
}

private int firstIndexOfLargest(int height, Deque<Integer> stack) {
    if (height == 0) throw new IllegalArgumentException("height == 0");
    int topValue = 0;
    int topIndex = 0;
    int i = 0;
    for (Integer e: stack) {
        if (e > topValue) {
            topValue = e;
            topIndex = i;
        }
        if (++i == height) break;
    }
    return topIndex;
}

private int lastIndexOfLargest(int height, Deque<Integer> stack) {
    if (height == 0) throw new IllegalArgumentException("height == 0");
    int topValue = 0;
    int topIndex = 0;
    int i = 0;
    for (Integer e: stack) {
        if (e >= topValue) {
            topValue = e;
            topIndex = i;
        }
        if (++i == height) break;
    }
    return topIndex;
}

private int valueOfLargest(int height, Deque<Integer> stack) {
    int v = Integer.MIN_VALUE;
    for (Integer e: stack) {
        if (height-- == 0) break;
        if (e > v) v = e;
    }
    return v;
}

private int elementsSmallerThan(Deque<Integer> stack, int value) {
    int i = 0;
    for (Integer e: stack) {
        if (e >= value) return i;
        i++;
    }
    return i;
}

The testcase:

public static void main(String[] args) throws Exception {
    HanoiSort hanoi = new HanoiSort();
    int N = 6;
    for (int len = 1; len <= N; len++) {
        Integer[] input = new Integer[len];
        List<Integer> inputList = Arrays.asList(input);
        int max = N;
        for (int i = 1; i < len; i++) max *= N;
        for (int run = 0; run < max; run++) {
            int n = run;
            for (int i = 0; i < len; n /= N, i++) {
                input[i] = (n % N)+1;
            }
            try {
                hanoi.sort(inputList);
            } catch (Exception e) {
                System.out.println(inputList);
                e.printStackTrace(System.out);
                return;
            }
        }
    }
    hanoi.done();
}

Cephalopod

Posted 2014-04-01T05:26:15.113

Reputation: 575

-1

C/C++ Didn't measure moves (pegs :p1, p2, p3) Don't know how to add C++ code that uses STL(formating issue). Loosing parts of code due to html tag formats in code.

  1. Get n : count of top sorted elements in p1
  2. Do a hanoi move of them (n) to p3 using p2(keeps the sorted property)
  3. Move the next m elems (atleast 1) in reverse order from p1 to p2.
  4. Merge move (n+m) data in p2 & p3 to p1.

         4.1 merge sort p2 & P3 (reverse) to p1
         4.2 move (n+m) to p3
         4.3 hanoi p3 to p1 using p2
    
  5. Call hanoi sort recursively which will sort atleast n+m+1 elements now.
  6. Stop when n = size of p1.
#include 
#include 
using namespace std;
/****************************************************************************** 
   Show the vector (Had to chance cout
*******************************************************************************/    

void show(vector p, string msg)
{
   vector::iterator it;
   printf(" %s \n", msg.c_str());
   for(it = p.begin(); it  &fr, vector &inter, vector &to, int n) {
   int d1;
   if (n  &p1, vector &p2, vector &p3) {
   int d3, d2, d1;
   int count,n;
   bool d2_added;

   d2 = p2.back(); p2.pop_back();
   // data will be descending in p1
   d2_added = false;
   while (p3.size() > 0) {
      d3 = p3.back(); p3.pop_back();
      if (d2_added == false && d2  0) {
      d1 = p1.back();p1.pop_back();
      p3.push_back(d1);
      --count;
   }
   //Move back with hanoi from p3 to p1
   // use p2 as inter
   hanoi(p3, p2, p1, n);
}
/*******************************************************************************
   Gets the count of first sorted elements
*******************************************************************************/    
int get_top_sorted_count(vector &p1) {
   vector::iterator it;
   int prev = 0;
   int n =1;

   for (it = --p1.end(); it >= p1.begin(); --it){
     if (it == --p1.end()) {
    prev = *it;
        continue;
     }
     if (*it  &p1, vector &p2, vector &p3) {
    int n, d1;
    n = get_top_sorted_count(p1);
    if (n == p1.size()) {
       return;
    }
    hanoi(p1, p2, p3, n);
    p2.push_back(p1.back());
    p1.pop_back();
    merge_move(p1, p2, p3);
    hanoi_sort(p1, p2, p3);
}
/*******************************************************************************    
    Sorts baed on hanoi
*******************************************************************************/    
int main (void)
{
  vector p1, p2, p3;
  p1.push_back(5);
  p1.push_back(4);
  p1.push_back(7);
  p1.push_back(3);
  p1.push_back(8);
  p1.push_back(2);
  show(p1, "...Bef Sort...");
  hanoi_sort(p1, p2, p3);
  show(p1, "...Aft Sort...");
}

Joji

Posted 2014-04-01T05:26:15.113

Reputation: 1

Can you write the code for this? Otherwise, this is not an answer. – Justin – 2014-05-14T03:35:57.777

I don't see the hanoi(...) function. Also, you have 2 #includes that don't compile. Please post the full code. – None – 2014-05-14T14:05:26.457