Take It or Leave It II: un programa de juegos para computadoras

20

Este es el segundo de una serie de rompecabezas que publicaré todos los lunes a la medianoche PST. El primer rompecabezas se encuentra aquí .

Contexto:

Un multimillonario solitario ha creado un programa de juegos para atraer a los mejores y más brillantes programadores del mundo. Los lunes a la medianoche, elige a una persona de un grupo de solicitantes para ser el concursante de la semana, y les proporciona un juego. ¡Eres el concursante afortunado de esta semana!

El juego de esta semana:

El host le proporciona acceso API a una pila de 10,000 sobres digitales. Estos sobres se ordenan aleatoriamente y contienen dentro de ellos un valor en dólares, entre $ 1 y $ 10,000 (no hay dos sobres que contengan el mismo valor en dólares).

Tienes 4 comandos a tu disposición:

  1. Leer (): lee la cifra en dólares en el sobre en la parte superior de la pila.

  2. Take (): Agregue la figura del dólar en el sobre a la billetera de su programa de juegos y saque el sobre de la pila.

  3. Pase (): salta el sobre en la parte superior de la pila.

  4. Oracle (M): Devuelve el valor medio de los siguientes sobres M en la pila, sin incluir el que actualmente puede Leer ().

Las normas:

  1. Si usa Pass () en un sobre, el dinero dentro se pierde para siempre.

  2. Si usa Take () en un sobre que contiene $ X, a partir de ese momento, nunca podrá usar Take () en un sobre que contenga <$ X. Tomar () en uno de estos sobres agregará $ 0 a su billetera.

  3. Si usa Oracle (M) en el turno T, se devolverán los sobres T + 1 a T + M. Oracle () está desactivado hasta el turno T + M.

Escribe un algoritmo que termine el juego con la cantidad máxima de dinero.

Si está escribiendo su algoritmo en Python, no dude en usar este controlador provisto por @Maltysen: https://gist.github.com/livinginformation/70ae3f2a57ecba4387b5

Notas 1: "Máximo" en este caso significa el valor medio en su billetera después de N> = 1000 ejecuciones. Espero, aunque me encantaría que me demuestren lo contrario, que el valor medio para un algoritmo dado convergerá a medida que N aumente hasta el infinito. En su lugar, siéntase libre de intentar maximizar la media, pero tengo la sensación de que es más probable que la media sea arrojada por una pequeña N que la mediana.

Nota 2: como todas las soluciones a la parte anterior de este rompecabezas son válidas aquí, volver a publicarlas tiene poco valor. Solo se considerarán mejoras algorítmicas de rompecabezas anteriores para la parte II.

Editar: La condición del premio se ha eliminado, a la luz de esta publicación en meta.

LivingInformation
fuente
Wow, no puedo creer que me haya quedado dormido: O
Beta Decay
¡El reloj de @Beta Decay está corriendo! :)
LivingInformation
¿Cuál es el sentido del ráculo? Podrías construir tu propio oráculo gratis simplemente manteniendo un conteo de todos los sobres leídos anteriormente. ¿Qué me estoy equivocando?
Luis Mendo
1
@LuisMendo Con su propia cuenta, solo puede conocer la media de todos los valores restantes. Con el oráculo, puede obtener la media de los siguientes Mvalores, donde puede elegir M.
Reto Koradi
1
Dado que todas las soluciones a su desafío anterior también son soluciones válidas para este desafío, ¿podemos considerarlas enviadas implícitamente?
Reto Koradi

Respuestas:

9

Groovy $ 713 337 $ 817 829 $ 818,227 mil

Código Bootstrap:

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]
    }
}

Algoritmo

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

Comparo los valores restantes con los posibles valores. Este script no es rápido (tarda 1 minuto por cada 1000 simulaciones) ... pero realizará las simulaciones simultáneamente.

No tengo idea de por qué funciona mi algoritmo, pero fue solo prueba y error: agrupar operaciones matemáticas y manipular las constantes. Lo ejecuté 5000x para el puntaje actual, en un intento de reducir las fluctuaciones del puntaje (es +/- $ 4000 dependiendo del recuento de iteraciones).

Incluso sin el oráculo al final, todavía debería estar (apenas) superando la solución de @ orlp para el rompecabezas anterior.

Wesley Wolfe
fuente
7

C # - $ 803.603 ahora -> $ 804.760 (con oráculo)

Código Bootstrap

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];
    }
}

Código de juego:

    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;
}

El crédito pertenece a Reto Koradi ( /codegolf//a/54181/30910 )

Editar: Uso básico de Oracle implementado. Si el siguiente oráculo está por encima del umbral a utilizar, expanda el sobre actual al índice del índice Oracle. Esto no golpea a menudo pero ES una mejora ;-)

Stephan Schinkel
fuente
44
No creo que sea muy productivo volver a publicar soluciones del desafío anterior. Todos reconocimos que esas soluciones pueden usarse como línea de base para este desafío, y ya había dejado un comentario para el OP preguntando cómo deberíamos manejar eso. La idea es que se te ocurra tu propia solución, que es idealmente mejor que las soluciones del desafío anterior.
Reto Koradi
por favor, dejen de votar :) se agregó la nota número 2 después de mi envío. y como es más efectivo que las otras soluciones, lo he publicado aquí. No es necesario utilizar Oracle para superar las soluciones existentes.
Stephan Schinkel
@StephanSchinkel Tienes mi voto positivo si logras incluir el Oracle para mejorar el puntaje actual. Incluso por solo $ 1.
Dorus
@BetaDecay, ¿qué es exactamente lo que está mal visto por la comunidad nuevamente? Acabo de seguir la pregunta de la operación. Una vez más, se agregó la Nota número 2 DESPUÉS de mi envío.
Stephan Schinkel
No utilizar una solución de la parte I de la prueba.
Stephan Schinkel
4

Python - $ 74112

Solo tome, si el valor actual es menor que el siguiente valor (es decir, puede tomar ambos).

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

Python - (aún calculando la media)

Esta respuesta tarda mucho en calcularse. Alcanza alrededor de 670.000 $ . Recuerdo cada sobre que vi. Cada vez que tengo que tomar una decisión, genero dos listas de sobres restantes que podría agregar a mi billetera si tomo el sobre actual o lo dejo, respectivamente.

No optimicé el código.

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()

Y init_game comienza así:

def init_game():
    global stack, wallet, max_taken, oracle_turns, past
    past=[]
TheEspinosa
fuente
3
Si usa conjuntos para representar pasado, venir y venirIfTake, y usa intersecciones, su código sería mucho más rápido.
Nathan Merrill
4

C # - $ 780.176

Compruebe si el siguiente valor está dentro del 5% inferior de todos los valores restantes. Relájate a medida que lleguemos al final.

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);
    }
}

Y mi clase de juego, muy fea, ni siquiera valida si Oracle está permitido, pero dado que solo uso Oracle (1) eso no debería ser un problema.

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();
    }
}
Dorus
fuente
4

Java, $ 804,991

La puntuación es de 1001 rondas. Probablemente esté demasiado cerca de llamar entre esta respuesta y la de Stephan Schinkel .

Esto se basa en mi respuesta en el desafío anterior, ya que usa el mismo cálculo basado en entropía para estimar los pagos. La principal diferencia es que ahora simplemente toma sobres en pares (1 y 2, luego 3 y 4, etc.) y analiza las posibles combinaciones de toma, toma, pase, pase, etc. También calcula el puntaje exacto estimado cuando el número de sobres válidos es realmente pequeño.

El "envoltorio" que escribí no es realmente un envoltorio verdadero, solo da sobres en pares en lugar de llamar a una Oracle(1)función cada dos asaltos.

En general, diría que, a pesar de la mayor complejidad, este bot realmente no es mejor que el anterior.

Jugador

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;
    }
}

Controlador

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;
        }
    }
}

Dirección de Bitcoin: 1BVBs9ZEP8YY4EpV868nxi2R23YfL7hdMq

PhiNotPi
fuente
3

Python 3 - $ 615570

En realidad no usa el oráculo ... Eh :)

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()

Construye una lista de todos los sobres anteriores y verifica si el sobre actual es menor que el número de sobres anteriores en incrementos de 10 sobres.

Decaimiento Beta
fuente
0

Python, 87,424

Aquí hay un algoritmo simple y llano, los siete afortunados.

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

test(LuckyNumber7)

Básicamente, lo que hace es convertir read () en una cadena y verifica si hay un siete. Si lo hay, toma el sobre. Si no, lo pasa.

Tiene un promedio de alrededor de 81,000, no he estado haciendo un seguimiento.

The_Basset_Hound
fuente
Entonces, ¿esto muestra que confiar en la suerte no es una estrategia exitosa? ;)
Reto Koradi
@RetoKoradi Sí: D
The_Basset_Hound