Мой ответ основан на ответе @ Джоэла, который, в свою очередь, основан на ответе @ Тимви.Я представляю его как самый красивый и простой правильный ответ, хотя, безусловно, не самый эффективный (фолд, использующий +
, убивает это; но главное улучшающее улучшение - использование 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)