Calcolatore di espansione binomiale
Quindi ho scritto un programma con la documentazione per il micropython della mia calcolatrice fx cg50 per calcolare vari elementi, ognuno dei quali è:
- Pascal Triangle Entry
- Livello triangolo Pascal / intera riga
- Espansione binomiale
- Ricerca di termini binomiali
- Primo numero di termini
- Combinazioni
se guardi il codice qui sotto, noterai che non ho usato alcun modulo e ho reinventato la ruota su alcune cose. Questo perché il linguaggio Python e la libreria standard di micropython sono molto limitati, quindi ho dovuto accontentarmi.
Vorrei alcuni consigli sull'ottimizzazione e la compattazione del mio programma e altri suggerimenti e trucchi per migliorare il modo in cui viene eseguita un'attività.
def float_integer(num):
"""
returns an integer if the float given, is a whole number.
otherwise returns the same value as the argument num.
Ex:
4.0 ---> 4
3.5 ---> 3.5
"""
if num == int(num):
return int(num)
return num
def seperate_to_pairs(iterator):
"""
changes it so that each item in the list pairs with its neighbor items.
Ex:
[1, 2, 1] ---> [[1, 2], [2, 1]]
[1, 2, 3, 1] ---> [[1, 2], [2, 3], [3, 1]]
[1, 2, 3, 2, 1] ---> [[1, 2], [2, 3], [3, 2], [2, 1]]
"""
return [iterator[i:i+2] for i in range(0, len(iterator)-1)]
def factorial(n, endpoint=1):
"""
acquires the factorial of n
Ex:
5 ---> 120
"""
res = 1
for i in range(endpoint, n+1):
res *= i
return res
def combinations(n, r):
"""
nCr - combination or number of ways of picking r items from n
OR
nCr = n!/r!(n-r)!
Ex:
4C2 ---> 6
6C3 ---> 20
"""
return (factorial(n, n-r+1) // factorial(r))
def pascal_triangle_entry(nth, rth):
"""
acquires the entry in the pascal's triangle at the nth row and rth term
Ex:
4th row, 2nd term ---> 3
"""
return combinations(nth-1, rth-1)
def pascal_triangle_level(level):
"""
acquires an entire row in the pascal triangle designated by the level number, where 0 is [1], and 1 is [1, 1]
Ex:
5 ---> [1, 5, 10, 10, 5, 1]
6 ---> [1, 6, 15, 20, 15, 6, 1]
"""
if level == 0:
return [1]
layer = [1, 1]
for _ in range(level-1):
current_layer = []
for pair in seperate_to_pairs(layer):
current_layer.append(sum(pair))
layer = [1] + current_layer + [1]
return layer
def binomial_expand(a, b, n):
"""
(a + bx)^n = a^n + (nC1) a^(n-1) bx + (nC2) a^(n-2) (bx)^2 + ... + (nCr) a^(n-r) (bx)^r + ... + (bx)^n
Ex:
a = 3, b = 2, n = 4 # example values for (3 + 2x)^4
OUTPUT FORMAT:
[4C0] --> 81.0
(3.0)^4
...
[nCr] --> Term_Value
nCr_value (a)^(n-r) (b)^(r)
...
[4C4] --> 16.0
(2.0)^4
"""
terms = []
coefficients = pascal_triangle_level(n)[1:-1]
for r, coefficient in zip(range(1, len(coefficients)+1), coefficients):
term_value = binomial_term_finder(a, b, n, r, coefficient)
terms.append("[{5}C{4}] --> {6}\n{0} ({1})^({2}) ({3})^({4})".format(coefficient, a, n-r, b, r, n, term_value))
return "\n".join(["[{1}C0] --> {2}\n({0})^{1}".format(a, n, a**n)] + terms + ["[{1}C{1}] --> {2}\n({0})^{1}".format(b, n, b**n)])
def binomial_term_finder(a, b, n, r, coefficient=None):
"""
calculates the coefficient of the rth term in (a + bx)^n
if coefficient is given, it skips calculating it.
Ex:
a = 3, b = 2, n = 4, r = 2 # example values for (3 + 2x)^4
---> 216
"""
if coefficient:
return coefficient * a**(n - r) * b**r
return combinations(n, r) * a**(n - r) * b**r
def first_rth_terms(a, b, n, rth):
"""
calculates the coefficients of x for the first rth terms in (a + bx)^n
Ex:
a = 3, b = 2, n = 4, rth = 3 # example values for (3 + 2x)^4
---> [81, 216, 216]
"""
return [binomial_term_finder(a, b, n, r) for r in range(rth)]
class BIOS:
"""
responsible for input and output operations
Hence called BIOS - Basic Input and Output System
"""
prompt = "\n".join(["a: pascal tri. entry", "b: pascal tri. row", "c: binomial expand", "d: binomial term finder", "e: first rth terms", "f: combinations"])
def __init__(self):
self.running = True
self.choices = {'a': self.pascal_triangle_entry, 'b': self.pascal_triangle_level, 'c': self.binomial_expand, 'd': self.binomial_term_finder, 'e': self.first_rth_terms, 'f': self.combinations}
def stop_decorator(func):
"""
Decorator for stopping certain functions, after they're done by asking with a prompt
"""
def wrapper(self):
func(self)
command = input("Enter nothing to stop: ")
if command == '':
self.running = False
return wrapper
def INPUT_a_b(self):
"""
input a and b for (a + bx)^n, using only one line
"""
return float_integer(float(input("Enter a: "))), float_integer(float(input("Enter b: ")))
@stop_decorator
def pascal_triangle_entry(self):
nth = int(input("Enter row number(n): "))
rth = int(input("Enter entry number(r): "))
print(pascal_triangle_entry(nth, rth))
@stop_decorator
def pascal_triangle_level(self):
level = int(input("Enter level: "))
print(pascal_triangle_level(level))
def binomial_expand(self):
a, b = self.INPUT_a_b()
nth = int(input("Enter nth: "))
self.running = False
print(binomial_expand(a, b, nth))
@stop_decorator
def binomial_term_finder(self):
a, b = self.INPUT_a_b()
nth = int(input("Enter nth: "))
rth = int(input("Enter rth: "))
print(binomial_term_finder(a, b, nth, rth))
@stop_decorator
def first_rth_terms(self):
a, b = self.INPUT_a_b()
nth = int(input("Enter nth: "))
rth = int(input("Enter first num terms: "))
print("First {} terms:".format(rth))
print(first_rth_terms(a, b, nth, rth))
@stop_decorator
def combinations(self):
nth = int(input("Enter nth: "))
rth = int(input("Enter rth: "))
print(combinations(nth, rth))
def main(self):
"""
main program loop, uses a dictionary as an alternative for a switch case
"""
while self.running:
print(self.prompt)
self.choices.get(input(">> "), lambda: None)()
program = BIOS()
program.main()
```
Risposte
Doc Test
Hai docstrings nel tuo codice che illustra gli input della funzione e gli output previsti. Perché non formattarli utilizzando lo stile del doctestmodulo?
def float_integer(num):
"""
Returns an ...
>>> float_integer(4.0)
4
>>> float_integer(3.5)
3.5
"""
Micropython potrebbe non avere il doctestmodulo (o forse lo ha, non lo so), ma puoi comunque eseguire doctest sullo stesso file sorgente in un ambiente Python completo per controllare il codice e la documentazione funziona come previsto.
Non puoi indicizzare un iteratore
Usare [interator[i:i+2] for i in range(...)]significa che la variabile iteratornon è un iteratore.
Un iteratore è costruito da un oggetto iterabile, come un elenco. Un iteratore attraverserà un oggetto iterabile esattamente una volta, quindi è inutile, ma più di un iteratore può essere creato da un oggetto iterabile. Gli elenchi sono indicizzabili direttamente, che è ciò che stai facendo alla iteratorvariabile.
Tuttavia, Python può essere orribilmente inefficiente nell'indicizzazione, poiché deve fare matematica e creare oggetti per risultati temporanei come i+2in ogni fase del ciclo; è molto più efficiente usare gli iteratori.
def separate_to_pairs(iterable):
iter1 = iter(iterable) # create 1st iterator
iter2 = iter(iterable) # create a 2nd iterator
next(iter2) # advance 2nd iterator one position
return [[a, b] for a, b in zip(iter1, iter2)]
Qui creiamo due iteratori dall'oggetto iterabile fornito. Questi sono iteratori sono entità indipendenti. Possono essere avanzati separatamente, e infatti avanziamo di una posizione il secondo iteratore in avanti. zipaccetta entrambi gli iteratori ed estrae un elemento da ciascuno, finché uno degli iteratori non esaurisce gli elementi.
Quanto sopra restituisce lo stesso tipo ( List[List[T]]) restituito dalla funzione. Se permettiamo di cambiare il tipo di ritorno dall'originale, la funzione può essere convertita per restituire un elenco di tuple, usando:
return [(a, b) for a, b in zip(iter1, iter2)]
O equivalentemente, in modo più efficiente ma forse un po 'più opaco:
return list(zip(iter1, iter2))
Infine, poiché si elabora l'elenco restituito separate_to_pairsutilizzando un for .. in ...ciclo, invece di restituire un elenco, possiamo restituire un generatore per le coppie, che fornisce l'implementazione più efficiente:
return zip(iter1, iter2)
Enumerare
binomial_expandutilizza zip(range(1, len(coefficients)+1), coefficients)per ottenere gli accoppiamenti di ciascun coefficiente e del suo indice a base uno.
Questa operazione è incorporata in Python (e si spera in micropython) ed è scritta enumerate.
for r, coefficient in enumerate(coefficients, 1):
Il secondo argomento viene spesso omesso e l'enumerazione inizia da zero, ma è possibile iniziare da qualsiasi valore di indice desiderato fornendo quel valore iniziale.
Poiché la documentazione di micropython menziona enumerate, ma la tua implementazione non sembra supportarla, forse potresti implementarla in modo condizionale da solo:
if 'enumerate' not in dir(__builtins__):
def enumerate(iterable, start=0):
"""Approximation of enumerate"""
return zip(range(start, len(iterable) + start), iterable)
Una enumeratefunzione appropriata non richiede che la lunghezza dell'iterabile sia conosciuta in anticipo; questa è solo la tua approssimazione, con un startargomento. Quando viene aggiunto un aggiornamento all'implementazione di micro python enumerate, la versione fai-da-te dovrebbe essere automaticamente ignorata.
Comprensione delle liste
Dichiarare una lista e poi chiamare ripetutamente appendun ciclo è spesso fatto meglio usando la comprensione della lista. Invece di:
current_layer = []
for pair in seperate_to_pairs(layer):
current_layer.append(sum(pair))
uso
current_layer = [sum(pair) for pair in seperate_to_pairs(layer)]:
UMIDO -vs- SECCO
"WET" sta per "Write Everything Twice" e "DRY" per "Don't Repeat Yourself". Vuoi che il tuo codice sia "DRY" ...
Inserimento dati e convalida dell'input
Hai un sacco di codice duplicato come int(input("...")). Hai definito una funzione per inserire una coppia di valori float. Perché non una funzione per inserire un numero intero?
@staticmethod
def input_int(prompt):
return int(input(prompt))
Come bonus, potresti aggiungere un ciclo con try ... exceptun'istruzione e non bloccare il programma se l'utente immette accidentalmente un valore non intero. Ogni chiamante di questo metodo otterrebbe la convalida dell'input, senza duplicarlo ovunque.
@staticmethod
def input_int(prompt):
while True:
try:
return int(input(prompt))
except ValueError:
print("Invalid input - Please enter an integer")
Menù
Hai una stringa di prompt, che elenca tutte le funzioni e la lettera corrispondente, e un dizionario, che elenca tutte le funzioni da chiamare e la lettera corrispondente. Se apporti una modifica, devi effettuare la modifica in entrambi i punti. È facile sbagliare e perderne uno.
Considera invece la generazione automatica del prompt dal dizionario. Forse qualcosa come:
prompt = "\n".join(key + ": " + method.__name__.replace('_', ' ')
for key, method in self.choices.items())
Violazioni PEP-0008
La Guida allo stile per Python ha molte regole per aiutare a rendere i programmi Python formattati in modo più coerente e quindi più facili da capire per le altre persone. Queste regole includono:
- Uno spazio intorno agli operatori binari (
n - r + 1, notn-r+1) - Funzione e metodi dovrebbero essere in minuscolo
snake_case.INPUT_a_bviola questo.