C'è una baggianata che gira ultimamente su WhatsApp relativamente a mesi nei quali avvengono coincidenze stranissime: 5 domeniche, 5 sabati, ecc.
L'evento viene citato come rarissimo, tanto che si verificherebbe solo 823 anni (o qualcosa di simile).
Ovviamente si tratta del solito spam mirato solo a collezionare informazioni e numeri di telefono.
Ma sarà vero?
Basta scrivere qualche riga Perl per sapere quali sono i mesi nei quali sono presenti un certo numero di ripetizioni dello stesso giorno della settimana.
Ecco quindi la mia soluzione:
L'idea è semplice: anzitutto si filtrano i mesi con 31 giorni, funzione svolta dall'operatore
Questi vengono usati per impostare il mese corrente, si seleziona il primo giorno e si mappa il
giorno della settimana e dei due successivi adiacenti (modulo 7, ovviamente per consentire il "giro"). Il tutto viene poi stampato.
Il trucco è che il ciclo è postfisso, quindi va letto da destra a sinistra; inoltre l'inizializzazione del mese corrente è in "and" logico alla stampa
del valore, fatto che consente di usare un solo ciclo.
Per andare avanti di un anno, beh, basta aggiungere un anno e reiterare.
L'output è semplice:
Questa soluzione si basa sul semplice fatto che tutti i mesi con 31 giorni hanno sempre 3 giorni consecutivi con ripetizione pari a 5.
Ma se non fosse noto ciò?
Beh, complicandosi la vita si può fare una soluzione general-purpose:
Il funzionamento è abbastanza semplice, anche se un po' compatto.
Anzitutto mi affido a DateTime che fornisce una interfaccia ad oggetti per la gestione delle date.
Si inizializzano una serie di variabili:
Da qui in poi si itera in un ciclo di 365 iterazioni per anno, salvando in un hash a piu' livelli il conteggio dei giorni della settimana di un mese.
L'hash assume una struttura dove al primo livello si trova l'anno corrente, poi il mese, poi il giorno della settimana, poi il conteggio di tale giorno.
Ad esempio una ipotetica iterazione potrebbe produrre:
La variabile
nel'hash globale.
Anzitutto si incrementa il contatore del giorno della settimana (già tradotto da numerico a mnemonico con
Segue un blocco
giorni della settimana superiore a quello cercato si procede alla stampa. Questo consente di valutare se stampare il mese corrente solo se si è
iterato su tutti i giorni del mese e solo se si sono trovate almeni
La parte di
E quindi si stampa il tutto, con risultato analogo a quello del caso precedente.
L'evento viene citato come rarissimo, tanto che si verificherebbe solo 823 anni (o qualcosa di simile).
Ovviamente si tratta del solito spam mirato solo a collezionare informazioni e numeri di telefono.
Ma sarà vero?
Basta scrivere qualche riga Perl per sapere quali sono i mesi nei quali sono presenti un certo numero di ripetizioni dello stesso giorno della settimana.
Ecco quindi la mia soluzione:
use v5.20; use DateTime; my $now = DateTime->now(); for ( 1..823 ){ $now->set( day => 1, month => $_ ) && say "Anno " . $now->year . " mese " . $now->month . " ha 5 " . join ', ', qw( Lun Mar Mer Gio Ven Sab Dom ) [ map { $_ , ( $_ + 1 ) % 7 , ( $_ + 2 ) % 7 } ($now->day_of_week - 1 ) ] for ( grep { ( $_ % 2 == 0 && $_ > 7 ) || ( $_ % 2 == 1 && $_ <= 7 ) } ( 1..12 ) ); $now->add( years => 1 ); }
L'idea è semplice: anzitutto si filtrano i mesi con 31 giorni, funzione svolta dall'operatore
grep
che estrae i numeri di mese 1,3,5,7,8,10,12.Questi vengono usati per impostare il mese corrente, si seleziona il primo giorno e si mappa il
day_of_week
in una lista di tre valori: la posizione del primogiorno della settimana e dei due successivi adiacenti (modulo 7, ovviamente per consentire il "giro"). Il tutto viene poi stampato.
Il trucco è che il ciclo è postfisso, quindi va letto da destra a sinistra; inoltre l'inizializzazione del mese corrente è in "and" logico alla stampa
del valore, fatto che consente di usare un solo ciclo.
Per andare avanti di un anno, beh, basta aggiungere un anno e reiterare.
L'output è semplice:
Anno 2838 mese 3 ha 5 Lun, Mar, Mer Anno 2838 mese 5 ha 5 Sab, Dom, Lun Anno 2838 mese 7 ha 5 Gio, Ven, Sab Anno 2838 mese 8 ha 5 Dom, Lun, Mar
Questa soluzione si basa sul semplice fatto che tutti i mesi con 31 giorni hanno sempre 3 giorni consecutivi con ripetizione pari a 5.
Ma se non fosse noto ciò?
Beh, complicandosi la vita si può fare una soluzione general-purpose:
use v5.10; use DateTime; my ( $count , $count_per_month, $years, $now, $dow, $ldom, $cons ) = ( 5, 3, 823, DateTime->now()->set( day => 1, month => 1 ), sub{ qw( Lun Mart Merc Giov Ven Sab Dom )[ $_[ 0 ] ] }, sub{ $_[ 0 ]->month != $_[ 0 ]->clone->add( days => 1 )->month }, sub{ $_[ 0 ] == $_[ 1 ] - 1 && $_[ 1 ] == $_[ 2 ] - 1 } ) ; for ( 1..( 365 * $years ) ) { my $me = \%{ $days_of->{ $now->year }->{ $now->month } }; ++ $me->{ $now->day_of_week - 1 }; say "Anno " . $now->year . " mese " . $now->month . " => " . join ', ', map { $me->{ $_ } . " " . $dow->( $_ ) } sort keys %$me if ( $ldom->( $now ) && ( grep { $_ >= $count } values %$me ) >= $count_per_month && $cons->( ( grep { $me->{ $_ } >= $count } sort keys %$me ) ) ); $now->add( days => 1 ); }
Il funzionamento è abbastanza semplice, anche se un po' compatto.
Anzitutto mi affido a DateTime che fornisce una interfaccia ad oggetti per la gestione delle date.
Si inizializzano una serie di variabili:
- $count rappresenta quante ripetizioni dello stesso giorno della settimana sto cercando (5);
- $countpermonth rappresenta quante ripetizioni di $count ci devono essere in un mese. Ad esempio impostandolo al valore 3 significa
cercare almeno 3 giorni in un mese che si ripetano 5 volte (ossia il mese deve avere, ad esempio, almeno 5 sabati, 5 domeniche e 5 lunedì); - $years su quanti anni da quello corrente si vuole iterare;
- $now la data corrente, impostata all'inizio dell'anno corrente;
- $dow è una routine di comodo che ritorna il nome simbolico del giorno della settimana (day of week);
- $ldom altra funzione di utilità che controlla se l'oggetto
DateTime
corrente rappresenta l'ultimo giorno della settimana. In particolare si controlla
se unDateTime
successivo di un giorno ha un mese differente; - $cons è una funzione che controlla che gli indici siano consecutivi fra loro (es 1,2,3), anche se ci vorrebbe la logica modulo 7 per
intercettare il giro di boa alla domenica…
Da qui in poi si itera in un ciclo di 365 iterazioni per anno, salvando in un hash a piu' livelli il conteggio dei giorni della settimana di un mese.
L'hash assume una struttura dove al primo livello si trova l'anno corrente, poi il mese, poi il giorno della settimana, poi il conteggio di tale giorno.
Ad esempio una ipotetica iterazione potrebbe produrre:
( 2017 => 1 => ( Lun => 3, Mart => 3, Mer => 4, Gio => 5, Ven => 3 Sab => 3, Dom => 3 );
$me
serve solo a tenere il codice un po' piu' compatto, ossia ad accedere alla parte dell'anno/mese correntenel'hash globale.
Anzitutto si incrementa il contatore del giorno della settimana (già tradotto da numerico a mnemonico con
$dow
).Segue un blocco
say..if
, quindi è opportuno partire prima dalla condizione if
: se il giorno è di fine mese e si ha un conteggio di valori di singoligiorni della settimana superiore a quello cercato si procede alla stampa. Questo consente di valutare se stampare il mese corrente solo se si è
iterato su tutti i giorni del mese e solo se si sono trovate almeni
$count_per_month
ripetizioni di giorni con conteggio di $count
.La parte di
say
include un map
preso un prestito da un idioma ben noto, quindi va letto da destra a sinistra:- si ordinano le chiavi (nomi mnemonici dei giorni della settimana) dell'hash del mese corrente;
- si crea una stringa con il conteggio di ripetizioni del giorno specificato e del suo nome mnemonico (parte
map
); - si effettua un join di tutte le stringhe così costruite.
E quindi si stampa il tutto, con risultato analogo a quello del caso precedente.
Nessun commento:
Posta un commento