2009-02-17 5 views
6

Poniższy ciąg próbował znaleźć różnicę między dwoma ciągami. Ale to jest strasznie powolny, gdyż iteracji długość sznurka:Obsługa bitu dla znalezienia różnicy łańcuchów

#include <string> 
#include <vector> 
#include <iostream> 
using namespace std; 


int hd(string s1, string s2) { 
    // hd stands for "Hamming Distance" 
    int dif = 0; 

    for (unsigned i = 0; i < s1.size(); i++) { 
     string b1 = s1.substr(i,1); 
     string b2 = s2.substr(i,1); 

     if (b1 != b2) { 
      dif++; 
     } 
    } 

    return dif; 
} 

int main() { 

    string string1 = "AAAAA"; 
    string string2 = "ATATT"; 
    string string3 = "AAAAA"; 

    int theHD12 = hd(string1,string2); 
    cout << theHD12 << endl; 

    int theHD13 = hd(string1,string3); 
    cout << theHD13 << endl; 
} 

istnieje szybka alternatywa to zrobić? W Perlu możemy mieć następujące podejście:

sub hd { 
    return ($_[0]^$_[1]) =~ tr/\001-\255//; 
} 

który much2 szybciej niż iteracji pozycję.

Zastanawiam się, co jest odpowiednikiem tego w C++?

+0

Jezu, nic dziwnego, że powolny, kiedy przydzielenie nowych ciągów jedynie trzymać pojedyncze 'char's, które można uzyskać od' operator [] ', przy każdym indeksie. –

Odpowiedz

8

Zabawa z STL:

#include <numeric> //inner_product 
#include <functional> //plus, equal_to, not2 
#include <string> 
#include <stdexcept> 

unsigned int 
hd(const std::string& s1, const std::string& s2) 
{ 
    // TODO: What should we do if s1.size() != s2.size()? 
    if (s1.size() != s2.size()){ 
     throw std::invalid_argument(
      "Strings passed to hd() must have the same lenght" 
    ); 
    } 

    return std::inner_product(
     s1.begin(), s1.end(), s2.begin(), 
     0, std::plus<unsigned int>(), 
     std::not2(std::equal_to<std::string::value_type>()) 
    ); 
} 
+0

7 lat później Samaras ma pytanie: czy możesz wyjaśnić? :) Muszę być bardzo głupi, aby pierwszy zapytać! :) – gsamaras

+2

@gsamaras: W wersji podstawowej inner_product oblicza sumę iloczynu dwóch zakresów, A i B: A [0] * B [0] + A [1] * B [1] + ... W wersji uogólnionej (tutaj użytej) dwie operacje (dodawanie i mnożenie) są wykonywane przez wywołującego. Chcemy, aby liczba par elementów była inna, więc nadal chcemy dodać pierwszą operację (std :: plus), ale chcemy, aby druga operacja była "nie równa się" (std :: not (std :: equal_to)) zamiast mnożenia. –

+0

Widzę Eric, dzięki, w tym [pytanie] (http://stackoverflow.com/questions/40773463/how-to-store-binary-data-when-you-only-care-about-speed), porównanie twojej funkcji i pętli for i jeśli! podejście jest wykonywane przy użyciu różnych struktur danych. – gsamaras

2

oczywistych punktów, które mogłyby sprawiają, że szybciej:

  1. Przepuścić ciągi jako odniesienia const, a nie według wartości
  2. Użyj operatora indeksowania [], aby uzyskać znaki, a nie wywołanie metody
  3. kompilacji z optymalizacją na
+0

Jak "kompilujesz z optymalizacją na"? – neversaint

+0

W dużym stopniu zależy od używanego kompilatora, obawiam się. Jeśli używasz GCC na przykład, użyj opcji -On, gdzie n jest cyfrą kontrolującą poziom optymalizacji. – unwind

10

spróbować zastąpić pętli przez:

for (unsigned i = 0; i < s1.size(); i++) { 
    if (b1[i] != b2[i]) { 
      dif++; 
    } 
} 

To powinno być o wiele szybsze, ponieważ nie są tworzone żadne nowe ciągi.

+0

lmao, nie zauważyłem, że przydzielano 2 x nowe ciągi w każdym indeksie, aby przechowywać kopie "char" ... –

3

Stosować iteratory:

int GetHammingDistance(const std::string &a, const std::string &b) 
{ 
    // Hamming distance is not defined for strings of different lengths. 
    ASSERT(a.length() == b.length()); 

    std::string::const_iterator a_it = a.begin(); 
    std::string::const_iterator b_it = b.begin(); 

    std::string::const_iterator a_end = a.end(); 
    std::string::const_iterator b_end = b.end(); 

    int distance = 0; 
    while (a_it != a_end && b_it != b_end) 
    { 
     if (*a_it != *b_it) ++distance; 
     ++a_it; ++b_it; 
    } 

    return distance; 
} 
3

Choice 1: Modyfikacja oryginalnego kodu być tak sprawna jak possable.

int hd(string const& s1, string const& s2) 
{ 
    // hd stands for "Hamming Distance" 
    int dif = 0; 

    for (std::string::size_type i = 0; i < s1.size(); i++) 
    { 
     char b1 = s1[i]; 
     char b2 = s2[i]; 

     dif += (b1 != b2)?1:0; 
    } 

    return dif; 
} 

Druga opcja wykorzystuje niektóre algorytmy STL do podnoszenia ciężkiego.

struct HammingFunc 
{ 
    inline int operator()(char s1,char s2) 
    { 
     return s1 == s2?0:1; 
    } 
}; 

int hd(string const& s1, string const& s2) 
{ 
    int diff = std::inner_product(s1.begin(),s1.end(), 
            s2.begin(), 
            0, 
            std::plus<int>(),HammingFunc() 
           ); 
    return diff; 
} 
1

Używasz ciągów.

Jak wyjaśniono tutaj The hunt for the fastest Hamming Distance C implementation jeśli można użyć char * moi experiements stwierdzić, że dla GCC 4.7.2 w sprawie Intel Xeon X5650 najszybszym funkcji ogólnego przeznaczenia odległość Hamminga obliczenie dla małych strun (tablice char) wynosi:

// na = length of both strings 
unsigned int HammingDistance(const char* a, unsigned int na, const char* b) { 

    unsigned int num_mismatches = 0; 
    while (na) { 
     if (*a != *b) 
      ++num_mismatches; 

     --na; 
     ++a; 
     ++b; 
    } 

    return num_mismatches; 
} 

Jeśli problem pozwala ustawić górny limit na odległość, tak, że nie dbam o większych odległościach i granica ta jest zawsze mniejsza niż długość struny, powyższy przykład może być furhterly zoptymalizowana do:

// na = length of both strings, dist must always be < na 
unsigned int HammingDistance(const char* const a, const unsigned int na, const char* const b, const unsigned int dist) { 

    unsigned int i = 0, num_mismatches = 0; 

    while(i <= dist) 
    { 
     if (a[i] != b[i]) 
      ++num_mismatches; 

     ++i; 
    } 

    while(num_mismatches <= dist && i < na) 
    { 
     if (a[i] != b[i]) 
      ++num_mismatches; 

     ++i; 
    } 

    return num_mismatches; 
} 

Nie jestem pewien, czy const ma nic odnośnie prędkości, ale używam go tak czy inaczej ...

+0

(1) Wydajność zależy między innymi od kompilatora * i * procesora. "To jest najszybszy" jest w najlepszym wypadku mylące i polega na tym, że kod jest kompilowany dokładnie tak, jak robił to twój kompilator - co nie jest wymagane przez żadne standardy. (2) Kochaj, jak ignorujesz fakt, że rozmówca musi znaleźć długości. Jeśli ten kod przeszkadza, jego prędkość zostanie zmniejszona o połowę. (3) C nie jest C++. Twoje "łańcuchy" nie są łańcuchami C++. Można to zrobić za pomocą łańcuchów C++ bez obniżania wydajności. (4) Poważnie? Wskrzesiłeś 4-letnie pytanie na ten temat? – cHao

+0

(1) Gcc 4.7.2 dla Intel Xeon X5650. (2-3-4 itd.) "Tak" powiedziałem, ponieważ już rozpocząłem nowy wątek, który jest uważany za duplikat tego. Ta odpowiedź jest dobrą odpowiedzią na mój oryginalny wątek, na który nie umiem odpowiedzieć, więc odpowiadam tutaj na mój wątek. Jeśli ta odpowiedź nie pasuje, oznacza to, że mój wątek nie jest duplikatem tego. Czy mogę wrzucić tę odpowiedź do mojego "duplikatu" w inny sposób? –

+0

I coś więcej. Autor powiedział, że jego kod był "beznadziejnie wolny". Jednym z powodów, dla których piszę to jest zaoferowanie mu alternatywy, która polega na "pozbyciu się struny" (jeśli to możliwe) i użyciu char *. W powyższej konfiguracji różnica była ogromna, gdy przekształciliśmy wszystkie ciągi znaków na char *. Może to być rozwiązanie dla niego, by zrobić to samo. (nie zauważyłem, ile lat miał post) –