Search code examples
f#functional-programmingimperative

Need ideas to transform F# imperative code to functional


I have a function witch is written in an imperative style and cant get my head around on how to convert it to a more robust functional approach.

The function takes a seq of strings and returns a seq of tuples where each tuple consists of the 2,7,12,.. and 5,10,15,.. item from the input.

Example:

Input = { "Lorem", "ipsum", "dolor", "set", "amet", "consectetuer", "adipiscing", "elit", "Aenean", "commodo", "ligula", "eget", "dolor", "Aenean", "massa" }

Ouput = { ("ipsum", "amet"), ("adipiscing", "commodo"), ("eget", "massa") }

let convert (input : seq<string>) : seq<(string * string)> =
    let enum = input.GetEnumerator()
    let index = ref 0
    let first = ref ""
    let second = ref ""

    seq {
        while enum.MoveNext() do
            let modIndex = !index % 5
            index := !index + 1

            if (modIndex % 2 = 0 && !first = "") then first := enum.Current
            if (modIndex % 5 = 0 && !second = "") then second := enum.Current

            if modIndex = 0  then
                let result = (!first, !second)
                first := ""
                second := ""
                yield result
    }

Any help or tip for a starting point is appreciated.


Solution

  • I do not completely understand the behaviour you want - what is the algorithm for generating indices that you want to pair? Anyway, one nice functional solution is to take the elements you want to pair separately and then combine them using Seq.zip.

    You can use Seq.mapi to add indices to the values and then use Seq.choose to get the values with the right index (and skip all other values). For hardcoded indices, you can write something like:

    let indexed = input |> Seq.mapi (fun i s -> i, s)
    Seq.zip 
      (indexed |> Seq.choose (fun (i, v) -> if i=1 || i=6 || i=11 then Some v else None))
      (indexed |> Seq.choose (fun (i, v) -> if i=4 || i=9 || i=14 then Some v else None))
    

    I used your numbers -1 because the indices are from 0 - so the above gives you the results you wanted. The second series looks like multiples of 5, so perhaps you wanted i%5 = 4 to generate second elements:

    let indexed = input |> Seq.mapi (fun i s -> i, s)
    Seq.zip 
      (indexed |> Seq.choose (fun (i, v) -> if i=1 || i=6 || i=11 then Some v else None))
      (indexed |> Seq.choose (fun (i, v) -> if i%5 = 4 then Some v else None))
    

    I still don't see the general mechanism for generating the first elements though!

    EDIT One more idea - is the first sequence generated by i*5 + 2 and the second by i*5? In that case, your example is wrong, but you could write it like this:

    let indexed = input |> Seq.mapi (fun i s -> i, s)
    Seq.zip 
      (indexed |> Seq.choose (fun (i, v) -> if i%5 = 2 then Some v else None))
      (indexed |> Seq.choose (fun (i, v) -> if i%5 = 0 then Some v else None))
    

    ... or if you want to make the code shroter, you can refactor:

    let filterNthElements div rem = 
      input |> Seq.mapi (fun i s -> i, s)
            |> Seq.choose (fun (i, v) -> if i%div = rem then Some v else None)
    
    Seq.zip (filterNthElements 5 2) (filterNthElements 5 0)