<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>Modul „array“</title>
  <date>2015-02-04</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
</meta>
<!--
  „“–…
  ←→ ↑↓ ↔↕
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

    <p>
        Pokud člověk začíná s Python'em jako prvním programovacím jazykem, klasický datový typ <em>pole</em> (aneb <em>array</em>) mu nijak zvlášť nechybí. Ostatně Python jako dynamicky typovaný a interpretovaný jazyk k nějaké superoptimalizaci z hlediska datových typů nijak nevede.
    </p>
    <p>
        Situace je jiná, pokud znáte <em>pole</em> ze staticky typovaných jazyků nebo vás rychlost trápí – pak vám se „samozřejmě“ se místo pole nabízející datovou strukturou <em>seznamu</em> asi poměrně rychle dojde trpělivost. A právě pro tyto případy je v pythoní knihovně k dispozici modul <strong><code>array</code></strong> – na první pohled jakoby seznam, ale s omezením na jeden konkrétní a dopředu vybraný datový typ (a to v podstatě jenom číselný).
    </p>

</slide>
<slide title="„array“ versus „seznam“">

    <p>
        Rozdíl mezi seznamem a (tradičním) polem z hlediska paměťové a časové náročnosti je velmi snadno zdůvodnitelný. Schematicky:
    </p>
    <example lang="python">
        # seznam
        [neznámý typ 1, neznámý typ 2, neznámý typ 3, …]
        
        # pole
        [typ 1, typ 1, typ 1, …]
    </example>
    <p>
        Tudíž zatímco u seznamu nikdy nevíte, co na daném místě je, dokud se tam nepodíváte (v Python'u se typy drží hodnot, nikoli proměnných), což s sebou mimo jiné nese, že je občas docela problém zjistit, kam v paměti se po příslušném prvku vlastně podívat (i když záleží na implementaci samozřejmě) plus jak s ním vůbec zacházet (protože to zjistíte, až se na něj podíváte), u pole je tohle všechno naprosto jasné dávno dopředu už při jeho zavedení.
    </p>
    <p>
        Z toho ovšem také nepřekvapivě vyplývá, že (nejen) pythoní typ <em>array</em> bude mít dosti omezenou množinu ukládatelných hodnot (mimo jiné aby všechny položky v paměti zabíraly stejné místo). Což samozřejmě vůbec není na škodu, když je to právě to, co potřebujete. Narozdíl od některých striktnějších jazyků však v Python'u není třeba dopředu znát velikost pole a podobně.
    </p>

</slide>
<slide title="Zavedení – prázdné pole daného typu">

    <p>
        Vzhledem k výše uvedenému je nutnou součástí zavedení pole určení typu dat v něm uložených. Jednoduchý příklad na zavedení pole celých čísel (klasický „int“):
    </p>
    <example lang="python">
        # naimportování konstrukturu pole „array“ z příslušného modulu
        >>> from array import array
        
        # zavedení pole celých čísel
        >>> ar = array('i')
        
        # pole je zatím prázdné
        >>> len(ar)
        0
        
        # je to skutečně pole „int“ů
        >>> ar.typecode
        'i'
        
        # jeden prvek zabírá 4 bajty
        >>> ar.itemsize
        4
    </example>

</slide>
<slide title="Zavedení – neprázdné pole daného typu">

    <p>
        Naplnit pole prvky jde mnoha různými způsoby (podobně jako u seznamu). Typicky při zavedení však můžete použít seznam vhodných prvků jako zde pro reálná čísla (klasické „float“y):
    </p>
    <example lang="python">
        # naimportování modulu „array“
        >>> import array
        
        # zavedení pole reálných čísel s několika počátečními prvky
        >>> ar = array.array('f', [1, 2.0, 3.14])
        
        # jak je vidět, konstruktur si na vstupu poradil s celými čísly
        >>> ar
        array('f', [1.0, 2.0, 3.140000104904175])        
        
        # pole obsahuje tři prvky
        >>> len(ar)
        3
        
        # je to skutečně pole „float“ů
        >>> ar.typecode
        'f'
        
        # jeden prvek zabírá 4 bajty
        >>> ar.itemsize
        4
    </example>

</slide>
<slide title="Podrobnosti k inicializaci">

    <p class="enumerate">
        Objekt použitý pro zavedení výchozích prvků pole může být tří různých variant:
    </p>
    <ul>
        <li>
            seznam – na jeho zpracování je zavolána metoda <strong><code>array.fromlist(xs)</code></strong> (která funguje stejně jako smyčka s <code>for x in xs: ar.append(x)</code>, ale v případě chyby nebude původní pole změněno);
        </li>
        <li>
            binární řetězec – na jeho zpracování je zavolána metoda <strong><code>array.frombytes(xb)</code></strong>;
        </li>
        <li>
            iterovatelný objekt – na jeho zpracování je zavolána metoda <code>array.extend(iterable)</code>.
        </li>
    </ul>
    <note>
        A samozřejmě ještě teoreticky deprikovaná možnost inicializace z unicodového řetězce pomocí <code>array.fromunicode(s)</code>.
    </note>
    
    <p class="enumerate">
        Již existující pole můžeme také doplnit prvky ze souboru pomocí metody <strong><code>array.fromfile(PROUD, PočetPrvků)</code></strong>. Obsahuje-li dostupný proud méně než požadovaný počet prvků, dojde k vyhození výjimky <code>EOFError</code>.
    </p>
    
    <p class="enumerate">
        K uvedeným <em>from</em>-metodám nepřekvapivě existují i odpovídající <em>to</em>-metody:
    </p>
    <ul>
        <li>
            <strong><code>array.tolist()</code></strong> – převede typované pole na standardní pythoní seznam;
        </li>
        <li>
            <strong><code>array.tofile(PROUD)</code></strong> – zapíše bajty do otevřeného binárního souboru;
        </li>
        <li>
            <strong><code>array.tobytes()</code></strong> – vrátí stejnou binární reprezentaci pole jako jsou data zapsaná předchozí metodou.
        </li>
    </ul>
    <note>
        Plus samozřejmě teoreticky deprikovaná metoda <code>array.tounicode()</code> pro pole typu <code>'u'</code>. Pro pole jiného typu se dá požadovaného výsledku dosáhnout pomocí <code>array.tobytes().decode(KÓDOVÁNÍ)</code>.
    </note>

</slide>
<slide title="Dostupné typy">

    <p>
        Seznam identifikátorů aktuálně dostupných typů si můžete nechat vypsat:
    </p>
    <example lang="python">
        >>> import array
        >>> array.typecodes
        'bBuhHiIlLqQfd'
    </example>
    <p>
    </p>
    <table style="font-family: monospace;" class="centerTable slim">
        <tr>
            <th>kód</th>
            <th>C</th>
            <th>Python</th>
        </tr>
        <tr>
            <td>'b'</td>
            <td>signed char</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'B'</td>
            <td>unsigned char</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'h'</td>
            <td>signed short</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'H'</td>
            <td>unsigned short</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'i'</td>
            <td>signed int</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'I'</td>
            <td>unsigned int</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'l'</td>
            <td>signed long</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'L'</td>
            <td>unsigned long</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'q'</td>
            <td>signed long long</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'Q'</td>
            <td>unsigned long long</td>
            <td>int</td>
        </tr>
        <tr>
            <td>'f'</td>
            <td>float</td>
            <td>float</td>
        </tr>
        <tr>
            <td>'d'</td>
            <td>double</td>
            <td>float</td>
        </tr>
    </table>
    <notes>
        <note>
            Pythoní <em>int</em> je „nafukovací“ a <em>float</em> odpovídá rovnou céčkovskému <em>double</em>.
        </note>
        <note>
            Typy <code>'q'</code> a <code>'Q'</code> nemusí být dostupné všude (záleží na podpoře <em>long long</em>, resp. <em>__int64</em>).
        </note>
        <note>
            Zatím je stále ještě dostupný také typ <code>'u'</code>, aneb unicodový znak, ale od verze Python'u 3.3 je nedoporučován k použití (<em>depricated</em>) a ve verzi 4.0 bude (spolu s odpovídajícím API <em>Py_UNICODE</em>) odstraněn.
        </note>
    </notes>

</slide>
<slide title="Pole jako sekvence">

    <p>
        Typovaná pole sice obsahují spoustu metod specifických pouze pro ně, ale v mnoha dalších ohledech se chovají jako sekvenční typ, jmenovitě jako <em>seznam</em>:
    </p>
    <example lang="python">
        >>> from array import array
        >>> ar = array('i', [1, 2, 3, 4, 5])
        
        # délka sekvence
        >>> len(ar)
        5
        
        # konkrétní prvek
        >>> ar[3]
        4
        >>> ar[-3]
        3
        
        # různé výřezy
        >>> ar[1:3]
        array('i', [2, 3])
        >>> ar[1::2]
        array('i', [2, 4])
        >>> ar[3:]
        array('i', [4, 5])
        >>> ar[-3:]
        array('i', [3, 4, 5])
        
        # dotaz na výskyt prvku
        >>> 1 in ar
        True
        >>> 6 in ar
        False
        
        # dvě spojené kopie
        >>> ar * 2
        array('i', [1, 2, 3, 4, 5, 1, 2, 3, 4, 5])
    </example>

</slide>
<slide title="Pole jako sekvence – smyčka „for-in“">

    <p>
        Po polích se přirozeně prochází smyčkou, a to ve všech jejích základních variantách (protože prvky pole jsou stejného typu, a tudíž <em>srovnatelné</em>):
    </p>
    <example lang="python">
        >>> from array import array
        >>> ar = array('i', [5,2,3,1,4])
        
        >>> for x in ar:
        ...   print(x)
        ...
        5
        2
        3
        1
        4
        
        >>> for i,x in enumerate(ar):
        ...   print(i,x)
        ...
        0 5
        1 2
        2 3
        3 1
        4 4
        
        >>> for x in reversed(ar):
        ...   print(x)
        ...
        4
        1
        3
        2
        5
        
        >>> for x in sorted(ar):
        ...   print(x)
        ...
        1
        2
        3
        4
        5
    </example>

</slide>
<slide title="Pole jako měnitelné sekvence">

    <p>
        Stejně jako seznamy jsou pole sekvence <strong>měnitelné</strong> (<em>mutable</em>), což znamená, že jejich obsah po vytvoření můžeme libovolně upravovat: 
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3, 4, 5])
        
        >>> ar[3]
        4
        >>> ar[3] = 7
        >>> ar[3]
        7
        >>> ar
        array('i', [1, 2, 3, 7, 5])
    </example>

</slide>
<slide title="Dotazování na prvky">
  
    <p class="enumerate">
        Kromě již dříve uvedeného a všem sekvencím společného operátoru <code>in</code>:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3, 4, 5])

        >>> 3 in ar
        True
        >>> 7 in ar
        False
    </example>
    
    <p class="enumerate">
        ...mají pole stejně jako seznamy k dispozici i metodu <code>index(PRVEK)</code>, která slouží k navrácení pozice prvního výskytu prvku s uvedenou hodnotou:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3, 4, 5])

        >>> ar.index(1)
        0
        >>> ar.index(3)
        2
    </example>
    <note>
        Není-li takový prvek v seznamu k dispozici, obdržíme výjimku <em>ValueError</em>.
    </note>
    
    <p class="enumerate">
        Kromě toho můžeme i počítat výskyty zadaného prvku pomocí metody <code>count(PRVEK)</code>:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3, 1, 5, 6])

        >>> ar.count(1)
        2
        >>> ar.count(2)
        1
        >>> ar.count(7)
        0
    </example>

</slide>
<slide title="Přidávání prvků">

    <p>
        Jak už jsme říkali, <a href="?slajd=5">metody používané interně pro inicializaci z přímého zadání pole</a> je také možné použít přímo pro přidávání prvků. Kromě nich ale nepřekvapivě funguje i standardní armáda operací ze seznamů, jen samozřejmě musíme přidávat prvky stejného typu:
    </p>
    
    <p class="enumerate">
        Předně můžeme využít metody pole <code>append(PRVEK)</code> pro přidání jednoho nového prvku na konec:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3])
        
        >>> ar.append(4)
        >>> ar
        array('i', [1, 2, 3, 4])
    </example>
    
    <p class="enumerate">
        Jeden prvek také můžeme pomocí metody <code>insert(NA_KTERÝ_NOVÝ_INDEX, PRVEK)</code> (resp. <code>PŘED_KTERÝ_STARÝ_INDEX</code>) vložit na vybrané místo:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3])
        
        >>> ar.insert(1, 4)
        >>> ar
        array('i', [1, 4, 2, 3])
        
        >>> ar.insert(-1, 5)
        >>> ar
        array('i', [1, 4, 2, 5, 3])
    </example>
    
    <p class="enumerate">
        Na konec pole můžeme také přidat více prvků jiného iterovatelného objektu (pochopitelně pouze pokud obsahuje prvky stejného typu), a to buď pomocí metody <code>extend(ITERABLE)</code>:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3])
        
        >>> ar.extend( array('i', [4, 5]) )
        >>> ar
        array('i', [1, 2, 3, 4, 5])
    </example>
    
    <p class="enumerate">
        ..nebo pomocí „obyčejného“ slepování sekvencí pomocí operátoru <code>+</code>:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3])
        
        >>> ar += array('i', [4, 5])
        >>> ar
        array('i', [1, 2, 3, 4, 5])
    </example>

</slide>
<slide title="Odebírání prvků">

    <p>
        Odebírání prvků z pole je prakticky kompletní kopií práce se seznamem:
    </p>
    
    <p class="enumerate">
        Odebrat prvek, resp. prvky, na základě jeho, resp. jejich, pozice (indexu) nám umožňuje univerzální příkaz <code>del</code> standardním způsobem pomocí výřezů:
    </p>
    <example lang="python">
        >>> del ar[1]
        >>> ar
        array('i', [1, 3, 4, 5, 6])
        
        >>> del ar[3:]
        >>> ar
        array('i', [1, 3, 4])
    </example>
    
    <p class="enumerate">
        Pro jeden prvek víceméně totéž zařídí metoda seznamu <code>pop([INDEX=-1])</code>. Rozdíl je v tom, že odebraný prvek je metodou vrácen:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3, 4, 5, 6])
        
        >>> ar.pop()     # ostraní poslední prvek
        6
        >>> ar
        array('i', [1, 2, 3, 4, 5])
        
        >>> ar.pop(0)    # ostraní první prvek
        1
        >>> ar
        array('i', [2, 3, 4, 5])
        
        >>> ar.pop(2)    # odstraní prvek na pozici 2, tj. třetí
        4
        >>> ar
        array('i', [2, 3, 5])
    </example>
    <note>
        Pokus o odstranění prvku na neexistující pozici nebo z prázdného seznamu skončí výjimkou <em>IndexError</em>.
    </note>
    
    <p class="enumerate">
        Poslední možností je odstranit prvek na základě jeho obsahu, a nikoli indexu. O to se stará metoda <code>remove(PRVEK)</code>. Odstraněn je vždy první výskyt (zleva) daného prvku:
    </p>
    <example lang="python">
    >>> ar = array('i', [1, 2, 3, 2, 5, 6])
    
    >>> ar.remove(2)
    >>> ar
    array('i', [1, 3, 2, 5, 6])
    </example>
    <note>
        Pokus o odstranění prvku, který se v seznamu nenachází, skončí výjimkou <em>ValueError</em>.
    </note>

</slide>
<slide title="Další metody polí">

    <p class="enumerate">
        Metoda <strong><code>array.reverse()</code></strong> nepřekvapivě obrátí pořadí prvků v poli:
    </p>
    <example lang="python">
        >>> ar = array('i', [1, 2, 3])
        >>> ar.reverse()
        >>> ar
        array('i', [3, 2, 1])
    </example>
    
    <p class="enumerate">
        Naproti tomu metoda <strong><code>array.byteswap()</code></strong> otočí pořadí jednotlivých bajtů každé konkrétní hodnoty pole – šikovné pro práci s daty z architektury s jiným pořadím zápisu bajtů vícebajtových hodnot.
    </p>
    <note>
        Metoda zabere pouze na data, jejichž hodnoty jsou zapsány pomocí právě 1, 2, 4 nebo 8 bajtů, jinak vyhodí výjimku <code>RuntimeError</code>.
    </note>

</slide>
<slide title="A kam dál?">

    <p>
        Jak je vidět, modul <em>array</em> toho zase až tak moc nenabízí – vlastně jen umí vytvořit a spravovat jednorozměrná pole homogenních číselných typů. Práce s ním je standardně pythonovsky příjemná, protože je to <em>sekvenční typ</em>, ale vlastně nic navíc oproti seznamům neumí (kromě toho, že zjevně bude „přívětivější“ na paměť i na rychlost).
    </p>
    <p>
        Pokud člověk potřebuje pořádnou práci s vícerozměrnými číselnými poli a nejlépe ještě i příslušné matematické operace nad nimi, musí se podívat někam dál, konkrétně na balík <a href="http://NumPy.org" class="external">NumPy</a> (jinak vlastně samostatná součást většího balíku pro vědecké a technické výpočty <a href="http://SciPy.org" class="external">SciPy</a>).
    </p>

</slide>


</lecture>
