Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#559: Fix Series.windowSize throw IndexOutOfRangeException when size bigger than input length. #560

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, 0L, 0L; DataSegmentKind.Incomplete, 0L, 1L
DataSegmentKind.Complete, 0L, 2L; DataSegmentKind.Complete, 1L, 3L |]

[<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, 0L, 0L; DataSegmentKind.Incomplete, 0L, 1L
DataSegmentKind.Complete, 0L, 2L |]

[<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, 0L, 0L; DataSegmentKind.Incomplete, 0L, 1L |]

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

[<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, 0L, 2L; DataSegmentKind.Complete, 1L, 3L
DataSegmentKind.Incomplete, 2L, 3L; DataSegmentKind.Incomplete, 3L, 3L |]

[<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, 0L, 2L
DataSegmentKind.Incomplete, 1L, 2L; DataSegmentKind.Incomplete, 2L, 2L |]

[<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, 0L, 1L; DataSegmentKind.Incomplete, 1L, 1L |]

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