10 lutego 2013

Funkcja lambda - python

Funkcja lambda jest funkcją, która pobiera dowolną liczbę argumentów i zwraca wartość, którą otrzymujemy po wykonaniu pojedynczego wyrażenia. Nie mogą one zawierać poleceń, ani więcej niż jednego wyrażenia. Stosuje się je tam, gdzie duża liczba zwykłych i bardzo prostych funkcji, po prostu zaśmiecałaby kod.
l = lambda a, b: a + b
print l(4, 5)

res = sorted([4, -5, 1, -2, -8, 7], cmp=lambda x, y: abs(y) - abs(x))
print res
Wynik:
9
[-8, 7, -5, 4, -2, 1]

9 lutego 2013

Generator expressions i generator function - Python

Generatory są szczególnie przydatne, gdy nie chcemy znać wszystkich wyników jakie może zwracać funkcja, a jedynie pierwszy, który będzie spełniać nasze kryteria. Generowanie kolejnych wyników, jest triggerowane przez nas.

Generatory expresion budujemy podobnie jak list comprehension (w tym celu korzystamy z nawiasów '()'). Aby wymusić wygenerowanie kolejnego wyniku korzystamy z metody bądź funkcji next(). Gdy generator nie jest w stanie już nic wygenerować, rzucany jest wyjątek "StopIteration".
gen_expression = (val for val in [1, 2, 3, 4, 7] if val % 2)

try:
    print gen_expression.next()
    print next(gen_expression)
    print gen_expression.next()
    print gen_expression.next()
except StopIteration:
    print 'Dostalem wyjatek'
Wynik:
1
3
7
Dostalem wyjatek
Drugim rodzajem generatora jest generator function, który wygląda jak zwykła funkcja. Pozwala jednak na korzystanie mechanizmu yield. W momencie, gdy metoda trafi na takie miejsce, jej działanie jest przerywane i zwracana jest kontrola do naszej części kodu.
def gen_fun(arg = 1):
    num = 0
    yield num
    while True:
        num += arg
        yield num
        yield -num

# Zwraca generator object
g = gen_fun()
print [g.next() for _ in xrange(15)]
Wynik:
[0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7]
Możemy również przekazać jakąś wartość do wnętrza generatora. W tym celu należy skorzystać z metody send(), które tak jak next() wznawia jego działanie. Jeżeli na takim generatorze zawołamy tylko next(), do środka przekazana zostanie wartość None.
def gen():
    arg = 1
    num = 0
    while True:
        num += arg
        a = yield num
        if a is not None:
            arg = a

g = gen()
print g.next()
print g.next()
print g.send(100)
print g.next()
Wynik:
1
2
102
202

8 lutego 2013

Range based for (C++11)

Chyba w odpowiedzi na całkiem udaną implementację BOOST_FOREACH (o której już pisałem) ficzer ten znalazł swoje miejsce w nowym standardzie. Znakomicie się on komponuje z wykorzystaniem słowa kluczowego auto niwelując pewne problemy (rozwijanie makr, np. w celu skorzystanie z iteratora do mapy trzeba było posłużyć się typedef) z którymi borykało się BOOST_FOREACH.
#include <iostream>
#include <string>

using namespace std;

int main()
{
    for (const auto& ch : string("rozciaganie"))
        cout << ch << " ";
    cout << endl;
    return 0;
}
Wynik:
r o z c i a g a n i e

7 lutego 2013

Detekcja typu (C++11) - auto i decltype

W nowym standardzie, możemy zlecić kompilatorowi wykrycie używanego przez nas typu. Służy do tego słowo kluczowe auto. Jest to szczególnie użyteczne dla typów zbudowanych przy pomocy szablów, które mogą być bardzo skomplikowane w czytaniu i zapisie. Uwaga! auto usuwa modyfikatory const i volatile, nowy wydedukowany typ nie będzie ich już posiadał.
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> v = {1, 2, 4, 6};
    auto vit = v.begin();
    cout << *vit << endl;
    return 0;
}
Drugim ficzerem jest detekcja typu przy pomocy decltype. Na podstawie zadanej zmiennej lub wyrażenia odgaduje typ, który będzie nadany nowej zmiennej. W przypadku wyrażenie, nie dochodzi do ewaluacji, wyciągany jest jedynie zwracany typ. Warto też pamiętać, że jeżeli wsadzimy do decltype zmienną i ujmiemy ją w dodatkowe nawiasy, typ który dostaniemy zawsze będzie referencją, a nowa zmienna będzie musiała być zainicjalizowana. Jeżeli natomiast z nawiasów nie skorzystamy to referencję uzyskamy tylko, gdy sama zmienna będzie typem referencyjnym.
#include <iostream>
#include <vector>
#include <list>
#include <boost/type_index.hpp>

using namespace std;

list<double>::iterator get_it(list<double> l)
{
    return l.begin();
}

int main()
{
    list<double> l = {7.1, 2.3, 4.6};
    decltype(get_it(l)) it = get_it(l); // list<double>::iterator it =  get_it(l);
    cout << boost::typeindex::type_id_with_cvr<decltype(it)>().pretty_name() << " " << *it << endl;

    int i = 879;
    decltype((i)) var1 = i;  // int& var1 = i;
    cout << boost::typeindex::type_id_with_cvr<decltype(var1)>().pretty_name() << " " << var1 << endl;

    int& j = i;
    decltype(j) var2 = i;    // int& var2 = i;
    cout << boost::typeindex::type_id_with_cvr<decltype(var2)>().pretty_name() << " " << var2 << endl;

    decltype(i) br = 54;     // int br = 54;
    cout << boost::typeindex::type_id_with_cvr<decltype(br)>().pretty_name() << " " << br << endl;

    return 0;
}
Wyniki:
std::_List_iterator<double> 7.1
int& 879
int& 879
int 54

5 lutego 2013

Deklaracja zmiennych i trochę o const/constexpr

Najpierw małe przypomnienie, jak wygląda deklaracja zmiennych w C++. Rzadko stosuję i spotykam deklaracje zmiennych jakiegoś typu po przecinku, stąd czytając książkę, sam byłem zaskoczony, co tak naprawdę znaczył ten kod.
int i, *ip = nullptr; // i jest typu int, ip jest wskaźnikiem na int.
int* p1, p2;          // p1 jest wskaźnikiem na int, p2 jest typu int.
int* a, &b = *ip;     // a jest wskaźnikiem na int, b jest referencją na wartość 
                      // wskazywaną przez ip.
A teraz trochę o const. Zawsze jest to dla mnie mylące, przynajmniej po dłuższym okresie czasu, gdy wiedza powolutku wyparowuje.
const int c = 8;  // c jest stałą typu int, jego wartość nie może być zmieniona.
const int &r = c; // ok, referencja na stały obiekt typu int - mówimy co będziemy mogli 
                  // robić przy  pomocy referencji - odwołując się do r nie damy rady 
                  // zmodyfikować c (chociaż sam w sobie nie jest const).
int &r2 = c;      // error, referencja na niestały obiekt.
Nie ma czegoś takiego jak stałe referencje (z technicznego punktu widzenia), są tylko referencje na stałe. Ponieważ nie można zmusić referencji by wskazywała na inny obiekt w jakimś sensie wszystkie referencje są stałe.
double dval = 3.14;
const double *pt = &dval; // ok (tak jak wyżej), ale nie możemy zmieniać dval, przy pomocy pt
A teraz co powstanie, gdy const znajdzie się w różnych miejscach? Pewne oznaczenia - cytując za książką:
Używamy terminu top-level const do oznaczenia, że sam wskaźnik jest stały. Kiedy wskaźnik może wskazywać na stały obiekt, odnosimy się do tego jako low-level const
int i = 0;
const int c1 = 88;        // c1 jest stałym obiektem typu int
int const c2 = 99;        // (TO SAMO) c2 jest stałym obiektem typu int

int *const p1 = &i;       // p1 jest stałym wskaźnikiem na obiekt typu int (top-level const).
const int ci = 42;        // ci jest stałym obiektem typu int (top-level const).
const int *p2 = &ci;      // p2 jest wskaźnikiem na stały obiekt typu int (low-level).
int const *p3 = &ci;      // (TO SAMO) p3 jest wskaźnikiem na stały obiekt typu int.
const int *const p4 = p2; // p3 jest stałym wskaźnikiem na stały obiekt typu int
                          // (prawy const jest top-level; lewy nie)
const int &r = i;         // referencja na stały obiekt typu int.
                          // const w typach referencyjnych zawsze jest low-level.
W odróżnieniu od referencji, wskaźniki są obiektami, więc mogą być const, co znaczy, że po ustawieniu w momencie inicjalizacji nie mogą na nic innego wskazywać.

A teraz o nowościach. const expression jest wyrażeniem, którego wartość nie może zostać zmieniona, i dla którego wartość powinna być (ale nie musi) obliczona podczas kompilacji. W C++11 pojawia się nowy modyfikator constexpr, który zbada, czy tak rzeczywiście się stanie. Funkcje z constexpr muszą być na tyle proste by kompilator poradził sobie z nimi podczas kompilacji, ale w kolejnych wersjach standardu konstrukcje będą stawać się coraz bardziej skomplikowane (pętle, algorytmy STL, kontenery itp.). W zamyśle constexpr ma stać się alternatywą dla metaprogramowania w szablonach.
#include <iostream>
#include <stdio.h>

using namespace std;

constexpr int get_size()
{
    return 6;
}

int main()
{
    constexpr int sz = get_size() + 80; // ok, pod warunkiem, że funkcja też jest constexpr.
    printf("%d", sz);
    return 0;
}
I podgląd tego co wyprodukował kompilator (g++ -std=c++17 -S -masm=intel main.cpp). Jak widać w pliku binarnym pojawia się już obliczona wartość (80+6=86).
main:
.LFB1824:
    .cfi_startproc
    endbr64
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov     rbp, rsp
    .cfi_def_cfa_register 6
    sub     rsp, 16
    mov     DWORD PTR -4[rbp], 86
    mov     esi, 86
    lea     rdi, .LC0[rip]
    mov     eax, 0
    call    printf@PLT
    mov     eax, 0
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc