admin 1Comment

Dzisiejsze zadanie pochodzi z rozdziału szóstego kursu Automate the Boring Stuff with Python. Jeśli nie znasz kursu lub nie czytałeś tekstu wyjaśniającego pomysł na serię wpisów, powinieneś zrobić to tutaj. Oto tekst zadania:

Table Printer

Write a function named printTable() that takes a list of lists of strings and displays it in a well organized table with each column right-justified. Assume that all the inner lists will contain the same number of strings. For example, the value could look like this:

tableData = [[‚apples’, ‚oranges’, ‚cherries’, ‚banana’],

[‚Alice’, ‚Bob’, ‚Carol’, ‚David’],

[‚dogs’, ‚cats’, ‚moose’, ‚goose’]]

Your printTable() function would print the following:

apples     Alice    dogs

oranges   Bob      cats

cherries  Carol   moose

banana    David   goose

Hint: Your code will first have to find the longest string in each of the inner lists so that the whole column can be wide enough to fit all the strings. You can store the maximum width of each column as a list of integers. The printTable() function can begin with colWidths = [0] * len(tableData), which will create a list containing the same number of 0 values as the number of inner lists in tableData. That way, colWidths[0] can store the width of the longest string in tableData[0], colWidths[1] can store the width of the longest string in tableData[1], and so on. You can then find the largest value in the colWidths list to find out what integer width to pass to the rjust() string method.

Mamy więc zadanie, w którym musimy skupić się na operowaniu zagnieżdżonymi listami. Oczywiście będziemy to wykonywać za pomocą iteracji po elementach, które listy zawierają. Kluczowe jest, aby nie pogubić się w wewnętrznych iteracjach, a najłatwiej do tego nie dopuścić, analizując krok po kroku, co powinniśmy wykonać.

Podzielmy zadanie na mniejsze problemy. Najpierw powinniśmy ustalić, jaką długość ma najdłuższy ze stringów, w każdej z kolumn wyniku (pamiętaj, że odpowiada im każda z zagnieżdżonych list).  Dane te umieścimy w zmiennej colWidths. Następnie przejdziemy do wyświetlania pożądanego wyniku, formatując go w komórkach o określonych szerokościach, korzystając z danym zebranych wcześniej.

Tak, jak sugeruje autor, naszą funkcję zaczynamy od stworzenia listy z długościami maksymalnego stringa dla każdej z końcowych kolumn, domyślnie ustawionymi na zero:

def printTable(table):
    colWidths = [0] * len(table)

W tym momencie przychodzi czas na iterację. Musimy zapisać ją tak, by zajmowała się pierwszymi elementami z każdej listy, potem drugimi, następnie trzecimi etc. Zanim przejdziesz dalej, spróbuj sam dość do tego, w jaki sposób zapisać ten fragment kodu.

Pokombinowałeś? Powinien on wyglądać mniej więcej tak:

def printTable(table):
    colWidths = [0] * len(table)
    for w in range(len(table[0])):
        for z in range(len(table)):

Pomocne przydaje się w takich sytuacjach policzenie ile razy mamy wykonywać każde zadanie i w jakiej kolejności. Tutaj musimy cztery razy iterować przez trzy elementy, co pomoże nam w określeniu, że najpierw odnosimy się do długości każdej z tabel wewnętrznych (równej cztery), a następnie, do długości tabeli głównej (składającej się z trzech tabel). Iteracja ta będzie zajmowała się elementami w takiej kolejności: apples, Alice, dogs, oranges, Bob, cats itd.

Ale jak sprawdzić długości stringów i przyporządkować je do odpowiednich wartości w zmiennej colWidths? Zauważmy, że zmienna „z” odpowiada ilości elementów w colWidths, co powinno nam sugerować jej wykorzystanie:

def printTable(table):
    colWidths = [0] * len(table)
    for w in range(len(table[0])):
        for z in range(len(table)):
            if len(table[z][w]) > colWidths[z]:
                colWidths[z] = len(table[z][w])

Każdy kolejny element (odpowiednio dla każdej kolumny!) zastąpi swoim rozmiarem wynik wcześniejszego, jeśli tylko jego rozmiar jest większy. W ten sposób w zmiennej colWidths mamy zapisane rozmiary najdłuższego stringa dla każdej z końcowych kolumn, co przyda nam się w kolejnej części zadania, do której teraz przechodzimy. Przypomnę, że dążymy do wyświetlania pożądanego wyniku (każda z wewnętrznych list w osobnej kolumnie), formatując go w komórkach o określonych szerokościach, korzystając z danym zebranych wcześniej.

Zauważcie, że wyświetlać będziemy kolejne elementy w takiej samej kolejności, w jakiej zajmowaliśmy się nimi w części pierwszej, co oznacza, że iteracja będzie taka sama:

(…)

for x in range(len(table[0])):
        for y in range(len(table)):

I sedno naszego zadania. Mamy wyświetlać każdy kolejny element, sformatowany do odpowiedniej długości. Pamiętajmy, że „zmieniamy” rząd dopiero po każdym trzecim elemencie – pomoże nam w tym ustawienie argumentu kluczowego end=” oraz „pusty” print na koniec każdej zewnętrznej iteracji:

(…)

for x in range(len(table[0])):
        for y in range(len(table)):
            print(table[y][x].rjust(colWidths[y] + 1), end='')
    print()

Wyświetlamy element wyjustowany do prawej w rozmiarze wynikającym z odpowiedniej wartości w colWidths, bez przechodzenia do kolejnej linii postępujemy tak samo z dwoma kolejnymi elementami, i dopiero wtedy przechodzimy do nowej linii.

Oto jak będzie wyglądał cały program wraz z przykładową listą:

tableData = [['apples', 'oranges', 'cherries', 'bananas'],
             ['Alice', 'Bob', 'Carol', 'David',],
             ['dogs', 'cats', 'moose', 'goose']]

def printTable(table):
    colWidths = [0] * len(table)
    for w in range(len(table[0])):
        for z in range(len(table)):
            if len(table[z][w]) > colWidths[z]:
                colWidths[z] = len(table[z][w])
    for x in range(len(table[0])):
        for y in range(len(table)):
            print(table[y][x].rjust(colWidths[y] + 1), end='')
        print()
   
printTable(tableData)

Oczywiście mogliście dojść do innego rozwiązania tego problemu, i nie oznacza to wcale, że jest ono gorsze. Każdy sposób powinniście oceniać z perspektywy uniwersalności rozwiązania oraz klarowności kodu. Jeśli potrzebujecie przykładu, jak wygląda niejasny kod – oto jedna linijka z mojej wstępnej wersji tego skryptu, który działał, ale nic ponad to:

print(tdata[i][n].rjust(int(len(colWidths[i])+2)), end='')

Piękne, prawda ;)? Zaproponowany przeze mnie powyżej kod jest zdecydowanie bardziej klarowny.

One thought on “Seria AtBSwP – piąte zadanie praktyczne

Dodaj komentarz