/Cw1

Z Brain-wiki

TI:WTBD/Ćwiczenia 1

Do zabawy używaliśmy m. in. plików Plik:PanTadeusz1.txt i Plik:PanTadeusz1 Latin2.txt.

Dla rozgrzewki i jako narzędzie do dalszej zabawy napisaliśmy moduł z funkcjami: konwertującą liczbę (int) na "bity" (jej zapis dwójkowy jako napis składający się z zer i jedynek), i funkcję do niej odwrotną. Oto pełny tekst kodu modułu:

#! /usr/bin/python
# coding: utf-8
# util.py

def tobits(N):
    s = list()
    while N:
        N, r = N / 2, N % 2
        s.append('%i' % r)
    s.reverse()
    return ''.join(s)

def frombits(bits):
    res = 0
    for c in bits:
        if c not in '01':
            raise ValueError('not bits!')
        res *= 2
        res += int(c)
    return res

Funkcji tych użyliśmy do przyjrzenia się, jak wyglądają sekwencje wielobajtowe reprezentujące znaki spoza zakresu ASCII w kodowaniu UTF-8. Informacje o zasadzie kodowania UTF-8 można znaleźć np. w podręczniku systemowym (man utf-8).

Oczywiście tak naprawdę nie było potrzeby pisania tych funkcji samemu. Proszę znaleźć sposób uzyskania tych konwersji wprost za pomocą wbudowanych funkcji/metod.

Napisaliśmy dwie funkcje odtwarzające zasadniczą funkcjonalność standardowego polecenia wc, różnią się one sposobem wykonania operacji wejścia. Obie nic nie wiedzą o kodowaniach tekstu, traktując zawartość pliku jako strumień bajtów.

def wc0(plik):
    '''zwraca (linie, słowa, znaki);
    ta wersja wc nic nie wie o kodowaniach, po prostu liczy bajty.
    '''
    try:
        s = plik.read()
    except AttributeError:
        s = open(plik).read()
    #return len(s.split('\n')), len(s.split()), len(s)
    # tradycyjne wc liczy wystąpienia '\n', co często nie zgadzałoby się o 1
    return s.count('\n'), len(s.split()), len(s)
def wc1(plik):
    '''na sposób linijka-po-linijce'''
    try:
        f = open(plik)
    except TypeError:
        f = plik
    l, w, c = 0, 0, 0
    for line in f:
        l += 1
        w += len(line.split())
        c += len(line)
    return l, w, c

Warto czasami sprawdzić, czy "ulepszona" implementacja nie daje innych wyników, niż oryginalna. Zwłaszcza, że z komentarza w pierwszej wersji wc wynika, że czasami wyniki tych funkcji mogą różnić się o 1 (dla liczby linijek) -- w jakich okolicznościach, i jak to poprawić?

def test(plik):
    if wc0(plik) == wc1(plik):
        print 'OK'
    else:
        print 'ERROR!'

Kolejna wersja zakłada, że plik jest zapisem tekstu w kodowaniu UTF-8.

def wc(plik, enc='utf-8'):
    '''ta wersja zakłada że plik jest w kodowaniu utf-8
    '''
    try:
        s = plik.read()
    except AttributeError:
        s = open(plik).read()
    s = s.decode(enc)
    return s.count('\n'), len(s.split()), len(s)

Ta wersja cierpi na tę słabość, że rzuci wyjątkiem jeśli treść pliku nie jest legalnym strumieniem UTF-8.

Jeżeli chcemy stworzyć program bezpośrednio wykonywalny, to potrzebny jest jeszcze blok mówiący o tym, co ma się stać w wyniku jego wywołania. Do tego musimy oczywiście dołączyć jeszcze w treści pliku programu którąś z wersji definicji funkcji wc.

if __name__ == '__main__':
    from sys import argv, stdin
    args = argv[1:]
    if args:
        reslines = list(wc(plik) + (plik,) for plik in args)
    else:
        reslines = [wc(stdin) + ('-',)]
    if len(reslines) > 1:
        reslines.append(tuple(sum(t) for t in zip(*reslines)[:3]) + ('RAZEM',))
    #print reslines # jeszcze trzeba to umiejętnie sformatować
    reslines = list(tuple(str(x) for x in line) for line in reslines)
    maxl = tuple(max(len(s) for s in col) + 1 for col in zip(*reslines))
    fmt = '%%%ds%%%ds%%%ds %%s' % maxl[:3]
    for line in reslines:
        print fmt % line

Co tu się dzieje?

  1. Dobieramy się do listy argumentów z linii poleceń (pomijając zerowy, będący nazwą samego programu) i traktujemy te argumenty jako nazwy plików.
  2. Iterujemy po elementach listy argumentów jeśli jest niepusta. WPP czytamy zawartość stdin i pracujemy na niej. Uwaga, iteracja jest wykonana nie za pomocą pętli for, a generatora.
  3. W przypadku gdy lista wynikowa ma długość > 1, dołączamy jeszcze jeden wiersz z podsumowaniem.
  4. Formatujemy wynik w zgrabną tabelkę o wyrównanych kolumnach. W tym celu obliczamy, jaką szerokość trzeba zarezerwować na każdą z 3 pierwszych kolumn, i na tej podstawie tworzymy napis formatujący fmt, służący do sformatowania linijek.