locked
Taking 2 consecutive elements of a sequence and make them a tuple RRS feed

  • Question

  • Hi all,

     

    I have a sequence of elements and I would like to take each two consecutive ones, make a tuple, and leave them in a new sequence. For example...

    Given: seq [1;2;3;4;5;6;7;8;9;10]

    Expected output:seq [(1,2);(3,4);(5,6);(7,8);(9,10)]

     

    I've managed to do it but have two questions...

    1. I tried doing it with pattern matching, but it seems like Sequences can't match to the "Empty pattern" [], am I right? If that is so... is there any way of using pattern matching with sequences ala h::t, and []? Just to avoid the overhead of converting a large sequence to a list.

    This is the code I tried to implement and didn't work, just in case:

    let bettingRowsPairs =
        let rec rowsIntoTuples (sequenceOfRows:seq<HtmlNode>) (acc) = 
            match sequenceOfRows with
            | [] -> acc
            | _ ->  let tuple = 
                       sequenceOfRows
                       |> Seq.take 2
                       |> Array.ofList
                       |> (fun x -> (x.[0] , x.[1]))
                    rowsIntoTuples (Seq.skip 2 sequenceOfRows) (Seq.append acc [tuple])
        
        rowsIntoTuples bettingRows Seq.empty


    2. As I have already gone through all of this, my final solution is the one below. Is there any more elegant way of doing it? As I'm learning F# now it'd be important to me to acquire good style of code and thinking:

    let bettingRowsPairs =
        let rec rowsIntoTuples (sequenceOfRows:seq<HtmlNode>) (acc:seq<HtmlNode * HtmlNode>) = 
            if Seq.isEmpty sequenceOfRows then
                acc
            else
                let tuple = 
                    sequenceOfRows
                    |> Seq.take 2
                    |> Array.ofSeq
                    |> (fun x -> (x.[0] , x.[1]))
                rowsIntoTuples (Seq.skip 2 sequenceOfRows) (Seq.append acc [tuple])

    Thanks a lot for helping the newbies!

    Tuesday, September 13, 2011 11:41 AM

Answers

  • I hadn't read the entire post.

    1- For seq {}, you could do a pattern match on the following | _ when Seq.empty sequenceOfRows

    2- I guess my previous solution is a possible answer... there definitely might be better.

    Tuesday, September 13, 2011 12:01 PM

All replies

  • This seems to work, I would've thought Seq.windowed at first but you would still have to filter on the even elements and it returns a seq of List.

    let v = [1..10];;
    
    v.Tail
    |> Seq.zip v
    |> Seq.mapi (fun i v -> (i, v))
    |> Seq.filter (fun (i,_) -> i % 2 = 0)
    |> Seq.map snd
    


    Tuesday, September 13, 2011 11:58 AM
  • I hadn't read the entire post.

    1- For seq {}, you could do a pattern match on the following | _ when Seq.empty sequenceOfRows

    2- I guess my previous solution is a possible answer... there definitely might be better.

    Tuesday, September 13, 2011 12:01 PM
  • The 'breakSeq' function below is a generally useful way to partition a seq into groups, which is based on code from here, I use this as the main implementation below.

    It is very easy to get into O(N^2) behavior or worse when you use Seq.skip/Seq.append.  If you don't need laziness, then lists are easy to recursively traverse for elegant solutions.  If you do need to be lazy, then use an enumerator to walk the Seq as below, or else use the LazyList type from the F# PowerPack.

    // break a sequence up into a sequence of arrays, 
    // each of length at most 'n'
    let breakSeq n (s:seq<_>) = 
        seq { 
            use e = s.GetEnumerator() 
            while e.MoveNext() do 
                let i = ref 0 
                    yield e.Current 
                    i := !i + 1 
                    while !i < n && e.MoveNext() do             
                        yield e.Current 
                        i := !i + 1 |] }

    let tupleSeq s =
        s |> breakSeq 2 |> Seq.filter (fun a -> a.Length=2) |> Seq.map (fun [|x;y|] -> x,y)

    tupleSeq [1;2;3;4;5;6] |> printfn "%A"
    tupleSeq [1;2;3;4;5] |> printfn "%A" 


    Brian McNamara [MSFT]
    • Proposed as answer by Ryan RileyMVP Tuesday, September 13, 2011 3:02 PM
    Tuesday, September 13, 2011 2:59 PM
    Moderator
  • You missed a line in your copy/paste, Brian. The complete code for breakSeq is

    let breakSeq n (s:seq<_>) =
        seq {
            use e = s.GetEnumerator()
            while e.MoveNext() do
                let i = ref 0
                yield [|
                    yield e.Current
                    i := !i + 1
                    while !i < n && e.MoveNext() do            
                        yield e.Current
                        i := !i + 1 |] }
    

    Also, Jacobo, you might find this question useful in answering why you cannot use :: and [] to pattern match sequences. In short, :: and [] are reserved for F# lists. You can use `Seq.head` to peek at the topmost element in a sequence, though as Brian mentioned above, Seq.skip 1 is ill-advised in loops. As David pointed out, you can also use Seq.isEmpty to match empty sequences.


    Ryan Riley @panesofglass

    • Edited by Ryan RileyMVP Tuesday, September 13, 2011 3:11 PM Address second part of question.
    Tuesday, September 13, 2011 3:05 PM
  • Thank you all! This has been very helpful.

    Step by step...

    @David:

    Thanks for your solution. I suppose it is better than mine although I find mine more expressive in the sense that it reflects better to me my own thought about the problem. I needed to take in a tuple of two every two elements and so I though something like:

    - I need to take the first to arguments of the sequence |> Make them a tuple |> repeat the operation with the rest of the sequence till the end.

    As I say, I'm a newbie so this may no be the best thinking at all, but after watching Luca Bolognese's presentation some time ago, I'm trying to keep the mental process and code as close as possible. Thanks a lot for the code, it has make me think about some cool things.

    About "| _ when Seq.empty sequenceOfRows", I really didn't think about it... I don't know why (I suppose is having too many news things in the head now). I think I'll go that way!

     

    @Brian:

    I really don't need lazyness... so I guess, is it generally better to add the overhead of converting a sequence to the list and manage the list than dealing with the overhead Seq.skip and Seq.append produce? Thanks for the breakSeq code, it's interesting, although I still don't have any scenario where I need lazyness.

     

    @Ryan:

    Thanks for the correction and the link, pretty interesting read...

     

    So in short, it seems like in trying my best to avoid the overhead of converting a sequence to a list, I added more overhead in other areas of my code. Is this conclusion right?

    Also, I'll use the "| _ -> when ..." pattern matching that I don't know how I didn't remember.

     

    Thanks a lot to all. This has been extremely helpful!

    Wednesday, September 14, 2011 8:54 AM