Διαδρομές
/
Factor
Factor
/
Ασκήσεις
/
Το καθολικό του βιβλιοθηκάριου
Το καθολικό του βιβλιοθηκάριου

Το καθολικό του βιβλιοθηκάριου

Άσκηση εκμάθησης

Εισαγωγή

Μερικές φορές θέλεις να συνδυάσεις μια ακολουθία σε μία μόνο τιμή· μερικές φορές θέλεις να δεις κάθε ενδιάμεση τιμή που παράγει ο συνδυασμός στην πορεία. Το Factor τα χωρίζει αυτά σε δύο εργαλεία: το reduce (στο sequences) για την αναδίπλωση σε μία τιμή, και τη σωρευτική οικογένεια στο math.statistics για τη μορφή με τα τρέχοντα αποτελέσματα.

reduce, η γενική αναδίπλωση

reduce ( seq init quot: ( prev elt -- next ) -- result )

Το reduce διατρέχει μια ακολουθία ένα στοιχείο τη φορά, κουβαλώντας μαζί του ένα τρέχον αποτέλεσμα (τον συσσωρευτή) και τροφοδοτώντας το σε μια παράθεση δύο ορισμάτων. Η παράθεση δέχεται τον τρέχοντα συσσωρευτή και το επόμενο στοιχείο· ό,τι αφήνει στη στοίβα γίνεται ο νέος συσσωρευτής.

USING: math sequences ;

{ 1 2 3 4 } 0 [ + ] reduce .         ! => 10
{ 1 2 3 4 } 1 [ * ] reduce .         ! => 24

Μια μη μηδενική αρχική τιμή και ένας προσαρμοσμένος συνδυαστής είναι τα σημεία του reduce που τα sum και product δεν μπορούν να καλύψουν. Για παράδειγμα, η μεγαλύτερη τιμή σε μια ακολουθία, με μια προεπιλεγμένη τιμή αν καμία τιμή δεν την ξεπερνά:

USING: math.order ;

{ 3 1 -4 5 -2 } 0 [ max ] reduce .   ! => 5
{ -3 -1 -4 }    0 [ max ] reduce .   ! => 0

Η αρχική τιμή 0 συμμετέχει στη σύγκριση: λειτουργεί ως το αποτέλεσμα όταν κάθε στοιχείο χάνει, ώστε μια ακολουθία με όλες τις τιμές αρνητικές να παράγει και πάλι 0 αντί για μια αυθαίρετη ελάχιστη τιμή.

Σωρευτικές αναγωγές

Μερικές φορές θέλεις κάθε ενδιάμεσο αποτέλεσμα, όχι μόνο το τελικό. Η σωρευτική οικογένεια στο math.statistics επιστρέφει μια ακολουθία ίδιου μήκους με την είσοδο, όπου κάθε θέση είναι η αναγωγή πάνω στο πρόθεμα που τελειώνει σε αυτή τη θέση:

cum-sum     ( seq -- newseq )    ! running total
cum-product ( seq -- newseq )    ! running product
cum-min     ( seq -- newseq )    ! running minimum
cum-max     ( seq -- newseq )    ! running maximum
USING: math.statistics ;

{ 3 1 4 1 5 9 2 6 } cum-sum .        ! => { 3 4 8 9 14 23 25 31 }
{ 1 2 3 4 } cum-product .            ! => { 1 2 6 24 }
{ 3 1 4 1 5 9 2 6 } cum-min .        ! => { 3 1 1 1 1 1 1 1 }
{ 3 1 4 1 5 9 2 6 } cum-max .        ! => { 3 3 4 4 5 9 9 9 }

Ένα χρήσιμο μοτίβο είναι οι αλυσιδωτές σωρευτικές αναγωγές: η έξοδος της μίας είναι και η ίδια μια ακολουθία, έτοιμη να τροφοδοτήσει μια άλλη. Έτσι, το "τρέχουσα σύνοψη μιας τρέχουσας σύνοψης" γίνεται εκφράσιμο με δύο λέξεις. Οι συνδυασμοί είναι ευέλικτοι: ταίριαξέ τους ανάλογα με το τι συνοψίζει κάθε βήμα.

produce, η ξεδίπλωση

Το reduce καταναλώνει μια ακολουθία σε μια τιμή. Το produce (στο sequences) πηγαίνει προς την άλλη κατεύθυνση και παράγει μια ακολουθία από μια αρχική τιμή, ελέγχοντας και βηματίζοντας επανειλημμένα:

produce ( pred quot -- seq )

Κάθε επανάληψη τρέχει πρώτα το pred στην τρέχουσα κατάσταση· αν επιστρέψει αληθή τιμή, καλείται το quot για να παράγει το επόμενο στοιχείο και να ενημερώσει την κατάσταση. Όταν το pred επιστρέψει f, η επανάληψη σταματά και επιστρέφονται τα στοιχεία που συλλέχθηκαν.

Ένα κλασικό παράδειγμα είναι η ακολουθία Fibonacci (κάθε αριθμός είναι το άθροισμα των δύο προηγούμενων). Η τρέχουσα κατάσταση είναι το ζεύγος (a, b). Κάθε βήμα εκπέμπει το b και μετά αντικαθιστά το ζεύγος με (b, a + b):

USING: kernel math sequences ;

! Fibonacci numbers strictly below 100:
0 1 [ dup 100 < ] [ tuck + over ] produce 2nip .
! => { 1 1 2 3 5 8 13 21 34 55 89 }

Η τρέχουσα κατάσταση απλώνεται σε δύο τιμές, οπότε το σώμα χρησιμοποιεί το tuck (στο kernel), δηλαδή την αναδιάταξη τριών στοιχείων που αντιγράφει την κορυφή κάτω από το δεύτερο, για να προχωρήσει το ζεύγος, και το 2nip (επίσης στο kernel, το ανάλογο του nip για δύο στοιχεία) τακτοποιεί τα πράγματα στο τέλος. Διαβάζοντας την κλήση από αριστερά προς τα δεξιά:

  • Το κατηγόρημα [ dup 100 < ] ρίχνει μια ματιά στην κορυφή του ζεύγους (τον επόμενο αριθμό που θα εκπεμφθεί) και συνεχίζει όσο είναι ακόμα κάτω από το όριο.
  • Το σώμα [ tuck + over ] προωθεί την κατάσταση σε (b, a + b) και εκπέμπει το b, αφήνοντας τρεις τιμές στη στοίβα: το νέο ζεύγος από κάτω, τον αριθμό που εκπέμφθηκε στην κορυφή.
  • Αφού το produce σταματήσει, οι δύο τελευταίες τιμές (το τελικό ζεύγος) απορρίπτονται με το 2nip, αφήνοντας μόνο την ακολουθία που παράχθηκε.

Το produce είναι ακριβώς το δυϊκό του reduce: όπου το reduce αναδιπλώνει μια ακολουθία σε μια τιμή, το produce ξεδιπλώνει μια τιμή σε μια ακολουθία.

Οδηγίες

Είσαι ο βιβλιοθηκάριος και τηρείς το καθολικό των λογαριασμών των χρηστών. Κάθε εβδομάδα, δύο είδη δουλειάς προσγειώνονται στο γραφείο σου:

  • Μια ουρά από αιτήματα: πιστώσεις που ζητά να εφαρμόσει ένας χρήστης (επιστροφές βιβλίων, εξοφλημένα πρόστιμα) και νέες χρεώσεις που έχει καταγράψει το σύστημα (νέα πρόστιμα καθυστέρησης που έχουν προκύψει). Ο λογαριασμός του χρήστη είναι προστατευμένος ως προς τις πιστώσεις: μια πίστωση αρκετά μεγάλη ώστε να ρίξει τον χρήστη στο κόκκινο εφαρμόζεται μόνο μέχρι το ποσό που οφείλεται, ώστε το τρέχον υπόλοιπο να μην πέφτει ποτέ κάτω από το μηδέν.
  • Μια λίστα από συναλλαγές: εγγραφές που έχουν ήδη καταχωριστεί στον λογαριασμό. Τα θετικά ποσά είναι χρεώσεις (νέα πρόστιμα), τα αρνητικά ποσά είναι πιστώσεις (πληρωμές).

Κάθε εβδομάδα κάνεις τον απολογισμό: ένα τελικό υπόλοιπο αφού ικανοποιήσεις τα αιτήματα, ένα ημερήσιο τρέχον υπόλοιπο από τις συναλλαγές, και ένα τρέχον χαμηλό σημείο για να επισημαίνεις διαστήματα όπου τα πρόστιμα εκτοξεύτηκαν.

1. Ικανοποίησε την ουρά των αιτημάτων

Όρισε τη protected-balance έτσι ώστε να δέχεται ένα αρχικό υπόλοιπο opening και έναν πίνακα από requests (προσημασμένα ποσά) και να επιστρέφει το τελικό υπόλοιπο αφού ικανοποιήσει κάθε αίτημα με τη σειρά. Μια ανάληψη που θα έριχνε το υπόλοιπο κάτω από το μηδέν ικανοποιείται μόνο μέχρι το διαθέσιμο ποσό, ώστε το τρέχον υπόλοιπο να έχει κατώτατο όριο το μηδέν.

100 { 50 -200 30 } protected-balance .
! => 30

500 { 100 -300 -250 } protected-balance .
! => 50

0 { -10 50 } protected-balance .
! => 50

2. Τρέχον υπόλοιπο

Όρισε τη running-balance έτσι ώστε να δέχεται έναν πίνακα από transactions και να επιστρέφει μια ακολουθία ίδιου μήκους, της οποίας το i-οστό στοιχείο είναι το υπόλοιπο μετά τις πρώτες i+1 συναλλαγές (σε σχέση με μηδενικό αρχικό υπόλοιπο).

{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }

3. Το μικρότερο υπόλοιπο μέχρι στιγμής

Όρισε τη least-balance-so-far έτσι ώστε να δέχεται έναν πίνακα από transactions και να επιστρέφει μια ακολουθία ίδιου μήκους, της οποίας το i-οστό στοιχείο είναι το χαμηλότερο τρέχον υπόλοιπο που έχει εμφανιστεί μέχρι και τη θέση i (συμπεριλαμβανομένης). Αυτό είναι το τρέχον χαμηλό σημείο, χρήσιμο για τον εντοπισμό ημερών όπου ο λογαριασμός φαινόταν επικίνδυνος.

{ 50 -30 -20 100 } least-balance-so-far .
! => { 50 20 0 0 }

{ 200 -50 -100 -200 } least-balance-so-far .
! => { 200 150 50 -150 }

4. Διαίρεσε στο μισό μέχρι τον στόχο

Η βιβλιοθήκη τρέχει ένα πρόγραμμα αμνηστίας προστίμων: το ανεξόφλητο υπόλοιπο ενός χρήστη υποδιπλασιάζεται σε κάθε περίοδο πληρωμής μέχρι να πέσει σε ένα όριο συγχώρεσης ή κάτω από αυτό. Όρισε τη halve-until έτσι ώστε να δέχεται ένα principal και ένα target και να επιστρέφει την ακολουθία των μισών τιμών (χρησιμοποιώντας ακέραια διαίρεση), ξεκινώντας από τον πρώτο υποδιπλασιασμό και συνεχίζοντας όσο η τρέχουσα τιμή είναι ακόμα αυστηρά πάνω από το target. Η τελευταία τιμή που παράγεται θα είναι η πρώτη που πέφτει στο target ή κάτω από αυτό.

100 5 halve-until .
! => { 50 25 12 6 3 }

64 1 halve-until .
! => { 32 16 8 4 2 1 }

3 5 halve-until .
! => { }
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Factor Exercism

Έτοιμος να ξεκινήσεις την άσκηση Το καθολικό του βιβλιοθηκάριου;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Factor με 47 έννοιες163 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.