Візьміть або залиште II: Ігрове шоу для комп’ютерів


20

Це друга з серії головоломок, які я буду публікувати кожного понеділка о півночі PST. Перша головоломка знаходиться тут .

Контекст:

Повторний мільярдер створив ігрове шоу, щоб залучити найкращих та найяскравіших програмістів у світі. По понеділках під час півночі він обирає одну людину з пулу претендентів, щоб бути учасником тижня, і надає їм гру. Ви щасливий учасник цього тижня!

Гра цього тижня:

Хост надає вам доступ до API до стопки 10 000 цифрових конвертів. Ці конверти розбиваються випадковим чином і містять у собі значення долара від 1 до 10 000 доларів (жоден два конверти не містять однакового значення долара).

У вас є 4 команди:

  1. Прочитати (): Прочитайте цифру долара в конверті у верхній частині стека.

  2. Take (): Додайте цифру долара в конверт до гаманця з ігровим шоу та висуньте конверт зі стопки.

  3. Pass (): висуньте конверт у верхній частині стека.

  4. Oracle (M): повертає середнє значення наступних конвертів M у стеку, не враховуючи того, яке ви можете прочитати ().

Правила:

  1. Якщо ви використовуєте Pass () на конверті, гроші всередині втрачаються назавжди.

  2. Якщо ви використовуєте Take () на конверті, що містить $ X, з цього моменту вперед, ви ніколи не можете використовувати Take () на конверті, що містить <$ X. Візьміть () на одному з цих конвертів, ви додасте 0 доларів у ваш гаманець.

  3. Якщо ви використовуєте Oracle (M) на повороті T, конверти T + 1 через значення T + M будуть повернуті. Oracle () відключений до повороту T + M.

Напишіть алгоритм, який закінчує гру максимальною сумою грошей.

Якщо ви пишете свій алгоритм на Python, сміливо використовуйте цей контролер, наданий @Maltysen: https://gist.github.com/livinginformation/70ae3f2a57ecba4387b5

Примітки 1: "Максимальне значення" в цьому випадку означає середнє значення у вашому гаманці після N> = 1000 пробігів. Я сподіваюсь, що хоч би хотів, щоб я був підтверджений неправильним, що середнє значення для заданого алгоритму буде збігатися, коли N зростає до нескінченності. Не соромтеся намагатися максимізувати середнє натомість, але у мене таке відчуття, що середнє значення швидше скине на малий N, ніж медіана.

Примітка 2: оскільки всі рішення попередньої частини цієї головоломки є дійсними тут, їх повторне розміщення має мало значення. У ІІ частині будуть розглянуті лише алгоритмічні вдосконалення попередніх головоломок.

Редагувати: Умови премії було видалено, зважаючи на цю публікацію про мета.


Нічого собі, я не можу повірити, що я проспав: O
бета-розпад

@Beta Розкладений годинник! :)
LivingInformation

Який сенс у роке? Ви можете створити власний безкоштовний оракул, просто зберігаючи підрахунок усіх прочитаних раніше конвертів. Що я помиляюся?
Луїс Мендо

1
@LuisMendo За допомогою власного підрахунку ви можете знати лише середнє значення всіх решти значень. За допомогою оракула ви можете отримати середнє Mзначення наступних значень, де ви можете вибрати M.
Рето Коради

1
Оскільки всі рішення попереднього виклику також є дійсними рішеннями цього завдання, чи можемо ми вважати, що вони подані неявно?
Рето Коради

Відповіді:


9

Groovy $ 713337 $ 817829 $ 818227

Код завантаження:

class Instance {
    List values = new ArrayList(1..10000); {
        Collections.shuffle(values)
    }
    int i = 0
    int value = 0
    int max = 0
    int nextOracle = 0

    def pass() {
        if (i >= 10000)
            throw new NoSuchElementException()
        i++
    }

    def take() {
        if (i >= 10000)
            throw new NoSuchElementException()
        int v = values[i]
        if (v > max) {
            max = v
            value += v
        }
        i++
    }

    double oracle(int m) {
        if (m <= 0 || i < nextOracle || i + m >= 10000)
            throw new NoSuchElementException()

        nextOracle = i + m
        values.subList(i + 1, i + m + 1).stream().reduce { l, r -> r+l }.get() / m
    }

    int read() {
        if (i >= 10000)
            throw new NoSuchElementException()
        values[i]
    }
}

Алгоритм

double square(double v) { v * v }
final double factor = Math.pow(1.5, 1.1)
int attempts = 5000
(1..attempts).stream().parallel().mapToLong {
    def puzzle = new Instance()

    int[] memory = 1..10000 // We will remember every envelope
    int memStart = 0

    while (memStart < 10000 - 3) {
        int value = puzzle.read()
        int i = Arrays.binarySearch(memory, memStart, 10000, value) - memStart
        if (i < 0) { // We can't use the money
            puzzle.pass()
            continue
        }
        if (i == 0) { // Of course we take the lowest
            puzzle.take()
            memStart++
            continue
        }
        int remaining = Arrays.stream(memory, i + 1 + memStart, 10000).sum() // Money we could win if taken
        int losing = Arrays.stream(memory, memStart, memStart + i).sum() // Money we cna't win if taken
        if (value > losing) { // If we pass, we lose money automatically
            puzzle.take()
            memStart += i + 1
        } else if ((losing - value * 16 / 7) * square(Math.log(i)) > remaining / factor) {
            System.arraycopy(memory, memStart, memory, ++memStart, i)
            puzzle.pass()
        } else {
            puzzle.take()
            memStart += i + 1
        }
    }

    // It's broken down to last three elements
    List values = Arrays.copyOfRange(memory, 10000 - 3, 10000)
    while (!values.contains(puzzle.read())) // Skip values we can't use
        puzzle.pass()
    int value1 = puzzle.read()
    int value2 = puzzle.oracle(1)
    if (value1 == values.max() && (
            values.contains(value2)
            ? (value1 * 2 < values.sum() && values.min() == value2)
            : (value1 < values.min() / 2 + (values - [value1]).max())
            )) {
        puzzle.pass()
    }

    // Finish it
    while (puzzle.i < puzzle.values.size()) {
        puzzle.take()
    }

    puzzle.value as Long
}.sum() / attempts // Sum runs and average

Я порівнюю залишкові значення з можливими значеннями. Цей сценарій не швидкий (займає 1 хвилину на 1000x моделювання) ... але він буде виконувати моделювання одночасно.

Я поняття не маю, чому працює мій алгоритм, але це були просто проби та помилки: згуртування математичних операцій разом та маніпулювання константами. Я провів її 5000x для поточного рахунку, намагаючись зменшити коливання балів (це +/- 4000 $ залежно від кількості ітерацій).

Навіть без оракула в кінці, він все одно повинен (ледь) бити рішення @ orlp для попередньої головоломки.


7

C # - $ 803.603 зараз -> 804.760 $ (з оракул)

Код завантаження

public static class ShuffleExtension
{
    private static Random rng = new Random();  

    public static void Shuffle<T>(this IList<T> list)  
    {  
        int n = list.Count;
        while (n > 1) {  
            n--;  
            int k = rng.Next(n + 1);  
            T value = list[k];  
            list[k] = list[n];  
            list[n] = value;  
        }  
    }
}

public class Puzzle
{
    public List<int> Values = new List<int>(10000);

    public Puzzle()
    {
        for ( int i = 1; i <= 10000; i++ )
        {
            Values.Add(i);
        }
        Values.Shuffle();
    }

    public int i = 0;
    public int value = 0;
    public int max = 0;
    public int nextOracle = 0;

    public void Pass() {
        if ( i >= Values.Count )
            throw new IndexOutOfRangeException();
        i++;
    }

    public void Take() {
        if (i >= Values.Count )
            throw new IndexOutOfRangeException();
        int v = Values[i];
        if (v > max) {
            max = v;
            value += v;
        }
        i++;
    }

    public double oracle(int m) {
    if (m <= 0) { 
        throw new IndexOutOfRangeException();
    }
    if ( i < nextOracle ) {
        throw new IndexOutOfRangeException();
    }
    if ( i + 1 + m > Values.Count ) {
        throw new IndexOutOfRangeException();
    }

    nextOracle = i + m;
    var oracleValues = new List<int>();
    for ( int l = 0; l < m; l++ )
    {
        oracleValues.Add(Values[i + 1 + l]);
    }
    return oracleValues.Average (v => v);
}

    public int Read() {
        if (i >= Values.Count )
            throw new IndexOutOfRangeException();
        return Values[i];
    }
}

Код гри:

    void Main()
{
    var m = 0;
    for ( int l = 0; l < 1000; l++ )
    {
        var game = new Puzzle();
        var maxVal = 0;
        var lastOracle = 0;
        var lastOracleValue = 0.0m;
        var oracleValueForIOf = 0;

        for ( int i = 0; i < 10000; i++ )
        {
            var val = game.Read();
            var oracleStep = 1;
            var canUseOracle = (i - lastOracle >= oracleStep) && i + oracleStep + 1 <= 10000;
            if ( canUseOracle )
            {
                var oracle = game.oracle(oracleStep);
                lastOracle = i;
                lastOracleValue = (decimal)oracle;
                oracleValueForIOf = i + 1;
            }
            if ( TakeTheMoney(val, maxVal, oracleValueForIOf, lastOracleValue, i) )
            {
                maxVal = val;
                game.Take();
            }
            else
            {
                game.Pass();
            }
        }
        m += game.value;
    }
    ((int)(m / 1000)).Dump();
}

private bool TakeTheMoney(int val, int maxVal, int oracleValueForIOf, decimal lastOracleValue, int i)
{
    if ( val > maxVal )
    {
        if ( oracleValueForIOf != i + 1
            &&
            (val < 466.7m + (0.9352m * maxVal) + (0.0275m * i))
            )
        {
            return true;
        }

        if (oracleValueForIOf == i + 1)
        {
            if ( val < 466.7m + (0.9352m * maxVal) + (0.0275m * i) )
            {
                return true;
            }
            if ( lastOracleValue > 466.7m + (0.9352m * val) + (0.0275m * i + 1) )
            {
                if ( val < 466.7m + (0.9352m * maxVal) + (0.0275m * i + 1) )
                {
                    return true;
                }
            }
        }
    }
    return false;
}

Кредит належить Reto Koradi ( /codegolf//a/54181/30910 )

Правка: Основне використання Oracle реалізовано. Якщо наступний оракул перевищує поріг, використовуйте розгорніть поточний конверт до індексу індексу Oracle. Це не часто, але це - Вдосконалення ;-)


4
Я не думаю, що перетворювати рішення з попереднього виклику не дуже результативно. Усі ми визнали, що ці рішення можуть бути використані як базові для цього виклику, і я вже залишив коментар до ОП, запитуючи, як нам це робити. Ідея полягає у тому, щоб ви придумали власне рішення, яке ідеально краще, ніж рішення попереднього завдання.
Рето Коради

будь ласка, припиніть забороняти :) Примітка № 2 була додана після мого подання. і як він більш ефективний, ніж інші рішення - я розмістив його тут. не потрібно використовувати Oracle, щоб перемогти існуючі рішення.
Стефан Шінкель

@StephanSchinkel Ви отримали мою нагоду, якщо вам вдасться включити Oracle, щоб покращити поточний рахунок. Навіть лише на 1 долар.
Дорус

@BetaDecay, що саме так знову нахмуриться громадою? Я щойно стежив за запитом з оп. Ще раз додано Примітка №2 ПІСЛЯ мого подання.
Стефан Шінкель

Не використовувати рішення з I частини вікторини.
Стефан Шінкель

4

Пітон - $ 74112

Візьміть лише, якщо поточне значення нижче наступного значення (тобто ви можете взяти обидва).

def algo():
  try:
    o=oracle(1)
  except ValueError:
    take()
  r=read()
  if r>o:
    passe()
  else:
    take()

Python - (все ще обчислює середнє значення)

Ця відповідь вимагає ДУЖЕ ДОВГИЙ для обчислення. Він сягає близько 670 000 $ . Я пам’ятаю кожен конверт, який я бачив. Кожного разу, коли мені потрібно прийняти рішення, я генерую два списки конвертів, що залишилися, які я потенційно можу додати до свого гаманця, якщо взяти поточний конверт або залишити його відповідно.

Я не оптимізував код.

def algo_2():
  global max_taken, past
  weight=0.92 #Empirically chosen.
  r=read()
  if len(past)==0:
    past.append(r)
    passe()
    return
  if r<max_taken:
    past.append(r)
    take() #the same as passe
    return
  coming=[x for x in range(1,10001) if x not in past and x>max_taken and x!=r ]
  comingIfTake=[x for x in range(1,10001) if x not in past and x>r ]
  if sum(coming)*weight<=sum(comingIfTake)+r:
    past.append(r)
    take()
  else:
    past.append(r)
    passe()

І init_game починається так:

def init_game():
    global stack, wallet, max_taken, oracle_turns, past
    past=[]

3
Якщо ви використовуєте набори для представлення минулого, майбутнього та прихідного, якщо ви використовуєте перехрестя, ваш код буде набагато швидшим.
Натан Меррілл

4

C # - 780,176 дол

Перевірте, чи знаходиться наступне значення в межах нижчих 5% від усіх залишилися значень. Будьте більш розслабленими, коли ми доходимо до кінця.

public class Taker
{
    private List<int> remaining;
    private Game game;

    public Taker(Game game)
    {
        this.game = game;
        remaining = Enumerable.Range(1, game.Size + 100).ToList();
    }

    int score = 0;

    public int PlayGame()
    {
        for (int i = 0; i < game.Size; i++)
        {
            if (game.Read() < game.Max ||
                game.Read() > selectThreshold() ||
                doOracle()
                )
            {
                remaining.Remove(game.Read());
                game.Pass();
                continue;
            }
            remaining = remaining.SkipWhile(j => j < game.Read()).ToList();
            score += game.Take();
        }
        return score;
    }

    private bool doOracle()
    {
        return game.Oracle(1) < game.Read() &&
            game.Oracle(1) > game.Max;
    }

    private int selectThreshold()
    {
        int selector = (int)(remaining.Count * 0.05);
        return remaining.ElementAt(selector);
    }
}

І мій ігровий клас, дуже некрасивий, ігровий клас навіть не підтверджує, якщо дозволено oracle, але оскільки я використовую лише Oracle (1), це не повинно бути проблемою.

public class Game
{
    private int[] list;
    private int position = 0;
    private int max = 0;
    public int Max { get { return max; } }
    public int Size { get { return list.Length; } }

    public Game(int[] list)
    {
        this.list = list;
    }

    public int Read()
    {
        return list[position];
    }

    public int Take()
    {
        if (list[position] < max)
        {
            position++;
            return 0;
        }
        max = list[position];
        return list[position++];
    }

    public void Pass()
    {
        position++;
    }

    public int Oracle(int M)
    {
        int next = position + 1;
        M = Math.Max(0, Math.Min(M, list.Length - next));
        return new ArraySegment<int>(list, next, M).Sum();
    }
}

4

Java, 804 991 дол

Оцінка - 1001 раунд. Напевно, це занадто близько, щоб зателефонувати між цією відповіддю та мовою Стефана Шінкеля .

Це ґрунтується на моїй відповіді в попередньому виклику, оскільки він використовує той же розрахунок на основі ентропії для оцінки виплат. Основна відмінність полягає в тому, що він просто зараз бере конверти в парах (1 і 2, потім 3 і 4 і так далі) і розглядає можливі комбінації "взяти", "взяти", "пройти", "тощо". Він також обчислює точний розрахунковий бал, коли кількість дійсних конвертів дійсно невелика.

"Обгортка", яку я написав, насправді не є справжньою обгорткою, вона просто дає конверти парами замість того, щоб викликати Oracle(1)функцію кожен інший раунд.

В цілому, я б сказав, що, незважаючи на підвищену складність, цей бот насправді не кращий за попередній.

Гравець

import java.lang.Math;
public class Player2
{
    public int[] V;

    public Player2(int s)
    {
        V = new int[s];
        for(int i = 0; i<V.length; i++)
        {
            V[i] = i+1;
        }
        ////System.out.println();
    }

    public boolean [] takeQ(int x, int y)
    {
        //System.out.println("Look: " + x + " " + y);
        boolean [] move = new boolean[]{false,false};
        double max = 0;
        double val = 0;
        int[] nextV = V;

        ////System.out.println("look " + x);
        int i = find(V,x);
        if(i >= 0)  //if found
        {
            //try taking first envelope
            int[] newVt = takeSlice(V,i);
            //System.out.println("  T: " + ats(newVt));
            int j = find(newVt,y);
            if(j >= 0)
            {
                //try taking first and second
                int[] newVtt = takeSlice(newVt,j);
                val = x + y + calcVal(newVtt);
                //System.out.println("  TT: " + ats(newVtt) + " " + val);
                if(val > max)
                {
                    move = new boolean[]{true,true};
                    max = val;
                    nextV = newVtt;
                }
            }
            //try taking first and passing second
            int[] newVtp = passSlice(newVt,j);

            val = x + calcVal(newVtp);
            //System.out.println("  TP: " + ats(newVtp) + " " + val);
            if(val > max)
            {
                move = new boolean[]{true,false};
                max = val;
                nextV = newVtp;
            }
        }
        int[] newVp = passSlice(V,i);
        //System.out.println("  V: " + ats(V));
        //System.out.println("  P: " + ats(newVp));
        int j = find(newVp,y);
        if(j >= 0)
        {
            //try passing first and taking second
            int[] newVpt = takeSlice(newVp,j);
            val = y + calcVal(newVpt);
            //System.out.println("  PT: " + ats(newVpt) + " " + val);
            if(val > max)
            {
                move = new boolean[]{false,true};
                max = val;
                nextV = newVpt;
            }
        }
        //try taking first and passing second
        int[] newVpp = passSlice(newVp,j);

        val = calcVal(newVpp);
        //System.out.println("  PP: " + ats(newVpp) + " " + val);
        if(val > max)
        {
            move = new boolean[]{false,false};
            max = val;
            nextV = newVpp;
        }
        V = nextV;
        //System.out.println("  NEW: " + ats(V));
        return move;
    }

    public static String ats(int [] a)
    {
        String s = "";
        for(int i = 0; i < a.length; i++)
        {
            s += a[i] + ",";
        }
        return s;
    }

    public static int[] takeSlice (int[] list, int loc)
    {
        int [] newlist = new int[list.length - loc - 1];
        for(int j = loc + 1; j < list.length; j++)
        {
            newlist[j - loc - 1] = list[j];
        }
        return newlist;
    }

    public static int[] passSlice (int[] list, int loc)
    {
        int [] newlist = list;
        if(loc >= 0)
        {
            newlist = new int[list.length-1];
            for(int k = 0; k < loc; k++)
            {
                newlist[k] = list[k];
            }
            for(int k = loc + 1; k < list.length; k++)
            {
                newlist[k-1] = list[k];
            }
        }
        return newlist;
    }

    public static double calcVal(int [] list)
    {
        if(list.length < 8)
        {
            for(int i : list)
            {
                ////System.out.print(i + ",");
            }

                ////System.out.println();
            return computeMean(list);

        }
        return smoothEstimate(list);
    }

    public static double computeMean(int[] V)
    {
        if(V.length == 1)
        {
            return V[0];
        }
        else if(V.length > 1)
        {
            double[] Es = new double[V.length];
            for(int i = 0; i < V.length; i++)
            {
                int[] newVp = new int[V.length - 1];
                for(int j = 0; j < i; j++)
                {
                    newVp[j] = V[j];
                }
                for(int j = i + 1; j < V.length; j++)
                {
                    newVp[j-1] = V[j];
                }
                double pass = computeMean(newVp);
                int[] newVt = new int[V.length - i - 1];
                for(int j = i + 1; j < V.length; j++)
                {
                    newVt[j - i - 1] = V[j];
                }
                double take = V[i] + computeMean(newVt);
                if(take > pass)
                {
                    Es[i] = take;
                }
                else
                {
                    Es[i] = pass;
                }
            }
            double sum = 0;
            for(double d : Es)
            {
                sum += d;
            }
            return sum/V.length;
        }
        else
        {
            return 0;
        }
    }

    public static double smoothEstimate(int [] list)
    {
        double total = 0;
        for(int i : list)
        {
            total+=i;
        }
        double ent = 0;
        for(int i : list)
        {
            if(i > 0)
            {
                ent -= i/total * Math.log(i/total);
            }
        }
        ////System.out.println("      total " + total);
        ////System.out.println("      entro " + Math.exp(ent));
        ////System.out.println("      count " + list.length);
        return total * Math.pow(Math.exp(ent),-0.5) * 4.0/3;// * 1.1287 + 0.05284);
    }

    public static int find(int[] list, int search)
    {
        int first  = 0;
        int last   = list.length - 1;
        int middle = (first + last)/2;

        while( first <= last )
        {
            if ( list[middle] < search )
                first = middle + 1;    
            else if ( list[middle] == search )
                break;
            else
                last = middle - 1;

            middle = (first + last)/2;
        }

        if(first > last)
        {
            return -1;
        }
        return middle;
    }
}

Контролер

import java.lang.Math;
import java.util.Random;
import java.util.ArrayList;
import java.util.Collections;
public class Controller2
{
    public static void main(String [] args)
    {
        int size = 10000;
        int rounds = 1001;
        ArrayList<Integer> results = new ArrayList<Integer>();
        for(int round = 0; round < rounds; round++)
        {
            int[] envelopes = new int[size];
            for(int i = 0; i<envelopes.length; i++)
            {
                envelopes[i] = i+1;
            }
            shuffleArray(envelopes);
            Player2 p = new Player2(size);
            int cutoff = 0;
            int winnings = 0;
            for(int i = 0; i<envelopes.length; i+=2)
            {
                boolean [] take = p.takeQ(envelopes[i],envelopes[i+1]);
                if(take[0] && envelopes[i] >= cutoff)
                {
                    winnings += envelopes[i];
                    cutoff = envelopes[i];
                }
                if(take[1] && envelopes[i+1] >= cutoff)
                {
                    winnings += envelopes[i+1];
                    cutoff = envelopes[i+1];
                }
            }
            results.add(winnings);
        }
        Collections.sort(results);
        System.out.println(rounds + " rounds, median is " + results.get(results.size()/2));

    }

    //stol... I mean borrowed from http://stackoverflow.com/questions/1519736/random-shuffling-of-an-array
    static void shuffleArray(int[] ar)
    {
        Random rnd = new Random();
        for (int i = ar.length - 1; i > 0; i--)
        {
            int index = rnd.nextInt(i + 1);
            // Simple swap
            int a = ar[index];
            ar[index] = ar[i];
            ar[i] = a;
        }
    }
}

Біткойн-адреса: 1BVBs9ZEP8YY4EpV868nxi2R23YfL7hdMq


3

Пітон 3 - $ 615570

Насправді не використовує оракул ... Е :)

def algo():
    global prevs

    try:
        prevs.append(read())
    except NameError:
        prevs = [read()]

    if len(prevs) > 10000:
        prevs = [prevs[-1]]

    if read() < round(len(prevs),-1):
        take()
    else:
        passe()

Складає список усіх попередніх конвертів і перевіряє, чи поточний конверт менший за кількість попередніх конвертів з кроком 10 конвертів.


0

Пітон, 87,424

Ось простий і простий алгоритм, щаслива сімка.

def LuckyNumber7():
Test = read()
if "7" in str(Test):
    take()
else:
    passe()

test(LuckyNumber7)

В основному, це робить, це перетворює read () у рядок і перевіряє, чи є в ньому сім. Якщо є, бере конверт. Якщо ні, то це передає.

Це в середньому близько 81 000, я не відстежував.


Отже, це показує, що покладатися на удачу - це не вдала стратегія? ;)
Ретро Кораді

@RetoKoradi Yep: D
The_Basset_Hound
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.