musifter

Untitled

Dec 22nd, 2019 (edited)
719
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 3.92 KB | None | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5.  
  6. use bignum;
  7.  
  8. use constant CARD_POS  => 2020;
  9.  
  10. #use constant DECK_SIZE => 10007;
  11. use constant DECK_SIZE => 119315717514047;
  12. use constant SHUFFLES  => 101741582076661;
  13.  
  14. # Linear function representing shuffle: f(x) = $func[0] * x + $func[1]
  15. my @func = (1, 0);
  16.  
  17. while (<>) {
  18.     if (m#^deal into new stack#) {
  19.         # compose (-x - 1) with Ax+B, mod N
  20.         # -(Ax+B) - 1 => -Ax + (-B - 1)
  21.         @func = (-$func[0] % DECK_SIZE, (-1 - $func[1]) % DECK_SIZE);
  22.  
  23.     } elsif (m#^cut (-?\d+)#) {
  24.         # compose (x - c) with Ax+B, mod N
  25.         # (Ax+B) - c => Ax + (B - c)
  26.         @func = ($func[0], ($func[1] - $1) % DECK_SIZE);
  27.  
  28.     } elsif (m#^deal with increment (\d+)#) {
  29.         # compose mx with Ax+B, mod N
  30.         # m(Ax+B) => (Am)x + Bm
  31.         @func = (($func[0] * $1) % DECK_SIZE, ($func[1] * $1) % DECK_SIZE);
  32.  
  33.     } else {
  34.         die "Unrecognized line: $_\n";
  35.     }
  36. }
  37.  
  38. print "f(x) = $func[0]x + $func[1]\n";
  39.  
  40. my $fx = ($func[0] * 2019 + $func[1]) % DECK_SIZE;
  41. print "Card 2019 is at position $fx\n";
  42.  
  43. #
  44. #   f(x) = y mod N, need to find f_inverse to get x = f_inv(y) mod N
  45. # Ax + B = y mod N
  46. #     Ax = (y - B) mod N
  47. #      x = A_inv * (y - B) mod N, A_inv is inverse of A in mod N
  48. #
  49. #my $y = (invmod( $func[0], DECK_SIZE ) * ($fx - $func[1])) % DECK_SIZE;
  50. my $y = (&prime_mod_inverse( $func[0], DECK_SIZE ) * ($fx - $func[1])) % DECK_SIZE;
  51. print "Card at position $fx is $y\n\n";
  52.  
  53. #
  54. # Recursive function to compose linear function n times.
  55. #
  56. sub apply_n_times {
  57.     my ($func, $n) = @_;
  58.  
  59.     return ($func->[0], $func->[1])  if ($n == 1);
  60.  
  61.     # divide and conqueur, get the function for half, h(x) = Ax + B
  62.     my ($A, $B) = &apply_n_times( $func, int( $n / 2 ) );
  63.  
  64.     #
  65.     # h(h(x)) = (A(Ax + B) + B
  66.     #         = A^2x + AB + B
  67.     #         = A'x + B'
  68.     #
  69.     ($A, $B) = (($A * $A) % DECK_SIZE, ($B * ($A + 1)) % DECK_SIZE);
  70.  
  71.     #
  72.     # Let f(x) = Cx + D
  73.     #
  74.     # h(h(x)) o f(x) = A'(Cx + D) + B'
  75.     #                = (A'C)x + A'D + B'
  76.     #
  77.     if ($n % 2 == 1) {
  78.         ($A, $B) = (($A * $func->[0]) % DECK_SIZE, $A * $func->[1] + $B);
  79.     }
  80.  
  81.     return ($A, $B);
  82. }
  83.  
  84. #
  85. # Calculate the inverse of a in mod n, where a and n are relatively prime,
  86. # so that this holds:
  87. #             a^n = a mod n
  88. #     a * a^(n-1) = a mod n
  89. #         a^(n-1) = 1 mod n
  90. #     a * a^(n-2) = 1 mod n, thus a^(n-2) is the inverse mod n of a
  91. #
  92. # We're using the above linear composer to do the exponentiation, as
  93. # composing (Ax + 0) with itself n times is (A^n)x.
  94. #
  95. sub prime_mod_inverse {
  96.     my ($a, $n) = @_;
  97.  
  98.     my @pow = &apply_n_times( [$a, 0], $n - 2 );
  99.     return ($pow[0]);
  100. }
  101.  
  102.  
  103. # 2423 is 101741582076661 % 10006, if testing with 10007 card deck, this
  104. # should be the position in the first cycle that's congruent, so it
  105. # should be equal to the big shuffle.
  106. foreach my $i (1 .. 8, 2423) {
  107.     my ($A, $B) = &apply_n_times( \@func, $i );
  108.  
  109.     #$y = (invmod( $A, DECK_SIZE ) * (CARD_POS - $B)) % DECK_SIZE;
  110.     $y = (&prime_mod_inverse( $A, DECK_SIZE ) * (CARD_POS - $B)) % DECK_SIZE;
  111.     print "Card at position ", CARD_POS, " after $i shuffles: $y\n";
  112. }
  113.  
  114. # Caluculating part 2:
  115. #
  116. #       Ax + B = 2020 mod n
  117. #           Ax = (2020 - B) mod n
  118. #            x = A^-1 * (2020 - B) mod n
  119. my ($A, $B) = &apply_n_times( \@func, SHUFFLES );
  120.  
  121. #$y = (invmod( $A, DECK_SIZE ) * (CARD_POS - $B)) % DECK_SIZE;
  122. $y = (&prime_mod_inverse( $A, DECK_SIZE ) * (CARD_POS - $B)) % DECK_SIZE;
  123. print "Card at position ", CARD_POS, " after ", SHUFFLES, " shuffles: $y\n";
  124.  
  125.  
  126. # Extra Challenge: Find the card that doesn't move.
  127. #       f(x) = x mod n
  128. #     Ax + B = x mod n
  129. #     x - Ax = B mod n
  130. #   x(1 - A) = B mod n
  131. #          x = B * mod_inv(1 - A) mod n
  132.  
  133. my $x_stable = ($B * &prime_mod_inverse( (1 - $A), DECK_SIZE )) % DECK_SIZE;
  134. $fx = ($func[0] * $x_stable + $func[1]) % DECK_SIZE;
  135.  
  136. print "Card at position $x_stable is always $fx.\n";
Advertisement
Add Comment
Please, Sign In to add comment