Keep your distance on a circle

9

0

This is based on this challenge and Geobits's/CarpetPython's idea to improve it:

Keep your distance!

For this challenge, the distance between two numbers is measured on a loop, so, for example, the distance between 0 and 999 is 1. This should prevent strategies like always picking the lowest or highest number from winning almost every time. The only other change is that the lowest number that can be chosen is now 0 instead of 1.

I'll summarize it here:

  • Write a function in Java, Python, or Ruby that takes three arguments:
    • the number of rounds played so far
    • the number of players
    • the numbers picked in the previous rounds, as an array of space-separated strings
  • It should return an integer from 0 to 999, inclusive
  • The score for a program each round is the sum of the square roots of the distances to the numbers each other program picked
  • The program with the highest score after 100 rounds wins.
  • One answer per person

The control program is here:

https://github.com/KSFTmh/src/

Leaderboard

NumberOne, by TheBestOne, is winning.

  • NumberOne - 9700
  • NumberOnePlusFourNineNine - 9623
  • AncientHistorian - 9425
  • FindCampers - 9259
  • WowThisGameIsSoDeep - 9069
  • Sampler - 9014
  • SabotageCampers - 8545

Apparently, my camper sabotage...er(?) doesn't work very well.

Here are the full results: https://github.com/KSFTmh/src/blob/master/results-3

I think this is different enough to not be a duplicate.

By the way, this is my first time asking a question on Stack Exchange, so let me know if I'm doing something wrong.

KSFT

Posted 2015-01-31T19:55:33.337

Reputation: 1 527

4Do we really want a question this similar ? – Optimizer – 2015-01-31T20:13:03.500

5@Optimizer A few people in the comments seemed to think this was a good idea. Answers from the original will work very differently here, so I don't think it's a duplicate. – KSFT – 2015-01-31T20:14:11.447

I want to do this like the original, but I don't know how it worked, exactly. Did the person who posted the challenge run the control program to find the scores? – KSFT – 2015-01-31T21:05:05.317

As the original question had a major loophole I wouldn't close this one. It might worth a meta topic to discuss if "fixing" a question is allowed if it considerably improves it's value. Unrelated but what if the answer would be also different thanks to the modification? – randomra – 2015-01-31T21:06:40.823

Thanks KSFT for organising this improved challenge. I have been AFK. As I understand it, the control program calls each answer program each round with the history of the previous calls (but I have not read the code). This may mean that you need to install a variety of obscure compilers and interpreters on your machine. You should also make sure the code does not do anything hostile before you run it. The output of the control program should provide the information for a leader board. – Logic Knight – 2015-02-01T02:28:59.810

1The credit for suggesting the challenge should go to @Geobits. I just agreed with him. – Logic Knight – 2015-02-01T02:31:36.097

@CarpetPython Should I run the control program with all of the submissions each time a new answer is submitted? – KSFT – 2015-02-01T03:18:27.590

@KSFT I am new to this leaderboard business. I would guess once per day or more often if more entries are added or changed (if your time allows). It should be up to you. – Logic Knight – 2015-02-01T03:22:30.417

You have probably already thought of this, but the controller's distance calculation needs to change to something like sum(sqrt(min(1000-abs(x-y), abs(x-y))) for y in others) to implement the wrap-around feature. – Logic Knight – 2015-02-01T05:12:45.877

@CarpetPython What I did was really long, but basically the same as that. I don't know Java very well, but I'm pretty sure it'll work right. – KSFT – 2015-02-01T05:14:28.710

We need "Keep your distance on a sphere" for the next question :). – kennytm – 2015-02-01T17:36:35.933

@KennyTM That's a good idea. I might post that if I ever get the controller for this one working. – KSFT – 2015-02-01T17:59:07.917

1Mmm. It seems that a constant number wins again. I am curious as to why that is. Could we see the 600 output numbers in the question, or on github or pastebin? I suspect some of our predictors have bugs. Possibly mine :-( – Logic Knight – 2015-02-02T03:07:34.603

Until when will the challenge run? I.e., a certain date – Sanchises – 2015-02-02T18:27:08.750

@KennyTM How about on a toroid? The distance function would be much simpler. – TheNumberOne – 2015-02-03T02:12:34.407

@TheBestOne Ooh, I like that idea. What other shapes would work? Would these be closed as duplicates? – KSFT – 2015-02-03T03:19:31.657

I suspect all puzzles using this idea will suffer from the same problem. The fixed point solutions will win. Predictors will stay away from the fixed points and crash into each other, ensuring the fixed points win. We would need a rule change to reward movement. – Logic Knight – 2015-02-03T03:28:54.123

@KFST A toroid is equivalent to a rectangle with wrap around its edges. I expect they would. – TheNumberOne – 2015-02-03T03:55:33.237

1@CarpetPython A simple change would be to compute the distance between the points from last around in addition to the points from this round. – TheNumberOne – 2015-02-03T03:57:29.000

@CarpetPython I had an idea about how to do that, actually. What if scores hare calculated based on distsance to numbers chosen in previous rounds, too? – KSFT – 2015-02-03T03:57:52.153

@TheBestOne Wow – KSFT – 2015-02-03T03:58:27.473

@CarpetPython/TheBestOne Should we use just one previous round or all of them? If we use just one round, what about the first round? If we use all previous rounds, should scores be multiplied in earlier rounds to compensate for the higher scores in later rounds because there are more points? – KSFT – 2015-02-03T04:03:29.710

Why the downvote? – KSFT – 2015-02-03T06:13:23.987

Am I allowed to add my own answer? – KSFT – 2015-02-07T02:32:58.977

I am no expert on the rules here, but it is fine with me. I have added (non competitive) answers to many of my challenges, and nobody has complained. – Logic Knight – 2015-02-08T15:36:08.283

The latest run seems to prove that camping is the best solution. Unlike most KOTH challenges, this one rewards being predictable. Rewarding predictability will mean the results will be, errr, predictable. Including previous numbers will just motivate campers to slowly (and predictably) move slightly each turn. – Logic Knight – 2015-02-09T05:17:01.740

I find it amusing that my answer has only 1 upvote. I think I wouldn't it upvote either. :) – TheNumberOne – 2015-02-10T20:03:20.577

By 'Python', do you mean Python 2, Python 3, or both? – ASCIIThenANSI – 2015-04-09T14:51:26.973

@ASCIIThenANSI Python 2 will definitely work, but I'm not sure about Python 3. – KSFT – 2015-04-09T15:16:29.583

Answers

3

Python 2, Sampler

This entry is based on the same code for Keep your distance, Sampler entry. I hope it will do better here where the 1 and 999 advantages do not exist.

Out of a list of places, choose the one that is farthest away from recently used numbers, ignoring the previous turn (because other entries may predict based on just the previous turn).

def choose(turn, players, history):
    sample = map(int, (' '.join( history[-5:-1] )).split())
    def distance(x):
        return sum(min(1000-abs(x-y), abs(x-y))**0.5 for y in sample)
    score, place = max((distance(x), x) for x in range(1000))
    return place

Logic Knight

Posted 2015-01-31T19:55:33.337

Reputation: 6 622

It looks like this one's winning, but that might be because I'm not compiling the controller right and the others are all crashing. – KSFT – 2015-02-01T18:29:25.570

2

Number OnePlusFourNineNine, Java

public static int choose(int round, int players, String[] args) {
    return 500;
}

The logic is really simple. Unless someone finds a real algorithm which takes previous scores into consideration, this answer is fairly optimized.

Now that we count the distance in a circle, the maximum distance of any two points can be 500. Now if all entries were generating random numbers (or pseudo random based on some algorithm), this answer would not have been in any advantage at all. But there is at least 1 entry which produces a constant answer which an almost maximum distance. The makes the score come in favor of 500 as there is a fixed source of maximum distance possible in each round :)

Optimizer

Posted 2015-01-31T19:55:33.337

Reputation: 25 836

You optimized my answer. ;) – TheNumberOne – 2015-02-01T22:34:26.000

@TheBestOne haha – Optimizer – 2015-02-02T05:00:44.043

2

AncientHistorian - Python

It is the same algorithm from the previous one, except when calculating the potential scores it uses the circular distance. Since I'm losing horribly and can't get the controller to compile, I'm just trying a new strategy, where I use the worst from the previous rounds.

def choose(round, players, scores):
    calc = lambda n, scores: sum([min(abs(int(i)-n), 1000-max(int(i),n)+min(int(i),n))**.5 for i in scores.split(' ')])
    return min(range(1000), key=lambda n: sum([calc(n, j) for j in scores[1:]])) if round>1 else 250

Maltysen

Posted 2015-01-31T19:55:33.337

Reputation: 25 023

This doesn't work. i is an element of scores.split(' '), meaning it's a string, not an int. – KSFT – 2015-02-01T18:25:07.540

@KSFT oh shoot, I really should have tested, updating. – Maltysen – 2015-02-01T23:10:09.630

2

FindCampers - Python 2

Find all the campers from the last 10 rounds and stay away from them. I'm hoping that predictors will run from me. I will now ignore my old choices.

def choose(rounds, players, previous):
    from collections import Counter

    def distance(x, y):
        return min(1000 - abs(x-y), abs(x-y))

    pastRounds = list(map(lambda x: Counter(map(int, x.split())), previous))
    me = 751
    for (index, round) in enumerate(pastRounds):
        round.subtract((me,))
        pastRounds[index] = set(round.elements())
        campers = reduce(lambda x,y: x.intersection(y), pastRounds[max(1, index-9):index], pastRounds[max(0,index-10)])
        if campers:
            dist, me = max(min((distance(x, y), x) for y in campers) for x in range(1000))
        else:
            me = 751
    return me

Jmac

Posted 2015-01-31T19:55:33.337

Reputation: 111

Aww...I was hoping this would go towards the campers when I saw the name... – KSFT – 2015-02-07T00:35:42.750

Lol. I could add an entry that will sabotage campers. – Jmac – 2015-02-07T00:46:46.860

Unfortunately, I only allowed one entry per person. – KSFT – 2015-02-07T00:47:34.727

I just posted an entry to sabotage campers myself. – KSFT – 2015-02-08T16:02:14.563

Mine doesn't work because I didn't realize that previous results were sorted. How does yours detect campers? – KSFT – 2015-02-08T16:21:37.900

It turns the lists of numbers from the last 5 rounds into sets. It then intersects each set with the others. This will return a set containing only the numbers chosen in every round. I assume that these are the campers. – Jmac – 2015-02-08T21:51:17.037

Does it avoid itself? – TheNumberOne – 2015-02-09T03:02:32.767

No. Every 10 turns, it will jump to the next best place, as it will think itself is a camper. – Jmac – 2015-02-09T03:15:39.623

2

SabotageCampers - Python

def choose(rounds, players, previous):
    if rounds<3:
        return 1
    prevchoices=[int(i) for i in " ".join(previous[-5:]).split(" ")]
    remove=[]
    for i in prevchoices:
        if prevchoices.count(i)<3:
            remove.append(i)
    campers=[i for i in prevchoices if i not in remove]
    return random.choice(campers)

The campers are still winning. Let me know if you have any suggestions for this.

KSFT

Posted 2015-01-31T19:55:33.337

Reputation: 1 527

1

Number One, Java

The first answer. Copied from my previous answer.

public static int choose(int round, int players, String[] args) {
    return 1;
}

TheNumberOne

Posted 2015-01-31T19:55:33.337

Reputation: 10 855

Someone seems to have downvoted all of the answers. – KSFT – 2015-02-01T04:18:24.950

1

WowThisGameIsSoDeep, Java

I have analyzed the game for 10 years on a 1 million-core cluster and found the optimal solution.

public static int choose(int round, int players,String[]spam) { return(int)(Math.random()*1e3); }

feersum

Posted 2015-01-31T19:55:33.337

Reputation: 29 566

This ain't no [tag:code-golf] – Optimizer – 2015-01-31T21:38:38.913

5That solution is not optimal. If you want a uniform distribution, you should use Random.nextInt(int). – Peter Taylor – 2015-01-31T22:20:47.793

This seems to always return 1. – KSFT – 2015-02-02T04:11:53.000

@KSFT I tested it and got many different numbers. Maybe it's sabotage after all? – feersum – 2015-02-02T04:16:12.953

@KSFT I see the controller's process method returns 1 if it hits any kind of exception, so it's probably an error in your code. – feersum – 2015-02-02T04:26:06.090

It doesn't seem to be throwing any exceptions. – KSFT – 2015-02-02T04:29:37.653

@KSFT How do you know it's not throwing exceptions if you catch them? – feersum – 2015-02-02T04:50:40.430

I have it print exception messages when it catches them. – KSFT – 2015-02-02T05:13:08.750

@KSFT In the code I'm looking at, a message is printed only in the event of a ClassNotFoundException. Any other Exception is silently discarded. – feersum – 2015-02-02T05:29:35.827

I modified it so it always prints the message. – KSFT – 2015-02-02T06:07:35.683

@KSFT If you aren't going to fix the controller, then please run "It's Sabotage" instead. – feersum – 2015-02-02T06:28:24.750

4Aha! I fixed it! I accidentally typed "WowThisGameIsSoDeep.py", and it was trying to run it as a Python file. – KSFT – 2015-02-02T16:16:24.313

1

Circilinear Extrapolator, Ruby

def choose(round, players, previous_choices)
  previous_rounds = previous_choices.map{ |round| round.split.map(&:to_i) }
  optimal_past_choices = previous_rounds.map do |choices|
    (0..999).max_by { |i| choices.map{ |c| root_distance(i,c) }.inject(:+) }
  end
  if (last_round = optimal_past_choices.last)
    (last_round + average_delta(optimal_past_choices).round) % 1000
  else
    750
  end
end

def root_distance(i,j)
  dist = (i-j).abs
  dist = [dist, 1000 - dist].min
  dist ** 0.5
end

def directed_distance(i,j)
  dist = j - i
  if dist > 500
    dist - 1000
  elsif dist < -500
    dist + 1000
  else
    dist
  end
end

def average_delta(ary)
  ary.each_cons(2).map{ |x,y| directed_distance(x,y) }.inject(0,:+)/ary.count
end

histocrat

Posted 2015-01-31T19:55:33.337

Reputation: 20 600

This is giving this error: NoMethodError: undefined method \split' for #Array:0x720f56e2 choose at CircilinearExtrapolator.rb:2`

– KSFT – 2015-02-01T21:56:19.413

Oh, is previous_choices an array of values like ["1 6 500","2 8 503"] ? – histocrat – 2015-02-01T22:17:10.233

It is. Did you think it was something else? If not, I probably just messed something up running it. – KSFT – 2015-02-01T22:18:02.350

I thought it was just a flat string, sorry. I'll edit. – histocrat – 2015-02-01T22:18:30.193

Edited. Now everybody knows I posted something without testing it... – histocrat – 2015-02-01T22:25:08.443

I'm now getting this: NoMethodError: undefined method \/' for nil:NilClass average_delta at CircilinearExtrapolator.rb:31 choose at CircilinearExtrapolator.rb:7` – KSFT – 2015-02-01T23:33:02.513

I don't know Ruby very well, but I can't figure out why that's happening. – KSFT – 2015-02-01T23:35:37.910

Ah, I got it! It's only happening the first round. It's because there's only one element in ary. – KSFT – 2015-02-01T23:45:57.960

Sorry again, fixed again. – histocrat – 2015-02-02T02:29:45.827