Κυριακή 6 Ιανουαρίου 2019

Εξάλειψη στηλών του Τζόκερ

Θέλουμε να βρούμε ένα ποσοστό από τις στήλες που δίνουν ένα σύνολο αριθμών στο πρώτο πεδίο όπου διαλέγουμε 5 αριθμούς από 45 νούμερα. Αυτές οι λιγότερες στήλες θα προκύπτουν με τυχαία επιλογή.
Το πρόγραμμα έχει δυο τμήματα και τις κλήσεις τους για τα παραδείγματα. Το πρώτο τμήμα απλά τυπώνει το Πάτα ένα πλήκτρο και περιμένει να πατήσουμε ένα πλήκτρο με την Κομ$ (κομβίο!). Η τιμή πάει σε μια τοπική την α$ και μένει εκεί (οι τοπικές διαγράφονται μετά τη κλήση).

Το κύριο τμήμα  δέχεται ένα πίνακα σε μορφή tuple ή σύνολο στοιχείων με παρενθέσεις (το (,) είναι το κενό σύνολο, ενώ το (1,) είναι σύνολο που περιέχει ένα στοιχείο, ενώ το (1) δεν είναι πίνακας αλλά η τιμή 1 σε παρένθεση. Επίσης δέχεται έναν αριθμό τον οποίο τον καταχωρεί σε ακέραια μεταβλητή. Είμαστε δηλαδή σίγουροι ότι ο αριθμός δεν θα έχει δεκαδικά. Οι μεταβλητές με το % είναι ακέραιες αλλά εσωτερικά μπορεί να είναι οποιουδήποτε αριθμητικού τύπου, όπως ο διπλός, αλλά δεν κρατάει δεκαδικά. Η αποκοπή δεκαδικών γίνεται με το μισή μονάδα να δίνει τον αμέσως επόμενο μεγαλύτερο ακέραιο. Έτσι έχουμε το περίεργο το Α%=1/2 να δώσει πάλι 1, γιατί το 1/2 δίνει 0.5 άρα θα γίνει 1. Αν χρησιμοποιήσουμε το δια τότε το Α%=1 δια 2 θα δώσει το 0. (μπορούμε να γράψουμε Α%=1δια2 και θα τρέξει σωστά - χωρίς κενά- αλλά ο χρωματιστής στον διορθωτή τμημάτων δεν θα χρωματίσει σωστά το δια, θα το δει σαν δια2 ως μεταβλητή). Η αφαίρεση κενών μπορεί να γίνει μόνο στους τελεστές με γράμματα όπως και το Δια και εφόσον ακολουθεί αριθμός ή πρόσημο ή τελεία ως σύμβολο δεκαδικών.

Αρχικά κοιτάμε αν το μήκος του συνόλου είναι μικρότερο από 6 ή αν το ποσοστό% είναι εκτός ορίων και αν συμβαίνει κάτι από αυτά τότε τερματίζουμε το τμήμα, δεν υπάρχει λόγος να γίνει κάποια άλλη ενέργεια.

Ο τρόπος υπολογισμού των συνδυασμών είναι κάπως παράξενος! Βασίζεται στο γεγονός ότι μια λάμδα συνάρτηση μπορεί να έχει "κλείσιμο" (closure) κάποιες τιμές, που μπορεί να είναι επίσης λάμδα συναρτήσεις.

Ξεκινάμε με μια λ1, αφήνουμε ένα αντίγραφο στη λ, προχωράμε σε μια Ενώ όπου φτιάχνουμε διαδοχικά την λ1 με κλείσιμο την λ2 που παίρνει την λ, η οποία λ θα πάρει την τιμή λ1 και στο τέλος θα έχουμε μια λ1 που θα περιέχει ένα κλείσιμο μιας λ2 η οποία θα περιέχει άλλο κλείσιμο μιας άλλης λ2....

Στην ουσία κατασκευάζουμε όλες τις επαναλήψεις που θα χρειαστεί να κάνουμε αργότερα. Στο τέλος κατασκευάζουμε μια λάμδα με το λ (που είναι αντίγραφο του τελευταίου λ1) με κλείσιμο επίσης την α, το σύνολο.

Οι λ συναρτήσεις μέσα στην τελική δέχονται δυο παραμέτρους με το & στην αρχή που δηλώνει "με αναφορά". Η συνάρτηση Συνδυασμός() γυρνάει μια συνάρτηση λάμδα, την οποία γράφουμε ως ΣτήληΤζοκερ. Αυτή τώρα δέχεται ένα &κ δηλαδή το κ με αναφορά ώστε κάθε φορά που την χρησιμοποιούμε να έχουμε μια στήλη (ως σύνολο, ως πίνακας) και πιθανός την τιμή του κ να έχει αλλάξει, και αυτό δηλώνει ότι δεν υπάρχουν άλλοι συνδυασμοί.

Δείτε επίσης ότι στη σειρά των κλεισιμάτων λ2 έχουμε ως το τελευταίο στο βάθος την αρχική λ1η οποία δέχεται το &φ (που είναι το &κ) και το &α, το οποίο αν ήταν μόνη της (δεν είχε δηλαδή κλειστεί στο λ2) θα ήταν όλος ο πίνακας. Κάθε φορά που την καλούμε δίνει το πρώτο στοιχείο του πίνακα (ως σύνολο ενός στοιχείου) και κάνει το α να έχει τα επόμενα στοιχεία του συνόλου. Το φ γίνεται αληθές αν το α δεν έχει τίποτα, δηλαδή όταν το μήκος του είναι 0 (μήκος εδώ σημαίνει αριθμός στοιχείων του συνόλου).

Δείτε όμως το λ1 πόσα κλεισίματα έχει, εκτός από το λ2 βασικό κλείσιμο είναι το ζ που ξεκινάει ως κενό σύνολο. Αν το ζ είναι κενό, έχει δηλαδή μήκος=0, τότε το γεμίζουμε με τα επόμενα του μ. Η λ1 δέχεται δυο ορίσματα το &φ (που είναι το &κ και το πάει μέχρι το βάθος) και το &μ που θα μπορούσαμε να το έχουμε ως &α (αφού και το μ και το α θα ήταν τοπικές σε αυτή τη λάμδα). Δείτε εδώ τι γίνεται παρακάτω. Ξέρουμε ότι από το σημείο αυτό η ζ δεν θα είναι κενό σύνολο. Επίσης ξέρουμε ότι θα έχει τουλάχιστον δυο στοιχεία επειδή ξέρουμε ότι το τελευταίο λ μπορεί να έχει 1ή περισσότερα, αν και εδώ βάζουμε από 6 στοιχεία συνολικά, άρα το τελευταίο θα έχει τουλάχιστον 2, μέχρι να τα εξαντλήσει. Έτσι επιστρέφουμε τιμή της λ1 την ένωση του πρώτου(μ) με ό,τι δώσει το λ2(&φ, &ζ). Οι συναρτήσεις στην Μ2000 δεν τερματίζουν με την απόδοση τιμής επιστροφής. Οπότε πάμε παρακάτω που ελέγχουμε το φ. Αν αυτό δηλώνει πέρας, δηλαδή αληθές, τότε κάνουμε κενό το σύνολο ζ, το μ να δείχνει ένα σύνολο εκτός το πρώτο στοιχείο του,  και το φ υπολογίζεται ξανά με την (μήκος(μ)+μήκος(ζ))<ν. Δείτε εδώ ότι έχω βάλει παρενθέσεις, ενώ στο κώδικα δεν έχει. Το άθροισμα θα γίνει πριν την σύγκριση. Το ν το είχαμε ως κλείσιμο κατά την κατασκευή. Το πρώτο λ1 πάνω από το αρχικό λ1 θα έχει ν=2, το δεύτερο ν=3 κ.ο.κ.
Όταν παίξουν όλοι οι συνδυασμοί στο πιο πάνω λ1, το φ θα γίνει αληθές (θα έχει ξεκινήσει να είναι αληθές από το εσωτερικό πρώτο λ1, και θα έχει καθαρίσει όλα τα ζ στην πορεία, μέχρι την έξοδο).

Χρειαζόμαστε ένα Ενώ να κοιτάει το κ και σε κάθε εκτέλεση να το περνάει ως &κ (με αναφορά). Η ΣτήληΤζοκερ(&κ) γυρνάει ένα σύνολο (ένα πίνακα στην ουσία, με δείκτη όπως λέμε στη Μ2000), και η εντολή Σειρά το βάζει στο τέλος του σωρού, έτσι ώστε το τελευταίο που θα μπει να βγει τελευταίο, ή όπως το λέμε ανάποδα, το πρώτο που θα μπει θα βγει πρώτο (FIFO, first in first out). Ο σωρός τιμών με την Βάλε (Push) βάζει στη κορυφή (από εκεί που διαβάζουμε), ενώ με την Σειρά (Data) βάζει στο πυθμένα (αν είναι άδειος ο σωρός τότε η κορυφή και ο πυθμένας είναι το ίδιο για ένα στοιχείο με όποιο τρόπο και αν το βάλουμε).

Αμέσως μετά φτιάχνουμε έναν πίνακα με παρενθέσεις Τελικός() χωρίς στοιχεία. Η διαφορά αυτού του πίνακα με το Σύνολο (ή αυτόματο πίνακα) είναι ότι δέχεται αντίγραφα και όχι απλά αντιγραφή δείκτη. Έτσι το Α()=Β() αν τα Α() και Β() είναι πίνακες κάνουν το Α() να έχει ένα ρηχό αντίγραφο του Β(). Η έννοια του ρηχού σημαίνει ότι αν το Β() έχει στοιχεία πίνακες, τότε θα έχουμε αντιγραφή δείκτη και όχι αντίγραφο πίνακα. Ισχύει το ίδιο για αντικείμενα που έχουν δείκτη όπως τύπου Σωρού, Κατάστασης, Διάρθρωσης. Τα αντικείμενα Ομάδα, Λάμδα, Γεγονός θα περάσουν με αντιγραφή. Οι δείκτες σε Ομάδες θα περάσουν με αντιγραφή αλλά πάλι θα δείχνουν την ομάδα που έδειχναν.
Σε αυτόν τον πίνακα θα αντιγράψουμε τα στοιχεία του σωρού αφού τον αδειάσουμε σε ένα πρόσκαιρο πίνακα. Τώρα κάθε στοιχείο του Τελικός() θα είναι ένας πίνακας (η κάθε πεντάδα από τις στήλες που βρήκαμε από την επεξεργασία των συνδυασμών).
Θέλουμε όμως να λιγοστέψουν τα στοιχεία του, κατά ένα ποσοστό. Η συνάρτηση Οροφ() ή Ceil() δίνει ακέραιο και αν το όρισμα έχει δεκαδικά δίνει το αμέσως μεγαλύτερο αλλιώς γυρνάει τον ακέραιο ως έχει. Έτσι αν ζητάμε 30% θα πάρουμε το 30+ αν το 30% δίνει σε ακέραια τιμή λιγότερο από 30%.
Αυτό που θέλουμε είναι να πάρουμε έναν ακέραιο για τα στοιχεία που θα κόψουμε. Πριν κόψουμε κάποιο στοιχείο θα το δοκιμάσουμε αν θα μείνει στην θέση του με μια διαδικασία αλλαγής τιμών. Αυτή προκύπτει από το τυχαίο νούμερο από το 0 έως το μέγιστο μείον ένα. Επειδή ο πίνακας έχει από 0 έως μέγιστο-1 στην ουσία θέλουμε το μέγιστο-2, έτσι ξεκινάμε με ένα μ-- που μειώνει μια φορά το μέγιστο μ (δεν χρειάζεται άλλες φορές). Αφού λοιπόν κάνουμε όλες τις αλλαγές, μετά έρχεται η σειρά να πετάξουμε τις επιπλέον στήλες. Αυτό γίνεται με αλλαγή της διάστασης του πίνακας. Οι αλλαγές στην διάσταση δεν διαγράφουν ό,τι μπορεί να μείνει στο πίνακα (εκτός και αν δώσουμε  μια Πίνακας Α(10)=0 όπου κάνει όλα τα στοιχεία 0). Εξ ορισμού ένας πίνακας ξεκινάει με άδεια στοιχεία (τύπος "Empty") αν δεν δώσουμε αρχική τιμή για κάθε στοιχείο. Αν και οι πίνακες μπορούν να έχουν ονόματα με $ και % στο τέλος (αλφαριθμητικά και ακέραιας τιμής), σαν στοιχείο μπορούν να πάρουν οτιδήποτε. Τα σύμβολα στο όνομα μετράνε στις παραστάσεις ή εκφράσεις ή πράξεις.
Επειδή το μ είναι το μέγιστο μείον ένα ορίζουμε το πίνακα με τιμή βάσης το 0 και τελική το μ, άρα θα πάρουμε μ+1 στοιχεία.
Στο τέλος χρησιμοποιούμε ένα αντικείμενο απαρίθμησης στοιχείων, το λεγόμενο επαναλήπτης (iterator). Αυτό το αντικείμενο το χειρίζεται η Ενώ. Όταν δεν έχει άλλο στοιχείο να απαριθμήσει, τότε δίνει Ψευδές. Η συνάρτηση Πίνακας(μια_στήλη) βλέπει ότι έχει επαναλήπτη και διαλέγει άμεσα το στοιχείο που ζητάμε, που τυγχάνει να είναι πίνακας. Αν θέλαμε να διαβάσουμε ένα αλφαριθμητικό τότε θα χρησιμοποιούσαμε την Πίνακας$(μια_στήλη). Ο επαναλήπτης κρατάει ένα δείκτη στο πίνακα και έτσι τον χρησιμοποιεί για να γυρίσει τιμή βάσει του δρομέα του. Μπορούμε να έχουμε πολλαπλούς επαναλήπτες για ένα πίνακα, και άλλοι να διατρέχουν προς τα πάνω και άλλοι προς τα κάτω από το στοιχείο που θέλουμε στο στοιχείο που επίσης θέλουμε, όπου ως στοιχείο  δίνουμε τη θέση του. Αν ο πίνακας είναι δυο ή περισσοτέρων διαστάσεων (μέχρι 10 υποστηρίζει η Μ2000), και επίσης αν έχει διαφορετικές βάσεις σε κάθε διάσταση, δεν ενοχλεί τον επαναλήπτη, επειδή αυτός βλέπει πάντα το πίνακα ως μονοδιάστατο.
Ελπίζω να απολαύσατε την ανάλυση του προγράμματος!



Τμήμα ΠάταΠλήκτρο {
      Τύπωσε "Πάτα ένα πλήκτρο"
      α$=Κομ$
}
Τμήμα Τζόκερ (εισαγωγή_αριθμών ως πίνακας, ποσοστό%) {
      Αν Μήκος(εισαγωγή_αριθμών)<6 or ποσοστό%<0 or ποσοστό%>100 Τότε Έξοδος
      Σταθερή Κέντρο=2
      Συνάρτηση Συνδυασμός(α, νν) {
            λ1=Λάμδα (, ) ->{
                  =Πρώτο(α) : α=Επόμενα(α) : φ=Μήκος(α)=0
            }
            μ=Μήκος(α)
            λ=λ1
            ν=μ-νν+1
            π=2
            Ενώ μ>ν
                  λ1=Λάμδα λ2=λ,ν=π, ζ=(,) (, ) ->{
                        Αν Μήκος(ζ)=0 Τότε ζ=Επόμενα(μ)
                        =Ένωση(Πρώτο(μ),λ2(&φ, &ζ))
                        Αν φ Τότε ζ=(,) : μ=Επόμενα(μ) : φ=Μήκος(μ)+Μήκος(ζ)<ν
                   }
                  λ=λ1
                  π++
                  μ--
            Τέλος Ενώ
            =Λάμδα λ, α () -> {
                  =λ(&φ, &α)
            }
      }
      κ=Ψευδές
      ΣτήληΤζόκερ=Συνδυασμός(εισαγωγή_αριθμών, 5)
      Άδειασε ' αδείαζει το σωρό, για σιγουριά!
      Ενώ όχι κ
                 Σειρά ΣτήληΤζόκερ(&κ)
      Τέλος Ενώ
      Πίνακας Τελικός()
      Τελικός()=Πίνακας([])
      μ=Μήκος(Τελικός())
      μ0=μ
      Αναφορά Κέντρο, "Εξάλειψη στηλών του Τζόκερ"
      Τύπωσε @(4) ' αριστερή εσοχή για την Αναφορά
      Αναφορά "Κάθε στήλη έχει 5 αριθμιύς από 45, και έναν αριθμό τζόκερ από 1 έως 20."
      Αναφορά "Οι αριθμοί τζόκερ δεν προσμετρούνται εδώ, σαν να έχουμε έναν οποιδήποτε αριθμό από 1 έως 20"
      Τύπωσε ' επιστροφή του δρομέα στο 0 (αρχή)
      Τύπωσε Μέρος "Ζητάμε κάθε 5 αριθμούς από  ";Μήκος(εισαγωγή_αριθμών);" αριθμούς"
      Τύπωσε
      Τύπωσε εισαγωγή_αριθμών
      Τύπωσε Μέρος "Συνολικές στήλες:";μ
      Τύπωσε
      επι_τοις_εκατο_υπόλοιπο%=μ-Οροφ(μ*ποσοστό%/100) ' 10%
      μ--
      Ενώ επι_τοις_εκατο_υπόλοιπο% Και μ>2
             επι_τοις_εκατο_υπόλοιπο%--
             k1=Τυχαίος(0, μ-1)
             Άλλαξε Τελικός(k1), Τελικός(μ)
             μ--
       Τέλος Ενώ
       Πίνακας Τελικός(0 to μ)
       μ=Μήκος(Τελικός())
       Τύπωσε Μέρος "Διάλεξε τυχαία:";μ;" στήλες (";Int(μ/μ0*100);"%)"
       Τύπωσε
       μια_στήλη=Κάθε(Τελικός()) ' αντικείμενο απαρίθμησης   
       Ενώ μια_στήλη
             Τύπωσε Πίνακας(μια_στήλη)
       Τέλος Ενώ
}
Τζόκερ (3,10,23,26,32,34,35,42,45), 10
ΠάταΠλήκτρο
Τζόκερ (3,10,23,26,32,34,35), 30
ΠάταΠλήκτρο
Τζόκερ (3,8,10,14,23,26,32,34,35,42,45), 4



Σε αγγλικά (τα while είναι με  {}, αλλά μπορούμε να τα αλλάξουμε σε While/End While)


Module PressAKey {
      Print "Press a key"
      a$=key$
}
Module joker (inp as array, pcnt%) {
      if len(inp)<6 or pcnt%<0 or pcnt%>100 then Exit
      const center=2
      Function CombinationsStep (a, nn) {
            c1=lambda (&f, &a) ->{
                  =car(a) : a=cdr(a) : f=len(a)=0
            }
            m=len(a)
            c=c1
            n=m-nn+1
            p=2
            while m>n {
                  c1=lambda c2=c,n=p, z=(,) (&f, &m) ->{
                        if len(z)=0 then z=cdr(m)
                        =cons(car(m),c2(&f, &z))
                        if f then z=(,) : m=cdr(m) : f=len(m)+len(z)<n
                   }
                  c=c1
                  p++
                  m--
            }
            =lambda c, a (&f) -> {
                  =c(&f, &a)
            }
      }
      k=false
      StepA=CombinationsStep(inp, 5)
      flush ' empty stack
      while not k {
                 Data StepA(&k)
      }
      Dim Final()
      Final()=Array([])
      m=Len(Final())
      m0=m
      report center, "Jocker columns eliminator"
      Print @(4) ' identation for Report
      Report "Each column has 5 from 45 numbers, plus a jocker from 1 to 20."
      Report "Jocker number(s) not accumulated here, so we say it is a number only (any from 1 to 20)."
      Print ' restore identation
      Print Part "We ask any 5 numbers from these ";len(inp);" numbers"
      Print
      Print inp
      Print Part "Total columns:";m
      Print
      percent%=m-Ceil(m*pcnt%/100) ' 10%
      m--
      While percent% and m>2 {
       percent%--
       k1=random(0, m-1)
       swap Final(k1), Final(m)
       m--
}
Dim Final(0 to m)
m=Len(Final())
Print Part "Choosen randomly:";m;" columns (";Int(m/m0*100);"%)"
Print
ee=each(Final())
While ee {
      Print Array(ee)
}
}
joker (3,10,23,26,32,34,35,42,45), 10
PressAKey
joker (3,10,23,26,32,34,35), 30
PressAKey
joker (3,8,10,14,23,26,32,34,35,42,45), 4
PressAKey
















Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου

You can feel free to write any suggestion, or idea on the subject.