2013-07-20 15 views
7

Potrzebuję szybkiej, prostej funkcji skrótu, która tworzy unikalny identyfikator dla pary wartości uint32_t - tak więc ta sama wartość mieszania dla (2,7) i (7,2).Funkcja skrótu przemiennego dla par wartości uint32_t

Każdy pomysł?

+0

Nie możesz po prostu dodać wyników 'std :: hash' dla' pair.first' i 'pair.second'? –

+2

Utwórz uint64, przesuwając bity na mniejsze (lub większe) z dwóch liczb i dodając drugie. Następnie wystarczy mieszać 64-bitowy int. (Alternatywnie, użyj funkcji mieszania, skopiuj parę, a następnie zamień elementy, aby zagwarantować porządek i zastosuj prawdziwą mieszankę dla tej pary) –

+0

@ DavidRodríguez-dribeas: Tak, tymczasem wymyśliłem rozwiązanie, dzięki. Miałem już dla niego coś bitshift, ale przypomniałeś mi, że porównanie to magia. – plasmacel

Odpowiedz

4

Aby odpowiedzieć na moje własne pytanie, rozwiązaniem jest:

uint64_t hash(uint32_t x, uint32_t y) 
{ 
    const uint64_t a = static_cast<uint64_t>(x); 
    const uint64_t b = static_cast<uint64_t>(y); 

    if (x < y) return (b << 32) | a; 
    else return (a << 32) | b; 
} 

które mogą być ulepszone do branchless wersji

uint64_t hash(uint32_t x, uint32_t y) 
{ 
    const uint64_t a = static_cast<uint64_t>(x); 
    const uint64_t b = static_cast<uint64_t>(y); 

    const uint64_t h0 = (b << 32) | a; 
    const uint64_t h1 = (a << 32) | b; 

    return (x < y) ? h0 : h1; // conditional move (CMOV) instruction 
} 

Metody te są doskonałe funkcje skrótu - gwarantują one zera kolizji. Mają jednak tę wadę, że nie można wartości mieszania powyżej 2^32 - 1.

+1

Podoba mi się pomysł zmiany, jest to naturalne i nie ma potrzeby wykazywania wyjątkowości. Jeśli chcesz zajmować się wartościami powyżej 2^32, możesz zwrócić ciąg jako unikalny identyfikator, w którym zarezerwujesz specjalny symbol, aby oddzielić dwie części hasha (zmiana reprezentacji bazy na większą niż 10 to także dobry pomysł) – pkacprzak

+0

" Zmiana bazy reprezentacji na większą niż 10 to także dobry pomysł "- jak to masz na myśli? – plasmacel

+1

jeśli reprezentujesz liczbę jako łańcuch, możesz użyć większego alfabetu niż {0,1, ..., 9}, np. system szesnastkowy lub nawet większy. Im większa podstawa, tym krótsza reprezentacja. – pkacprzak

2
constexpr uint32_t hash_max = ...;  

constexpr uint32_t commutative_hash(uint32_t i, uint32_t j) { 
    return (i*j + (i*i)*(j*j) + (i*i*i)*(j*j*j)) % hash_max; 
}; 

Dodatkowe nawiasy są dla kompilatora - łatwiej będzie zoptymalizować to wyrażenie.

Nie używaj żadnych instrukcji warunkowych (lub std::max/std::min) , które przerywają procesor (i jest wolny), jeśli chcesz wykonać szybką funkcję.

+0

Dzięki, ale to jest bardzo powolne ze względu na wiele multiplikacji. Zobacz moją odpowiedź. – plasmacel

+1

Mogę się założyć, że moja funkcja jest szybsza. Czy porównałeś swoją funkcję z moją? Dodałem wyjaśnienie w odpowiedzi, dlaczego jest ono szybsze. –

+0

@LeonidVolnitsky +1 dla właściwego wyjaśnienia. Instrukcja warunkowa może w niektórych przypadkach zmylić prognozę rozgałęzień. – NumberFour

Powiązane problemy