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.

piątek, 9 listopada 2012

Rozwiązywanie równań - aplet z grą

Wracam do tematu rozwiązywania prostych równań, o którym pisałem przy okazji zadania z cegłą. Poniżej przedstawiam prototyp pewnej gry matematycznej (właściwie to nie aplet, jak napisałem w tytule, a zwykły javascript). Pierwsze wrażenia są zachęcające - może ktoś to rozwinie, a może ja to zrobię, jeśli znajdę czas.

ROZWIĄŻ RÓWNANIE !

Zadaniem gracza jest rozwiązanie równania. Przycisk z symbolem losuje nowe równanie do rozwiązania. Kolejne przyciski służą do wykonywania operacji na równaniu (pamiętacie zadanie o cegle - dokładanie cegieł na obie szalki, itp). Należy doprowadzić do sytuacji, w której po jednej stronie równania zostanie pojedyncze "X", nie pomnożone przez żadną liczbę, a po drugiej liczbowa wartość (np. X = 4).

To prosta gra, pojedyncza partia odpowiada dokładnie rozwiązaniu równania. Gdyby rozwinąć ten temat (może ktoś to zrobi - jeśli znajdę kiedyś podobny program/aplet - postaram się zamieścić tutaj informację) - otrzymalibyśmy narzędzie dydaktyczne !

Osoby, które uczą matematyki, albo - jeszcze lepiej - udzielają korepetycji (korepetycje pozwalają z bliższej odległości obserwować zmagania niedoświadczonego umysłu z matematyczną materią) uznają ten pomysł na pewno za coś ciekawego.

Tego typu gry pozwalają w przyjemny sposób przyspieszyć moment, w którym osoba ucząca się przeżywa coś w rodzaju olśnienia (albo jak niektórzy mówią - coś jej w umyśle "przeskakuje") i nagle wie już, jak się to robi ! (W tym przypadku - jak się rozwiązuje równania z jedną niewiadomą. Rzeczywiście, zadanie to należy do prostych - łatwo je zautomatyzować.) Jest to coś takiego, jak odkrycie taktyki w prostej grze komputerowej - sposobu, który gwarantuje, że z każdego labiryntu znajdziemy wyjście w skończonej liczbie kroków.

Gra zawiera pewien haczyk: niektóre rozwiązania prowadzą do ułamkowych wartości, a przyciski pozwalają operować tylko na liczbach całkowitych. Mnożenie i dzielenie może doprowadzić do utraty precyzji - błędy zaokrągleń nakładają się, kumulują i w rezultacie możemy skończyć w sytuacji bez wyjścia. Trzeba zawsze starać się zachowywać całkowite współczynniki, zwykle jest to możliwe.

Gra zawiera też ciekawostkę: mnożenie i dzielenie operuje na liczbach pierwszych; w pierwszej wersji miałem tylko mnożenie i dzielenie przez liczby od 2 do 7, okazało się to jednak niewystarczające kiedy przeglądarka wylosowała równanie:

5X + 1 = - 6X

Chcąc więc pomnożyć (lub podzielić) równanie przez jakąś liczbę, której nie ma na przyciskach (np. 10) trzeba ją rozłożyć w pamięci na czynniki (tutaj 10 = 2 × 5) i pomnozyć (lub podzielić) po kolei przez te czynniki.

A oto dłuższy przykład:

4X - 4 = -2 - X | / 4
X - 1 = -0.5 - 0.25X | + 1
X = 0.5 - 0.25X | - X
0 = 0.5 - 1.25X | × 2
0 = 1 - 2.5X | - 1
-1 = - 2.5X | × 2
-2 = - 5X | / 5
-0.4 = - X | × (-1)
0.4 = X | ROZWIĄZANIE !

Gracz otrzymał poprawne rozwiązanie, jak się jednak nabiedził ! Wszystko dlatego, że niepotrzebnie na początku szybko podzielił wszystko przez 4, musiał potem pomnożyć ! Widać tu, że przykład ten powstał zanim odkryłem, że brakuje dzielenia przez 11 !

Właściwa taktyka jest taka: po pierwsze zgromadzić wszystkie X-y po lewej (stosując dodawanie i odejmowanie), potem wszystkie liczby bez X po prawej (takim samym sposobem), a na końcu podzielić wszystko przez czynnik przy X.

I jeszcze kilka pomysłów na rozwinięcie i udoskonalenie tego programiku:

  • Cofanie ruchu ("Undo")
  • Zapis (przy użyciu "local storage" HTML5)
  • Mierzenie czasu rozwiązywania
  • Możliwość wpisywania własnego równania
  • Podpowiedzi systemu
  • Grupowanie (zwijanie) ciągów operacji (np. dwie operacje +3 i +4 dają +7)
  • Równania ze współczynnikami symbolicznymi (np. aX + 2b = b - 1)
  • Układy równań
  • Operacje na ułamkach zwykłych (widziałem gdzieś w sieci bibliotekę implementującą wieżę numeryczną ze Scheme w JavaScripcie)
  • ... i pewnie wiele innych możliwości

Zagadki i ćwiczenia

  1. Co zrobi program, jeśli równanie nie ma rozwiązania (np. 2X + 1 = 2X) ?
  2. Albo gdy równanie ma nieskończenie wiele rozwiązań (np. 3X = 3X) ?
  3. Jak długo trzeba było klikać przycisk , żeby uzyskać przykłady odpowiadające powyższym sytuacjom 😉 ?
  4. * Czy każde zadanie wylosowane przez program da się rozwiązać używając zestawu przycisków, który jest do dyspozycji ?

wtorek, 6 listopada 2012

Jeszcze raz o foreksie

Jeden z niedoszłych adeptów foreksu upierał się kiedyś przy poglądzie, że na wykresach kursów walutowych istnieją wyraźne trendy i że powinno dać się je wykorzystać. Pytany o źródło takiego przekonania, pokazywał wykresy i twierdził, że to po prostu widać. Kolega odpowiedział mu (znam osobiście obu), że to złudzenie, a dowodem tego miał być prosty eksperyment: wygenerowanie wykresu losowego przebiegu, który z prawdopodobieństwem 1/2 w każdej chwili czasu idzie w górę lub w dół - ruch Browna. Podobno bardzo często na takim wykresie człowiek dostrzega trendy i tendencje, a nawet formacje analizy technicznej.

Kolega był szybkim koderem, więc błyskawicznie napisał coś w tym rodzaju:

awk 'BEGIN {srand();x=1000;for (i=0;i<1000;i++) {if (rand()>rand())x++; else x--; print x;}}' > tmp.txt;echo "plot 'tmp.txt' with lines; pause 10"|gnuplot

Miał trochę szczęścia (lub pecha), bo wykres był akurat wyraźnie stromy, trend był widoczny jak na dłoni ! Kliknij poniżej klawisz "LOSUJ", a wylosujesz kolejną instancję danych.

W praktyce CZASEM trafi się przebieg losowy, który WYGLĄDA na losowy, zwykle wygląda na regularny. Wniosek z tego jest taki: nie patrz zbyt naiwnie na wykresy, stosuj matematykę. Jeśli wierzysz w to, że odkryłeś Świętego Graala, sprawdź to pięć razy. Słuchaj ostrzeżeń brokerów o wysokim stopniu ryzyka związanym z handlem na foreksie ! Istnieje wiele przesłanek skłaniających do wniosku, że dla małych graczy forex to czysty hazard. Więksi być może wiedzą więcej.

wtorek, 16 października 2012

Nowe auto

Ach, ile bym dał to,
By mieć nowe auto.
Jak wiele bym zniósł,
By mieć nowy wóz.

Dałbym złota górę,
By mieć nową furę.
Całą swą krwawicę
Poświęciłbym bryce.

Byłoby wspaniale !
Tak, to prawda, ale
Mam za mały dochód,
By kupić samochód.

niedziela, 7 października 2012

Przekręcanie słów

Człowiek jest w stanie bez trudu odczytać tekst, w którym wprowadzone zostały błędy - język ludzki jest nadmiarowy. Poniżej znajduje się przycisk, który miesza tekst widoczny na ekranie w sposób, który pozwala jednak go odczytać.

Sztuczka polega na tym, żeby za bardzo tego tekstu nie pomieszać. Dozwolona jest zamiana blisko położonych liter, przy czym pierwsza i ostatnia litera wyrazu powinny pozostać na swoich miejscach. Tak zakłócony kod mózg ludzki jest w stanie odszyfrować. Zobacz, co stanie się, jeśli dopuscimy większy stopień pomieszania liter.