12

I have a sequence with {"1";"a";"2";"b";"3";"c";...}.

How can I transform this seq into {("1","a");("2","b");("3","c");...}

TrueWill
  • 25,132
  • 10
  • 101
  • 150
functional
  • 151
  • 1
  • 4
  • Here's a related answer which might interest you: http://stackoverflow.com/questions/833180/handy-f-snippets/2271132#2271132, though it's for lists, not Seq. – Benjol Nov 09 '10 at 05:59

6 Answers6

21

Here is a much-too-clever solution:

let s = ["1";"a";"2";"b";"3";"c"]

let pairs s =
    s |> Seq.pairwise 
      |> Seq.mapi (fun i x -> i%2=0, x) 
      |> Seq.filter fst 
      |> Seq.map snd

printfn "%A" (pairs s)
Brian
  • 117,631
  • 17
  • 236
  • 300
12

Enumerators are not always evil.

let pairs (source: seq<_>) =
    seq { 
        use iter = source.GetEnumerator() 
        while iter.MoveNext() do
            let first = iter.Current
            if iter.MoveNext() then
                let second = iter.Current 
                yield (first, second)
    }

Here is the F# source code of Seq.pairwise taken from FSharp.Core/seq.fs

[<CompiledName("Pairwise")>]
let pairwise (source: seq<'T>) = //'
    checkNonNull "source" source
    seq { use ie = source.GetEnumerator() 
          if ie.MoveNext() then
              let iref = ref ie.Current
              while ie.MoveNext() do
                  let j = ie.Current 
                  yield (!iref, j)
                  iref := j }
gradbot
  • 13,732
  • 5
  • 36
  • 69
8

Since F# 4.0, you can now use chunkBySize

let source = seq ["1";"a";"2";"b";"3";"c"]

let pairs source =
    source
    |> Seq.chunkBySize 2
    |> Seq.map (fun a -> a.[0], a.[1])

;;
printfn "%A" (pairs source)
Patrick McDonald
  • 64,141
  • 14
  • 108
  • 120
  • 1
    This does the trick for me! But we should consider to use the Seq.tryItem to retrieve the indexed values. It could throw an exception if the source list has an odd number of values. – skamlet Mar 01 '21 at 10:31
2

Here's a variation on @Brian's solution:

["1";"a";"2";"b";"3";"c";"4";"d";"5";"e";"6";"f"]
|> Seq.pairwise
|> Seq.mapi (fun i x -> if i%2=0 then Some(x) else None)
|> Seq.choose id

And here's a brain-melter using Seq.scan:

["1";"a";"2";"b";"3";"c";"4";"d";"5";"e";"6";"f"]
|> Seq.scan (fun ((i,prev),_) n -> match prev with
                                   | Some(n') when i%2=0 -> ((i+1,Some(n)), Some(n',n))
                                   | _ -> ((i+1,Some(n)), None))
            ((-1,None), None)
|> Seq.choose snd
Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
2

You might consider using LazyLists for this.

let (|Cons|Nil|) = LazyList.(|Cons|Nil|)

let paired items =
    let step = function
        | Cons(x, Cons(y, rest)) ->
            Some((x, y), rest)
        | _ ->
            None
    Seq.unfold step (LazyList.ofSeq items)
Greg
  • 180
  • 1
  • 1
  • 6
0

You can use pattern matching in the following way:

let list = ["1";"2";"3";"4";"5";"6"]

let rec convert l =
    match l with
        x :: y :: z -> (x,y) :: convert z
        | x :: z -> (x,x) :: convert z
        | [] -> []

let _ = 
  convert list

but you have to decide what to do if the list has an odd number of elements (in my solution a pair with same value is produced)

Jack
  • 131,802
  • 30
  • 241
  • 343
  • (I don't know if there are clever constructs in F#, I'm used to OCaml :) – Jack Nov 08 '10 at 17:46
  • this would work if it was a list but i have a very large seq. not sure if this pattern matching approach will work on a Seq – functional Nov 08 '10 at 17:49
  • why shouldn't it work? It goes through the list an build the new one by concatenating. It should be linear complexity.. or you are worried about stack overflow? – Jack Nov 08 '10 at 17:51
  • oh you mean that you want to keep it lazy? – Jack Nov 08 '10 at 17:52
  • @functional: here's the same code, but uses continuation passing to make it tail-recursive http://pastebin.com/VTkqgi8k – Juliet Nov 08 '10 at 22:35