Al ya da Bırak: Bilgisayarlar İçin Bir Oyun Gösterisi


28

Bağlam:

Mütevazi bir milyarder, dünyanın en iyi ve en parlak programcılarını çekmek için bir oyun programı yarattı. Pazartesi günleri gece yarısına inerken, haftanın yarışmacısı olmak için bir başvuru havuzundan bir kişi seçer ve onlara bir oyun sunar. Bu haftanın şanslı yarışmacısın!

Bu haftanın oyunu:

Ana bilgisayar size 10.000 dijital zarf yığınına API erişimi sağlar. Bu zarflar rastgele sıralanır ve içinde 1 ile 10,000 ABD Doları arasında bir dolar değeri içerir (iki zarfta aynı dolar değerini içermez).

Emrinde 3 emir var:

  1. Oku (): Zarfın üzerindeki zarftaki dolar şeklini okuyun.

  2. Al (): Zarftaki dolar şeklini oyun şov cüzdanınıza ekleyin ve zarfı yığından çıkarın.

  3. Pass (): Zarfı yığının üstüne çıkarın.

Kurallar:

  1. Zarfta Pass () kullanıyorsanız, içindeki para sonsuza dek kaybedilir.

  2. Take () öğesini $ X içeren bir zarfta kullanırsanız, bu noktadan itibaren, <$ X içeren bir zarfta Take () özelliğini asla kullanamazsınız. Alınan () bu zarflardan birine cüzdanınıza 0 dolar ekleyecektir.

Oyunu en fazla para ile bitiren bir algoritma yaz.

Python'da bir çözüm yazıyorsanız, algoritmaları test etmek için bu denetleyiciyi kullanmaktan çekinmeyin, @Maltysen'in izniyle: https://gist.github.com/Maltysen/5a4a33691cd603e9aeca

Denetleyiciyi kullanırsanız, küresellere erişemezsiniz, yalnızca sağlanan 3 API komutunu ve yerel kapsamlı değişkenleri kullanabilirsiniz. (@Beta Decay)

Notlar: Bu durumda "Maksimal", N> 50 çalıştıktan sonra cüzdanınızdaki medyan değer anlamına gelir. Yanlış olduğunu kanıtlamayı sevmeme rağmen, belirli bir algoritma için ortanca değerin N sonsuzluğa yükseldikçe birleşeceğini umuyorum. Bunun yerine ortalamayı en üst düzeye çıkarmak için çekinmeyin, ancak ortalamanın medyandan küçük bir N tarafından atılma ihtimalinin daha yüksek olduğunu hissediyorum.

Düzenleme: kolay işlem için zarf sayısını 10k olarak değiştirdi ve Take () 'ı daha açık hale getirdi.

Düzen 2: Ödül koşulu metada verilen bu yazı ışığında kaldırıldı .

Güncel yüksek puanlar:

PhiNotPi - 805,479 Dolar

Reto Koradi - 803,960 ABD doları

Dennis - 770,272 ABD Doları (Değişiklik)

Alex L. - 714,962 ABD Doları (Değişiklik)


Sadece False döndürecek şekilde uyguladım. Okuyabildiğiniz için, tüm oyunun başarısız bir başarısızlıkla sonuçlanmasının gerçek bir anlamı yoktur ()
OganM

4
Herhangi birinin kullanmak istemesi durumunda,
Maltysen 31:15

8
PS Güzel soru ve Programlama Bulmacalar ve Kod Golf hoş geldiniz :)
trichoplax

3
@Maltysen Kontrolörünüzü OP'ye dahil ettim, katkılarınız için teşekkürler!
LivingInformation

1
Bitcoin ödülleri hakkında açık bir kural bulamadım, ancak insanların katkıda bulunabileceği gerçek dünya ödülleri hakkında bazı meta tartışmalar var .
trichoplax

Yanıtlar:


9

CJam, 87,143 $ 700,424 $ 720,327 $ 727,580 $ 770,272

{0:T:M;1e4:E,:)mr{RM>{RR(*MM)*-E0.032*220+R*<{ERM--:E;R:MT+:T;}{E(:E;}?}&}fRT}
[easi*]$easi2/=N

Bu program tüm oyunu birkaç kez simüle eder ve ortancaları hesaplar.

Nasıl çalıştırılır

Gönderimimi 100001 test çalışması yaparak aldım:

$ time java -jar cjam-0.6.5.jar take-it-or-leave-it.cjam 100001
770272

real    5m7.721s
user    5m15.334s
sys     0m0.570s

yaklaşım

Her zarf için aşağıdakileri yaparız:

  • Zarfı alarak kaçınılmaz olarak kaybedilecek para miktarını tahmin edin.

    Eğer R, içerik ve M miktarı, tahmin edilebilir, alınmıştır maksimum R (R-1) / 2 - M (M + 1) / 2 içeriği ile para bütün zarflar verir, X in aralık (M, R) içerir.

    Henüz zarf gelmediyse, tahmin mükemmel olurdu.

  • Zarfı geçirerek kaçınılmaz olarak kaybedilecek para miktarını hesaplayın.

    Bu sadece zarfın içerdiği para.

  • Her ikisinin de bölümünün 110 + 0.016E'den az olup olmadığını kontrol edin; burada E , kalan zarfların sayısıdır (artık alınamayan zarfları saymaz).

    Eğer öyleyse, al. Aksi takdirde, geçmek.


5
Çünkü bir golf dili kullanmak herhangi bir şekilde yardımcı olur. Algo için P + 1;
Maltysen

2
Sonuçlarınızı bir Python klonu kullanarak çoğaltamıyorum: gist.github.com/orlp/f9b949d60c766430fe9c . 50.000 dolar civarında puan. Bu bir büyüklük sırası.
orlp

1
@LivingInformation Deneme ve hata. Şu anda tahminler yerine tam tutarı kullanmaya çalışıyorum, ancak sonuçta ortaya çıkan kod çok yavaş.
Dennis,

2
Bu cevabın benimkinden daha fazla oyuna ihtiyacı var! Daha akıllı, daha yüksek puanlar ve hatta golf oynuyor!
Alex L

1
@LivingInformation Bu benim adresim: 17uLHRfdD5JZ2QjSqPGQ1B12LoX4CgLGuV
Dennis

7

Python, 680,646 $ 714,962

f = (float(len(stack)) / 10000)
step = 160
if f<0.5: step = 125
if f>0.9: step = 190
if read() < max_taken + step:
    take()
else:
    passe()

125 ila 190 dolar arasında değişen adımlarla daha büyük ve daha büyük miktarlar alır. N = 10.000 ile koştum ve medyanı 714962 dolar aldı. Bu adım boyutları deneme yanılma geldi ve kesinlikle en uygun değil.

@ Maltysen'in denetleyicisinin değiştirilmiş bir sürümü de dahil olmak üzere, çalışırken bir çubuk grafik basan tam kod :

import random
N = 10000


def init_game():
    global stack, wallet, max_taken
    stack = list(range(1, 10001))
    random.shuffle(stack)
    wallet = max_taken = 0

def read():
    return stack[0]

def take():
    global wallet, max_taken
    amount = stack.pop(0)
    if amount > max_taken:
        wallet += amount
        max_taken = amount

def passe():
    stack.pop(0)

def test(algo):
    results = []
    for _ in range(N):
        init_game()
        for i in range(10000):
            algo()
        results += [wallet]
        output(wallet)
    import numpy
    print 'max: '
    output(max(results))
    print 'median: '
    output(numpy.median(results))
    print 'min: '
    output(min(results))

def output(n):
    print n
    result = ''
    for _ in range(int(n/20000)):
        result += '-'
    print result+'|'

def alg():
    f = (float(len(stack)) / 10000)
    step = 160
    if f<0.5: step = 125
    if f>0.9: step = 190
    if read() < max_taken + step:
        #if read()>max_taken: print read(), step, f
        take()
    else:
        passe()

test(alg)

BitCoin adresi: 1CBzYPCFFBW1FX9sBTmNYUJyMxMcmL4BZ7

Vay OP teslim! Teşekkürler @LivingInformation!


1
Kontrolör Maltysen’e ait, benim değil.
orlp

2
Onaylandı. Daha yeni bir kontrol cihazı kurmuştum ve çözümünüz için çok benzer numaralar elde ettim. Açıkçası max_taken, resmi oyun API'sinin bir parçası olmadığından, kendi kodunuzdaki değerini korumanız gerektiğini düşünüyorum . Ama bu yapmak için önemsiz.
Reto Koradi

1
Evet, max_taken @ Maltysen'in kontrolünde. Yararlı olursa, tüm çözümü (kontrolör + algoritma) tek bir satırda gönderebilirim.
Alex L

Gerçekten önemli değil. Ama en temiz yaklaşım sadece kullanmak olacağını düşünüyorum read(), take()ve pass()bu yana nakledilen kod yöntemleri, söz konusu tanım temel "emrinde 3 komutları" dir.
Reto Koradi

@Reto Ne komutları en anlamlı olursa olsun sorusunu gözden geçirmek için hazırım. Oku, Al ve Geçiş, 4 karakterin tümü ve uygun hissettirdi, ancak önerilere açığım (örneğin, "geçmeyi" "bırak" olarak değiştirmeyi düşündüm, çünkü "gönder" başlığını aldım veya bıraktım. ").
LivingInformation

5

C ++, 803,960 ABD doları

for (int iVal = 0; iVal < 10000; ++iVal)
{
    int val = game.read();
    if (val > maxVal &&
        val < 466.7f + 0.9352f * maxVal + 0.0275f * iVal)
    {
        maxVal = val;
        game.take();
    }
    else
    {
        game.pass();
    }
}

Raporlanan sonuç, 10.001 oyundan elde edilen medyandır.


Tahmin et ve kontrol et. Yoksa sabitler için bir çeşit giriş bulanıklaştırıcısı kullandın mı?
LivingInformation

Sabitleri belirlemek için bir optimizasyon algoritması kullandım.
Reto Koradi

Her noktada dinamik bir hesaplamanın daha etkili olacağını mı düşünüyorsunuz yoksa bunun alabileceğiniz maksimum değere yaklaştığını mı düşünüyorsunuz?
LivingInformation

Bunun ideal bir strateji olduğuna inanmak için hiçbir nedenim yok. Umarım bu parametreler ile doğrusal bir fonksiyon için maksimumdur. Çeşitli doğrusal olmayan terimlere izin vermeye çalışıyorum, ancak şu ana kadar önemli ölçüde daha iyi bir şey bulamadım.
Reto Koradi

1
Bunun simüle edilmesinin 800.000 $ 'dan biraz fazla rapor verdiğini teyit edebilirim.
orlp 16 '48'de

3

C ++, ~ 815.000 ABD Doları

Reto Koradi'nin çözümüne dayanarak, ancak 100 (geçerli) zarf kaldıktan sonra daha karmaşık bir algoritmaya geçer, rastgele permütasyonları karıştırır ve bunların en ağır artan sırasını hesaplar. Zarfı alıp almamanın sonuçlarını karşılaştırır ve açgözlülükle en iyi seçimi seçer.

#include <algorithm>
#include <iostream>
#include <vector>
#include <set>


void setmax(std::vector<int>& h, int i, int v) {
    while (i < h.size()) { h[i] = std::max(v, h[i]); i |= i + 1; }
}

int getmax(std::vector<int>& h, int n) {
    int m = 0;
    while (n > 0) { m = std::max(m, h[n-1]); n &= n - 1; }
    return m;
}

int his(const std::vector<int>& l, const std::vector<int>& rank) {
    std::vector<int> h(l.size());
    for (int i = 0; i < l.size(); ++i) {
        int r = rank[i];
        setmax(h, r, l[i] + getmax(h, r));
    }

    return getmax(h, l.size());
}

template<class RNG>
void shuffle(std::vector<int>& l, std::vector<int>& rank, RNG& rng) {
    for (int i = l.size() - 1; i > 0; --i) {
        int j = std::uniform_int_distribution<int>(0, i)(rng);
        std::swap(l[i], l[j]);
        std::swap(rank[i], rank[j]);
    }
}

std::random_device rnd;
std::mt19937_64 rng(rnd());

struct Algo {
    Algo(int N) {
        for (int i = 1; i < N + 1; ++i) left.insert(i);
        ival = maxval = 0;
    }

    static double get_p(int n) { return 1.2 / std::sqrt(8 + n) + 0.71; }

    bool should_take(int val) {
        ival++;
        auto it = left.find(val);
        if (it == left.end()) return false;

        if (left.size() > 100) {
            if (val > maxval && val < 466.7f + 0.9352f * maxval + 0.0275f * (ival - 1)) {
                maxval = val;
                left.erase(left.begin(), std::next(it));
                return true;
            }

            left.erase(it);
            return false;
        }

        take.assign(std::next(it), left.end());
        no_take.assign(left.begin(), it);
        no_take.insert(no_take.end(), std::next(it), left.end());
        take_rank.resize(take.size());
        no_take_rank.resize(no_take.size());
        for (int i = 0; i < take.size(); ++i) take_rank[i] = i;
        for (int i = 0; i < no_take.size(); ++i) no_take_rank[i] = i;

        double take_score, no_take_score;
        take_score = no_take_score = 0;
        for (int i = 0; i < 1000; ++i) {
            shuffle(take, take_rank, rng);
            shuffle(no_take, no_take_rank, rng);
            take_score += val + his(take, take_rank) * get_p(take.size());
            no_take_score += his(no_take, no_take_rank) * get_p(no_take.size());
        }

        if (take_score > no_take_score) {
            left.erase(left.begin(), std::next(it));
            return true;
        }

        left.erase(it);
        return false;
    }

    std::set<int> left;
    int ival, maxval;
    std::vector<int> take, no_take, take_rank, no_take_rank;
};


struct Game {
    Game(int N) : score_(0), max_taken(0) {
        for (int i = 1; i < N + 1; ++i) envelopes.push_back(i);
        std::shuffle(envelopes.begin(), envelopes.end(), rng);
    }

    int read() { return envelopes.back(); }
    bool done() { return envelopes.empty(); }
    int score() { return score_; }
    void pass() { envelopes.pop_back(); }

    void take() {
        if (read() > max_taken) {
            score_ += read();
            max_taken = read();
        }
        envelopes.pop_back();
    }

    int score_;
    int max_taken;
    std::vector<int> envelopes;
};


int main(int argc, char** argv) {
    std::vector<int> results;
    std::vector<int> max_results;
    int N = 10000;
    for (int i = 0; i < 1000; ++i) {
        std::cout << "Simulating game " << (i+1) << ".\n";
        Game game(N);
        Algo algo(N);

        while (!game.done()) {
            if (algo.should_take(game.read())) game.take();
            else game.pass();
        }
        results.push_back(game.score());
    }

    std::sort(results.begin(), results.end());
    std::cout << results[results.size()/2] << "\n";

    return 0;
}

İlginç. Son birkaç zarf için geriye kalan değerlere bakarak iyileştirmenin mümkün olacağı aklımdan geçti. Stratejileri değiştirdiğin kesim noktasıyla oynadığını sanıyorum. Daha erken yer değiştirirseniz çok mu yavaş oluyor? Yoksa sonuçlar gerçekten daha mı kötüleşiyor?
Reto Koradi

@RetoKoradi Eşik noktasıyla oynadım ve önceki eşiklerin ikisi de daha yavaş ve daha kötüye gitti. Çok 100 zarf biz zaten mümkün 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000. dışında sadece 1000 permütasyon örnek almıyor, dürüst şaşırtıcı değil
orlp

3

Java, 806,899 dolar

Bu 2501 tur denemesinden. Hala onu optimize etmeye çalışıyorum. İki sınıf yazdım, bir sarmalayıcı ve bir oyuncu. Sarıcı, oynatıcıyı zarf sayısıyla (her zaman için 10000 olan) başlatır ve sonra yöntemi takeQüst zarfın değeriyle çağırır . Oyuncu daha sonra truealırlarsa, falsegeçerse geri döner .

oyuncu

import java.lang.Math;

public class Player {
  public int[] V;

  public Player(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) {

    // System.out.println("look " + x);

    // http://www.programmingsimplified.com/java/source-code/java-program-for-binary-search
    int first = 0;
    int last = V.length - 1;
    int middle = (first + last) / 2;
    int search = x;

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

      middle = (first + last) / 2;
    }

    int i = middle;

    if (first > last) {
      // System.out.println(" PASS");
      return false; // value not found, so the envelope must not be in the list
                    // of acceptable ones
    }

    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 = calcVal(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] + calcVal(newVt);
    // System.out.println(" take " + take);
    // System.out.println(" pass " + pass);

    if (take > pass) {
      V = newVt;
      // System.out.println(" TAKE");
      return true;
    } else {
      V = newVp;
      // System.out.println(" PASS");
      return false;
    }
  }

  public double calcVal(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);
  }
}

sarıcı

import java.lang.Math;
import java.util.Random;
import java.util.ArrayList;
import java.util.Collections;

public class Controller {
  public static void main(String[] args) {
    int size = 10000;
    int rounds = 2501;
    ArrayList<Integer> results = new ArrayList<Integer>();
    int[] envelopes = new int[size];
    for (int i = 0; i < envelopes.length; i++) {
      envelopes[i] = i + 1;
    }
    for (int round = 0; round < rounds; round++) {
      shuffleArray(envelopes);

      Player p = new Player(size);
      int cutoff = 0;
      int winnings = 0;
      for (int i = 0; i < envelopes.length; i++) {
        boolean take = p.takeQ(envelopes[i]);
        if (take && envelopes[i] >= cutoff) {
          winnings += envelopes[i];
          cutoff = envelopes[i];
        }
      }
      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 Random rnd = new Random();

  static void shuffleArray(int[] ar) {
    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;
    }
  }
}

Optimizasyonları bitirdikten sonra yakında daha ayrıntılı bir açıklama gelecek.

Temel fikir, belirli bir zarf kümesinden bir oyun oynamanın ödülünü tahmin edebilmektir. Geçerli zarf kümesi {2,4,5,7,8,9} ve üst zarf 5 ise, iki olasılık vardır:

  • Beşi al ve {7,8,9} ile bir oyun oyna
  • 5'i geç ve {2,4,7,8,9} oyununu oyna

Beklenen {7,8,9} ödülünü hesaplarsak ve bunu {2,4,7,8,9} beklenen ödül ile karşılaştırırsak, 5'i almaya değip değmeyeceğini söyleyebiliriz.

Şimdi soru, {2,4,7,8,9} gibi bir dizi zarf verildiğinde beklenen değer nedir? Beklenen değerin, kümedeki toplam para miktarıyla orantılı olduğunu, ancak paranın bölündüğü zarf sayısının karekökü ile ters orantılı olduğunu gördüm. Bu, tüm zarfların neredeyse aynı değerde olduğu birkaç küçük oyun oynamaktan "mükemmel" bir şeydi.

Bir sonraki sorun, " etkili zarf sayısının " nasıl belirleneceğidir . Her durumda, zarf sayısı tam olarak gördüklerinizi ve yaptıklarınızı takip ederek bilinir. {234,235,236} gibi bir şey kesinlikle üç zarf, {231,232,233,234,235} kesinlikle 5, ancak {1,2,234,235,236} gerçekten 3 sayılmalı, çünkü 5 ve 1 zarf neredeyse hiç değersizdir, ve 234'te hiç PASS yapmazsınız. daha sonra 1 veya 2 puan alabilirsin. Etkili zarf sayısını belirlemek için Shannon entropy'i kullanma fikrim vardı.

Hesaplamalarımı zarfın değerlerinin belli bir aralıkta eşit dağıldığı durumlara hedefledim, bu da oyun sırasında olan şey. {2,4,7,8,9} alırsam ve bunu olasılık dağılımı olarak kabul edersem, entropisi 1.50242'dir. Sonra exp()etkili zarf sayısı olarak 4.49254 almak.

{2,4,7,8,9} 'dan tahmini ödül 30 * 4.4925^-0.5 * 4/3 = 18.87

Kesin sayı 18.1167.

Bu kesin bir tahmin değil, ancak zarflar belli aralıklarla eşit olarak dağıtıldığında verinin ne kadar iyi uyduğuyla gerçekten gurur duyuyorum. Doğru çarpandan emin değilim (şimdilik 4/3 kullanıyorum), ancak burada çarpanı hariç tutan bir veri tablosu var.

Set of Envelopes                    Total * (e^entropy)^-0.5      Actual Score

{1,2,3,4,5,6,7,8,9,10}              18.759                        25.473
{2,3,4,5,6,7,8,9,10,11}             21.657                        29.279
{3,4,5,6,7,8,9,10,11,12}            24.648                        33.125
{4,5,6,7,8,9,10,11,12,13}           27.687                        37.002
{5,6,7,8,9,10,11,12,13,14}          30.757                        40.945
{6,7,8,9,10,11,12,13,14,15}         33.846                        44.900
{7,8,9,10,11,12,13,14,15,16}        36.949                        48.871
{8,9,10,11,12,13,14,15,16,17}       40.062                        52.857
{9,10,11,12,13,14,15,16,17,18}      43.183                        56.848
{10,11,12,13,14,15,16,17,18,19}     46.311                        60.857

Beklenen ve gerçekleşen arasındaki doğrusal regresyon , 0.999994 olan bir R ^ 2 değeri verir .

Bu cevabı geliştirmek için bir sonraki adımım zarf sayısı azalıyorsa, zarfların yaklaşık olarak eşit dağılmadığı ve problemin granülleşmeye başladığı zamanki tahminleri iyileştirmektir.


Düzenleme: Eğer bu bitcoin değerinde görülüyorsa, adresimi yeni aldım 1PZ65cXxUEEcGwd7E8i7g6qmvLDGqZ5JWg. Teşekkürler! (Bu, meydan yazıcısının ödülleri dağıttığı zamandan beri buradaydı.)


Yanlışlıkla 805.479’dan 20 bin satoshi göndermişsin. Başvuru için, tutarın puanınız olması gerekiyordu .
Hatamın

Daha fazla turla sayı mı koşacaksınız? Gördüklerime göre, biraz değişkenlik var ve 500 kararlı bir ortanca elde etmek için yeterli değil. Sadece 500 tur attıysam puanım seninkine çok yakın, ama her şey rastgele sayıların nasıl düştüğüne bağlı. Değişken bir tohum kullanırsam ve birkaç kez 500 çalıştırırsam, muhtemelen daha yüksek bir puan alabilirim.
Reto Koradi

@RetoKoradi Kesinlikle daha fazla tur yapacağım.
PhiNotPi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.