Skip to content

Commit

Permalink
fslaborg#559: Fix Series.windowSize throw IndexOutOfRangeException wh…
Browse files Browse the repository at this point in the history
…en size bigger than input length.
  • Loading branch information
Matthew Thornton committed Jul 12, 2023
1 parent 93c624e commit 8cce0a3
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 6 deletions.
17 changes: 11 additions & 6 deletions src/Deedle/Common/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` values, which makes it
/// The result is a sequence of `DataSegment<T>` 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
Expand All @@ -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 }


Expand Down Expand Up @@ -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`:
Expand Down
84 changes: 84 additions & 0 deletions tests/Deedle.Tests/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,51 @@ 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 |]) |]

[<Test>]
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 |]) |]

[<Test>]
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 |]) |]

[<Test>]
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 |]) |]

[<Test>]
let ``Seq.windowedWithBounds is empty when input length is less than size`` () =
Seq.windowedWithBounds 10 Boundary.Skip [ 1; 2; 3; 4 ] |> Array.ofSeq
|> shouldEqual
[| |]

[<Test>]
let ``Seq.windowedWithBounds can generate boundary at the ending`` () =
Seq.windowedWithBounds 3 Boundary.AtEnding [ 1; 2; 3; 4 ] |> Array.ofSeq
|> shouldEqual
[| DataSegment(Complete, [| 1; 2; 3 |]); DataSegment(Complete, [| 2; 3; 4 |])
DataSegment(Incomplete, [| 3; 4 |]); DataSegment(Incomplete, [| 4 |]) |]

[<Test>]
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 |]) |]

[<Test>]
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 |]) |]

[<Test>]
let ``Seq.chunkedWithBounds works when length is multiple of chunk size`` () =
Seq.chunkedWithBounds 3 Boundary.AtBeginning [ 1 .. 9 ] |> Array.ofSeq
Expand Down Expand Up @@ -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|]) |]

[<Test>]
let ``Seq.windowRangesWithBounds can generate boundary at the beginning`` () =
Seq.windowRangesWithBounds 3L Boundary.AtBeginning 4L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Incomplete, 0, 0; DataSegmentKind.Incomplete, 0, 1
DataSegmentKind.Complete, 0, 2; DataSegmentKind.Complete, 1, 3 |]

[<Test>]
let ``Seq.windowRangesWithBounds can generate boundary at the beginning when input length equals size`` () =
Seq.windowRangesWithBounds 3L Boundary.AtBeginning 3L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Incomplete, 0, 0; DataSegmentKind.Incomplete, 0, 1
DataSegmentKind.Complete, 0, 2 |]

[<Test>]
let ``Seq.windowRangesWithBounds can generate boundary at the beginning when input length is less than size`` () =
Seq.windowRangesWithBounds 10L Boundary.AtBeginning 2L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Incomplete, 0, 0; DataSegmentKind.Incomplete, 0, 1 |]

[<Test>]
let ``Seq.windowRangesWithBounds can skip boundaries`` () =
Seq.windowRangesWithBounds 3L Boundary.Skip 4L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Complete, 0, 2; DataSegmentKind.Complete, 1, 3 |]

[<Test>]
let ``Seq.windowRangesWithBounds is empty when input length is less than size`` () =
Seq.windowRangesWithBounds 10L Boundary.Skip 4L |> Array.ofSeq
|> shouldEqual
[| |]

[<Test>]
let ``Seq.windowRangesWithBounds can generate boundary at the ending`` () =
Seq.windowRangesWithBounds 3L Boundary.AtEnding 4L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Complete, 0, 2; DataSegmentKind.Complete, 1, 3
DataSegmentKind.Incomplete, 2, 3; DataSegmentKind.Incomplete, 3, 3 |]

[<Test>]
let ``Seq.windowRangesWithBounds can generate boundary at the ending when input length equals size`` () =
Seq.windowRangesWithBounds 3L Boundary.AtEnding 3L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Complete, 0, 2
DataSegmentKind.Incomplete, 1, 2; DataSegmentKind.Incomplete, 2, 2 |]

[<Test>]
let ``Seq.windowRangesWithBounds can generate boundary at the ending when input length is less than size`` () =
Seq.windowRangesWithBounds 10L Boundary.AtEnding 2L |> Array.ofSeq
|> shouldEqual
[| DataSegmentKind.Incomplete, 0, 1; DataSegmentKind.Incomplete, 1, 1 |]

[<Test>]
let ``Seq.alignOrdered (union) satisfies basic conditions`` () =
let comparer = System.Collections.Generic.Comparer<int>.Default
Expand Down

0 comments on commit 8cce0a3

Please sign in to comment.