Προγραμματιστική πρόκληση Νο 4: Ταχυδακτυλουργικά με τράπουλα

Πάμε ανάποδα αυτή την φορά. Ιδού ο κώδικάς

int main() {
  vector<int> cards(24); 
  iota(cards.begin(),cards.end(),0);
  mt19937 r(random_device{}()); 
  shuffle(cards.begin(),cards.end(),r);
  int U; cin>>U; 
  cout<<"cards["<<U<<"]: "; 
  U = cards[U]; cout<<U<<"\n";
  shuffle(cards.begin(),cards.end(),r);
  
  for (int i: {0,1,2}) {
    vector<int> decks[4];
    accumulate(cards.begin(),cards.end(),0,[&](int d, int c) {
      decks[d%4].insert(decks[d%4].begin(),c); return (d+1)%4; });

    int d = distance(decks, find_if(decks,decks+4,
      [&](auto &x) { return count(x.begin(),x.end(),U) > 0; }));
    
    cards.clear(); 
    for (int i: {d,d+1,d+2,d+3}) {
      move(decks[i%4].begin(),decks[i%4].end(),back_inserter(cards)); }
  }

  cout<<cards[4]<<" == "<<U<<"\n"; assert(cards[4] == U);
} 

Ξέρω δύσκολα θα βγάλεις νόημα από τον κώδικα. Εδώ ή πρόκληση είναι να καταλαβαίνεις μια γλώσσα που δεν ξέρεις και να εξαγάγεις τον αλγόριθμο.

Είναι ένα γνωστό ταχυδακτυλουργικό κόλπο. Διαλέγεις ένα χαρτί ο μάγος χωρίζει τα χαρτιά σε ομάδες εσύ λες κάθε φορά σε ποια ομάδα είναι, διαβάζει το μυαλό σου και σου λέει ποιο διάλεξες.

Μπορείς με μια τράπουλα να κάνεις το ταχυδακτυλουργικό στους φίλους σου; Μπορείς να γράψεις τον κώδικα σε μια άλλη γλώσσα;

Ωραίο τρικ αυτό διότι βασικά, σε αντίθεση με το τίτλο και όπως φαίνεται όταν το κάνεις με κανονική τράπουλα, δεν χρειάζεται κάποιο ταχυδακτυλουργικό. Το αποτέλεσμα προκύπτει μαθηματικά αν ακολουθηθούν τα βήματα. Κάποτε το είχα μάθει αλλά νομίζω με 27 χαρτιά και 3 στοιβάδες (και το σωστό χαρτί ήταν στη θέση 10+).

H σχολιασμένη μορφή του κώδικα (μαζί με τις βιβλιοθήκες που χρειάζεται να τρέξει) δίνεται παρακάτω.

#include <vector>
#include <algorithm>
#include <random>
#include <iostream>
#include <initializer_list>
#include <cassert>
using namespace std;

int main() {
  // τράπουλα με 24 κάρτες
  vector<int> cards(24); 
  iota(cards.begin(),cards.end(),0);
  mt19937 r(random_device{}()); 
  // ανακάτεμα της τράπουλας
  shuffle(cards.begin(),cards.end(),r);
  // επιλογή μιας κάρτας
  int U; cin>>U; 
  cout<<"cards["<<U<<"]: "; 
  // εμφάνιση της κάρτας
  U = cards[U]; cout<<U<<"\n";
  // πάλι ανακάτεμα
  shuffle(cards.begin(),cards.end(),r);
  
  // η διαδικασία επαναλαμβάνεται 3 φορές
  for (int i: {0,1,2}) {
    // δημιουργία 4 στοιβάδων
    vector<int> decks[4];
    accumulate(cards.begin(),cards.end(),0,[&](int d, int c) {
      // τοποθέτηση καρτών κυκλικά στις στοιβάδες
      // δηλ. 1 => 2 => 3 => 4 => 1 ...
      // η τοποθέτηση γίνεται πάνω της προηγούμενης
      // δηλ. 1 => 2, 1 => 3, 2, 1 ...
      decks[d%4].insert(decks[d%4].begin(),c); return (d+1)%4; });

    // εύρεση της στοιβάδας που περιέχει τη κάρτα
    int d = distance(decks, find_if(decks,decks+4,
      [&](auto &x) { return count(x.begin(),x.end(),U) > 0; }));
    
    cards.clear(); 
    // ξεκινώντας από τη στοίβα που έχει τη κάρτα οι υπόλοιπες
    // τοποθετούνται κυκλικά από κάτω
    for (int i: {d,d+1,d+2,d+3}) {
      move(decks[i%4].begin(),decks[i%4].end(),back_inserter(cards)); }
  }

  // η 5η κάρτα είναι η κάρτα που επιλέχθηκε
  cout<<cards[4]<<" == "<<U<<"\n"; assert(cards[4] == U);
} 

Και μια υλοποίηση σε Julia:

using Random

cards = collect(1:24)
shuffle!(cards)
I = parse(Int, readline())
U = cards[I]
println("cards[$I]: $U")
shuffle!(cards)

for _ = 1:3
    # χώρισε τη τράπουλα σε 4 στοιβάδες (αποτελούν σειρές ενός πίνακα)
    decks = reshape(cards, (4, 6))
    d = getindex(findfirst(x -> U in x, decks), 1)
    # πριν ενωθούν βάση αυτής που περιέχει τη κάρτα ανέστρεψε τη κάθε στοιβάδα
    cards = vcat(map(i -> reverse(decks[mod1(d + i, 4), :]), 0:3)...)
end

println("$(cards[5]) == $U")
@assert cards[5] == U
3 «Μου αρέσει»

Επιτέλους πήρε μόλις 3 χρόνια :slight_smile: