Reputation: 4923
Now I am using such a method:
let x_rev = new string(x.Reverse().ToArray())
Upvotes: 13
Views: 9840
Reputation: 5074
I can't believe that nobody here is providing a generic solution to this!
Generic reverse with O(n) runtime.
Then, just use:
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
That's what F# is all about!
Upvotes: 5
Reputation: 22297
My answer is based on @Joel's answer which was in turn based on @Timwi's answer. I present it as the most beautiful and simple correct answer, though certainly not the best performing (the fold using +
kills that; but the main beautifying improvement is using ParseCombiningCharacters
and GetNextTextElement
instead of that sanity testing TextElementEnumerator
. And adding Reverse
as an extension of String
is nice too):
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 ) ""
Usage:
> "\uD800\uDC00\u0061\u0300\u00C6".Reverse();;
val it : string = "Æà𐀀"
Edit:
I dreamed up this novel variation as well on the car-ride home, which likely performs better since we use String.concat
. The type extension is omitted:
let rev str =
StringInfo.ParseCombiningCharacters(str)
|> Array.rev
|> Seq.map (fun i -> StringInfo.GetNextTextElement(str, i))
|> String.concat ""
Edit (best solution so far):
This solution uses yet another StringInfo
method for enumerating text elements which again avoids using the unpleasant to work with TextElementEnumerator
but doesn't result in twice as many calls to the internal StringInfo.GetCurrentTextElementLen
like the previous solution. I also use in-place array reversal this time which results in a notable performance improvement.
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)
The above solution is basically equivalent to the following (which I worked up in hopes that we could squeak out a bit more performance, but I can measure no significant difference):
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)
Upvotes: 8
Reputation: 28765
Here's some code based on Timwi's comment on Nate's answer. There are single logical letters (as displayed on screen) that are made up of more than one actual character. Reversing the order of the characters turns these letters into gibberish.
Timwi helpfully points out that the framework provides a TextElementEnumerator that works in terms of logical text elements rather than characters, and handles these multi-character letters correctly. I hadn't heard of this class before, so I wrote some code that uses a TextElementEnumerator to reverse a string correctly, and compare the results to a naive string reversal.
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 ""
Upvotes: 13
Reputation: 89171
Combining the best of the previous answers, with a little update:
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"
Upvotes: 1
Reputation: 30636
If what you are doing is from MSDN on Enumerable.Reverse() then you've probably got the most simple solution.
If you're not using .NET 3.5 (read LINQ (not sure if F# was around before then anyway)) you could use the Array.Reverse() method, however, the resulting code is very much the same.
Suffice it to say, what you have is the most elegant way I can come up with to reverse a string, I have used Enumerable.Reverse()
many times to reverse order of strings in my projects. Obviously, if the String constructor took an IEnumerable<Char>
we could skip the .ToArray()
bit which would in my opinion make the code a bit better, but as it stands, an extra .ToArray()
isn't all that bad.
If you really wanted, you could write an extension method in C# and add a reference to that library in your F# project, that C# extension method would look something like this:
public static String ReverseString(this String toReverse)
{
return new String(toReverse.Reverse().ToArray());
}
That adds an extra dependency who's only real benefit is making your F# code a bit more simple, if you're reversing strings all over the place, it might be worth it, otherwise, I'd just wrap up what you've got in a normal F# method and use it that way.
Although, someone much smarter than I may have a more beautiful way to do it.
Upvotes: 2