Теперь я использую такой метод:
let x_rev = new string(x.Reverse().ToArray())
Теперь я использую такой метод:
let x_rev = new string(x.Reverse().ToArray())
Здесь некоторый код, основанный на комментариях Тимви на Nate. Существуют отдельные логические буквы (как показано на экране), которые состоят из более чем одного фактического символа. Реверсирование порядка символов превращает эти буквы в тарабарщину.
Timwi с благодарностью указывает, что структура обеспечивает TextElementEnumerator, который работает с точки зрения логических текстовых элементов, а не символов, и обрабатывает эти мульти -character letters правильно. Я раньше не слышал об этом классе, поэтому написал код, который использует 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 ""
Мой ответ основан на ответе @Joel, который, в свою очередь, основан на ответе @Timwi. Я представляю его как самый красивый и простой правильный ответ, хотя, конечно, не самый лучший (сгиб с использованием +
убивает это, но главное улучшение улучшения заключается в использовании 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 = "Æà𐀀"
Edit:
Я тоже мечтал об этом новом варианте, а также о поездке на автомобиле, который, вероятно, работает лучше, поскольку мы используем 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)
Если вы делаете это из MSDN на Enumerable.Reverse(), то вы, вероятно, получили самое простое решение.
Если вы не используете .NET 3.5 (читайте LINQ (не уверен, что F # был вокруг до этого в любом случае)), вы можете использовать Array.Reverse( ), однако полученный код очень похож.
Достаточно сказать, что у вас есть самый элегантный способ, с помощью которого можно изменить строку, я использовал Enumerable.Reverse()
много раз, чтобы изменить порядок строк в моих проектах. Очевидно, что если конструктор String взял IEnumerable<Char>
, мы могли бы пропустить бит .ToArray()
, который, на мой взгляд, сделал бы код немного лучше, но, как он есть, дополнительный .ToArray()
не так уж плох.
Если вы действительно хотели, вы могли бы написать метод расширения в С# и добавить ссылку на эту библиотеку в проекте F #, что метод расширения С# будет выглядеть примерно так:
public static String ReverseString(this String toReverse)
{
return new String(toReverse.Reverse().ToArray());
}
Это добавляет дополнительную зависимость, которая только реальная выгода делает ваш код F # немного более простым, если вы меняете строки по всему месту, это может стоить того, в противном случае я бы просто закончил то, вы получили нормальный метод F # и используете его таким образом.
Хотя, у кого-то гораздо умнее, чем у меня может быть более красивый способ сделать это.
Я не могу поверить, что здесь никто не предоставляет общее решение!
Общий реверс с 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 #!
Сочетание лучших предыдущих ответов с небольшим обновлением:
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"