2009-09-22 13 views
6

UPDATE:Jak przekonwertować alfanumeryczny numer telefonu do cyfry

Ostateczna wersja mojego narzędzia wygląda następująco:

StringBuilder b = new StringBuilder(); 

for(char c : inLetters.toLowerCase().toCharArray()) 
{ 
    switch(c) 
    { 
    case '0':           b.append("0"); break; 
    case '1':           b.append("1"); break; 
    case '2': case 'a': case 'b': case 'c':   b.append("2"); break; 
    case '3': case 'd': case 'e': case 'f':   b.append("3"); break; 
    case '4': case 'g': case 'h': case 'i':   b.append("4"); break; 
    case '5': case 'j': case 'k': case 'l':   b.append("5"); break; 
    case '6': case 'm': case 'n': case 'o':   b.append("6"); break; 
    case '7': case 'p': case 'q': case 'r': case 's': b.append("7"); break; 
    case '8': case 't': case 'u': case 'v':   b.append("8"); break; 
    case '9': case 'w': case 'x': case 'y': case 'z': b.append("9"); break; 
    } 
} 

return builder.toString(); 

oryginalne pytanie:

I Podjęłam prostą czynność polegającą na przekonwertowaniu alfanumerycznego numeru telefonu na ciąg cyfr. Na przykład 1-800-HI-HAXOR stanie się 1-800-44-42967. Moją pierwszą próbą było stworzenie paskudnego oświadczenia, ale chciałbym, aby było to bardziej eleganckie i wydajne rozwiązanie. Oto, co mam:

for(char c : inLetters.toLowerCase().toCharArray()) 
{ 
    switch(c) 
    { 
    case '0':           result+="0"; break; 
    case '1':           result+="1"; break; 
    case '2': case 'a': case 'b': case 'c':   result+="2"; break; 
    case '3': case 'd': case 'e': case 'f':   result+="3"; break; 
    case '4': case 'g': case 'h': case 'i':   result+="4"; break; 
    case '5': case 'j': case 'k': case 'l':   result+="5"; break; 
    case '6': case 'm': case 'n': case 'o':   result+="6"; break; 
    case '7': case 'p': case 'q': case 'r': case 's': result+="7"; break; 
    case '8': case 't': case 'u': case 'v':   result+="8"; break; 
    case '9': case 'w': case 'x': case 'y': case 'z': result+="9"; break; 
    } 
} 

Dzięki!

+2

Pomimo tego, o ile bardziej "elegancka" większość ludzi myśli, że może to zrobić, twoje "oświadczenie na temat nieprzyjemnej zmiany" jest dużo łatwiejsze do zrozumienia. Ironiczny :) – Nippysaurus

+0

Jeśli chcesz to poprawić, masz za dużo czasu. ;) – simon

Odpowiedz

9

Instrukcja przełącznika nie jest tak źle. Twój algorytm jest liniowy w odniesieniu do długości numeru telefonu. Kod jest czytelny i łatwy do sprawdzenia przez inspekcję. Nie zadzierałbym tego, poza dodaniem przypadku default do obsługi błędów. (Nie jestem programista Java, więc wybacz mi, jeśli to się nazywa coś innego.)

Jeśli masz zrobić to szybciej, wstępnie zainicjowana tabeli indeksowanej charakteru byłoby uniknąć porównań poza podstawowe sprawdzania błędów. Można nawet uniknąć konwersji przypadku przez powielenie wartości w tabeli (digit['A'] = digit['a'] = "2";). Koszt zainicjowania tabeli zostanie zamortyzowany w stosunku do całkowitej liczby konwersji.

+1

To też by działało, ale ja wahałbym się przedwcześnie optymalizować w taki sposób. * jest amortyzowany w stosunku do wszystkich konwersji, ** ** jest trwałe i prawdopodobnie jest całkowitym marnowaniem wysiłku dla oszczędności czasu, które możesz zyskać .. –

+0

Podoba mi się, łatwiej czytać i dobrze się spisuje. Nie wydaje mi się, żeby 100 bajtów, o które prosiła tabela, byłby wskaźnikiem w wielu środowiskach. – erickson

+0

Och, nie uważałbym tego za korek w prawie każdym * środowisku * Jestem po prostu przeciw niepotrzebnemu wzdęciu w małych instancjach, ponieważ się sumują. Uważam, że programiści, którzy chcą spędzić tu i tam 100 bajtów na obiektach trwałych przez cały czas trwania programu, w końcu zdradzą sobie, dlaczego ich program kończy się na używaniu zbyt dużej ilości pamięci i nie mają wielkiego pomysłu, jak zmniejszyć ich ilość. ślad stopy. Wciąż jest prawdą, że rejestry procesorów są znacznie szybsze niż pamięć RAM, a ładowanie czegoś do pamięci dla wygody może zaszkodzić wydajności innych części aplikacji. Dlatego najpierw zmień. –

0

oświadczenia Przełączanie się skompilowany do podobnej postaci if-else, (każda instrukcja case jest zasadniczo if (c == '...') testy w przebraniu), więc chociaż to jest wizualnie bardziej kompaktowy niż kaskadowych, jeśli jest to test dla każdego znaku, nie może lub może nie być prawdziwą korzyścią z wydajności.

Można potencjalnie usprawnić go, eliminując niektóre porównania. Klucz jest taki, że char jest typem całkowitym (dlatego można włączyć char), aby można było korzystać z operatorów porównania numerycznego. i „aAssuming ciąg inLetters zawiera tylko znaki alfanumeryczne, to powinno działać ... (Wszystkie inne znaki będą przechodzić przez niezmienione.)

String result = ""; 
for (char c : letters.toLowerCase().toCharArray()) { 
    if  (c <= '9') result += c; 
    else if (c <= 'c') result += "2"; 
    else if (c <= 'f') result += "3"; 
    else if (c <= 'i') result += "4"; 
    else if (c <= 'l') result += "5"; 
    else if (c <= 'o') result += "6"; 
    else if (c <= 's') result += "7"; 
    else if (c <= 'v') result += "8"; 
    else if (c <= 'z') result += "9"; 
    else    result += c; 
} 

Znaki interesujące mają wartości szesnastkowe:«0»= 0x30” 9 '= 0x39, "a" = 0x61, a "z" = 0x7a.

Edit: Lepiej praktyką jest stosowanie StringBuilder i append() aby utworzyć ciąg, ale dla małych strun to nie jest prawdopodobne, aby być znacznie szybciej. (Amdahl's Law demonstruje, że rzeczywiste przyspieszenie, jakiego można się spodziewać po kodzie optymalizacyjnym, jest ograniczone przez procent czasu faktycznie spędzonego w tym kodzie.) Użyłem tylko połączonych ciągów, aby uczynić algorytm zrozumiałym dla OP.

+3

To jest złe. Instrukcje przełączania są kompilowane do przeskoków opartych na przesunięciach lub drzewku binarnym wyszukiwania, w zależności od tego, jak gęstą są stałe. – erickson

+1

Dane szczegółowe mogą w dużej mierze zależeć od kompilatora i maszyny wirtualnej. Nie interesuje mnie tasowanie kodu bajtowego - niezależnie od tego, w jaki sposób układane są instrukcje maszyny, chodzi o to, że zmniejszenie liczby porównań spowoduje szybszy kod. Odzwierciedlając poziom efektywności wykonawczej, takie podejście można argumentować za "bardziej eleganckim" rozwiązaniem. (Zależy od tego, jak dobrze zrozumie się zmienne char, oczywiście.) –

+2

Szczerze mówiąc, oryginał jest dla mnie bardziej czytelny. Nie muszę w ogóle o tym myśleć.Aby to zrozumieć, muszę recytować alfabet, aby ustalić, która klauzula dotyczy określonej postaci. Wolę instrukcję switch dla jasności. – tvanfosson

5

Użyj Map, gdzie klawisze są literami i cyframi, a wartość jest numerem na klawiaturze. (Tak więc każdy numer klawiatury będzie indeksowany trzema lub czterema literami i jedną cyfrą).

Map<Character, Character> keypad = new HashMap<Character, Character>(); 
... 
StringBuilder buf = new StringBuilder(inLetters.length()); 
for (int idx = 0; idx < inLetters.length(); ++idx) { 
    Character ch = keypad.get(inLetters.charAt(idx)); 
    if (ch != null) 
    buf.append(ch); 
} 

Aktualizacja: Byłem ciekaw, czy tabeli odnośników ręcznie kodowane będzie działać lepiej niż gęsty zestaw switch przypadkach. W moim przypadkowym testów, znalazłem poniższy kod, żeby być najszybciej mogłem wymyślić:

private static final char[] lut = 
    ":;<=>[email protected][\\]^_`22233344455566677778889999".toCharArray(); 

    private static final char min = lut[0]; 

    String fastest(String letters) 
    { 
    int n = letters.length(); 
    char[] buf = new char[n]; 
    while (n-- > 0) { 
     int ch = letters.charAt(n) - min; 
     buf[n] = ((ch < 0) || (ch >= lut.length)) ? letters.charAt(n) : lut[ch]; 
    } 
    return new String(buf); 
    } 

Niespodziewanie był ponad dwukrotnie szybciej niż podobny kod za pomocą instrukcji switch (który skompilowany z instrukcją tableswitch). To było dla zabawy, ale na moim laptopie, działającym w jednym wątku, mogłem przekonwertować 10 milionów 10-literowych "liczb" w około 1,3 sekundy. Byłem bardzo zaskoczony, ponieważ jak rozumiem, tableswitch działa w zasadzie w ten sam sposób, ale spodziewałem się, że będzie szybszy, ponieważ jest to instrukcja JVM.

Oczywiście, jeśli nie otrzymuję zapłaty tylko za każdą z nieograniczonych ilości numerów telefonów, które mógłbym przekonwertować, nigdy nie napisałbym takiego kodu. Przełącznik jest znacznie bardziej czytelny, działa dobrze jak jest, i prawdopodobnie uzyska dodatkową poprawę wydajności w niektórych przyszłych maszynach JVM.

Najdalsze i największe ulepszenie oryginalnego kodu pochodzi z użycia StringBuilder zamiast łączenia łańcuchów, a to nie wpływa negatywnie na czytelność kodu. Użycie charAt zamiast konwertowania danych wejściowych na char[] powoduje również, że kod jest prostszy i łatwiejszy do zrozumienia, co poprawia także wydajność. Wreszcie, dołączenie literówek char zamiast String literałów ('1' zamiast "1") jest ulepszeniem wydajności, które nieco poprawia czytelność.

+0

To podejście jest podobne do @ Adrian, ale obejmuje również niewidoczne rozpakowywanie z postaci do char i naprawdę nie oszczędza zbyt wiele na wydajności. –

+0

Został zaprojektowany z myślą o jasności i łatwości konserwacji, a nie dla surowej wydajności ... mimo że został zgadnięty, mógłbym ryzykować, że jest szybszy niż kaskadowe if-elses. Rozwiązanie Adriana powinno dać najlepszą wydajność i lepszą klarowność niż przełącznik lub, jeśli nie. – erickson

+0

Z pewnością jest to porównywalne lub nawet szybsze (nie mierzyłem), ale zapoznanie się z mapą * nie * wymaga załadowania jej do pamięci podręcznej (która jest szybka, ale nie darmowa) i wywołania funkcji hash() na co najmniej 2 obiektach znaków. Prognozowanie gałęzi CPU/JVM jest również dość szybkie, więc prawdopodobnie są bardzo bliskie w praktyce. –

2

Jeśli chcesz rozwiązanie, które nie zmuszają do wyliczyć wszystkie litery, można zrobić coś takiego:

char convertedChar = c; 
if (Character.isLetter(c)) { 
    //lowercase alphabet ASCII codes: 97 (a)-122 (z) 
    int charIndex = ((int)c) - 97; 
    //make adjustments to account for 's' and 'z' 
    if (charIndex >= 115) { //'s' 
     charIndex--; 
    } 
    if (charIndex == 121) { //'z'-1 
     charIndex--; 
    } 
    convertedChar = (char)(2 + (charIndex/3)); 
} 
result += convertedChar; 
6

Można to zrobić przy użyciu Apache Commons Lang StringUtils, co następuje:

String output = StringUtils.replaceChars(StringUtils.lowerCase(input), 
        "abcdefghijklmnopqrstuvwxyz", 
        "22233344455566677778889999"); 

Zakładając prędkość nie jest głównym problemem, oczywiście, i chcesz kompaktowe rozwiązanie;)

+0

Od kiedy to "efektywne" nie oznacza prędkości? –

+1

To naprawdę fajne rozwiązanie! Jest zdecydowanie kompaktowy, ale szukam czegoś bardziej wydajnego. Nie sądzę, że chcę załadować słoik Apache Commons dla tej konwersji. –

+1

@Quinn efficient może oznaczać mniejszą ilość kodu. W tym kontekście eleganckie i efektywne lepiej pasowałyby do siebie. Efektywność może również oznaczać pamięć. Biorąc pod uwagę, że OP nie korzystał z StringBuilder, prędkość nie wydaje się być najwyższym priorytetem. W każdym razie, w zależności od tego, co robi StringUtils, prędkość może być dobra, a ślad pamięci nie musi się zwiększać, może wykonywać swoją pracę za pomocą prymitywów i przekazywanych referencji. – Yishai

4

powiesz po prostu:

String convert(String inLetters) { 
     String digits = "22233344455566677778889999"; 
     String alphas = "abcdefghijklmnopqrstuvwxyz"; 
     String result = ""; 
     for (char c : inLetters.toLowerCase().toCharArray()) { 
      int pos = alphas.indexOf(c); 
      result += (pos == -1 ? c : digits.charAt(pos)); 
     } 
     return result; 
    } 
+2

Poza tym użyłbym StringBuilder dla przetłumaczonego wyniku. – camickr

+0

Zarówno mniej wydajne i mniej czytelne, IMO. –

1

Jeśli uruchomisz to 10^9 razy w ciasnej pętli i ctrl-break kilka razy, mój zakład jest taki, że prawie za każdym razem, gdy będzie w głębi klasy smyczków próbujących wykonać jedną z tych niewinnych ... szukając operatorów "+ =".

+1

Bardzo prawdziwe - w rzeczywistej implementacji utknąłem z instrukcją switch, ale użyłem zamiast tego konstruktora stringów. –

+0

Tak. Nawet wtedy założę się, że czas spędzony w kodzie przełączania nie jest dużą częścią. –

Powiązane problemy