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

feat(collections): add slidingWindows #1191

Merged
merged 14 commits into from
Sep 6, 2021
Merged

feat(collections): add slidingWindows #1191

merged 14 commits into from
Sep 6, 2021

Conversation

ayame113
Copy link
Contributor

@ayame113 ayame113 commented Aug 30, 2021

for #1173

  • Did you support importing from mod.ts as well as your functions file?
  • Have you made sure to allocate as little memory and do as little steps in your algorithm as possible?
  • Did you add/adapt JSDoc comments, add/adapt an example, made sure that the example uses the ts markdown tag and assertEquals to show the result?
  • Did you add/adapt your functions documentation in README.md?
  • Did you make sure not to mutate the arguments passe to your function?
  • Did you add tests ensuring no mutation, empty input and corner cases?
  • Are your types flat, meaning there is no unnecessary alias in them that makes your users "go to type definition" twice to understand what to pass?

@ayame113 ayame113 marked this pull request as ready for review August 30, 2021 21:45
@LionC LionC mentioned this pull request Aug 31, 2021
44 tasks
Copy link
Contributor

@LionC LionC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor comments, well done overall, thanks!

collections/windowed.ts Outdated Show resolved Hide resolved
Comment on lines 69 to 74
const length = Math.floor((array.length - (partial ? 1 : size)) / step + 1);

return Array.from(
{ length },
(_, i) => array.slice(i * step, i * step + size),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this - it is concise, understandable but also not trivial.

Comment on lines 69 to 73
const length = Math.floor((array.length - (partial ? 1 : size)) / step + 1);

return Array.from(
{ length },
(_, i) => array.slice(i * step, i * step + size),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal nitpick: When too many things happen on one line, it starts to become hard to read. You could definitely split some value (like the ternary) into their own intermediary const. That is really just a nitpick though, so don't bother if you do not agree :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, the result of deno fmt is within single line, so I'd like to leave it as it is.

Comment on lines 159 to 160
// @ts-ignore: for test
windowed([1, 2, 3, 4, 5], "invalid");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to test for invalid TS calls - if we start doing that, you might as well check every parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we forgot to throw an error here, the length will be NaN and we will get the unexpected behavior of returning an empty array. I'd like to leave this test alone to make sure that an error is thrown when passing a non-numeric value.

collections/windowed_test.ts Outdated Show resolved Hide resolved
@@ -0,0 +1,220 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very thorough tests overall - thanks!

@ayame113
Copy link
Contributor Author

ayame113 commented Sep 1, 2021

I found it faster to use the for statement and array.push than to use Array.from, so I modified the code accordingly.

Benchmark for arrays of length 10000:

  • before -> 1.107292060000005ms

    return Array.from(
      { length },
      (_, i) => array.slice(i * step, i * step + size),
    );
  • after -> 0.36173926999999984ms

    const result = [];
    for (let i = 0; i < length; i++) {
      result.push(array.slice(i * step, i * step + size));
    }
    return result;

/** length of the return array */
const length = Math.floor((array.length - (partial ? 1 : size)) / step + 1);

const result = [];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const result = [];
const result = new Array(length);

Copy link
Contributor Author

@ayame113 ayame113 Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed Thank you.
Sure, new Array(length) (0.33261830000000053ms) is a bit faster than array.push (0.36173926999999984ms).
However, according to this V8 blog post, it seems that V8 can optimize the code better with array.push than with new Array(length). I would like to use array.push to improve the performance of subsequent code that uses the return value of this function.

Copy link

@ghost ghost Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are pushing arrays into array, primitive value optimization is impossible in this case (not to mention type hint does not make up for time you waste reallocing memory)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devcat what @ayame113 is (I think correctly) getting at, is that when you build an Array using the constructor with a length value, you are saving some performance for reallocation when filling it, but that Array is and will forever be markes as HOLEY_, which prevents v8 from applying performance optimizations to further operations on it like .map().

I think this is actually really interesting here and in all the other functions. We have been doing new Array(length) whenever possible, but thinking about it now, that means that we improve the performance of our function a bit, but return an unoptimizable HOLEY_ Array to the user that will perform worse in their code. Specifically for bigger Arrays, I don't think that is desirable, as the reallocation impact is only O(log n) and only happens once, while the impact of the user carrying a HOLEY_ Array through their code happens ok every single further operation on it.

I plan to do a benchmark suite for the whole module as soons as we are API complete, we should investigate that further once that lands.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LionC
Thanks for the explanation, that's what I wanted to say. 👍

@kt3k
Copy link
Member

kt3k commented Sep 2, 2021

@ayame113 Thank you for your contribution! Nice work!

The implementation and tests looks good to me.

But we try to avoid using adjective name for public API in general. ref: #1098 Can you think of any alternative name for this function?

@ayame113
Copy link
Contributor Author

ayame113 commented Sep 2, 2021

@kt3k

I found a discussion that took place when I introduced the windowed function to Kotlin.
Kotlin/KEEP#11

According to it, other names used in other languages or suggested are:

  • slideWindow
  • subLists (subArrays)
  • eachSlice
  • collat​​e
  • partition

I like slideWindow. I changed it to use slideWindow for the time being, but other names are fine.

@LionC
Copy link
Contributor

LionC commented Sep 2, 2021

@kt3k

I found a discussion that took place when I introduced the windowed function to Kotlin.

Kotlin/KEEP#11

According to it, other names used in other languages or suggested are:

  • slideWindow

  • subLists (subArrays)

  • eachSlice

  • collat​​e

  • partition

I like slideWindow.

Something with "sliding" is very good I think. But why only one "window" when it returns multiple?

What about slidingWindows?

@ayame113
Copy link
Contributor Author

ayame113 commented Sep 2, 2021

Something with "sliding" is very good I think. But why only one "window" when it returns multiple?
What about slidingWindows?

Oops, I missed this comment before commit 7677fd2. I think slidingWindows is good too.
At first, I thought slide was good because sliding sounds like a noun, but I'm not sure because I'm not a native speaker.

@LionC
Copy link
Contributor

LionC commented Sep 3, 2021

I would vote strongly for slidingWindows over slideWindow - I get the "verb" idea, but trying to understand what I get when reading slideWindow is hard for a non-native speaker (or for someone who does not expect a verb). Either way, both would be fine and are mergeable IMO, @kt3k can you make a call?

@kt3k
Copy link
Member

kt3k commented Sep 4, 2021

Thank you @ayame113 for researching the past discussion in kotlin community.

Among 2 candidates, I slightly prefer slidingWindows because it exactly describes the returned value. slideWindow sounds a little bit obscure about what the returned value is.

@ayame113
Copy link
Contributor Author

ayame113 commented Sep 4, 2021

Among 2 candidates, I slightly prefer slidingWindows because it exactly describes the returned value. slideWindow sounds a little bit obscure about what the returned value is.

I updated to use the slidingWindows.

@ayame113 ayame113 changed the title feat(collections): add windowed feat(collections): add slidingWindows Sep 4, 2021
Copy link
Member

@kt3k kt3k left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ayame113 Thank you for updating the name 🙏

LGTM. Nice work!

@kt3k kt3k merged commit 3efa178 into denoland:main Sep 6, 2021
@ayame113 ayame113 deleted the add-collection-windowed branch September 13, 2021 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants