di Francesco BalenaQuasi non ci speravo più, ma alla fine qualcuno è riuscito a risolvere il Quizzettone #9, ovvero come determinare una mano di poker in solo otto istruzioni. Anzi, il bravissimo Matteo Conta è riuscito a farlo in solo sette istruzioni. Graaaaande! Trovate la sua soluzione in coda ai vari commenti al post.
Come promesso, passo invece a illustrare la mia soluzione in otto istruzioni. La soluzione è in VB2005 (tratta dal nuovo libro Programming Microsoft Visual Basic 2005: The Language), ma potrebbe essere facilmente resa anche in C#:
Public Shared Function EvalPokerScore2(ByVal ParamArray cards() As String) As String ' Sort the array and create the sequence of values and of suits. Array.Sort(cards) Dim values As String = cards(0)(0) + cards(1)(0) + cards(2)(0) + cards(3)(0) + cards(4)(0) Dim suits As String = cards(0)(1) + cards(1)(1) + cards(2)(1) + cards(3)(1) + cards(4)(1)
Dim scores(,) As String = { _ {"12345|23456|34567|45678|56789|6789T|789JT|89JQT|9JKQT|1JKQT", "(.)\1\1\1\1", "StraightFlush"}, _ {"(.)\1\1\1", ".", "FourOfAKind"}, _ {"(.)\1\1(.)\2|(.)\3(.)\4\4", ".", "FullHouse"}, _ {".", "(.)\1\1\1\1", "Flush"}, _ {"12345|23456|34567|45678|56789|6789T|789JT|89JQT|9JKQT|1JKQT", ".", "Straight"}, _ {"(.)\1\1", ".", "ThreeOfAKind"}, _ {"(.)\1.?(.)\2", ".", "TwoPairs"}, _ {"(.)\1", ".", "OnePair"}} For i As Integer = 0 To scores.GetUpperBound(0) If Regex.IsMatch(values, scores(i, 0)) AndAlso Regex.IsMatch(suits, scores(i, 1)) Then Return scores(i, 2) End If Next Return "HighCard" End Function
(Nel computo delle istruzioni, non si conta nè End If nè Next, secondo le regole enunciate nel quiz...)
Per prima cosa il programma crea due stringhe, values contentente la sequenza dei valori delle carte, suits contenente la sequenza dei semi, poi definisce il vettore bidimensionale scores con le informazioni per cercare i vari punteggi. Ogni riga del vettore scores contiene due regular expression - una che viene applicata alla stringa values, l'altra alla stringa suits. Se entrambe le regex sono soddisfatte, il programma restituisce il terzo elemento della riga, ovvero il nome della combinazione. Notate che la seconda regex (quella che testa i colori) viene usata solo in due casi, per StraightFlush (scala reale) e Flush (colore), quindi in tutti gli altri casi basta usare la regex "." (qualsiasi carattere).
La maggior parte delle regex che testano i valori usano le cosiddette "backreference", ad es. "(.)\1\1" significa "cerca un carattere qualsiasi (il punto) poi ricercalo altre due volte (\1\1): il risultato della ricerca è positivo se vi sono tre carte uguali una dopo l'altra. Notare che l'array cards è ordinato rispetto al valore delle carte, quindi abbiamo la sicurezza che in values le carte con lo stesso valore sono adiacenti.
Proprio perche la stringa values è ordinata per valori, le regex da usare per testare le scale (StraightFlush e Straight) contengono dei caratteri apparentemente fuori ordine. Ad esempio, la scala massima si testa con la regex "1JKQT" e non con sequenza più intuitiva (ma errata), "TJQK1".
Concludendo, anche se la soluzione di Matteo è a dir poco sbalorditiva e assolutamente geniale, direi che quella basata su regex è decisamente più leggibile e manutenibile.
NOTA: Questa soluzione è in 8 istruzioni, ma ho spiegato che esiste anche la possibilità di scriverla in sole sette istruzioni. Questo si ottiene evitando di dividere le carte in due stringhe distinte (values e suits), ed usando invece una sola stringa data dalla concatenazione delle coppie (valore,seme). Anche in questo caso è possibile scrivere delle regex che trovano correttamaente tutte le combinazioni, ma le regex stesse sono molto più complesse, quindi ho deciso che nel libro avrei pubblicato questa versione più lunga ma più leggibile.
In realtà è persino possibile modificare il precedente codice per ottenere una soluzione in sole sei istruzioni: infatti, invece di creare le stringhe values e suits fuori dal ciclo, potrei crearle al volo ad ogni iterazione del ciclo For..Next. Non solo, ma se aggiungo una ulteriore riga alla matrice scores, contenente i sequenti valori:
{".", ".", "HighCard"}}
Allora posso evitare anche l'istruzione Return finale, portando il totale a sole cinque istruzioni, anche se in quest'ultimo caso devo tenermi il warning che VB emette perchè non tutti i possibili percorsi della funzione restituiscono un valore. Insomma, se davvero volete scrivere codice illeggibile, non c'è che l'imbarazzo della scelta 
Ultimissima nota: un pubblico ringraziamento e una pubblica lode anche a Paolo Possanzini, per essere stato il primo ad accanirsi e a risolvere, qualche mese fa mentre scrivevo il libro, questo quiz. Onore al merito!
di Francesco BalenaFinalmente il mio ultimo librone è fisicamente disponibile su Amazon, e in un solo giorno è risalito di 8 mila posizioni in classifica: al momento in cui scrivo è alla posizione 1171, che è un ottimo piazzamento. Vi ricordo che sono disponibili altre informazioni sul libro, incluso un paio di capitoli di esempio in PDF, sulla home page che gestisco sul sito americano di dot-net-2-the-max.
Speriamo che anche l'analogo libro su C# e le classi base del Framework, di cui in questi giorni sto completando la revisione, abbia eguale fortuna...
Nel frattempo, il mio ultimo quizzettone è ancora insoluto, cosa insolita (ma non troppo, visto che questa volta era davvero tosto). Magari un aiutino ve lo posso dare: la mia soluzione in otto istruzioni si basa sulle regular expression e appare proprio nel capitolo sulle regex del libro in questione.
di Francesco BalenaCi sono quizzettini relativamente semplici (come quelli che ho pubblicati negli ultimi mesi) e altre sfide logiche che richiedono un po' più di qualche secondo per essere risolti. Quello di oggi appartiene a questa seconda categoria, e merita quindi in pieno l'appellativo di QUIZZETTONE.
Il quesito è relativamente semplice: occorre scrivere un metodo EvalPokerScore in grado di determinare a quale punto del poker corrisponde una mano di cinque carte. Ciascuna carta è rappresentata da una stringa di due lettere, la prima delle quali rappresenta il valore della carta, a scelta tra le seguenti 1, 2, 3, 4, 5, 6, 7, 8, 9, T (ten=dieci), J (jack), Q (queen), K (king) la seconda lettera rappresenta ovviamente il seme, ed è una delle seguenti H (hearts=cuori), D (diamonds=quadri), S (spades=picche), C (clubs=fiori)
Il metodo EvalPokerScore deve restituire una stringa che indica il punto formato dalle cinque carte passate come argomento. Il risultato deve essere una delle stringhe seguenti "StraightFlush" (scala reale), "FourOfAKind" (poker), "FullHouse" (full), "Flush" (colore), "Straight" (scala), "ThreeOfAKind" (tris), "TwoPairs" (doppia coppia), "OnePair" (coppia), "HighCard" (nessun punto=carta più alta)
Notate che l'ordine dei punti è differente da quello che vi potreste aspettare: giocando al poker americano con tutte le 52 carte del mazzo, la probabilità e quindi il valore relativo di alcune combinazioni è differente da quello del poker come lo giochiamo dalle nostre parti. Ad esempio, il full batte sempre il colore. Ecco una lista di chiamate al metodo e il risultato atteso da ciascuna:
// These calls return "StraightFlush"
EvalPokerScore("1H", "2H", "3H", "4H", "5H"); EvalPokerScore("3H", "4H", "5H", "6H", "7H"); EvalPokerScore("6C", "7C", "8C", "9C", "TC"); EvalPokerScore("7S", "8S", "9S", "TS", "JS"); EvalPokerScore("8S", "9S", "TS", "JS", "QS"); EvalPokerScore("TD", "JD", "QD", "KD", "1D"); // These calls return "Flush" EvalPokerScore("8S", "JS", "QS", "KS", "1S"); EvalPokerScore("7H", "8H", "9H", "TH", "KH"); EvalPokerScore("8C", "9C", "TC", "QC", "KC"); // These calls return "FourOfAKind" EvalPokerScore("8C", "9D", "8D", "8H", "8S"); EvalPokerScore("TS", "TC", "QD", "TD", "TH"); EvalPokerScore("1C", "1S", "8S", "1H", "1D"); // These calls return "FullHouse" EvalPokerScore("1C", "1D", "8C", "1D", "8S"); EvalPokerScore("9C", "9S", "8D", "8S", "9H"); EvalPokerScore("QC", "TD", "QD", "TS", "QS"); // These calls return "Straight" EvalPokerScore("3S", "7S", "5S", "4S", "6D"); EvalPokerScore("6D", "7S", "8H", "9H", "TD"); EvalPokerScore("7S", "8D", "9D", "TH", "JC"); EvalPokerScore("TC", "JS", "QS", "KS", "1S"); // These calls return "ThreeOfAKind" EvalPokerScore("9C", "9S", "8H", "TD", "9D"); EvalPokerScore("TC", "TS", "7S", "TH", "1D"); EvalPokerScore("TC", "TS", "8S", "TH", "KH"); // These calls return "TwoPairs" EvalPokerScore("1C", "1F", "8F", "DP", "8Q"); EvalPokerScore("9C", "KF", "8F", "8P", "9Q"); EvalPokerScore("1C", "DF", "QF", "DP", "QQ"); // These calls return "OnePair" EvalPokerScore("1C", "1H", "KH", "TS", "8S"); EvalPokerScore("9C", "KH", "QH", "8S", "9D"); EvalPokerScore("1S", "TS", "QC", "8C", "QC"); // These calls return "HighCard" EvalPokerScore("1C", "QC", "KC", "TD", "8D"); EvalPokerScore("TC", "KC", "QD", "8D", "9H"); EvalPokerScore("1C", "TC", "KD", "8D", "QH"); EvalPokerScore("8D", "QD", "TD", "9D", "KH");
Scrivere il metodo EvalPokerScore non è proprio banale, ma neanche difficilissimo. Se siete in gamba potete risolverlo in una decina di minuti, se siete molto in gamba in cinque o meno. Ma adesso viene il bello: l'obiettivo del quizzettone non è solo di riuscire a risolvere il problema, ma di farlo con il minor numero di istruzioni possibile. Quindi
Riuscite a scrivere il metodo EvalPokerScore con non più di otto istruzioni?
Nel conto delle istruzioni non è contaggiata la dichiarazione del metodo stesso. In altre parole:
// C# public string EvalPokerScore( params string[] cards ) { // Non più di otto istruzioni qui }
' VB Public EvalPokerScore( ParamArray cards() As String ) As String ' Non più di otto istruzioni qui End Function
Nel conteggio delle istruzioni, una istruzione corrisponde ad una assegnazione di variabile, chiamata a metodo e in generale a una sequenza di keyword che in C# sarebbe terminata con un punto-e-virgola e in VB con un newline oppure con il due-punti. Un blocco for, do, foreach, if corrisponde a una istruzione più quelle contenute nel blocco (questa precisazione è necessaria per non fare differenze tra C# e VB, poichè quest'ultimo richiede una keyword Next o EndIf per chiudere il blocco). Le graffe di apertura e chiusura blocchi in C# non sono conteggiate. Se si usa un anonymous delegates, occorre contare tutte le istruzioni nel metodo anonimo.
Se avete altre domande, lasciate pure un commento qui. Potete anche mandarmi le vostre soluzioni via email, se preferite, visto che potrebbe essere complicato formattarle a dovere (alcune istruzioni saranno necessariamente lunghe....)
|
Come ho anticipato, la soluzione non è proprio immediata nè semplice, però rappresenta quella che io chiamo "programmazione elegante". L'idea di questo quizzettone è tratta dal mio nuovo libro Programming Microsoft Visual Basic 2005: The Language, dove ovviamente mostro la soluzione e la spiego nei dettagli.
Il tema del quizzettone è un ottimo esempio del codice a corredo del libro e esemplifica il concetto alla base del libro stesso, ovvero che conoscere la sintassi del linguaggio e delle classi base di .NET Framework è una cosa, sapere applicare queste nozioni è una cosa completamente differente, ed è questo che distingue uno sviluppatore davvero in gamba dagli altri.
Come al solito, nessun premio in vile denaro al primo risolutore, ma solo imperitura fama sulle pagine di questo blog. 
NOTA: se otto istruzioni vi sembrano troppo poche, sappiate che è persino possibile farlo con sette, però a costo di complicare la struttura del codice. Meglio quindi una istruzione in più, ma mantenere l'eleganza del codice...
|
di Francesco BalenaIncoraggiato dalle buone risposte al mio ultimo quizzettino sulla ottimizzazione, ne ho preparato un'altro sullo stesso tenore, anzi abbastanza simile, anche se credo sia abbastanza più semplice del precedente. Ma potro' dirlo solo dopo aver visto in quanto tempo lo risolverete (perchè oramai sono sicuro che lo risolverete, e presto!)
Supponiamo di avere un vettore bidimensionale di interi, e di voler calcolare il massimo della somma dei numeri in ciascuna colonna. Ecco il programma C# che risolve il problema
const int ROWS = 4000; const int COLS = 4000; int[,] arr = new int[ROWS, COLS]; // fill the array with random data (or read it from file, etc.) Random rnd = new Random(1234); for (int r = 0; r < ROWS; r++) for (int c = 0; c < COLS; c++) arr[r, c] = rnd.Next(1, 10000); // eval the max of the sum of all elements in each given column Stopwatch sw = Stopwatch.StartNew(); int sumMax = int.MinValue ; for (int c = 0; c < COLS; c++) { // eval the sum of elements in this column int colsum = arr[0,c]; for (int r = 1; r < ROWS; r++) colsum += arr[r, c]; // update the summax value if ( sumMax < colsum ) sumMax = colsum; } Console.WriteLine(sw.ElapsedMilliseconds); Console.WriteLine("Max is {0}", sumMax); // Max is 20576003
Ed ecco la versione VB
Const ROWS As Integer = 4000 Const COLS As Integer = 4000 Dim arr(ROWS - 1, COLS - 1) As Integer ' fill the array with random data (or read it from file, etc.) Dim rnd As New Random(1234) For r As Integer = 0 To ROWS - 1 For c As Integer = 0 To COLS - 1 arr(r, c) = rnd.Next(1, 10000) Next Next ' eval the max of the sum of all elements in each given column Dim sw As Stopwatch = Stopwatch.StartNew() Dim sumMax As Integer = Integer.MinValue For c As Integer = 0 To COLS - 1 ' eval the sum of elements in this column Dim colsum As Integer = arr(0, c) For r As Integer = 1 To ROWS - 1 colsum += arr(r, c) Next ' update the summax value If sumMax < colsum Then sumMax = colsum Next Console.WriteLine(sw.ElapsedMilliseconds) Console.WriteLine("Max is {0}", sumMax) ' Max is 20576003
La domanda è la solita: come possiamo ottimizzare questo codice in misura significativa?
Come ho detto, il quesito è abbastanza semplice e mi aspetto una soluzione in poche ore...
di Francesco BalenaAnche oggi un quizzettino davvero facile, anzi banale, tanto per scaldarci i neuroni in vista della WPC. Considerate questo codice VB2003 che converte gli elementi di un vettore di interi in un array di stringhe contentente la rappresentazione esadecimale di ciascun elemento
Dim intArray() As Integer = { 4, 6, 9, 10, 99, 233, 34, 88, 189} ' ecc ecc. Dim hexArray(intArray.Length - 1) As String For i As Integer = 0 To intArray.Length - 1 hexArray(i) = intArray(i).ToString("X") Next
La domanda è: qual'è il numero minimo di istruzioni C# 2.0 o VB2005 che occorre scrivere per ottenere lo stesso risultato?
Nota per i C# 2.0: le istruzioni negli anonymous method vanno contate a parte. Quindi, una istruzione che usa un anonymous method che contiene uno statement conta come due istruzioni.
di Enrico SabbadinTelefona una signora in ufficio e dice: nel programma c'è una cosa che non funziona, il punto che c'è nel tastierino numerico che uso per inserire gli importi delle fatture non si comporta come un "separatore di decimali ma di migliaia" (non dice così evidentemente ma è per farvi capire il concetto con poche parole). Beh, dico io, è chiaro, in italiano il punto del tastierino numerico è il separatore delle migliaia .. se vuole può modificare le impostazioni del programma (che non è windows regional settings aware) affinchè consideri il punto come separatore dei decimali (e la virgola delle migliaia) .. no dice la signora .. se faccio così poi le cifre mi sono mostrate con il punto come separatore dei decimali .. io voglio che le cifre siano mostrate come sono adesso .. quel che voglio è che il tasto sul tastierino numerico dove c'è il punto in realtà "scriva" virgola..
cavoli penso io :) allora non è così facile .. considerando che non ho accesso al codice del programma .. voi come suggerite di fare ? Io una soluzione che mi pare piuttosto efficace l'ho trovata , ma non sono sicuro che sia quella migliore. Qualche suggerimento ? .. a breve la mia soluzione.
di Francesco BalenaSto revisionando il 3° capitolo sul controllo del flusso, e ho pensato di mettere in forma di quiz un piccolo tip che spiego nel libro. Considerate il seguente codice:
For i As Integer = 1 To 10 Dim exiting As Boolean = False For j As Integer = 1 To 20 ' If the Evaluate function returns zero you want to exit both loops If Evaluate(i, j) = 0 Then exiting = True Exit For End If Next If exiting Then Exit For Next
Non è importante sapere cosa fa la funzione Evaluate, ma solo che se questa funzione restituisce zero allora dovete uscire immediatamente da entrambi i cicli. Il ciclo di cui sopra non è molto ottimizzato, perchè deve testare in continuazione la variabile exiting. Si potrebbe ottimizzare il codice usando una istruzione Goto che punta a una etichetta che si trova dopo il secondo Next, ma sappiamo bene che le Goto sono per gli smanettoni, non per i programmatori seri come noi. Allora, la domanda del quiz è semplice:
E' possibile ottimizzare questo codice eliminando la variabile "exiting" ed uscendo dal ciclo più interno senza usare una istruzione Goto?
La risposta è semplice, quindi mi aspetto una soluzione in tempi stretti...
di Enrico SabbadinPensate di sapere tutto su cosa significa ByVal e ByRref, di come queste keyword interagiscono con i value type e i reference type?
Bene allora considerate questo metodo definito nella classe "pippo":
public void ChangeName(Person p) { p.Name= "Enrico";}
Il chiamante attiva la classe che contiene questo metodo e chiama il metodo ChangeName:
Person p = new Person(); p.Name = "Francesco"; pippo x = new pippo(); x.ChangeName(p); Console.WriteLine(p.Name);
Cosa verrà scritto sulla console? Sappiate che la proprietà Name di Person è implementata in maniera "standard", e la risposta non cambia se Name è un field pubblico.
La risposta alla domanda è: "dipende"... da cosa?
|
|
Feed di Blog2theMax
Cerca nel blog
Archivio
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 28 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Categorie
Powered by: newtelligence dasBlog 1.7.5016.2
|