Take It or Leave It II: A Game Show for Computers


20

Questo è il secondo di una serie di enigmi che posterò ogni lunedì a mezzanotte PST. Il primo puzzle si trova qui .

Contesto:

Un miliardario solitario ha creato uno spettacolo di gioco per attirare i programmatori migliori e più brillanti del mondo. Il lunedì allo scoccare della mezzanotte, sceglie una persona da un pool di candidati come concorrente della settimana e fornisce loro un gioco. Sei il fortunato concorrente di questa settimana!

Il gioco di questa settimana:

L'host ti fornisce l'accesso API a una pila di 10.000 buste digitali. Queste buste sono ordinate casualmente e contengono al loro interno un valore in dollari, compreso tra $ 1 e $ 10.000 (non esistono due buste con lo stesso valore in dollari).

Hai 4 comandi a tua disposizione:

  1. Leggi (): leggi la cifra in dollari nella busta nella parte superiore della pila.

  2. Prendi (): aggiungi la cifra in dollari nella busta al portafoglio del tuo game show e togli la busta dallo stack.

  3. Pass (): espelle la busta in cima alla pila.

  4. Oracle (M): restituisce il valore medio delle buste M successive nello stack, escluso quello che puoi attualmente leggere ().

Le regole:

  1. Se usi Pass () su una busta, il denaro all'interno viene perso per sempre.

  2. Se usi Take () su una busta contenente $ X, da quel momento in poi, non puoi mai usare Take () su una busta contenente <$ X. Take () su una di queste buste aggiungerà $ 0 al tuo portafoglio.

  3. Se si utilizza Oracle (M) al turno T, verranno restituite le buste da T + 1 a T + M. Oracle () è disabilitato fino alla svolta T + M.

Scrivi un algoritmo che termina il gioco con la massima quantità di denaro.

Se stai scrivendo il tuo algoritmo in Python, sentiti libero di usare questo controller fornito da @Maltysen: https://gist.github.com/livinginformation/70ae3f2a57ecba4387b5

Note 1: "Massimo" in questo caso indica il valore mediano nel tuo portafoglio dopo N> = 1000 corse. Mi aspetto, anche se mi piacerebbe essere smentito, che il valore mediano di un determinato algoritmo converge quando N aumenta all'infinito. Sentiti libero di provare a massimizzare la media invece, ma ho la sensazione che la media abbia più probabilità di essere scartata da una piccola N rispetto alla mediana.

Nota 2: poiché tutte le soluzioni alla parte precedente di questo puzzle sono valide qui, ripubblicarle ha poco valore. Solo i miglioramenti algoritmici dei puzzle precedenti saranno presi in considerazione per la parte II.

Modifica: la condizione del premio è stata rimossa, alla luce di questo post su meta.


Wow, non posso credere di aver dormito troppo: O
Decadimento beta

@Beta Decay clock sta ticchettando! :)
LivingInformation

Qual è il senso del racle? Puoi costruire il tuo oracolo gratuito semplicemente tenendo un conteggio di tutte le buste lette in precedenza. Cosa mi sbaglio?
Luis Mendo,

1
@LuisMendo Con il tuo conteggio, puoi solo conoscere la media di tutti i valori rimanenti. Con l'oracolo, puoi ottenere la media dei Mvalori successivi , dove puoi scegliere M.
Reto Koradi,

1
Dal momento che tutte le soluzioni alla tua precedente sfida sono valide anche per questa sfida, possiamo considerarle implicitamente presentate?
Reto Koradi,

Risposte:


9

Groovy $ 713337 $ 817829 $ 818227

Codice 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

Confronto i valori rimanenti con i possibili valori. Questo script non è veloce (richiede 1 minuto per simulazioni 1000x) ... ma eseguirà le simulazioni contemporaneamente.

Non ho idea del perché il mio algoritmo funzioni, ma era solo prova ed errore: raggruppare operazioni matematiche e manipolare le costanti. L'ho eseguito 5000x per il punteggio corrente, nel tentativo di ridurre le fluttuazioni del punteggio (è +/- $ 4000 a seconda del conteggio delle iterazioni).

Anche senza l'oracolo alla fine, dovrebbe comunque (a malapena) battere la soluzione di @ orlp per il puzzle precedente.


7

C # - $ 803.603 ora -> $ 804.760 (con oracolo)

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

Codice del gioco:

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

Il credito appartiene a Reto Koradi ( /codegolf//a/54181/30910 )

Modifica: utilizzo di base di Oracle implementato. Se l'oracolo successivo è al di sopra della soglia da utilizzare, espandere la busta corrente sull'indice dell'indice Oracle. Questo non colpisce spesso ma è un miglioramento ;-)


4
Non credo sia molto produttivo ripubblicare le soluzioni della sfida precedente. Abbiamo tutti riconosciuto che tali soluzioni possono essere utilizzate come base per questa sfida e avevo già lasciato un commento per il PO chiedendomi come dovremmo gestirlo. L'idea è che ti viene in mente la tua soluzione, che è idealmente migliore delle soluzioni alla sfida precedente.
Reto Koradi,

si prega di interrompere il downvoting :) nota numero 2 è stato aggiunto dopo la mia presentazione. e poiché è più efficace delle altre soluzioni, l'ho pubblicato qui. non c'è bisogno di usare Oracle per battere le soluzioni esistenti.
Stephan Schinkel,

@StephanSchinkel Hai il mio voto se riesci a includere Oracle per migliorare il punteggio attuale. Anche solo per $ 1.
Dorus,

@BetaDecay cosa è esattamente ciò che è di nuovo malvisto dalla comunità? Ho appena seguito la domanda dall'op. Ancora una volta la nota numero 2 è stata aggiunta DOPO la mia presentazione.
Stephan Schinkel,

Non usare una soluzione dalla parte I del quiz.
Stephan Schinkel,

4

Python - $ 74112

Prendi solo se il valore corrente è inferiore al valore successivo (cioè puoi prendere entrambi).

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

Python - (ancora calcolando la media)

Questa risposta richiede MOLTO LUNGO per calcolare. Raggiunge intorno 670.000 $ . Ricordo ogni busta che ho visto. Ogni volta che devo prendere una decisione, generi due elenchi di buste rimanenti che potrei potenzialmente aggiungere al mio portafoglio se prendo la busta corrente o la lascio rispettivamente.

Non ho ottimizzato il codice.

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

E init_game inizia così:

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

3
Se usi i set per rappresentare passato, venire e comingIfTake e usi le intersezioni, il tuo codice sarebbe molto più veloce.
Nathan Merrill,

4

C # - $ 780.176

Verificare che il valore successivo sia compreso nel 5% inferiore di tutti i valori rimanenti. Rilassati man mano che arriviamo alla fine.

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

E la mia classe di gioco, molto brutta, non convalida nemmeno se l'oracolo è permesso, ma dal momento che uso solo Oracle (1) non dovrebbe essere 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();
    }
}

4

Java, $ 804.991

Il punteggio è di 1001 round. Probabilmente è troppo vicino per chiamare tra questa risposta e quella di Stephan Schinkel .

Questo si basa sulla mia risposta nella sfida precedente, in quanto utilizza lo stesso calcolo basato sull'entropia per stimare i profitti. La differenza principale è che ora prende semplicemente buste in coppia (1 e 2, quindi 3 e 4, ecc.) E osserva le possibili combinazioni di take-take, take-pass, pass-take, ecc. punteggio esatto stimato quando il numero di buste valide è veramente ridotto.

Il "wrapper" che ho scritto non è in realtà un vero wrapper, ma dà solo buste a coppie invece di chiamare una Oracle(1)funzione ogni altro round.

Nel complesso, direi che, nonostante la maggiore complessità, questo bot non è davvero migliore del mio precedente.

Giocatore

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

controllore

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

Indirizzo bitcoin: 1BVBs9ZEP8YY4EpV868nxi2R23YfL7hdMq


3

Python 3 - $ 615570

In realtà non usa l'oracolo ... 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()

Crea un elenco di tutte le buste precedenti e controlla se la busta corrente è inferiore al numero di buste precedenti con incrementi di 10 buste.


0

Python, 87.424

Ecco un algoritmo semplice e chiaro, i sette fortunati.

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

test(LuckyNumber7)

Fondamentalmente quello che fa è che converte read () in una stringa e controlla se ci sono sette. Se c'è, prende la busta. In caso contrario, lo passa.

La media è di circa 81.000, non ho tenuto traccia.


Questo dimostra che fare affidamento sulla fortuna non è una strategia di successo? ;)
Reto Koradi,

@RetoKoradi Sì: D
The_Basset_Hound
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.