Kalkulator Ekspansi Binomial

Sep 11 2020

Jadi saya menulis program dengan dokumentasi untuk mikropython kalkulator fx cg50 saya untuk menghitung berbagai item, masing-masing adalah:

  1. Entri Segitiga Pascal
  2. Tingkat Segitiga Pascal / Seluruh Baris
  3. Ekspansi Binomial
  4. Penemu Istilah Binomial
  5. Jumlah suku pertama
  6. Kombinasi

jika Anda melihat kode di bawah ini, Anda akan melihat, saya belum pernah menggunakan modul apa pun, dan menemukan kembali roda pada beberapa hal. Itu karena, bahasa python micropython dan pustaka standar sangat terbatas, jadi saya harus melakukannya.

Saya membutuhkan beberapa saran tentang pengoptimalan, dan pemadatan program saya, serta tip dan trik lain untuk meningkatkan cara menyelesaikan tugas.

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()
```

Jawaban

2 AJNeufeld Sep 13 2020 at 05:32

Tes Doc

Anda memiliki dokumen dalam kode Anda yang menggambarkan input fungsi dan output yang diharapkan. Mengapa tidak memformatnya menggunakan gaya doctestmodul?

def float_integer(num):
    """
    Returns an ...

    >>> float_integer(4.0)
    4

    >>> float_integer(3.5)
    3.5
    """

Micropython mungkin tidak memiliki doctestmodul (atau mungkin memang demikian, saya tidak tahu), tetapi Anda masih dapat menjalankan doctest pada file sumber yang sama dalam lingkungan Python lengkap untuk memeriksa kode dan dokumentasi berfungsi seperti yang diharapkan.

Anda Tidak Dapat Mengindeks Iterator

Menggunakan [interator[i:i+2] for i in range(...)]berarti variabel iteratorbukan iterator.

Sebuah iterator dibangun dari objek yang dapat diulang, seperti daftar. Sebuah iterator akan melintasi objek iterable tepat satu kali, dan kemudian tidak berguna, tetapi lebih dari satu iterator dapat dibuat dari objek iterable. Daftar secara langsung dapat diindeks, itulah yang Anda lakukan pada iteratorvariabel.

Namun, Python dapat menjadi sangat tidak efisien dalam pengindeksan, karena ia harus melakukan matematika dan membuat objek untuk hasil sementara seperti i+2pada setiap langkah dari loop; jauh lebih efisien menggunakan iterator.

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)]

Di sini, kami membuat dua iterator dari objek iterable yang diberikan. Ini adalah iterator adalah entitas independen. Mereka dapat dimajukan secara terpisah, dan memang kami memajukan iterator kedua satu posisi ke depan. zipmengambil kedua iterator, dan mengekstrak satu elemen dari masing-masing, hingga salah satu iterator kehabisan elemen.

Di atas mengembalikan type ( List[List[T]]) yang sama dengan fungsi Anda. Jika kami mengizinkan perubahan tipe pengembalian dari aslinya, fungsi tersebut dapat diubah untuk mengembalikan daftar tupel, menggunakan:

    return [(a, b) for a, b in zip(iter1, iter2)]

Atau dengan kata lain, lebih efisien tetapi mungkin sedikit lebih tidak jelas:

    return list(zip(iter1, iter2))

Terakhir, karena Anda memproses daftar yang dikembalikan dari separate_to_pairsmenggunakan for .. in ...loop, alih-alih mengembalikan daftar, kita dapat mengembalikan generator untuk pasangan, yang memberikan implementasi paling efisien:

    return zip(iter1, iter2)

Menghitung

binomial_expanddigunakan zip(range(1, len(coefficients)+1), coefficients)untuk mendapatkan pasangan dari masing-masing koefisien dan indeks satu basisnya.

Operasi ini dibangun di Python (dan mudah-mudahan micropython), dan dieja enumerate.

for r, coefficient in enumerate(coefficients, 1):

Argumen kedua sering diabaikan dan pencacahan dimulai dari nol, tetapi Anda dapat memulai pada nilai indeks apa pun yang Anda inginkan dengan memberikan nilai awal tersebut.

Karena dokumentasi micropython menyebutkan enumerate, tetapi implementasi Anda tampaknya tidak mendukungnya, mungkin Anda dapat mengimplementasikannya sendiri secara bersyarat:

if 'enumerate' not in dir(__builtins__):
    def enumerate(iterable, start=0):
        """Approximation of enumerate"""
        return zip(range(start, len(iterable) + start), iterable)

enumerateFungsi yang tepat tidak memerlukan panjang iterable diketahui sebelumnya; ini hanya perkiraan Anda, dengan sebuah startargumen. Ketika pembaruan untuk implementasi micro python ditambahkan enumerate, versi do-it-yourself secara otomatis akan dilewati.

Pemahaman Daftar

Mendeklarasikan daftar dan kemudian berulang kali memanggil appenddalam satu lingkaran seringkali lebih baik dilakukan dengan menggunakan pemahaman daftar. Dari pada:

        current_layer = []
        for pair in seperate_to_pairs(layer):
            current_layer.append(sum(pair))

menggunakan

        current_layer = [sum(pair) for pair in seperate_to_pairs(layer)]:

BASAH -vs- KERING

"WET" adalah singkatan dari "Write Everything Twice", dan "DRY" untuk "Don't Repeat Yourself". Anda ingin kode Anda menjadi "KERING" ...

Entri data & validasi input

Anda memiliki banyak kode duplikat seperti int(input("...")). Anda telah menentukan fungsi untuk memasukkan sepasang nilai float. Mengapa bukan fungsi untuk memasukkan bilangan bulat?

    @staticmethod
    def input_int(prompt):
        return int(input(prompt))

Sebagai bonus, Anda dapat menambahkan loop dengan try ... exceptpernyataan, dan tidak merusak program jika pengguna secara tidak sengaja memasukkan nilai non-integer. Setiap pemanggil metode ini akan mendapatkan validasi input, tanpa menduplikasi di mana-mana.

    @staticmethod
    def input_int(prompt):
        while True:
            try:
                return int(input(prompt))
            except ValueError:
                print("Invalid input - Please enter an integer")

Tidak bisa

Anda memiliki string prompt, yang mencantumkan semua fungsi dan huruf terkait, dan kamus, yang mencantumkan semua fungsi untuk dipanggil dan huruf terkait. Jika Anda melakukan perubahan, Anda harus melakukan perubahan di kedua tempat tersebut. Sangat mudah untuk membuat kesalahan dan melewatkan satu kesalahan.

Sebagai gantinya, pertimbangkan untuk membuat perintah secara otomatis dari kamus. Mungkin sesuatu seperti:

prompt = "\n".join(key + ": " + method.__name__.replace('_', ' ')
                   for key, method in self.choices.items())

PEP-0008 Pelanggaran

Panduan Gaya untuk Python memiliki banyak aturan untuk membantu membuat program Python diformat lebih konsisten dan dengan demikian lebih mudah dipahami orang lain. Aturan ini meliputi:

  • Satu spasi di sekitar operator biner ( n - r + 1, bukan n-r+1)
  • Fungsi & metode harus menggunakan huruf kecil snake_case. INPUT_a_bmelanggar ini.