2016-01-07 2 views
6

Jeśli mam ciąg znaków, taki jak 1 2 3 i identyfikuję pozycję podciągu zawierającego double, jak mogę przeanalizować go bezpośrednio z podciągu bez tworzenia tymczasowego ciągu?Podkład parse do podwójnie bezpośrednio

Na przykład mógłbym zrobić System.Double.Parse(str.Substring(0, 1)), ale utworzyłoby to tymczasowy ciąg, który jest powolny i niepotrzebny. Czy możliwe jest parsowanie double bezpośrednio z części oryginalnego napisu?

EDIT

Eric Lippert zakwestionował moje motywy tutaj, stwierdzając, że "Małe ciągi są tanie". Motywacja do tego bierze się z tego, że robię to samo dla parsowania int i widzę ogromną poprawę wydajności, ponieważ, jak widać, małe struny nie są tak tanie.

Oto funkcja, która lexes sekwencję wskazówki poprzez tymczasowych ciągów:

let lex f (s: string) = 
    let rec inside i0 (s: string, i) = 
    if i = s.Length then 
     f (s.Substring(i0, i-i0) |> System.Int32.Parse) 
    else 
     let c = s.[i] 
     if '0'<=c && c<='9' then 
     inside i0 (s, i+1) 
     else 
     f (s.Substring(i0, i-i0) |> System.Int32.Parse) 
     outside (s, i) 
    and outside (s: string, i) = 
    if i < s.Length then 
     let c = s.[i] 
     if '0'<=c && c<='9' then 
     inside i (s, i) 
     else 
     outside (s, i+1) 
    outside (s, 0) 

ten trwa 2.4s do lex 15,625,000 ints z ciągiem.

Tutaj jest wersja, że ​​unika tymczasowych ciągów:

let lex f (s: string) = 
    let rec inside n (s: string, i) = 
    if i = s.Length then f n else 
     let c = s.[i] 
     if '0'<=c && c<='9' then 
     inside (10*n + int c - int '0') (s, i+1) 
     else 
     f n 
     outside (s, i) 
    and outside (s: string, i) = 
    if i < s.Length then 
     let c = s.[i] 
     if '0'<=c && c<='9' then 
     inside 0 (s, i) 
     else 
     outside (s, i+1) 
    outside (s, 0) 

Dzieje 0.255s ponad 9x szybciej niż rozwiązanie, które wykorzystuje tymczasowe struny!

Nie widzę żadnego powodu, dla którego wartości pływające powinny być inne. W związku z tym, nie zapewniając możliwości analizowania float z podłańcucha. NET pozostawia rząd wielkości w wydajności na stole. Robię wiele naukowych obliczeń i często muszę usuwać duże ilości danych, zwłaszcza przy starcie, więc naprawdę nie chcę, aby wydajność była tak silna jak wiatr.

+4

Wygląda na to, że mam do czynienia z ekstremalną mikrooptymalizacją. Będziesz potrzebować biblioteki lub napisać pełnoprawny podwójny parser, co nie jest prostym zadaniem. – Rob

+4

Czy rzeczywiście masz tu określony problem z wydajnością? Małe struny są tanie. Oczywiście można napisać lekser, który kopiuje tylko pojedyncze postacie. –

+2

@EricLippert: Zaktualizowałem to pytanie za pomocą kodu testu porównawczego do analizowania danych bez tworzenia tymczasowych plików i jest ponad 9 razy szybsze. Zakładam, że parsowanie parsowania przyniosłoby podobnie ogromny wzrost wydajności. Dość powiedzieć, nie powiedziałbym, że "małe struny są tanie". –

Odpowiedz

-2

To jest najlepsze, co możesz zrobić

static void Main(string[] args) 
{ 
    string input = "1 2 3"; 
    double[] output = input.Split(new char[] {' '},StringSplitOptions.RemoveEmptyEntries).Select(x => double.Parse(x)).ToArray(); 
} 
+2

To dodaje dodatkowe alokacje 3 tablic, 'IEnumerable' i' Func'. Biorąc pod uwagę, że OP nie toleruje nawet pojedynczego przydziału napisów, nie sądzę, żeby pasowało to do wniosku. – latkin

+0

Co gorsza, długo utrzymywałeś wszystkie tymczasowe elementy, utrzymując je w szyku, aby przetrwały dwa pokolenia za każdym razem, płacąc za oznaczenia, ewakuację i aktualizacje wskaźnika, aby rozwiązanie było prawdopodobnie 40 razy wolniejsze, niż to konieczne. –

+0

Wiem, ale prośba dotyczyła "parsowania bezpośrednio". Unikanie zmiennych tymczasowych nieuchronnie zwiększa prędkość dzięki kompilatorowi optymalizującemu. Przejście na linq nie jest najskuteczniejszą metodą. Jeśli kolumny są stałe, szerokość podciągu prawdopodobnie byłaby bardziej wydajna niż metoda Split(), ale jeśli kolumny nie są ustalone, Split() byłby bardziej wydajny. – jdweng

2

Tak, myślę, że jest to całkowicie wykonalne. Możesz napisać własną funkcję do parsowania, możesz nawet oprzeć ją na actual kodzie źródłowym Double.Parse(). Ten kod nie wygląda na duży i przerażający i myślę, że możesz zoptymalizować go jeszcze bardziej do swoich potrzeb.

+3

[Faktyczna metoda robocza] (http://referencesource.microsoft.com/#mscorlib/system/number.cs,04291cc3a0b10032) to 200 linii na gęstym, niebezpiecznym C# z głęboko zagnieżdżoną pętlą i rozgałęzieniem. I to poza pomocniczymi typami/metodami pomocnika. Nie chciałbym tego tak lekceważyć. (Nie wspominając już o wymogu '/ unsafe' oznacza, że ​​nasz zespół nie jest już weryfikowalny i ma wiele innych potencjalnie niesmacznych ograniczeń). – latkin

+0

Dla niektórych typów zadań może być tego wart. Myślę, że brak podciągu nie jest dużą optymalizacją, ale sama funkcja analizowania ma wielki potencjał optymalizacji. –

+0

"Wymóg/niebezpieczny". Eh? Zrobili to z tego powodu, że użyli niebezpiecznego kodu, a następnie zaczęli go owijać w interfejs API, który zmusza do kopiowania podciągów, więc jest miażdżąco wolny. Orzechy! –

1

Można analizować ciąg cyfr po cyfrze, coś takiego:

static double CustomConvertToDouble(string input, int startIndex, int length) 
{ 
    double result = 0d; 
    int lastDigitIndex = startIndex + length - 1; 
    int power = 0; 
    for (int i = lastDigitIndex; i >= startIndex; i--) 
    { 
     int digit = (input[i] - '0'); 
     result += (Math.Pow(10, power++)) * digit; 
    } 
    return result; 
} 

Zastosowanie:

string tmp = "1 2 3"; 
double result = CustomConvertToDouble(tmp, 0, 1); 
Console.WriteLine(result); // 1 

Można rozwinąć to wziąć punkty dziesiętne itp pod uwagę.

Ale naprawdę wątpię, czy normalny sposób może być wąskim gardłem wydajności i chciałbym się dowiedzieć, dlaczego chcesz rozwiązać problem. Jeśli ten fragment kodu jest naprawdę tak ważny dla wydajności, może najlepszą drogą jest pisanie go w innym języku?

+0

Myślę, że byłoby to wiele razy wolniej niż "System.Double.Parse (str.Substring (0, 1))" – Rob

+0

Niestety, nie widziałem tej części odpowiedzi, usunąłem z komentarza – Rob

-1
for (int x = 0; x < input.Length; x++) 
{ 
    if(input[x] != ' ') 
     Console.WriteLine(Double.Parse(input[x].ToString())); 
} 

Nie tworzy żadnych dodatkowych obiektów podlegających wymianie, ale Double.Parse zawiera tylko łańcuchy, dlatego wymagany jest toString.

0

Jeśli tylko szukasz jednocyfrowych, jest to dość łatwe:

let readDigit s i = 
    let getDigit x = 
     if '0' <= x && x <= '9' 
     then byte x - 48uy // byte value of '0' 
     else failwith "Not a digit" 
    s |> Seq.item i |> getDigit |> double 

To F # implementacja wykorzystuje że string realizuje char seq, a wartość char mogą być konwertowane do wartości byte.

Wątpię, czy jest to szybsze niż użycie Double.Parse(str.Substring(0, 1)).

+0

Dlaczego anonimowy downwise? –

+0

To nie ja. Jednak ten kod będzie bardzo powolny! :-) –