diff --git a/src/Deedle/Common/Common.fs b/src/Deedle/Common/Common.fs index c8fa3f12..95e8c9f5 100644 --- a/src/Deedle/Common/Common.fs +++ b/src/Deedle/Common/Common.fs @@ -860,11 +860,12 @@ module Seq = /// size) are returned at the beginning. /// * `Boundary.AtEnding` - incomplete windows are returned at the end. /// - /// The result is a sequence of `DataSegnebt` values, which makes it + /// The result is a sequence of `DataSegment` values, which makes it /// easy to distinguish between complete and incomplete windows. let windowedWithBounds size boundary (input:seq<'T>) = seq { let windows = Array.create size [] let currentWindow = ref 0 + let inputLength = ref 0 for v in input do for i in 0 .. windows.Length - 1 do windows.[i] <- v::windows.[i] let win = windows.[currentWindow.Value] |> Array.ofList |> Array.rev @@ -875,10 +876,12 @@ module Seq = else yield DataSegment(Complete, win) windows.[currentWindow.Value] <- [] currentWindow := (!currentWindow + 1) % size + inputLength := !inputLength + 1 // If we are supposed to generate boundary at the end, do it now if boundary = Boundary.AtEnding then - for _ in 1 .. size - 1 do - yield DataSegment(Incomplete, windows.[currentWindow.Value] |> Array.ofList |> Array.rev) + for i in 1 .. min (size - 1) (windows.Length - 1) do + if i >= size - inputLength.Value then + yield DataSegment(Incomplete, windows.[currentWindow.Value] |> Array.ofList |> Array.rev) currentWindow := (!currentWindow + 1) % size } @@ -934,22 +937,24 @@ module Seq = /// The windows are specified by *inclusive* indices, so, e.g. the first window is returned /// as a pair (0, 0). let windowRangesWithBounds size boundary length = seq { + let maxIndex = length - 1L + let maxIncompleteIndex = min (size - 2L) maxIndex // If we want incomplete windows at the beginning, // generate "size - 1" windows always starting from 0 if boundary = Boundary.AtBeginning then - for i in 1L .. size - 1L do yield DataSegmentKind.Incomplete, 0L, i - 1L + for i in 0L .. maxIncompleteIndex do yield DataSegmentKind.Incomplete, 0L, i // Generate all windows in the middle. There is always length - size + 1 of those for i in 0L .. length - size do yield DataSegmentKind.Complete, i, i + size - 1L // If we want incomplete windows at the ending // gneerate "size - 1" windows, always ending with length-1 if boundary = Boundary.AtEnding then - for i in 1L .. size - 1L do yield DataSegmentKind.Incomplete, length - size + i, length - 1L } + for i in maxIncompleteIndex .. -1L .. 0L do yield DataSegmentKind.Incomplete, maxIndex - i, maxIndex } /// Generates addresses of windows in a collection of size 'length'. For example, consider /// a collection with 7 elements (and indices 0 .. 6) and the requirement to create windows /// of length 3: - /// + // /// 0 1 2 3 4 5 6 /// /// When the `AtEnding` flag is set for `boundary`: diff --git a/tests/Deedle.Tests/Common.fs b/tests/Deedle.Tests/Common.fs index 2a573dc5..47a57cec 100644 --- a/tests/Deedle.Tests/Common.fs +++ b/tests/Deedle.Tests/Common.fs @@ -195,12 +195,31 @@ let ``Seq.windowedWithBounds can generate boundary at the beginning`` () = [| DataSegment(Incomplete, [| 1 |]); DataSegment(Incomplete, [| 1; 2 |]) DataSegment(Complete, [| 1; 2; 3 |]); DataSegment(Complete, [| 2; 3; 4 |]) |] +[] +let ``Seq.windowedWithBounds can generate boundary at the beginning when input length equals size`` () = + Seq.windowedWithBounds 3 Boundary.AtBeginning [ 1; 2; 3 ] |> Array.ofSeq + |> shouldEqual + [| DataSegment(Incomplete, [| 1 |]); DataSegment(Incomplete, [| 1; 2 |]) + DataSegment(Complete, [| 1; 2; 3 |]) |] + +[] +let ``Seq.windowedWithBounds can generate boundary at the beginning when input length is less than size`` () = + Seq.windowedWithBounds 10 Boundary.AtBeginning [ 1; 2; ] |> Array.ofSeq + |> shouldEqual + [| DataSegment(Incomplete, [| 1 |]); DataSegment(Incomplete, [| 1; 2 |]) |] + [] let ``Seq.windowedWithBounds can skip boundaries`` () = Seq.windowedWithBounds 3 Boundary.Skip [ 1; 2; 3; 4 ] |> Array.ofSeq |> shouldEqual [| DataSegment(Complete, [| 1; 2; 3 |]); DataSegment(Complete, [| 2; 3; 4 |]) |] +[] +let ``Seq.windowedWithBounds is empty when input length is less than size`` () = + Seq.windowedWithBounds 10 Boundary.Skip [ 1; 2; 3; 4 ] |> Array.ofSeq + |> shouldEqual + [| |] + [] let ``Seq.windowedWithBounds can generate boundary at the ending`` () = Seq.windowedWithBounds 3 Boundary.AtEnding [ 1; 2; 3; 4 ] |> Array.ofSeq @@ -208,6 +227,19 @@ let ``Seq.windowedWithBounds can generate boundary at the ending`` () = [| DataSegment(Complete, [| 1; 2; 3 |]); DataSegment(Complete, [| 2; 3; 4 |]) DataSegment(Incomplete, [| 3; 4 |]); DataSegment(Incomplete, [| 4 |]) |] +[] +let ``Seq.windowedWithBounds can generate boundary at the ending when input length equals size`` () = + Seq.windowedWithBounds 3 Boundary.AtEnding [ 1; 2; 3 ] |> Array.ofSeq + |> shouldEqual + [| DataSegment(Complete, [| 1; 2; 3 |]) + DataSegment(Incomplete, [| 2; 3 |]); DataSegment(Incomplete, [| 3 |]) |] + +[] +let ``Seq.windowedWithBounds can generate boundary at the ending when input length is less than size`` () = + Seq.windowedWithBounds 10 Boundary.AtEnding [ 1; 2 ] |> Array.ofSeq + |> shouldEqual + [| DataSegment(Incomplete, [| 1; 2 |]); DataSegment(Incomplete, [| 2 |]) |] + [] let ``Seq.chunkedWithBounds works when length is multiple of chunk size`` () = Seq.chunkedWithBounds 3 Boundary.AtBeginning [ 1 .. 9 ] |> Array.ofSeq @@ -250,6 +282,58 @@ let ``Seq.chunkedWithBounds can skip incomplete chunk at the end`` () = [| DataSegment(Complete, [|1; 2; 3|]); DataSegment(Complete, [|4; 5; 6|]); DataSegment(Complete, [|7; 8; 9|]) |] +[] +let ``Seq.windowRangesWithBounds can generate boundary at the beginning`` () = + Seq.windowRangesWithBounds 3 Boundary.AtBeginning 4L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Incomplete, 0, 0; DataSegmentKind.Incomplete, 0, 1 + DataSegmentKind.Complete, 0, 2; DataSegmentKind.Complete, 1, 3 |] + +[] +let ``Seq.windowRangesWithBounds can generate boundary at the beginning when input length equals size`` () = + Seq.windowRangesWithBounds 3 Boundary.AtBeginning 3L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Incomplete, 0, 0; DataSegmentKind.Incomplete, 0, 1 + DataSegmentKind.Complete, 0, 2 |] + +[] +let ``Seq.windowRangesWithBounds can generate boundary at the beginning when input length is less than size`` () = + Seq.windowRangesWithBounds 10 Boundary.AtBeginning 2L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Incomplete, 0, 0; DataSegmentKind.Incomplete, 0, 1 |] + +[] +let ``Seq.windowRangesWithBounds can skip boundaries`` () = + Seq.windowRangesWithBounds 3 Boundary.Skip 4L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Complete, 0, 2; DataSegmentKind.Complete, 1, 3 |] + +[] +let ``Seq.windowRangesWithBounds is empty when input length is less than size`` () = + Seq.windowRangesWithBounds 10 Boundary.Skip 4L |> Array.ofSeq + |> shouldEqual + [| |] + +[] +let ``Seq.windowRangesWithBounds can generate boundary at the ending`` () = + Seq.windowRangesWithBounds 3 Boundary.AtEnding 4L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Complete, 0, 2; DataSegmentKind.Complete, 1, 3 + DataSegmentKind.Incomplete, 2, 3; DataSegmentKind.Incomplete, 3, 3 |] + +[] +let ``Seq.windowRangesWithBounds can generate boundary at the ending when input length equals size`` () = + Seq.windowRangesWithBounds 3 Boundary.AtEnding 3L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Complete, 0, 2 + DataSegmentKind.Incomplete, 1, 2; DataSegmentKind.Incomplete, 2, 2 |] + +[] +let ``Seq.windowRangesWithBounds can generate boundary at the ending when input length is less than size`` () = + Seq.windowRangesWithBounds 10 Boundary.AtEnding 2L |> Array.ofSeq + |> shouldEqual + [| DataSegmentKind.Incomplete, 0, 1; DataSegmentKind.Incomplete, 1, 1 |] + [] let ``Seq.alignOrdered (union) satisfies basic conditions`` () = let comparer = System.Collections.Generic.Comparer.Default