Hot Potato Salesman

23

8

Given a list of points, find the shortest path that visits all points and returns to the starting point.

The Travelling Salesman Problem is well-known in the computer science field, as are many ways to calculate/approximate it. It's been solved for very large groups of points, but some of the largest take many CPU-years to finish.

Don't get burned by the potato.

Hot Potato is a game where 2+ players pass a "potato" around in a circle while music plays. The object is to pass it to the next player quickly. If you are holding the potato when the music stops, you're out.


The object of Hot Potato Salesman is:

Given a set of 100 unique points, return those points in a better order (shorter total distance as defined further down). This will "pass" the problem to the next player. They have to improve it and pass it to the next, etc. If a player cannot improve it, they are out and play continues until one player is left.

To keep this from being a "brute-force-me-a-path" competition, there are these stipulations:

  • You cannot take more than one minute to pass the potato. If you haven't found and passed a shorter solution by the time one minute is up, you're out.

  • You cannot change the position of more than 25 points. To be exact, >= 75 points must be in the same position as you received them. It does not matter which ones you decide to change, just the amount you change.

When only one player is left, he is the winner of that game, and receives one point. A tourney consists of 5*n games, where n is the number of players. Each game, the starting player will be rotated, and the remaining player order will be shuffled. The player with the most points at the end is the winner of the tourney. If the tourney ends with a tie for first place, a new tourney will be played with only those contestants. This will continue until there is no tie.

The starting player for each game will receive a set of pseudorandomly selected points in no particular order.

Points are defined as a pair of integer x,y coordinates on a cartesian grid. Distance is measured using Manhattan distance, |x1-x2| + |y1-y2|. All coordinates will lie in the [0..199] range.

Input

Input is given with a single string argument. It will consist of 201 comma-separated integers representing the number of current players(m) and 100 points:

m,x0,y0,x1,y1,x2,y2,...,x99,y99

The order of these points is the current path. The total distance is obtained by adding the distance from each point to the next (dist(0,1) + dist(1,2) + ... + dist(99,0)). Don't forget to return to start when calculating total distance!

Note that m is not the number of players that started the game, it is the number that are still in.

Output

Output is given in the same way as input, minus m; a single string containing comma-separated integers representing the points in their new order.

x0,y0,x1,y1,x2,y2,...,x99,y99

The control program will wait for output for one minute only. When output is received, it will verify that:

  • the output is well-formed
  • the output consists of only and all the 100 points present in the input
  • >=75 points are in their original positions
  • the path length is less than the previous path

If any of these checks fail (or there is no output), you are out and the game will proceed to the next player.

Control Program

You can find the control program at this link. The control program itself is deterministic, and is posted with a dummy seed of 1. The seed used during scoring will be different, so don't bother trying to analyze the turn order/point lists it spits out.

The main class is Tourney. Running this will do a full tournament with contestants given as arguments. It spits out the winner of each game and a tally at the end. A sample tourney with two SwapBots looks like:

Starting tournament with seed 1

(0) SwapBot wins a game! Current score: 1
(1) SwapBot wins a game! Current score: 1
(1) SwapBot wins a game! Current score: 2
(1) SwapBot wins a game! Current score: 3
(0) SwapBot wins a game! Current score: 2
(1) SwapBot wins a game! Current score: 4
(1) SwapBot wins a game! Current score: 5
(1) SwapBot wins a game! Current score: 6
(1) SwapBot wins a game! Current score: 7
(1) SwapBot wins a game! Current score: 8

Final Results:

Wins        Contestant
2       (0) SwapBot
8       (1) SwapBot

If you want to test just one game at a time, you can run the Game class instead. This will run one game with players in the order given as arguments. By default, it will also print a play-by-play showing the current player and path length.

Also included are a few test players: SwapBot, BlockPermuter, and TwoSwapBot. The first two will not be included in scoring runs, so feel free to use and abuse them during testing. TwoSwapBot will be included in judging, and he's no slouch, so bring your A-game.

Miscellany

  • You cannot save state information, and each turn is a separate run of your program. The only information you will receive each turn is the set of points.

  • You cannot use external resources. This includes network calls and file access.

  • You cannot use library functions designed to solve/assist with the TSP problem or its variants.

  • You cannot manipulate or interfere with other players in any way.

  • You cannot manipulate or interfere with the control program or any included classes or files in any way.

  • Multi-threading is allowed.

  • One submission per user. If you submit more than one entry, I will only enter the first one submitted. If you want to change your submission, edit/delete the original.

  • The tournament will be running on Ubuntu 13.04, on a computer with an i7-3770K CPU and 16GB RAM. It will not be run in a VM. Anything I perceive as malicious will immediately disqualify the current and any future entry you submit.

  • All entries must be runnable from the command line with free (as in beer) software. If I have problems compiling/running your entry, I will request aid in the comments. If you do not respond or I cannot ultimately get it running, it will be disqualified.

Results (22 May 2014)

New results are in! UntangleBot has beaten the competition quite soundly. TwoSwapBot managed seven wins, and SANNbot snuck a victory in as well. Here's a scoreboard, and a link to the raw output:

Wins        Contestant
22      (2) ./UntangleBot
7       (0) TwoSwapBot
1       (5) SANNbot.R
0       (1) BozoBot
0       (3) Threader
0       (4) DivideAndConquer

As it stands now, UntangleBot has won the checkmark. Don't let that discourage you from entering, though, since I'll run the tournament as more contestants appear and change the accepted answer accordingly.

Geobits

Posted 2014-05-13T01:35:41.393

Reputation: 19 061

Comments purged. Please notify me for any possible lost information. – Doorknob – 2014-05-23T12:14:50.613

Man why did this challenge have to be during my final exams (finally, done with school yeay). You sure are a bad planner Geobits ;) Weird/sadly enough at that time there were tons of king-of-the-hill questions and now there aren't any (maybe it works better if there is only one at a time, hint hint) ... – Herjan – 2014-05-28T16:16:45.800

@Herjan Feel free to try to tackle the current champ. I'll run the tourney again as new contestants appear, so the contest isn't over or anything. Beat SirDarius and it may spur him or someone else to beat yours, breathing life back into it ;) – Geobits – 2014-05-28T16:19:20.833

@Herjan Please do submit an entry ! I believe there is a lot of room for improvement here. Most of the solutions here, including mine, do not rely on clever algorithms specific to this problem. – SirDarius – 2014-05-29T17:57:56.030

I think there is a problem with modification limitation. As some times it will take to modify the entire set of data to get better solution. – Ilya Gazman – 2014-07-01T21:16:55.183

Answers

8

UntangleBot (formerly NiceBot)

A C++11 bot that uses two strategies.
At first it will try to "untangle" the path if possible, by detecting intersections between paths that are closer than 25 points (since untangling implies modifying all points in-between).
If the first strategy fails, it randomly swaps points to find better distances until a better path has been found.

This bot consistently beats TwoSwapBot with an approximate ratio of five victories for one loss in my test tourneys.

// g++ -std=c++11 -O3 -o UntangleBot UntangleBot.cpp
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iostream>
#include <iterator>
#include <random>
#include <set>
#include <sstream>

const int NPOINTS = 100;

struct Point {
    int x, y;

    Point():x(0),y(0) {}    
    Point(int x, int y):x(x),y(y) {}

    int distance_to(const Point & pt) const {
        return std::abs(x - pt.x) + std::abs(y - pt.y);
    }
};

std::ostream & operator<<(std::ostream & out, const Point & point) {
    return out << point.x << ',' << point.y;
}

int total_distance(const Point points[NPOINTS]) {
    int distance = 0;
    for (int i = 0; i < NPOINTS; ++i) {
        distance += points[i].distance_to(points[(i+1)%NPOINTS]);
    }
    return distance;
}

bool intersects(const Point & p1, const Point & p2, const Point & p3, const Point & p4) {
    double s1_x, s1_y, s2_x, s2_y;
    s1_x = p2.x - p1.x;
    s1_y = p2.y - p1.y;
    s2_x = p4.x - p3.x;
    s2_y = p4.y - p3.y;

    double s, t;
    s = (-s1_y * (p1.x - p3.x) + s1_x * (p1.y - p3.y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p1.y - p3.y) - s2_y * (p1.x - p3.x)) / (-s2_x * s1_y + s1_x * s2_y);

    return s >= 0 && s <= 1 && t >= 0 && t <= 1;
}

int main(int argc, char ** argv) {
    Point points[NPOINTS];

    using Clock = std::chrono::system_clock;
    const Clock::time_point start_time = Clock::now();

    // initialize points
    if (argc < 2) {
        std::cerr << "Point list is missing" << std::endl;
        return 1;
    }
    std::stringstream in(argv[1]);
    int players;
    char v;
    in >> players >> v;
    for (int i = 0; i < NPOINTS; ++i) {
        in >> points[i].x >> v >> points[i].y >> v;
    }

    int original_distance = total_distance(points);

    // detect intersection between any 4 points
    for (int i = 0; i < NPOINTS; ++i) {
        for (int j = i+1; j < NPOINTS; ++j) {
            Point & p1 = points[i];
            Point & p2 = points[(i+1)%NPOINTS];
            Point & p3 = points[j];
            Point & p4 = points[(j+1)%NPOINTS];

            // points must all be distinct
            if (p1.distance_to(p3) == 0 || p1.distance_to(p4) == 0 || p2.distance_to(p3) == 0 || p2.distance_to(p4) == 0) {
                continue;
            }

            // do they intersect ?
            if (!intersects(p1, p2, p3, p4)) {
                continue;
            }

            // can modify less than 25 points ?
            if (std::abs(j-i) > 25) {
                continue;
            }

            // swap points
            for (int m = 0; m < std::abs(j-i)/2; ++m) {
                if (i+1+m != j-m) {
                    std::swap(points[i+1+m], points[j-m]);
                    //std::cerr << "untangle " << i+1+m << " <=> " << j-m << '\n';
                }
            }

            int new_distance = total_distance(points);
            if (new_distance < original_distance) {
                std::copy(std::begin(points), std::end(points)-1, std::ostream_iterator<Point>(std::cout, ","));
                std::cout << points[NPOINTS-1];
                return 0;
            }
            else {
                // swap points back
                for (int m = 0; m < std::abs(j-i)/2; m++) {
                    if (i+1+m != j-m) {
                        std::swap(points[i+1+m], points[j-m]);
                    }
                }
            }
        }
    }

    // more traditional approach if the first fails
    std::mt19937 rng(std::chrono::duration_cast<std::chrono::seconds>(start_time.time_since_epoch()).count());
    std::uniform_int_distribution<> distr(0, NPOINTS-1);
    while (true) {
        // try all possible swaps from a random permutation
        int p1 = distr(rng);
        int p2 = distr(rng);
        std::swap(points[p1], points[p2]);

        for (int i = 0; i < NPOINTS; ++i) {
            for (int j = i+1; j < NPOINTS; ++j) {
                std::swap(points[i], points[j]);
                if (total_distance(points) < original_distance) {
                    std::copy(std::begin(points), std::end(points)-1, std::ostream_iterator<Point>(std::cout, ","));
                    std::cout << points[NPOINTS-1];
                    return 0;
                }
                else {
                    std::swap(points[i], points[j]);
                }
            }
        }

        // they didn't yield a shorter path, swap the points back and try again
        std::swap(points[p1], points[p2]);
    }
    return 0;
}

SirDarius

Posted 2014-05-13T01:35:41.393

Reputation: 378

You beat me by 19 minutes! – Rainbolt – 2014-05-14T16:17:06.007

This should have more upvotes, judging from today's results. – Geobits – 2014-05-16T13:56:20.150

@Geobits I'm still surprised the simplest thing I came up with performs so well. Hoping more challenging contestants will enter ! – SirDarius – 2014-05-16T13:58:14.160

@SirDarius Okay, fine. Have a bit of challenge.

– Geobits – 2014-05-16T18:25:59.833

4

SANNbot

(a simulated annealing bot in R)

Should be called with Rscript SANNbot.R.

input <- strsplit(commandArgs(TRUE),split=",")[[1]]
n <- as.integer(input[1])                            # Number of players
init_s <- s <- matrix(as.integer(input[-1]),ncol=2,byrow=TRUE) # Sequence of points
totdist <- function(s){                              # Distance function
    d <- 0
    for(i in 1:99){d <- d + (abs(s[i,1]-s[i+1,1])+abs(s[i,2]-s[i+1,2]))}
    d
    }
gr <- function(s){                                   # Permutation function
    changepoints <- sample(1:100,size=2, replace=FALSE)
    tmp <- s[changepoints[1],]
    s[changepoints[1],] <- s[changepoints[2],]
    s[changepoints[2],] <- tmp
    s
    }
d <- init_d <- totdist(s)                            # Initial distance
k <- 1                                               # Number of iterations
v <- 0
t <- n*(init_d/12000)                                 # Temperature
repeat{
    si <- gr(s)                                      # New sequence
    di <- totdist(si)                                # New distance
    dd <- di - d                                     # Difference of distances
    if(dd <= 0 | runif(1) < exp(-dd/t)){             # Allow small uphill changes
        d <- di
        s <- si
        v <- v+2
        }
    k <- k+1
    if(k > 10000){break}
    if(d > init_d & v>20){s <- init_s; d <- init_d; v <- 0}
    if(v>23){break}
}
cat(paste(apply(s,1,paste,collapse=","),collapse=","))

The idea is relatively simple: each turn is one "cooling step" of a simulated annealing with the number of players still in the game as "temperature" (modified by the current distance over 12000, i.e. roughly the initial distance). Only difference with a proper simulated annealing is that I checked that i don't permute more than 25 elements and if i used up 20 moves but the resulting sequence is worth than the initial then it start over again.

plannapus

Posted 2014-05-13T01:35:41.393

Reputation: 8 610

@Geobits ah sorry deleted the line where init_s was defined by accident (well "accident": i saw the line and thought "why is it here again?" and deleted it :) ). Corrected. – plannapus – 2014-05-13T14:49:34.157

I tried your program using java Tourney "java Swapbot" "Rscript SANNbot.R" and it seemed to work. – plannapus – 2014-05-13T14:54:45.410

Yea, it seems to be working now. – Geobits – 2014-05-13T14:55:34.693

In theory (if I'm not completely mistaken) it should perform better when more players enter the game. – plannapus – 2014-05-13T15:04:59.763

As-is, this program always goes out early due to "too many points changed" in my testing. If I bump the u check limits up, this doesn't happen (and it performs much better). While your code seems pretty straightforward, I don't know the quirks of R so I can't tell if the logic is wrong. (the latest version of the controller will give messages about why a bot goes out when running Game, so that might help pinpoint the problem) – Geobits – 2014-05-14T14:08:35.343

well the u is the number of points which are at the exact same index as in the initial sequence (the one taken as input). Since my permutation function only switch two point at each iteration, it seemed to me that if i checked it was higher than 76 at the beginning of the iteration i was sure it wouldn't be lower than 75 at the end... I'll look into it... – plannapus – 2014-05-14T15:05:12.380

Yea, that's the way it looks to me, too. I figured there was something I just wasn't seeing causing it. – Geobits – 2014-05-14T15:06:28.407

still have no idea why it didn t work but now it seems not to fail because of that anymore (though it loses pretty consistently). – plannapus – 2014-05-15T09:10:24.673

4

BozoBot

Utilizes the complex logic behind Bozosort to find a better path. It looks like this.

  • Swap random points
  • If we improved
    • Return the answer
  • Otherwise
    • Try again

BozoBot has now been improved with Multithreading! Four minions now aimlessly juggle points until they stumble upon an improvement. The first one to find a solution gets a cookie!

Apparently I fail at multithreading.

import java.util.Random;
public class BozoBot {
    public static String[] input;
    public static void main(String[] args) {
        input = args;
        for(int i = 0; i < 4; i++) new Minion().run();
    }
}

class Minion implements Runnable {
    public static boolean completed = false;
    public static synchronized void output(int[] x, int[] y) {
        if(!completed) {
            completed = true;
            String result = x[0]+","+y[0];
            for (int i = 1; i < 100; i++)
                result+=","+x[i]+","+y[i];
            System.out.print(result);
            // receiveCookie(); // Commented out to save money
        }
    }
    public void run() {
        String[] args = BozoBot.input[0].split(",");
        int[] x = new int[100];
        int[] y = new int[100];
        for (int i = 1; i < 201; i+=2) {
            x[(i-1)/2] = Integer.parseInt(args[i]);
            y[i/2] = Integer.parseInt(args[i+1]);
        }
        int startDistance = 0;
        for (int i = 1; i < 100; i++)
            startDistance += Math.abs(x[i-1]-x[i]) + Math.abs(y[i-1]-y[i]);
        int r1, r2, r3, r4, tX, tY, newDistance;
        Random gen = new java.util.Random();
        while (true) {
            r1 = gen.nextInt(100);
            r2 = gen.nextInt(100);
            r3 = gen.nextInt(100);
            r4 = gen.nextInt(100);
            tX = x[r1];
            x[r1] = x[r2];
            x[r2] = tX;
            tY = y[r1];
            y[r1] = y[r2];
            y[r2] = tY;
            tX = x[r3];
            x[r3] = x[r4];
            x[r4] = tX;
            tY = y[r3];
            y[r3] = y[r4];
            y[r4] = tY;
            newDistance = 0;
            for (int i=1; i < 100; i++)
                newDistance += Math.abs(x[i-1]-x[i]) + Math.abs(y[i-1]-y[i]);
            if(newDistance < startDistance)
                break;
            tX = x[r1];
            x[r1] = x[r2];
            x[r2] = tX;
            tY = y[r1];
            y[r1] = y[r2];
            y[r2] = tY;
            tX = x[r3];
            x[r3] = x[r4];
            x[r4] = tX;
            tY = y[r3];
            y[r3] = y[r4];
            y[r4] = tY;
        }
        output(x,y);
    }
}

Rainbolt

Posted 2014-05-13T01:35:41.393

Reputation: 6 176

Ehhmm... Shouldn't you put your minions into a thread instead of calling run()? AFAIK this isn't multithreading... – CommonGuy – 2014-05-20T11:59:22.810

@Manu You caught me! I tried to learn it on my own and failed. Any pointers? – Rainbolt – 2014-05-20T13:16:44.107

I think it should be new Thread(new Minion()).start() – CommonGuy – 2014-05-20T13:23:22.337

1

@Manu Thanks. Apparently I only read half of this tutorial when I was coding.

– Rainbolt – 2014-05-20T13:33:27.013

3

TwoSwapBot

An upgrade to SwapBot, this guy checks for every pair of swaps. First, he checks if any single swap will shorten the path. If it does, it returns immediately. If not, he checks each to see if another swap will shorten it. If not, he just dies.

While the path is still semi-random, it normally returns in about 100ms. If he has to check each and every 2-swap (roughly 25M), it takes about 20 seconds.

At the time of submission, this beat all other competitors in test rounds.

public class TwoSwapBot {

    static int numPoints = 100;

    String run(String input){
        String[] tokens = input.split(",");
        if(tokens.length < numPoints*2)
            return "bad input? nope. no way. bye.";

        Point[] points = new Point[numPoints];  
        for(int i=0;i<numPoints;i++)
            points[i] = new Point(Integer.valueOf(tokens[i*2+1]), Integer.valueOf(tokens[i*2+2]));
        int startDist = totalDistance(points);

        Point[][] swapped = new Point[(numPoints*(numPoints+1))/2][];       
        int idx = 0;
        for(int i=0;i<numPoints;i++){
            for(int j=i+1;j<numPoints;j++){
                Point[] test = copyPoints(points);
                swapPoints(test,i,j);
                int testDist = totalDistance(test);
                if( testDist < startDist)
                    return getPathString(test);
                else
                    swapped[idx++] = test;
            }
        }

        for(int i=0;i<idx;i++){
            for(int k=0;k<numPoints;k++){
                for(int l=k+1;l<numPoints;l++){
                    swapPoints(swapped[i],k,l);
                    if(totalDistance(swapped[i]) < startDist)
                        return getPathString(swapped[i]);
                    swapPoints(swapped[i],k,l);
                }
            }
        }
        return "well damn";
    }

    void swapPoints(Point[] in, int a, int b){
        Point tmp = in[a];
        in[a] = in[b];
        in[b] = tmp;
    }

    String getPathString(Point[] in){
        String path = "";
        for(int i=0;i<numPoints;i++)
            path += in[i].x + "," + in[i].y + ",";
        return path.substring(0,path.length()-1);
    }

    Point[] copyPoints(Point[] in){
        Point[] out = new Point[in.length];
        for(int i=0;i<out.length;i++)
            out[i] = new Point(in[i].x, in[i].y);
        return out;
    }

    static int totalDistance(Point[] in){
        int dist = 0;
        for(int i=0;i<numPoints-1;i++)
            dist += in[i].distance(in[i+1]);
        return dist + in[numPoints-1].distance(in[0]);
    }

    public static void main(String[] args) {
        if(args.length < 1)
            return;
        System.out.print(new TwoSwapBot().run(args[0]));
    }

    class Point{
        final int x; final int y;
        Point(int x, int y){this.x = x; this.y = y;}
        int distance(Point other){return Math.abs(x-other.x) + Math.abs(y-other.y);}
    }
}

Geobits

Posted 2014-05-13T01:35:41.393

Reputation: 19 061

2

Threader

This bot

  1. Splits the 100 points up in 410 pieces of 2510 points
  2. Starts a thread for each piece
  3. In the thread, randomly shuffle the array, while keeping the start- and endpoint fixed
  4. If distance of new array is shorter, keep it
  5. After 59s, the main thread collects the results and prints it out

The idea is to find the best improvement to the path, so that the other bots will fail with their logic.

import java.util.Arrays;
import java.util.Collections;

public class Threader {
    public static final int THREAD_COUNT = 10;
    public static final int DOT_COUNT = 100;
    private final Dot[] startDots = new Dot[THREAD_COUNT];
    private final Dot[] endDots = new Dot[THREAD_COUNT];
    private final Dot[][] middleDots = new Dot[THREAD_COUNT][DOT_COUNT/THREAD_COUNT-2];
    private final Worker worker[] = new Worker[THREAD_COUNT];
    private final static long TIME = 59000; 

    public static void main(String[] args) {
        Threader threader = new Threader();
        //remove unnecessary player count to make calculation easier
        threader.letWorkersWork(args[0].replaceFirst("^[0-9]{1,3},", "").split(","));
    }

    public void letWorkersWork(String[] args) {
        readArgs(args);
        startWorkers();
        waitForWorkers();
        printOutput();
    }

    private void readArgs(String[] args) {
        final int magigNumber = DOT_COUNT*2/THREAD_COUNT;
        for (int i = 0; i < args.length; i += 2) {
            Dot dot = new Dot(Integer.parseInt(args[i]), Integer.parseInt(args[i + 1]));
            if (i % magigNumber == 0) {
                startDots[i / magigNumber] = dot;
            } else if (i % magigNumber == magigNumber - 2) {
                endDots[i / magigNumber] = dot;
            } else {
                middleDots[i / magigNumber][(i % magigNumber) / 2 - 1] = dot;
            }
        }
    }

    private void startWorkers() {
        for (int i = 0; i < THREAD_COUNT; i++) {
            worker[i] = new Worker(startDots[i], endDots[i], middleDots[i]);
            Thread thread = new Thread(worker[i]);
            thread.setDaemon(true);
            thread.start();
        }
    }

    private void waitForWorkers() {
        try {
            Thread.sleep(TIME);
        } catch (InterruptedException e) {
        }
    }

    private void printOutput() {
        //get results
        Worker.stopWriting = true;
        int workerOfTheYear = 0;
        int bestDiff = 0;
        for (int i = 0; i < THREAD_COUNT; i++) {
            if (worker[i].diff() > bestDiff) {
                bestDiff = worker[i].diff();
                workerOfTheYear = i;
            }
        }
        //build output
        StringBuilder result = new StringBuilder(1000);
        for (int i = 0; i < THREAD_COUNT; i++) {
            result.append(startDots[i]);
            Dot middle[] = middleDots[i];
            if (i == workerOfTheYear) {
                middle = worker[i].bestMiddle;
            }
            for (int j = 0; j < middle.length; j++) {
                result.append(middle[j]);
            }
            result.append(endDots[i]);
        }
        result.replace(0, 1, ""); //replace first comma
        System.out.print(result);
    }

}

class Worker implements Runnable {

    public Dot start;
    public Dot end;
    private Dot[] middle = new Dot[Threader.DOT_COUNT/Threader.THREAD_COUNT-2];
    public Dot[] bestMiddle = new Dot[Threader.DOT_COUNT/Threader.THREAD_COUNT-2];
    public static boolean stopWriting = false;
    private int bestDist = Integer.MAX_VALUE;
    private final int startDist;

    public Worker(Dot start, Dot end, Dot[] middle) {
        this.start = start;
        this.end = end;
        System.arraycopy(middle, 0, this.middle, 0, middle.length);
        System.arraycopy(middle, 0, this.bestMiddle, 0, middle.length);
        startDist = Dot.totalDist(start, middle, end);
    }

    @Override
    public void run() {
        while (true) {
            shuffleArray(middle);
            int newDist = Dot.totalDist(start, middle, end);
            if (!stopWriting && newDist < bestDist) {
                System.arraycopy(middle, 0, bestMiddle, 0, middle.length);
                bestDist = newDist;
            }
        }
    }

    public int diff() {
        return startDist - bestDist;
    }

    private void shuffleArray(Dot[] ar) {
        Collections.shuffle(Arrays.asList(ar));
    }

}

class Dot {

    public final int x;
    public final int y;

    public Dot(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int distTo(Dot other) {
        return Math.abs(x - other.x) + Math.abs(y - other.y);
    }

    public static int totalDist(Dot start, Dot[] dots, Dot end) {
        int distance = end.distTo(start);
        distance += start.distTo(dots[0]);
        distance += end.distTo(dots[dots.length - 1]);
        for (int i = 1; i < dots.length; i++) {
            distance += dots[i].distTo(dots[i - 1]);
        }
        return distance;
    }

    @Override
    public String toString() {
        return "," + x + "," + y;
    }
}

CommonGuy

Posted 2014-05-13T01:35:41.393

Reputation: 4 684

2Note: I changed println to print to get rid of the newline at the end of your output. Otherwise it crashes out. – Geobits – 2014-05-22T01:23:43.910

Changed println to print and made the thread-count dynamic. It now starts with 10 threads... – CommonGuy – 2014-05-24T09:34:27.107

1

Divide And Conquer + Greedy Bot

NOTE: I looked at your code for Game which includes the following in Game.parsePath:

for(int i=0;i<numPoints;i++){
        test[i] = new Point(Integer.valueOf(tokens[i*2]), Integer.valueOf(tokens[i*2+1]));
        if(test[i].equals(currentPath[i]))
            same++;

However, there is no catch(NumberFormatException) block, so your program will likely crash when a player program outputs a string (as demonstrated at the end of my program's main method). I advise you fix this, because programs could output exceptions, stack traces, or the like. Otherwise, comment out that line in my program before you run it.

Back to the topic

This implementation (in Java) splits the list of points into chunks of 25, randomly spaced on the list. Then, it creates threads to shorten the path between the points in each chunk (Hence, "Divide and Conquer"). The main thread monitors the others and makes sure to present the shortest solution within the time limit. If a thread dies with or without a solution, it starts another thread off again on a different chunk.

Each thread uses the "greedy" algorithm, which starts off on a random point, goes to the closest point, and repeats until all the points are covered (Hence, "greedy").

Notes

  • This will run through the full 1 minute (I gave 3 seconds for program startup/shutdown and JVM startup - you never know what the JVM startup routines get caught up on next...)
  • Even if it has found solutions, it will keep searching and after the 1 minute is up it presents the best solution it found.
  • I'm not sure if this implementation is actually any good. I had some fun coding it though :)
  • Since a lot of things are random, this may not give the same output for the same input.

Just compile and use java DivideAndConquer.class to run.

public class DivideAndConquer extends Thread {
    static LinkedList<Point> original;
    static Solution best;
    static LinkedList<DivideAndConquer> bots;
    static final Object _lock=new Object();

    public static void main(String[] args){
        if(args.length != 1) {
            System.err.println("Bad input!");
            System.exit(-1);
        }
        // make sure we don't sleep too long... get the start time
        long startTime = System.currentTimeMillis();
        // parse input
        String[] input=args[0].split(",");
        int numPlayers=Integer.parseInt(input[0]);
        original=new LinkedList<Point>();
        for(int i=1;i<input.length;i+=2){
            original.add(new Point(Integer.parseInt(input[i]), Integer.parseInt(input[i+1])));
        }
        // start threads
        bots=new LinkedList<DivideAndConquer>();
        for(int i=0;i<6;i++)
            bots.add(new DivideAndConquer(i));
        // sleep
        try {
            Thread.sleep(57000 - (System.currentTimeMillis() - startTime));
        } catch(Exception e){} // should never happen, so ignore
        // time to collect the results!
        Solution best=getBestSolution();
        if(best!=null){
            best.apply(original);
            String printStr="";
            for(int i=0;i<original.size();i++){
                Point printPoint=original.get(i);
                printStr+=printPoint.x+","+printPoint.y+",";
            }
            printStr=printStr.substring(0, printStr.length()-1);
            System.out.print(printStr);
        } else {
            System.out.println("Hey, I wonder if the tournament program crashes on NumberFormatExceptions... Anyway, we failed");
        }
    }

    // check the distance
    public static int calculateDistance(List<Point> points){
        int distance = 0;
        for(int i=0;i<points.size();i++){
            int next=i+1;
            if(next>=points.size())next=0;
            distance+=points.get(i).distance(points.get(next));
        }
        return distance;
    }

    public static void solutionFound(Solution s){
        // thanks to Java for having thread safety features built in
        synchronized(_lock){
            // thanks to Java again for short-circuit evaluation
            // saves lazy programmers lines of code all the time
            if(best==null || best.distDifference < s.distDifference){
                best=s;
            }
        }
    }

    public static Solution getBestSolution(){
        // make sure we don't accidentally return
        // the best Solution before it's even
        // done constructing
        synchronized(_lock){
            return best;
        }
    }

    List<Point> myPoints;
    int start;
    int length;
    int id;

    public DivideAndConquer(int id){
        super("DivideAndConquer-Processor-"+(id));
        this.id=id;
        myPoints=new LinkedList<Point>();
        start=(int) (Math.random()*75);
        length=25;
        for(int i=start;i<start+length;i++){
            myPoints.add(original.get(i));
        }
        start();
    }

    public void run(){
        // copy yet again so we can delete from it
        List<Point> copied=new LinkedList<Point>(myPoints);
        int originalDistance=calculateDistance(copied);
        // this is our solution list
        List<Point> solution=new LinkedList<Point>();
        int randomIdx=new Random().nextInt(copied.size());
        Point prev=copied.get(randomIdx);
        copied.remove(randomIdx);
        solution.add(prev);
        while(copied.size()>0){
           int idx=-1;
           int len = -1;
           for(int i=0;i<copied.size();i++){
               Point currentPoint=copied.get(i);
               int dist=prev.distance(currentPoint);
               if(len==-1 || dist<len){
                   len=dist;
                   idx=i;
               }
           }
           prev=copied.get(idx);
           copied.remove(idx);
           solution.add(prev);
        }
        // aaaand check our distance
        int finalDistance=calculateDistance(solution);
        if(finalDistance<originalDistance){
            // yes! solution
            Solution aSolution=new Solution(start, length, solution, originalDistance-finalDistance);
            solutionFound(aSolution);
        }
        // start over regardless
        bots.set(id, new DivideAndConquer(id));
    }

    // represents a solution
    static class Solution {
        int start;
        int length;
        int distDifference;
        List<Point> region;
        public Solution(int start, int length, List<Point> region, int distDifference){
            this.region=new LinkedList<Point>(region);
            this.start=start;
            this.length=length;
            this.distDifference=distDifference;
        }
        public void apply(List<Point> points){
            for(int i=0;i<length;i++){
                points.set(i+start, region.get(i));
            }
        }
    }

    // copied your Point class, sorry but it's useful...
    // just added public to each method for aesthetics
    static class Point{
        int x;
        int y;
        Point(int x, int y){
            this.x = x;
            this.y = y;
        }
        Point(Point other){
            this.x = other.x;
            this.y = other.y;
        }
        public boolean equals(Point other){
            if(this.x==other.x && this.y==other.y)
                return true;
            return false;
        }

        public int distance(Point other){
            return Math.abs(x-other.x) + Math.abs(y-other.y);
        }
    }
}

DankMemes

Posted 2014-05-13T01:35:41.393

Reputation: 2 769

Can you believe it? SX asked me for a captcha when I submitted this! Does this look bot-made to you? Seriously? – DankMemes – 2014-05-14T19:27:49.600

1Fix for NFException pushed. It will now just kill the player instead of killing the program. – Geobits – 2014-05-14T19:33:46.357

For the record, I don't think it would have crashed on your "Hey, I wonder if..." line anyway. It checks if there are <200 tokens before it tries parsing. Still better to check for it anyway. – Geobits – 2014-05-14T23:56:21.200

@Geobits haha didn't realize that – DankMemes – 2014-05-15T01:35:53.700

Note: To get this to compile, I had to add a ) on line 19; change substr to substring on 38; initialize idx to something in the run() method. – Geobits – 2014-05-16T01:01:05.150

Whoops I was going to test it (wrote this all on my mobile) but I never got around to it, sorry – DankMemes – 2014-05-16T12:51:12.127

Fixed the code, I'll try to get around to testing this today – DankMemes – 2014-05-16T12:56:56.163