Есть ли более простой или красивый способ перевернуть строку? - PullRequest
12 голосов
/ 29 декабря 2010

Сейчас я использую такой метод:

let x_rev = new string(x.Reverse().ToArray())

Ответы [ 5 ]

9 голосов
/ 29 декабря 2010

Вот код, основанный на комментарии Тимви к ответу Нейта.Есть отдельные логические буквы (как показано на экране), которые состоят из более чем одного фактического символа.Изменение порядка символов превращает эти буквы в бред.

Тимви услужливо указывает, что платформа предоставляет TextElementEnumerator , который работает в терминах логических текстовых элементов, а не символов, и правильно обрабатывает эти многосимвольные буквы.Я не слышал об этом классе раньше, поэтому я написал некоторый код, который использует TextElementEnumerator для правильного обращения строки и сравнения результатов с наивным обращением строки.

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 ""
5 голосов
/ 30 декабря 2010

Мой ответ основан на ответе @ Джоэла, который, в свою очередь, основан на ответе @ Тимви.Я представляю его как самый красивый и простой правильный ответ, хотя, безусловно, не самый эффективный (фолд, использующий +, убивает это; но главное улучшающее улучшение - использование ParseCombiningCharacters и GetNextTextElement вместоэто здравомыслие TextElementEnumerator. И добавить Reverse как расширение String тоже приятно):

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 ) ""

Использование:

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

Редактировать:

Я придумал этот новый вариант и для дома на машине, который, вероятно, работает лучше, так как мы используем String.concat.Расширение типа опущено:

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

Редактировать (лучшее решение на данный момент):

В этом решении используется еще один метод StringInfo для перечисления текстовых элементов, который сноваизбегает использования неприятного для работы с TextElementEnumerator, но не приводит к вдвое большему количеству обращений к внутреннему StringInfo.GetCurrentTextElementLen, как в предыдущем решении.В этот раз я также использую инверсию массивов на месте, что приводит к заметному улучшению производительности.

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)

Приведенное выше решение в основном эквивалентно следующему (которое я разработал в надежде, что мы сможем немного пискнуть)больше производительности, но я не могу измерить никакой существенной разницы):

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)
4 голосов
/ 25 июня 2014

Я не могу поверить, что никто здесь не предлагает общего решения для этого!

Общий реверс с O (n) временем выполнения.

Тогда просто используйте:

 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

Вот что такое F #!

2 голосов
/ 29 декабря 2010

Если вы делаете из MSDN на Enumerable.Reverse () , то у вас, вероятно, самое простое решение.

Если вы не используете .NET 3.5 (читайте LINQ (в любом случае не был уверен, был ли F # раньше), вы можете использовать метод Array.Reverse () , однако полученный код почти так же.

Достаточно сказать, что у вас есть самый элегантный способ, которым я могу придумать, чтобы перевернуть строку, я много раз использовал Enumerable.Reverse(), чтобы изменить порядок строк в моих проектах. Очевидно, что если бы конструктор String занял IEnumerable<Char>, мы могли бы пропустить бит .ToArray(), что, на мой взгляд, сделало бы код немного лучше, но в нынешнем виде дополнительный .ToArray() не так уж плох.

Если вы действительно хотите, вы можете написать метод расширения в C # и добавить ссылку на эту библиотеку в вашем проекте F #, чтобы этот метод расширения C # выглядел примерно так:

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

Это добавляет дополнительную зависимость, единственная реальная выгода которой делает ваш код F # немного более простым, если вы переворачиваете строки повсюду, это может стоить того, иначе я просто заверну то, что вы ' Мы использовали обычный метод F # и используем его таким образом.

Хотя у кого-то намного умнее, чем у меня, может быть, есть более красивый способ сделать это.

1 голос
/ 30 декабря 2010

Объединение лучших из предыдущих ответов с небольшим обновлением:

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"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...