AndrewShmig
AndrewShmig

Reputation: 4923

Is there more simple or beautiful way to reverse a string?

Now I am using such a method:

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

Upvotes: 13

Views: 9840

Answers (5)

devinbost
devinbost

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

Stephen Swensen
Stephen Swensen

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

Joel Mueller
Joel Mueller

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

Markus Jarderot
Markus Jarderot

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

Nate
Nate

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

Related Questions