wtorek, 17 czerwca 2014

Słownik argumentów

W pewnej pythonowej bibliotece była pewna funkcja, która akceptowała dowolnie nazwane argumenty, a potem zapisywała je jako atrybuty znacznika HTML.

Coś w tym rodzaju:

def  tag( t, **keys ):
    print( "<" + t, end='' )
    for k in keys:
        print( ' ' + k + '="' + str(keys[k]) + '"', end = '' )
    print( "/>" )

Z rozpędu próbowałem ostatnio wywołać ją w taki sposób:

   tag( "circle", cx=100, cy=20, fill-opacity=0.6 )

To oczywiście nie działa, bo - cytuję:

>>> tag( "circle", cx=100, cy=90, fill-opacity=0.9 )
  File "", line 1
SyntaxError: keyword can't be an expression

Trzeba to zrobić tak:

tag( "circle", cx=100, cy=90, **{"fill-opacity":0.9} )
Używamy tu pythonowego zapisu służącego do przekazywania nazwanych argumentów przez słownik.

Pojedyncza gwiazka służy do przekazywania argumentów przez listę. Przekazywanie dowolnej liczby argumentów za pomocą tablicy nie jest niczym nowym. W języku C można to robić używając makrodefinicji va_start, va_list i pokrewnych, ale nie jest to elastyczny mechanizm. W JavaScript jest już lepiej - mamy apply() i call() w duchu lispowym. Cała idea pojawia się zresztą wyraźnie w sławnej SICP , ale, co ciekawe w oryginalnym interpreterze Johna McCarthy'ego - nie pojawia się wcale, ten pierwszy interpreter Lispu oparty był na eval.

To, co mamy w Pythonie i Javascripcie jest bardziej elastyczne i estetyczne - lista (słownik) argumentów może być zbudowana niezależnie, jako obiekt pierwszej klasy. W C to niemożliwe - musi ona pochodzić z wywołania naszej funkcji. Ale przynajmniej można ją przekazać dalej - robi się to tak:

#include 
#include 

void somefun( const char *fmt, ... )
{
        va_list  ap;
        va_start( ap, fmt );
        vprintf( fmt, ap );
        va_end( ap );
}

piątek, 13 czerwca 2014

Minority game

Jednym z najprostszych możliwych do wyobrażenia modeli rynku giełdowego (albo aukcyjnego, albo walutowego) jest minority game, czyli gra mniejszościowa. Polega ona na tym, że gracze podejmują decyzje równocześnie, niezaleznie, na podstawie danych historycznych, a wygrywają ci, którzy byli w mniejszości.

Rzeczywiście - na przykład rynek forex w przybliżeniu działa właśnie w ten sposób. Gracze decydują, czy kupić, czy sprzedać, a wygrywają ci, których było mniej.

Okazuje się, że badacze potrafili dojść do ciekawych wniosków konstruując tego typu modele [1]

Spróbujmy stworzyć minimalny nietrywialny model takiej gry. Najprostszy z najprostszych modeli składa się z trzech graczy (dwóch to za mało - musi być liczba nieparzysta, jeden to też za mało - zawsze przegrywa sam ze sobą, więc model jest trywialny) podejmujących decyzje zero-jedynkowe. Przykładowa partia gry wygląda np. tak:

WierszDecyzjaWynikPunkty
A B C A B C
1. 1 1 0 0 0 0 1
2. 1 0 0 1 1 0 1
3. 0 1 1 0 2 0 1
4. 0 0 0 1 2 0 1
5. 1 1 1 0 2 0 1
6. 1 0 1 0 2 1 1

Wygrał gracz A, bo aż dwa razy potrafił być w mniejszości: w wierszu 2. i 3.

Jak może wyglądać najprostsza możliwa taktyka gracza ? Może wykonywać zawsze ten sam ruch, może zmieniać je cyklicznie, może powtarzać wartość, która wygrała, powtarzać wartość, która przegrała, wybierać wartość, która najczęściej wygrała/przegrała w pewnym odcinku czasu itp itd. Intuicja mówi nam, że im dłuższą historię gracz bierze pod uwagę, tym lepiej będzie mógł przewidzieć wyniki. Wydaje się też, że bardziej wyrafinowany algorytm decyzji będzie wygrywał z prostymi strategiami.

Skupmy się na najprostszych taktykach. Wykonałem eksperyment obliczeniowy z czterema strategiami:

  1. strategia stałej decyzji 0
  2. strategia stałej decyzji 1
  3. strategia powtórzenia pozycji, która ostatnio wygrała
  4. strategia powtórzenia pozycji, która ostatnio przegrała

Skrypt jest tu:

#! /usr/bin/env python3

def  Player( mode ):
    """  Player( mode ) zwraca funkcję gracza w trybie mode, gdzie
         mode = 0, 1, 2, 3
         Dla mode=0,1 gracz powtarza zawsze 0 lub 1, dla mode=2,3
         gracza powtarza lub neguje poprzedni wynik

         Funkcja gracza wywoływana jest z poprzednim (historycznym)
         wynikiem gry (jako 0 lub 1), ma zwrócić decyzję gracza, jako 0 lub 1.
    """     
    def  pfun0( prev ):
        return 0

    def  pfun1( prev ):
        return 1

    def  pfun2( prev ):
        return  prev

    def  pfun3( prev ):
        return  1 - prev

    return (pfun0, pfun1, pfun2, pfun3)[mode]

def  game( a, b, c, len ):
    """  Wykonanie gry o dlugości len
         a, b, c - gracze
    """
    prev = 0
    score = [0, 0, 0]

    for i in range(0, len):
        bid1 = a( prev )
        bid2 = b( prev )
        bid3 = c( prev )

        count = 0
        if bid1: count += 1
        if bid2: count += 1
        if bid3: count += 1

        # Co wygrywa - prev = głos mniejszości
        prev = 1 if count<2 else 0

        if (bid1 == prev):
            score[0] += 1

        if (bid2 == prev):
            score[1] += 1

        if (bid3 == prev):
            score[2] += 1

    return score

if __name__=="__main__":
    total = [0, 0, 0, 0]

    for i in 0,1,2,3:
        for j in 0,1,2,3:
            for k in 0,1,2,3:
                score = game( Player(i), Player(j), Player(k), 41 )
                print( i, score[0] )
                print( j, score[1] )
                print( k, score[2] )

                total[ i ] += score[0]
                total[ j ] += score[1]
                total[ k ] += score[2]


    print( total )            


Okazuje się, że jeżeli obliczymy średnią efektywaność tych strategii we wszystkich kombinacjach (to znaczy - zakładając, że pozostali gracze stosują jedną ze strategii 1,2,3,4 z równym prawdopodobieństwem), to najlepsza jest strategia 3 - powtórzenia pozycji, która ostatnio wygrała.

Wyniki (w umownych punktach):

  1. strategia stała 0 : 672
  2. strategia stała 1 : 312
  3. strategia powtórzenia wygranej : 852
  4. strategia powtórzenia przegranej :132

Dociekliwy czytelnik zapyta, dlaczego strategie 1 i 2 mają różne wyniki, skoro są symetryczne. Wynika to z niesymetryczności skryptu (startuję zawsze od 0 - linia 30, można zamienić na 1 i wtedy będzie odwrotnie).

Można drążyć ten temat dalej - obliczeniowo lub googlowo - szukać narzędzi teorii gier, które mają tu zastosowanie. Ale jedne wniosek może się nasunąć od razu - na rynku forex i na giełdzie mogą występować trendy (przynajmniej w krótkiej skali czasowej) - powtórzenie ruchu wygrywającego ma szanse powodzenia.

[1] Challet, Damien, Matteo Marsili, and Yi-Cheng Zhang. "Modeling market mechanism with minority game." Physica A: Statistical Mechanics and its Applications 276.1 (2000): 284-315.

środa, 7 maja 2014

Myślenie abstrakcyjne w Pythonie

Wpływ używanego języka na myślenie można nieraz odczuć samemu, jeśli w tym samym czasie programuje się dużo w różnych językach. Mając głowę wypełnioną pythonowymi narzędziami (comprehension, generatory, iteratory) próbowałem odruchowo zamienić wektor par w C++ (uzyskany z jakiejś funkcji) na odpowiednią mapę:
   std::vector< std::pair >  ->  std::map
W Pythonie analogiczna konstrukcja jest natychmiastowa:
   >>> x = [ (1,"a"), (2,"b") ]
   >>> dict( x )
   {1: 'a', 2: 'b'}
W C++ jednak analogia nie działa: Zamiast :
   std::vector< std::pair > v;
   std::map  m( v );
Musimy napisać :
   std::vector< std::pair > v;
   std::map  m( v.begin(), v.end() );
Ale to jest jeszcze nic. Trudnośc pojawiła się dopiero, gdy uświadomiłem sobie, że potrzebuję odwrotnej mapy (nie int -> string, a string -> int). W Pythonie mam od razu:
   >>> {name:value for value, name in x}
   {'a': 1, 'b': 2}

Próby użycia swap, transform itp. narzędzi w C++ zniechęciły mnie szybko, użyłem iteratorów i zrobiłem wszystko pętlami. Kod działa, ale nie jest ani prosty ani ładny, zamiast z jednej składa się z kilku linijek i zawiera dodatkowe obiekty, stworzone tylko po to, by to przekonwertować.

Z ciekawości uruchomiłem ghci i próbowałem analogiczną konstrukcję wykonać w Haskellu. Zaraz jednak okazało się, że nie znam go na tyle, by zrobić to od razu, potrzebuję importować jakieś moduły, szukać API w hoogle itd. itp. Python zwycięża wszystko równowagą pomiędzy prostotą a możliwościami ekspresji. Mamy tam comprehension, iteratory, generatory, yield i obiekty. Kilka narzędzi, które pozwalają budować złożone struktury listowe, mapy i inne zwierzęta z ZOO funkcyjnego, a wszystko w sposób miły i prosty. Mamy interaktywną konsolę, help do API i to już dośc, by szybko pisać złożony kod. Po prostu zapisywać myśl w postaci kodu.