(Etwas) Pedantisches Geburtstagsparadoxon

20

Hintergrund

Das Geburtstagsparadoxon ist ein beliebtes Problem in der Wahrscheinlichkeitstheorie, das sich der mathematischen Intuition der meisten Menschen entzieht. Die Problemstellung lautet:

Wie hoch ist bei N Personen die Wahrscheinlichkeit, dass mindestens zwei von ihnen den gleichen Geburtstag haben (ohne Berücksichtigung des Jahres)?

Das Problem wird normalerweise vereinfacht, indem Schalttage vollständig ignoriert werden. In diesem Fall ist die Antwort für 23 N = ist , P (23) ≈ 0,5072972 (als ein allgemeines Beispiel). Der verlinkte Wikipedia-Artikel erklärt, wie man zu dieser Wahrscheinlichkeit kommt. Alternativ leistet dieses Numberphile-Video wirklich gute Arbeit.

Für diese Herausforderung wollen wir es jedoch richtig machen und Schaltjahre nicht ignorieren. Dies ist etwas komplizierter, da jetzt der 29. Februar hinzugefügt werden muss, aber dieser besondere Geburtstag ist weniger wahrscheinlich als alle anderen.

Wir werden auch die Regeln für das gesamte Schaltjahr anwenden:

  • Wenn ein Jahr durch 400 teilbar ist, ist es ein Schaltjahr.
  • Ansonsten ist ein Jahr, das durch 100 teilbar ist, kein Schaltjahr.
  • Ansonsten ist ein Jahr, das durch 4 teilbar ist, ein Schaltjahr.
  • Sonst ist es kein Schaltjahr.

Verwirrt? Dies bedeutet, dass die Jahre 1700, 1800, 1900, 2100, 2200, 2300 keine Schaltjahre sind, sondern 1600, 2000, 2400 (sowie jedes andere durch 4 teilbare Jahr). Dieser Kalender wird alle 400 Jahre wiederholt, und wir gehen von einer einheitlichen Verteilung der Geburtstage über diese 400 Jahre aus.

Das korrigierte Ergebnis für N = 23 ist nun P (23) ≤ 0,5068761 .

Die Herausforderung

1 ≤ N < 100Bestimmen Sie bei einer Ganzzahl die Wahrscheinlichkeit, dass unter NMenschen mindestens zwei den gleichen Geburtstag haben, unter Berücksichtigung der Schaltjahrregeln. Das Ergebnis sollte eine Gleitkomma- oder Festkommazahl sein, die auf mindestens 6 Dezimalstellen genau ist. Es ist akzeptabel, nachfolgende Nullen abzuschneiden.

Sie können ein Programm oder eine Funktion schreiben, indem Sie eine Eingabe über STDIN (oder die nächstgelegene Alternative), ein Befehlszeilenargument oder ein Funktionsargument vornehmen und das Ergebnis über STDOUT (oder die nächstgelegene Alternative), einen Funktionsrückgabewert oder einen Funktionsparameter (out) ausgeben.

Ihre Lösung muss in der Lage sein, innerhalb von Sekunden eine Ausgabe für alle 99 Eingaben zu erstellen. Dies dient hauptsächlich dazu, Monte-Carlo-Methoden mit Tonnen von Samples auszuschließen. Wenn Sie also einen prinzipiell schnellen und genauen Algorithmus in einer zu langsamen esoterischen Sprache verwenden, bin ich bereit, dieser Regel Spielraum einzuräumen.

Testfälle

Hier ist die vollständige Ergebnistabelle:

 1 => 0.000000
 2 => 0.002737
 3 => 0.008195
 4 => 0.016337
 5 => 0.027104
 6 => 0.040416
 7 => 0.056171
 8 => 0.074251
 9 => 0.094518
10 => 0.116818
11 => 0.140987
12 => 0.166844
13 => 0.194203
14 => 0.222869
15 => 0.252642
16 => 0.283319
17 => 0.314698
18 => 0.346578
19 => 0.378764
20 => 0.411063
21 => 0.443296
22 => 0.475287
23 => 0.506876
24 => 0.537913
25 => 0.568260
26 => 0.597796
27 => 0.626412
28 => 0.654014
29 => 0.680524
30 => 0.705877
31 => 0.730022
32 => 0.752924
33 => 0.774560
34 => 0.794917
35 => 0.813998
36 => 0.831812
37 => 0.848381
38 => 0.863732
39 => 0.877901
40 => 0.890932
41 => 0.902870
42 => 0.913767
43 => 0.923678
44 => 0.932658
45 => 0.940766
46 => 0.948060
47 => 0.954598
48 => 0.960437
49 => 0.965634
50 => 0.970242
51 => 0.974313
52 => 0.977898
53 => 0.981043
54 => 0.983792
55 => 0.986187
56 => 0.988266
57 => 0.990064
58 => 0.991614
59 => 0.992945
60 => 0.994084
61 => 0.995055
62 => 0.995880
63 => 0.996579
64 => 0.997169
65 => 0.997665
66 => 0.998080
67 => 0.998427
68 => 0.998715
69 => 0.998954
70 => 0.999152
71 => 0.999314
72 => 0.999447
73 => 0.999556
74 => 0.999645
75 => 0.999717
76 => 0.999775
77 => 0.999822
78 => 0.999859
79 => 0.999889
80 => 0.999913
81 => 0.999932
82 => 0.999947
83 => 0.999959
84 => 0.999968
85 => 0.999976
86 => 0.999981
87 => 0.999986
88 => 0.999989
89 => 0.999992
90 => 0.999994
91 => 0.999995
92 => 0.999996
93 => 0.999997
94 => 0.999998
95 => 0.999999
96 => 0.999999
97 => 0.999999
98 => 0.999999
99 => 1.000000

(Natürlich ist P (99) aufgrund der Rundung nur 1,0 . Die Wahrscheinlichkeit wird erst bei P (367) genau 1,0 erreichen .)

Martin Ender
quelle
7
1. Wenn Sie pedantisch sein wollen, sollten Sie berücksichtigen, dass Geburtstage nicht gleichmäßig über das ganze Jahr verteilt sind. 2. Die genaue Relevanz der Schaltjahrregeln hängt davon ab, welche Annahmen über die Lebensdauer des Menschen getroffen werden. Ist die Idee, sich über den gesamten 400-Jahres-Zyklus amortisieren zu lassen?
Peter Taylor
1
@PeterTaylor Ja, nehmen Sie eine gleichmäßige Verteilung über den gesamten 400-Jahres-Zyklus an. Ich habe nie gesagt, dass die Gruppe von N Personen gleichzeitig am Leben ist. ;)
Martin Ender

Antworten:

6

Pyth, 31 34 Bytes

Jt.2425K366-1c.xX0rK-KQ*JQ^+KJQ

Demonstration , Testgeschirr

Dies funktioniert ähnlich wie bei der alten Version, außer dass der Wert (366 + n * (.2425 - 1)) nicht separat generiert und multipliziert wird, sondern zunächst eine Liste generiert wird, die von 366 bis 365 - n + 2 reicht. Ändert dann den vorhandenen 366 in (366 + n * (.2425 - 1)) und nimmt dann das Produkt der Liste. Außerdem werden die Konstanten 366 und -.7575 anstelle von 365 und .2425 verwendet.


Alte Version:

Pyth, 34 Bytes

J.2425K365-1c*+hK*QtJ.xrK-hKQ^+KJQ

Es gibt zwei Möglichkeiten, wie es kein Paar mit demselben Geburtstag geben kann: Jeder hat unterschiedliche Geburtstage und keiner hat am 29. Februar Geburtstag, und jeder hat am 29. Februar Geburtstag und alle anderen haben unterschiedliche Geburtstage Geburtstage an normalen Tagen.

Die Wahrscheinlichkeit des ersten Auftretens ist (365 * 364 * ... 365 - n + 1) / (365.2425 ^ n).

Die Wahrscheinlichkeit des zweiten Auftretens ist (365 * 364 * ... 365 - n + 2) * .2425 * n / (365.2425 ^ n)

Diese können zusammen geschrieben werden als (365 * 364 * ... 365 - n + 2) * (365 - n + 1 + .2425 * n) / (365.2425 ^ n) = (365 * 364 * ... 365 - n + 2) * (365 + 1 + (0,2425 - 1) * n) / (365,2425 ^ n).

Dies ist die Wahrscheinlichkeit, dass keine Paare vorhanden sind. Daher ist die Wahrscheinlichkeit, dass mindestens ein Paar vorhanden ist, eins minus der obigen Zahl.

J = .2425
K = 365
.xrK-hKQ = (365 * 364 * ... 365 - n + 2)
+hK*QtJ = (365 + 1 + n * (.2425 - 1))
^+KJQ = (365.2425 ^ n)
isaacg
quelle
5

Python, 179 178 144 143 140 136 135 133

f=.2425
g=365+f
a=lambda n:(n and(365-n)*a(n-1)or 365)/g
b=lambda n:(n<2and f or(367-n)*b(n-1)+a(n-2)*f)/g
p=lambda n:1-a(n-1)-b(n)

p(n)gibt das Ergebnis. Wechseln Sie .2425zu fractions.Fraction(97,400), um ein genaues Ergebnis zu erhalten.

orlp
quelle
Sie brauchen kein Leerzeichen zwischen 2und and.
Isaacg
Können Sie sich stattdessen nicht 1/dafür einsetzen gund durch sie teilen?
XNOR
@xnor Ja, im Laufe der Zeit gehen diese Dinge verloren :) Was einst eine Optimierung war, wird später suboptimal.
Orlp
Sie könnten e=365365 durch e und 367 durch e + 2 ersetzen
Willem
@willem Das ist nicht kürzer.
Orlp
2

Python 155 153 151 142 140 Bytes

d=146097
b=d/400
c=97/d
e=lambda n:n<2and 1-97/d or e(n-1)*(366-n)/b
f=lambda n:n<2and c or f(n-1)*(367-n)/b+e(n-1)*c
a=lambda n:1-e(n)-f(n)

Fordern Sie a(n)das Ergebnis an. Für genaue Ergebnisse deine Fraktion einwickeln .

Hier testen

Verwendet die gleiche Technik wie hier , jedoch mit modifizierten Konstanten.

Die Nummer eins
quelle
Sie brauchen kein Leerzeichen zwischen 2und and.
Isaacg
Ich dachte, es wäre 98 (obwohl ich vielleicht einen Rechenfehler gemacht habe ...)
Tim
1

80386 Maschinencode, 43 Bytes

Hexdump des Codes:

68 75 6e 33 3b 68 5a eb 07 3b 8b c4 49 d9 e8 d9
e8 d8 00 d9 e8 d9 40 04 de ea d8 c9 d9 00 de eb
e2 f3 d8 ca d9 e8 d8 e1 58 58 c3

Ich ging von folgender Formel aus (für die komplementäre Wahrscheinlichkeit):

\ prod \ limits_ {i = 0} ^ {k-2} (1- \ frac {97 + 400 * i} {d}) * (1- \ frac {303 * (k-1)} {d})

(hier d = 366 * 400 - 303ist die Anzahl der Tage in 400 Jahren)

Hier ist C ++ - Code, der es implementiert (es ist bereits ein wenig optimiert):

double it_opt(int k)
{
    double d = 366 * 400 - 303; // number of days in 400 years
    double result = 1; // probability of no coincidences
    const float const1 = float(400 / d);
    const float const2 = float(303 / d);
    double v1 = 1 + const2;
    double v2 = 1;

    for (int i = 0; i < k - 1; ++i)
    {
        v1 -= const1;
        result *= v1;
        v2 -= const2;
    }
    result *= v2;
    return 1 - result;
}

Der Code ist so angeordnet, dass nur zwei ( 400 / dund 303 / d) Konstanten erforderlich sind . Ich benutze den floatTyp, um sie darzustellen, weil er weniger Platz einnimmt (4 Bytes pro Konstante). Außerdem wollte ich nicht zu multiplizieren const2durch k - 1(denn das ist die Umwandlung erfordern würde k - 1zu float); der Code wird const2stattdessen wiederholt subtrahiert .

Hier ist die Liste der Assemblersprachen:

    ; // fastcall convention - parameter k is in ecx
    ; // result must be returned in st
    push dword ptr 0x3b336e75; // const1 = [esp + 4]
    push dword ptr 0x3b07eb5a; // const2 = [esp]
    mov eax, esp;              // use [eax] instead of [esp] - 1 byte less
    dec ecx;                   // calculate k - 1
    fld1;                      // initiaze result = 1
    fld1;                      // initialize v1
    fadd [eax];
    fld1;                      // initialilze v2
myloop:
    fld dword ptr [eax + 4];
    fsubp st(2), st;            // update v1
    fmul st, st(1);             // update result
    fld dword ptr [eax];
    fsubp st(3), st;            // update v2
    loop myloop;                // loop
    fmul st, st(2);             // update result by v2
    fld1;
    fsub st, st(1);             // complement result
    pop eax;                    // restore stack
    pop eax;                    // restore stack
    ret;                        // return
anatolyg
quelle