À prendre ou à laisser II: un jeu télévisé pour les ordinateurs

20

Ceci est le deuxième d'une série de puzzles que je publierai tous les lundis à minuit PST. Le premier puzzle se trouve ici .

Le contexte:

Un milliardaire reclus a créé un jeu télévisé pour attirer les meilleurs programmeurs du monde. Le lundi à minuit, il choisit une personne parmi un groupe de candidats pour être le candidat de la semaine et leur propose un jeu. Vous êtes l'heureux candidat de cette semaine!

Jeu de cette semaine:

L'hôte vous offre un accès API à une pile de 10 000 enveloppes numériques. Ces enveloppes sont triées au hasard et contiennent en leur sein une valeur en dollars, entre 1 $ et 10 000 $ (il n’existe pas deux enveloppes ayant la même valeur en dollars).

Vous avez 4 commandes à votre disposition:

  1. Lire (): lire le chiffre en dollars dans l'enveloppe en haut de la pile.

  2. Take (): Ajoutez le chiffre en dollars dans l'enveloppe à votre portefeuille de jeu télévisé et sortez l'enveloppe de la pile.

  3. Pass (): Retirez l'enveloppe sur le dessus de la pile.

  4. Oracle (M): renvoie la valeur moyenne des prochaines enveloppes M dans la pile, sans inclure celle que vous pouvez actuellement lire ().

Les règles:

  1. Si vous utilisez Pass () sur une enveloppe, l'argent à l'intérieur est perdu pour toujours.

  2. Si vous utilisez Take () sur une enveloppe contenant $ X, à partir de ce moment, vous ne pourrez jamais utiliser Take () sur une enveloppe contenant <$ X. Prenez () sur l'une de ces enveloppes ajoutera 0 $ à votre portefeuille.

  3. Si vous utilisez Oracle (M) au tour T, les enveloppes T + 1 à la moyenne de T + M seront retournées. Oracle () est désactivé jusqu'au tour T + M.

Écrivez un algorithme qui termine le jeu avec le montant maximal d'argent.

Si vous écrivez votre algorithme en Python, n'hésitez pas à utiliser ce contrôleur fourni par @Maltysen: https://gist.github.com/livinginformation/70ae3f2a57ecba4387b5

Notes 1: "Maximal" dans ce cas signifie la valeur médiane de votre portefeuille après N> = 1000 exécutions. Je m'attends, bien que j'aimerais qu'on me prouve à tort, que la valeur médiane d'un algorithme donné convergera lorsque N augmentera à l'infini. N'hésitez pas à essayer de maximiser la moyenne à la place, mais j'ai le sentiment que la moyenne est plus susceptible d'être rejetée par un petit N que la médiane.

Remarque 2: comme toutes les solutions à la partie précédente de ce puzzle sont valables ici, les republier a peu de valeur. Seules les améliorations algorithmiques des puzzles précédents seront prises en compte pour la partie II.

Edit: La condition de prix a été supprimée, à la lumière de ce post sur meta.

LivingInformation
la source
Wow, je ne peux pas croire que j'ai dormi trop longtemps: O
Beta Decay
L'horloge @Beta Decay tourne! :)
LivingInformation
Quel est le sens du rracle? Vous pouvez créer votre propre oracle gratuit en gardant simplement un décompte de toutes les enveloppes précédemment lues. Qu'est-ce que je me trompe?
Luis Mendo
1
@LuisMendo Avec votre propre décompte, vous ne pouvez connaître que la moyenne de toutes les valeurs restantes. Avec l'oracle, vous pouvez obtenir la moyenne des Mvaleurs suivantes , où vous pouvez choisir M.
Reto Koradi
1
Étant donné que toutes les solutions à votre défi précédent sont également des solutions valables à ce défi, pouvons-nous les considérer comme implicitement soumises?
Reto Koradi

Réponses:

9

Groovy 713337 $ 817829 $ 818227 $

Code d'amorçage:

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

Algorithme

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

Je compare les valeurs restantes avec des valeurs possibles. Ce script n'est pas rapide (prend 1 minute par 1000x simulations) ... mais il effectuera les simulations simultanément.

Je ne sais pas pourquoi mon algorithme fonctionne, mais ce n'était que des essais et des erreurs: regrouper les opérations mathématiques et manipuler les constantes. Je l'ai exécuté 5000x pour le score actuel, dans le but de réduire les fluctuations du score (c'est +/- 4000 $ selon le nombre d'itérations).

Même sans l'oracle à la fin, il devrait toujours battre (à peine) la solution de @ orlp pour le puzzle précédent.

Wesley Wolfe
la source
7

C # - 803,603 $ maintenant -> 804,760 $ (avec oracle)

Code d'amorçage

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

Code de jeu:

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

Le crédit appartient à Reto Koradi ( /codegolf//a/54181/30910 )

Edit: Utilisation basique d'Oracle implémentée. Si l'oracle suivant est au-dessus du seuil à utiliser, développez l'enveloppe actuelle jusqu'à l'index de l'index Oracle. Cela ne frappe pas souvent mais C'EST une amélioration ;-)

Stephan Schinkel
la source
4
Je ne pense pas qu'il soit très productif de republier les solutions du défi précédent. Nous avons tous reconnu que ces solutions peuvent servir de base à ce défi, et j'avais déjà laissé un commentaire au PO pour lui demander comment nous devrions y faire face. L'idée est que vous trouviez votre propre solution, qui est idéalement meilleure que les solutions au défi précédent.
Reto Koradi
veuillez arrêter de voter :) la note numéro 2 a été ajoutée après ma soumission. et comme il est plus efficace que les autres solutions - je l'ai posté ici. pas besoin d'utiliser Oracle pour battre les solutions existantes.
Stephan Schinkel
@StephanSchinkel Vous avez mon vote positif si vous parvenez à inclure l'Oracle pour améliorer le score actuel. Même avec seulement 1 $.
Dorus
@BetaDecay qu'est-ce qui est encore mal vu par la communauté? Je viens de suivre la question de l'op. Une fois de plus, la note numéro 2 a été ajoutée APRÈS ma soumission.
Stephan Schinkel
Ne pas utiliser une solution de la partie I du quiz.
Stephan Schinkel
4

Python - 74112 $

Ne prenez que si la valeur actuelle est inférieure à la valeur suivante (c'est-à-dire que vous pouvez prendre les deux).

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

Python - (calculant toujours la moyenne)

Cette réponse est TRÈS LONGUE à calculer. Il atteint environ 670 000 $ . Je me souviens de chaque enveloppe que j'ai vue. Chaque fois que je dois prendre une décision, je génère deux listes d'enveloppes restantes que je pourrais potentiellement ajouter à mon portefeuille si je prends l'enveloppe actuelle ou la laisse respectivement.

Je n'ai pas optimisé le code.

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

Et init_game commence comme ceci:

def init_game():
    global stack, wallet, max_taken, oracle_turns, past
    past=[]
TheEspinosa
la source
3
Si vous utilisez des ensembles pour représenter le passé, le futur et le comingIfTake, et utilisez des intersections, votre code serait beaucoup plus rapide.
Nathan Merrill
4

C # - 780,176 $

Vérifiez si la valeur suivante se situe dans les 5% inférieurs de toutes les valeurs restantes. Soyez plus détendu à la fin.

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

Et ma classe de jeu, très moche, ne classe même pas si l'oracle est autorisé, mais comme j'utilise uniquement Oracle (1), cela ne devrait pas être un problème.

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
la source
4

Java, 804 991 $

Le score est de 1001 tours. Il est probablement trop proche d'appeler entre cette réponse et celle de Stephan Schinkel .

Ceci est basé sur ma réponse dans le défi précédent, en ce qu'il utilise le même calcul basé sur l'entropie pour estimer les gains. La principale différence est qu'il prend maintenant simplement les enveloppes par paires (1 & 2, puis 3 & 4, etc.) et examine les combinaisons possibles de prise-prise, prise-passe, passe-prise, etc. Il calcule également la score estimé exact lorsque le nombre d'enveloppes valides est vraiment faible.

Le "wrapper" que j'ai écrit n'est pas vraiment un vrai wrapper, il donne juste des enveloppes par paires au lieu d'appeler une Oracle(1)fonction tous les deux tours.

Dans l'ensemble, je dirais que, malgré la complexité accrue, ce bot n'est vraiment pas meilleur que le précédent.

Joueur

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

Manette

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

Adresse Bitcoin: 1BVBs9ZEP8YY4EpV868nxi2R23YfL7hdMq

PhiNotPi
la source
3

Python 3 - 615570 $

N'utilise pas vraiment l'oracle ... 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()

Construit une liste de toutes les enveloppes précédentes et vérifie si l'enveloppe actuelle est inférieure au nombre d'enveloppes précédentes en 10 incréments d'enveloppe.

Beta Decay
la source
0

Python, 87 424

Voici un algorithme simple et simple, les sept chanceux.

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

test(LuckyNumber7)

Fondamentalement, il convertit read () en chaîne et vérifie s'il y en a sept. S'il y en a, il prend l'enveloppe. Sinon, ça passe.

Il est en moyenne de 81 000, je n'ai pas suivi.

The_Basset_Hound
la source
Cela montre donc que compter sur la chance n'est pas une stratégie réussie? ;)
Reto Koradi
@RetoKoradi Yep: D
The_Basset_Hound