是否有更简单或美丽的方法来扭转字符串?

时间:2010-12-29 17:13:39

标签: string f#

现在我正在使用这样的方法:

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

5 个答案:

答案 0 :(得分:9)

以下是一些基于Timwi对Nate答案的评论的代码。屏幕上显示单个逻辑字母(由屏幕上显示),由多个实际字符组成。颠倒字符的顺序会使这些字母变成乱码。

Timwi帮助指出框架提供了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 ""

答案 1 :(得分:5)

我的回答是基于@Joel的回答,而回答是基于@Timwi的回答。我将它呈现为最美丽和最简单的正确的答案,尽管肯定不是最佳表现(使用+的折叠会导致这种情况;但主要的美化改进是使用ParseCombiningCharactersGetNextTextElement代替那次健全性测试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)

答案 2 :(得分:4)

我无法相信这里没有人为此提供通用的解决方案!

Generic reverse with O(n) runtime.

然后,只需使用:

 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#的全部意义!

答案 3 :(得分:2)

如果你正在做的是来自Enumerable.Reverse()的MSDN,那么你可能得到了最简单的解决方案。

如果您没有使用.NET 3.5(读取LINQ(不知道F#是否在此之前就已存在)),您可以使用Array.Reverse()方法,但是,生成的代码非常相似。 / p>

我只想说,你所拥有的是我能用来反转字符串的最优雅方式,我多次使用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#方法并以这种方式使用它。

虽然,比我更聪明的人可能有更美好的方式去做。

答案 4 :(得分:1)

结合以前最好的答案,稍加更新:

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"