8
\$\begingroup\$

I wrote some code to solve this problem https://www.hackerrank.com/challenges/string-o-permute which basically states: take an even length string and swap all the even indexed characters. For example the string "abcd" should become "badc". I'm looking for a general code review as I'm very new to F# and specifically I'd really like to get rid of the ignoredTupleValue variable, it seems like a kludge to me. I'd also like to collapse the whole thing into one function but I was running into scoping issues with the StringBuilder.

open System
open System.Text

[<EntryPoint>]
let main argv =
    let numCases = Console.ReadLine() |> int
    for i = numCases downto 1 do
        let strIn = Console.ReadLine()
        let pairs = Seq.pairwise strIn
        let ignoredTupleValue = ('1', '1')
        let swappedTuples = pairs |> Seq.mapi (fun idx pair ->
                                if idx % 2 = 0 then
                                    let (a, b) = pair
                                    (b, a)
                                else
                                    ignoredTupleValue)
        let answer = Seq.fold (fun (sb : StringBuilder) pair ->
                                if pair <> ignoredTupleValue then
                                    let (a, b) = pair
                                    sb.Append(a) |> ignore
                                    sb.Append(b) |> ignore
                                sb)
                                (new StringBuilder())
                                swappedTuples
        printfn "%s" (answer.ToString())
    0
\$\endgroup\$
  • 2
    \$\begingroup\$ There are many ways to do this problem, but if you're interested in removing ignoredTupleValue , think about using Seq.filter(...) \$\endgroup\$ – Ray May 27 '16 at 20:16
  • \$\begingroup\$ I changed the title to describe the task the code accomplishes, this makes contrnt morr easily searchable and accesible. \$\endgroup\$ – Caridorc Jul 8 '16 at 14:38
5
+50
\$\begingroup\$

Building off Caridorc's pseudocode, this function would do it:

let switchOddAndEven (str : string) =
    str.ToCharArray()
    |> Array.chunkBySize 2
    |> Array.map Array.rev
    |> Array.concat
    |> System.String.Concat

The first and last methods are just switching between a char array and a string. Usage is simply:

switchOddAndEven "abcd";;
// val it : string = "badc"

or in your main method

[<EntryPoint>]
let main argv =
    let numCases = Console.ReadLine() |> int
    for i = numCases downto 1 do
        Console.ReadLine()
        |> switchOddAndEven
        |> printfn "%s"
    0

Note you don't need a full blown console app to do these kind of things. Create a new fsx file in Visual Studio (File -> New -> File -> F# Script File) then type your functions there. To execute just select it all and send it to the F# Interactive with Alt + Enter.

Edit

From the comments: one could take advantage of the fact that string is an IEnumerable and use the Seq module, which saves one method call:

let switchOddAndEven (str : string) = 
    str 
    |> Seq.chunkBySize 2 
    |> Seq.map Seq.rev 
    |> Seq.concat 
    |> System.String.Concat
\$\endgroup\$
  • \$\begingroup\$ I'll give you the bounty if you change the name of the function to something descriptive, I hate names with "my" in them \$\endgroup\$ – reggaeguitar Jul 8 '16 at 17:21
  • \$\begingroup\$ Heh. Changed it. What's in a name. \$\endgroup\$ – asibahi Jul 8 '16 at 17:22
  • 1
    \$\begingroup\$ Everything is in a name :) \$\endgroup\$ – reggaeguitar Jul 8 '16 at 18:05
  • \$\begingroup\$ Good that F# has all the necessary built-ins including chunks_of . I just think that strings should be a kind a list (strings subset_of lists) to avoid this back & forth conversion, but the designers of the language chose them to be different and there is no going back. \$\endgroup\$ – Caridorc Jul 9 '16 at 14:10
  • \$\begingroup\$ @Caridorc that's likely more of a side effect of being built on top of the CLR more than a decision made by the F# team. \$\endgroup\$ – RubberDuck Jul 9 '16 at 16:13
7
\$\begingroup\$

The solution should look like (pseudocode, I know little F# too ;) ):

String
  |> ChunksOf 2
  |> Map Reverse
  |> Flatten

An example explains clearly

"abcd" -> ["ab", "cd"] -> ["ba", "dc"] -> "badc"

The key of effective Functional Programming is making good use of the built-in functions and carefully combining them.

\$\endgroup\$
3
\$\begingroup\$

I'm not very good at F#, but anytime I see a for or a do (i.e. a loop) I cringe a little bit. It's more idiomatic to use Seq.Map to apply a function to each element of the range [numCases..1]. Which, since it's descending, you need to apply the step: [numCases..-1..1].

\$\endgroup\$
2
\$\begingroup\$

You could use Printf.bprintf to easily use a StringBuilder.

You could also use pattern matching to extract your length 2 array easily
(it'll cost you a warning for not handling every case but you [as a developer] know for sure it'll have a length of 2 because of the chunkBySize)

#nowarn "25" // to not see the aforementioned warning

let permuteEvenOdd str =
  let builder = StringBuilder (String.length str)
  let bprint format = Printf.bprintf builder format // helper to ease readability

  str
  |> Seq.chunkBySize 2
  |> Seq.iter (fun [| c1; c2 |] -> bprintf "%c%c" c2 c1)

  string builder

Edit

As another alternative you can use Seq.permute
(as it's documentation is not yet available I give List.permute documentation instead, the behaviour is the same)

let permuteEvenOdd str =
  str
  |> Seq.permute (fun index -> index + if index % 2 = 0 then 1 else -1)
  // |> Seq.permute (fun index -> -2 * (index % 2) + 1) // alternate version ; harder to understand
  |> Seq.fold (sprintf "%s%c") "" // we could reuse bprintf here instead
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.