2010-12-29 16 views

Odpowiedz

7

Oto kod oparty na komentarzu Timwi'ego do odpowiedzi Nate'a. Istnieją pojedyncze litery logiczne (wyświetlane na ekranie), które składają się z więcej niż jednego rzeczywistego znaku. Odwracanie kolejności znaków zamienia te litery w bełkot.

Timwi wskazuje, że framework zapewnia TextElementEnumerator, który działa w kategoriach logicznych elementów tekstowych, a nie znaków i obsługuje poprawnie te litery. Nie słyszałem o tej klasie wcześniej, więc napisałem kod, który używa TextElementEnumerator, aby poprawnie odwrócić ciąg znaków i porównać wyniki z naiwnym odwróceniem łańcucha znaków.

open System 
open System.Globalization 

// five characters that render as three letters: "àÆ" 
let myString = "\uD800\uDC00\u0061\u0300\u00C6" 

// naive reversal scrambles the string: "Æ̀a��" 
// (note that while this produces the wrong results, 
// it probably is faster on large strings than using 
// LINQ extension methods, which are also wrong) 
let naive = String(myString.ToCharArray() |> Array.rev) 

// use a TextElementEnumerator to create a seq<string> 
// that handles multi-character text elements properly 
let elements = 
    seq { 
     let tee = StringInfo.GetTextElementEnumerator(myString) 
     while tee.MoveNext() do 
      yield tee.GetTextElement() 
    } 

// properly reversed: "Æà" 
let reversed = elements |> Array.ofSeq |> Array.rev |> String.concat "" 
+0

dzięki za drugą metodę ... jej najpiękniejszy i najprostszy sposób myślę: niech naive = String (myString.ToCharArray() |> Array.rev) – AndrewShmig

+1

sprawdź moje najnowsze rozwiązanie, znalazłem inny sposób uniknięcia ' TextElementEnumerator' który tym razem nie powoduje trafienia wydajności. –

1

Jeśli to, co robisz jest z MSDN na Enumerable.Reverse() to prawdopodobnie dostał najprostsze rozwiązanie.

Jeśli nie korzystasz z .NET 3.5 (przeczytaj LINQ (nie wiesz, czy przedtem F # było wcześniej), możesz użyć metody Array.Reverse(), ale wynikowy kod jest bardzo podobny.

Wystarczy powiedzieć, że masz najbardziej elegancki sposób na odwrócenie łańcucha, wielokrotnie używałam Enumerable.Reverse(), aby odwrócić kolejność ciągów w moich projektach. Oczywiście, jeśli konstruktor String przyjąłby IEnumerable<Char>, możemy pominąć bit .ToArray(), który moim zdaniem sprawiłby, że kod byłby nieco lepszy, ale w jego stanie, dodatkowy .ToArray() nie jest wcale taki zły.

Jeśli naprawdę chciał, można napisać metodę rozszerzenia w C# i dodać odwołanie do tej biblioteki w # projekcie F, że metoda C# przedłużenie będzie wyglądać mniej więcej tak:

public static String ReverseString(this String toReverse) 
{ 
    return new String(toReverse.Reverse().ToArray()); 
} 

który dodaje dodatkowe Zależność, która jest prawdziwą korzyścią, sprawia, że ​​twój kod F # staje się nieco prostszy, jeśli odwracasz ciągi w całym miejscu, to może być tego warte, w przeciwnym razie po prostu podsumowałbym to, co masz w normalnej metodzie F # i używać go w ten sposób.

Chociaż ktoś o wiele mądrzejszy ode mnie może mieć piękniejszy sposób na zrobienie tego.

+3

Zwróć uwagę, że jest to rzeczywiście subtelnie błędne: ciągi znaków w .NET są kodowane w UTF-16, co oznacza, że ​​jeśli masz jakieś sekwencje specjalne (znane jako pary zastępcze), Twój odwrócony ciąg znaków będzie niepoprawny. –

+0

@romkyns To prawda, ale w typowych przypadkach jest to wystarczająco dobre, pod warunkiem, że wiesz, że nie będzie działać, gdy napotkasz zastępcze pary lub znaki, których nie można przedstawić za pomocą jednego bajtu/znaku. – Nate

+2

To zależy, tak. To dlatego, że ASCII zostało uznane za "wystarczająco dobre", że wiele osób spoza USA nadal cierpi z powodu słabego wsparcia językowego. Z UTF-16 jest gorzej, ponieważ tym razem większość 1. świata jest w porządku, a programy łamią się tylko w mało znanych krajach. Więc tak, jest wystarczająco dobre dla wewnętrznej aplikacji, ale nie odcina jej na coś takiego, jak odtwarzacz multimedialny o otwartym kodzie źródłowym. –

4

Moja odpowiedź opiera się na odpowiedzi @ Joela, która z kolei została oparta na odpowiedzi @ Timwi. Przedstawię ją za najpiękniejszą i prosty prawidłowego odpowiedź, choć nie na pewno najlepszym wykonaniu (fałda użyciu + zabija to, ale głównym poprawa beautifying korzysta ParseCombiningCharacters i GetNextTextElement zamiast tego rozsądku testowania TextElementEnumerator i dodanie Reverse jako. rozszerzenie String jest zbyt miły):

open System 
open System.Globalization 

type String with 
    member self.Reverse() = 
     StringInfo.ParseCombiningCharacters(self) 
     |> Seq.map (fun i -> StringInfo.GetNextTextElement(self, i)) 
     |> Seq.fold (fun acc s -> s + acc) "" 

Zastosowanie:

> "\uD800\uDC00\u0061\u0300\u00C6".Reverse();; 
val it : string = "Æà" 

Edit:

Wymyśliłem tę nowatorską odmianę również w samochodzie dojazdowym, która prawdopodobnie działa lepiej, ponieważ używamy String.concat.Rozszerzenie typ zostanie pominięty:

let rev str = 
    StringInfo.ParseCombiningCharacters(str) 
    |> Array.rev 
    |> Seq.map (fun i -> StringInfo.GetNextTextElement(str, i)) 
    |> String.concat "" 

Edit (najlepsze rozwiązanie do tej pory):

Rozwiązanie to wykorzystuje kolejną metodę StringInfo za wyliczanie elementów tekstowych, które znów unika się stosowania nieprzyjemne pracować TextElementEnumerator ale nie robi spowoduje dwa razy więcej połączeń z wewnętrznym StringInfo.GetCurrentTextElementLen, podobnie jak poprzednie rozwiązanie. W tym czasie korzystam również z odwrócenia tablicy w miejscu, co powoduje znaczną poprawę wydajności.

let rev str = 
    let si = StringInfo(str) 
    let teArr = Array.init si.LengthInTextElements (fun i -> si.SubstringByTextElements(i,1)) 
    Array.Reverse(teArr) //in-place reversal better performance than Array.rev 
    String.Join("", teArr) 

Powyższe rozwiązanie jest w zasadzie równoważne następujące (które przerabia się w nadziei, że możemy pisk się nieco większą wydajność, ale mogę zmierzyć żadnej znaczącej różnicy):

let rev str = 
    let ccIndices = StringInfo.ParseCombiningCharacters(str) 
    let teArr = 
     Array.init 
      ccIndices.Length 
      (fun i -> 
       if i = ccIndices.Length-1 then 
        str.Substring(i) 
       else 
        let startIndex = ccIndices.[i] 
        str.Substring(startIndex, ccIndices.[i+1] - startIndex)) 
    Array.Reverse(teArr) //in-place reversal better performance than Array.rev 
    String.Join("", teArr) 
+0

Twoja druga wersja jest bardzo sprytna! Moje pobieżne testy nie wykazują znaczącej różnicy w wydajności między tą wersją a wersją, która używa 'TextElementEnumerator' z wyrażeniem sekwencji. Myślę, że wolę twoje podejście. –

+0

Spojrzałem z reflektorem i wierzę, że twoje podejście kończy się wywoływaniem metody wewnętrznej 'StringInfo.GetCurrentTextElementLen' dwa razy tyle razy, co' TextElementEnumerator'. Jeśli wydajność jest krytyczna, a łańcuchy są duże, pewna zmiana za pomocą modułu wyliczającego prawdopodobnie będzie najlepsza. –

+0

Witam @Joel, dzięki za to, że to odzwierciedlałem, byłam ciekawa, jak powtarzające się połączenia z 'GetNextTextElement' powinny być porównywane z użyciem' TextElementEnumerator'. Równie dobrze dla 'TextElementEnumerator' jest lepsze działanie, tworzenie elementu rozszerzenia' String', być może właściwość getter o nazwie 'TextElements', która zwraca sekwencję podobną do implementacji' element', z którą najłatwiej będzie pracować na dłuższą metę. –

0

Łącząc najlepsze z poprzednich odpowiedzi, z niewielką aktualizacją:

module String = 
    open System.Globalization 
    let rev s = 
    seq { 
     let rator = StringInfo.GetTextElementEnumerator(s) 
     while rator.MoveNext() do 
     yield rator.GetTextElement() 
    } 
    |> Array.ofSeq 
    |> Array.rev 
    |> String.concat "" 

String.rev "\uD800\uDC00\u0061\u0300\u00C6" 
1

Nie mogę uwierzyć, że nikt tutaj nie zapewnia ogólnego rozwiązania tego problemu!

Generic reverse with O(n) runtime.

Następnie wystarczy użyć:

let rec revAcc xs acc = 
    match xs with 
    | [] -> acc 
    | h::t -> revAcc t (h::acc) 

let rev xs = 
    match xs with 
    | [] -> xs 
    | [_] -> xs 
    | h1::h2::t -> revAcc t [h2;h1] 

let newValues = 
    values 
    |> Seq.toList 
    |> rev 
    |> List.toSeq 

newValues 

To właśnie F # jest o!

Powiązane problemy