From e86dacdf9c08e80fe767c4db07eede154a503d46 Mon Sep 17 00:00:00 2001 From: Miorel-Lucian Palii Date: Fri, 6 Sep 2024 03:42:38 -0700 Subject: [PATCH 1/3] wip --- .../2634-filter-elements-from-array/README.md | 2 +- .../solution.md | 262 +++++++++++++----- 2 files changed, 186 insertions(+), 78 deletions(-) diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/README.md b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/README.md index 697bade3..b52aba51 100644 --- a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/README.md +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/README.md @@ -2,7 +2,7 @@ [View Problem on LeetCode](https://leetcode.com/problems/filter-elements-from-array/) -This is one of many problems that essentially ask us to implement some built-in JavaScript function. Here, we're asked to implement filtering without using [`.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), so of course the first thing we should do is get accepted using `.filter`! +This is one of several problems that essentially ask us to implement some built-in JavaScript function. Here, we're asked to implement filtering without using [`.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), so of course the first thing we should do is get accepted using `.filter`! For a more serious solution, a loop that builds the result works just fine. diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md index 9271fcf6..829587f7 100644 --- a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md @@ -1,6 +1,6 @@ # 2634. Filter Elements from Array -[View this Write-up on LeetCode TODO](https://leetcode.com/problems/filter-elements-from-array/solutions/) | [View Problem on LeetCode](https://leetcode.com/problems/filter-elements-from-array/) +[View this Write-up on LeetCode](https://leetcode.com/problems/filter-elements-from-array/solutions/5746669/content/) | [View Problem on LeetCode](https://leetcode.com/problems/filter-elements-from-array/) > [!WARNING] > This page includes spoilers. For a spoiler-free introduction to the problem, see [the README file](README.md). @@ -17,14 +17,80 @@ Practically speaking, I think the iterative solutions using simple loops are the If you're practicing for interviews, I'd also recommend knowing how to do it with `.reduce`, since it's sometimes asked. -## Background +It's also possible to solve this problem through [`.flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/flatMap), by returning an empty array from the mapping function when we want to exclude a value. -If you've been following [my recommended sequence of JavaScript-themed LeetCode problems](../..), then you solved [2635. Apply Transform Over Each Element in Array](../2635-apply-transform-over-each-element-in-array/) right before solving this problem. The background I shared in [that write-up](../2635-apply-transform-over-each-element-in-array/solution.md) applies here as well. +## Background -I also want to explicitly discuss an additional concept, however: +> [!TIP] +> This problem is similar to [2635. Apply Transform Over Each Element in Array](../2635-apply-transform-over-each-element-in-array/) in that we're asked to reimplement a core array method. Check out [the write-up for that problem](../2635-apply-transform-over-each-element-in-array/solution.md) as well, because the background shared there will also be relevant here. ### Conditional Execution +JavaScript supports [`if`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) statements, of course, but there's another idiom that's commonly used for conditional execution, based on [the logical AND operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND), `&&`. Specifically, rather than writing: + +```javascript [] +if (condition) { + doSomething(); +} +``` + +JavaScript developers will often favor: + +```javascript [] +condition && doSomething(); +``` + +This works because `&&` has short-circuiting behavior, meaning that it won't evaluate both operands if the result is clear after evaluating only the left operand. When is the result clear after evaluating only the left operand? Remember that the stated job of `&&` is to evaluate an _AND_ operation. It returns a truthy value if the left _and_ the right operands are both truthy. If the left operand is falsy, then it's already not the case that both operands are truthy. It doesn't matter what the right operand would evaluate to, it won't affect the overall truthiness. So we skip evaluating it entirely. + +This isn't unique to JavaScript. Other languages that can implicitly treat values as booleans, such as C and C++, could also use this idiom. However JavaScript does have a feature that makes the idiom much more convenient: we don't have to think about whether `doSomething` is a `void` function or not, since `void` values can still be used as part of expressions. That is, if `doSomething` is a function that doesn't return anything then a C or C++ compiler would not be happy that we're trying to use it as part of a condition. It might emit an error message like "void value not ignored as it ought to be" and refuse to compile the code. But in JavaScript, every function returns something when it terminates. Even a function that contains no `return` statements is implicitly returning `undefined`, which is a valid value that can be used as part of a larger expression. + +So `condition && doSomething()` will first evaluate `condition`, and then: + +- if `condition` evaluated to falsy, the `&&` has enough context to know that the overall expression is falsy, so it short circuits and `doSomething()` never runs +- if `condition` evaluated to truthy, the `&&` doesn't have enough context and it must therefore evaluate the second operand; `doSomething()` runs and then: + - if `doSomething()` returned a truthy value, the overall `&&` evaluates to truthy as well + - if `doSomething()` returned a falsy value, the overall `&&` evaluates to falsy as well + - if `doSomething()` never explicitly returned anything, it evaluates to `undefined`, which is falsy; the overall `&&` evaluates to falsy as well + +The use of "truthy" and "falsy" in the above text is deliberate -- `&&` doesn't necessarily evaluate to `true` or `false`, it evaluates to the same thing as the last operand that gets evaluated. So if `condition` is falsy, then it will also be the last operand evaluated, and the overall `&&` evaluates to the same thing as `condition`, whether that's an actual `false`, a `null`, an empty string, etc. However if `condition` is a truthy value and `doSomething()` runs, then `doSomething()` will be the last operand evaluated, and the overall `&&` returns the result of `doSomething()`, which will have the appropriate truthiness. + +Most of the time we don't care what the overall `&&` evaluated to -- we just piggyback on its short-circuiting behavior to get concise conditional execution. However, sometimes we do want to use the overall result. For example, [React](https://react.dev/) code often uses logical AND for conditional rendering: + +```typescript [] +return ( +
+ {error && There was an error: {error.message}} +
Some content that should always show up
+
+); +``` + +Outside JavaScript, you might have also seen `&&` used to chain a sequence of multiple shell commands: in shells that support `&&` (e.g. [Bash]()) if we run `cmd1 && cmd2 && cmd3 && cmd4` then the overall command stops with a failure if any command in the sequence fails. This allows us to express a linear ordering of commands such as `build && test && deploy`, which makes sure the deploy only happens if the build and the tests were successful. + +JavaScript can likewise chain multiple actions or conditions with this idiom: + +```javascript [] +doSomething() && doSomethingElse() && doEvenMoreStuff(); +``` + +In a large group of operands joined by multiple `&&`s, the overall `&&` expression evaluates to the same thing as the last operand that got executed, among all the operands. + +Prior to the introduction of the [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) operator, `?.`, logical AND was often used instead. For example, to safely invoke `a.b.c.d()` or do nothing if any of the intermediate objects don't exist: + +```javascript [] +a && a.b && a.b.c && a.b.c.d && a.b.c.d(); +``` + +Incidentally, Python also has the necessary features to support this use of logical AND, but for some reason it hasn't taken off, as far as I'm aware. (Ruby and Perl also support it, and I've seen it used, though Ruby and Perl also support the syntax `do_something() if condition`.) + +> [!TIP] +> In JavaScript, using `&&` for conditional execution is wide-spread, so even if you choose not to use this pattern in your own code, you should feel comfortable reading code that does. + +One case in which `&&` can't replace `if` is in early returns. For the same reason we can't do something like `console.log(return someValue)` because there's nothing to print, we're exiting the function, we also can't do `condition && (return someValue)`. There isn't anything that the overall `&&` could evaluate to, we're exiting the function. + +> [!NOTE] +> **What about the [logical OR](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR) operator, `||`, could we use it for conditional execution or evaluation?** Take a moment to formulate your answer. I provided mine at the bottom of this write-up. + ## Solutions We're now ready to discuss some solutions! @@ -33,7 +99,7 @@ We're now ready to discuss some solutions! As always, it's fun to ignore the demands of the problem statement and get a quick accept. Note that for implementations consisting of one statement, I opted to use arrow function syntax. -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381111784/) ```typescript [] const filter = ( @@ -42,9 +108,12 @@ const filter = ( ): T[] => arr.filter(fn); ``` +> [!NOTE] +> **Why use `(element: T, index: number) => unknown` as the type annotation for the filtering function?** In particular, why not use `boolean` for the filtering function's return type? See an answer at the bottom of this write-up! + We can also get weird. If you're new to JavaScript, please ignore this next solution, it's not meant to be easy to interpret. -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381112268/) ```javascript [] /** @@ -55,13 +124,13 @@ We can also get weird. If you're new to JavaScript, please ignore this next solu const filter = Function.prototype.call.bind(Array.prototype.filter); ``` -Understanding why the above works was a bonus question in [an earlier write-up](../2635-apply-transform-over-each-element-in-array/solution.md). Read about it there if you're curious, but otherwise don't worry about this code too much. +Understanding why the above works was a bonus question in [an earlier write-up](../2635-apply-transform-over-each-element-in-array/solution.md). Read about it there if you're curious, but otherwise don't worry about this code at this time. ### Iterate and Build -Any of the many ways to iterate over an array will work, for example a classic `for` loop: +Any of the many ways to iterate over an array will work, for example a classic `for` loop. I'll be using `&&` rather than `if` throughout the code today, though you are of course welcome to use `if` statements if you prefer. -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381112503/) ```typescript [] function filter( @@ -71,16 +140,17 @@ function filter( const res: T[] = []; for (let i = 0; i < arr.length; ++i) { - fn(arr[i], i) && res.push(arr[i]); + const element = arr[i]; + fn(element, i) && res.push(element); } return res; } ``` -A `.forEach`: +We could also use a `.forEach`: -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381112774/) ```typescript [] function filter( @@ -99,14 +169,14 @@ function filter( And even a `for...of` loop, using [`.entries`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries) to have access to the index: -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381113259/) ```typescript [] function filter( arr: readonly T[], fn: (element: T, index: number) => unknown, ): T[] { - const res: TOut[] = []; + const res: T[] = []; for (const [index, element] of arr.entries()) { fn(element, index) && res.push(element); @@ -118,7 +188,7 @@ function filter( However, as I mentioned in [another write-up](../2677-chunk-array/solution.md), I consider it a code smell to build an array through repeated, conditional `.push` in a loop. It's begging to be replaced with a `.filter`! Since this is the implementation of `.filter` maybe it's the one place where the repeated `.push` pattern is forgivable. But we can also pretend we're not using that pattern by using a generator and converting it to an array in one statement: -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381137577/) ```typescript [] function* lazyFilter( @@ -126,23 +196,25 @@ function* lazyFilter( fn: (element: T, index: number) => unknown, ): Generator { for (const [index, element] of arr.entries()) { - fn(element, index) && yield element; + fn(element, index) && (yield element); } } const filter = ( arr: readonly T[], fn: (element: T, index: number) => unknown, -): TOut[] => Array.from(lazyFilter(arr, fn)); +): T[] => Array.from(lazyFilter(arr, fn)); ``` +Note that unlike `return`, a `yield` _can_ be used as one of the operands of `&&`. Whereas `return` terminates the function, `yield` merely interrupts it, and it can bring values from the outside world back into the function. + ### Using [`Array.prototype.reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) Some companies like asking candidates to implement other operations using only `.reduce`, so let's practice doing so. -Implementing mapping using [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) looks satisfying, and follows the functional programming principle of immutability, but the downside is that we are re-copying the result each time, so this solution has quadratic complexity. However it still gets accepted by LeetCode because of the small input sizes: +Using [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) looks satisfying, and follows the functional programming principle of immutability, but the downside is that we are re-copying the result each time, so this solution has quadratic complexity. However it still gets accepted by LeetCode because of the small input sizes: -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381114338/) ```typescript [] const filter = ( @@ -158,7 +230,7 @@ const filter = ( For linear complexity, we should always reuse the same array for each step, mutating it if appropriate: -[View submission on LeetCode TODO]() +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381114592/) ```typescript [] const filter = ( @@ -173,7 +245,7 @@ const filter = ( If you're a contrarian, you may prefer to [`.reduceRight`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight) and [`.reverse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse): -[View submission on LeetCode](https://leetcode.com/problems/apply-transform-over-each-element-in-array/submissions/1377344271/) +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381115218/) ```typescript [] const filter = ( @@ -188,12 +260,9 @@ const filter = ( .reverse(); ``` -> [!NOTE] -> **Instead of `.reverse`, why not replace the `.push` with [`.unshift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)?** The answer is at the bottom of the doc. - -You might also see code like the following, using the somewhat obscure [comma operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_operator) to keep the body of the arrow function "one statement". I don't encourage this practice, but then again I don't encourage `.reduce` to begin with: +You might also see code like the following, using the somewhat obscure [comma operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_operator) to keep the body of the arrow function "one statement". I don't encourage writing code like this, but it's a good idea to be comfortable reading it. -[View submission on LeetCode](https://leetcode.com/problems/apply-transform-over-each-element-in-array/submissions/1377344634/) +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381115765/) ```typescript [] const filter = ( @@ -210,87 +279,126 @@ const filter = ( Note that throughout these solutions, we annotated _one_ of the arguments of the reducer function, specifically the accumulator, to help TypeScript understand that we'll end up with a `T[]`. -> [!TIP] -> After learning `.reduce`, many programmers seem compelled to use it everywhere. I think `.reduce` is neat, but I find that it can often _reduce_ readability as well. We often have to trade off between code elegance and efficiency, as in the first example that used spread syntax. In such cases, let's resist the urge to show off with `.reduce` and stick to regular loops. I think `.reduce` is best when the reducer's `ElementType` and `ReturnType` (to reuse the type names from the Background section) are the same. So keep using `arr.reduce((a, b) => a + b, 0)` to sum arrays, but use it sparingly for more complex operations. - -### Using Other Built-Ins - -TODO: flatMap - ### Recursion Although I like recursion, I don't think there's a good reason to use it in this problem, when iteration with simple loops works so well. But to make this write-up more comprehensive, here's a recursive solution, using an inner arrow function so that `arr` and `fn` are in scope without us having to explicitly pass them around: -[View submission on LeetCode](https://leetcode.com/problems/apply-transform-over-each-element-in-array/submissions/1377324948/) +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381116246/) ```typescript [] -function map( - arr: readonly TIn[], - fn: (element: TIn, index: number) => TOut, -): TOut[] { - const res: TOut[] = []; +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] { + const res: T[] = []; - const doMap = (index: number) => { + const doFilter = (index: number) => { if (index === arr.length) { return; } - res.push(fn(arr[index], index)); - doMap(index + 1); + const element = arr[index]; + fn(element, index) && res.push(element); + doFilter(index + 1); }; - doMap(0); + doFilter(0); return res; } ``` -We don't necessarily need an `index` argument, we can also use the size of the result so far: - -[View submission on LeetCode](https://leetcode.com/problems/apply-transform-over-each-element-in-array/submissions/1377325373/) +Unlike in [2635. Apply Transform Over Each Element in Array](../2635-apply-transform-over-each-element-in-array/), it's a bit harder to get rid of the index variable, because the size of the result array doesn't necessarily match the number of elements we've processed so far. -```typescript [] -function map( - arr: readonly TIn[], - fn: (element: TIn, index: number) => TOut, -): TOut[] { - const res: TOut[] = []; - - const doMap = () => { - if (res.length === arr.length) { - return; - } +However, we can still merge the inner function into the main function, by using additional argument with [default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). I don't love this because it pollutes the interface of our function, but it works: - res.push(fn(arr[res.length], res.length)); - doMap(); - }; +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381119504/) - doMap(); +```typescript [] +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, + index: number = 0, + res: T[] = [], +): T[] { + if (index === arr.length) { + return res; + } - return res; + const element = arr[index]; + fn(element, index) && res.push(element); + return filter(arr, fn, index + 1, res); } ``` -And, we can even merge the inner function into the main function, by using an additional argument with a [default value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). I don't love this because it pollutes the interface of our function, but it works: +### Using Other Built-Ins -[View submission on LeetCode](https://leetcode.com/problems/apply-transform-over-each-element-in-array/submissions/1377325862/) +The [`.flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/flatMap) method is used to map each array element to a list of values, rather than a single value as im `.map`. Although not explicitly designed as filtering behavior, it can be used to filter out array elements by returning an empty list! To handle arbitrary kinds of data in the input array, elements we keep are also wrapped in lists: -```typescript [] -function map( - arr: readonly TIn[], - fn: (element: TIn, index: number) => TOut, - res: TOut[] = [], -): TOut[] { - if (res.length === arr.length) { - return res; - } +[View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381120126/) - res.push(fn(arr[res.length], res.length)); - return map(arr, fn, res); -} +```typescript [] +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => + arr.flatMap((element, index) => (fn(element, index) ? [element] : [])); ``` +Looks pretty nice, doesn't it? It does have one downside, however. + +> [!NOTE] +> **Can you think of a downside to the `.flatMap` implementation of filtering?** See an answer at the bottom of the write-up. + ## Answers to Bonus Questions +1. **Can the logical OR operator, `||` be used for conditional execution or evaluation?** + + Absolutely. It also has short-circuiting behavior, so it's adequate for specifying fallback behavior: + + ```javascript [] + doSomethingThatReportsFailureViaReturnValue() || logThatItFailed(); + ``` + + Before JavaScript supported [default values for function arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters), `||` was often used to implement them: + + ```javascript [] + function getDigits(num, base) { + // Default to base 10. + base = base || 10; + + // ... + } + ``` + + The `||=` operator can make that pattern even more concise, for example for lazy initialization: + + ```javascript [] + let someObject = null; + + function doSomethingWithSomeObject() { + someObject ||= initializeSomeObject(); + + // use someObject + } + ``` + + However, in modern JavaScript I recommend `??=`, the [nullish coalescing assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment) operator. + +2. **Why use `(element: T, index: number) => unknown` as the type annotation for the filtering function?** In particular, why not use `boolean` for the filtering function's return type? + + JavaScript will implicitly convert any value to boolean when used in a boolean context, so we don't have to force the function to return a boolean. Returning any kind of value will work fine. In fact, returning the original element within the filtering function is a pattern used for filtering out falsy values: + + ```javascript [] + const arr = [null, 42, "", 0, "hi", NaN, undefined, [], {}, true, false]; + console.log(arr.filter((elem) => elem)); // prints [ 42, "hi", [], {}, true ] + ``` + + (The [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/Boolean) constructor can be used as a function to convert any value to a boolean, so `arr.filter(Boolean)` is also commonly used for this purpose.) + +3. **What's the downside to the `.flatMap` implementation of filtering?** + + It creates a lot more array objects in intermediate values, one per element to be exact. Compare this with the loop-based iterative implementations or the linear implementations using `.reduce`, which create only the output array. + > [!TIP] -> Thanks for reading! If you enjoyed this write-up, feel free to [up-vote it on LeetCode TODO]()! 🙏 +> Thanks for reading! If you enjoyed this write-up, feel free to [up-vote it on LeetCode](https://leetcode.com/problems/filter-elements-from-array/solutions/5746669/content/)! 🙏 From 474441e37f3b1e4d2ea2b2aeb550725e2df4d171 Mon Sep 17 00:00:00 2001 From: Miorel-Lucian Palii Date: Fri, 6 Sep 2024 22:36:36 -0700 Subject: [PATCH 2/3] Fix a couple of typos --- .../problems/2634-filter-elements-from-array/solution.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md index 829587f7..990468cc 100644 --- a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solution.md @@ -310,7 +310,7 @@ function filter( Unlike in [2635. Apply Transform Over Each Element in Array](../2635-apply-transform-over-each-element-in-array/), it's a bit harder to get rid of the index variable, because the size of the result array doesn't necessarily match the number of elements we've processed so far. -However, we can still merge the inner function into the main function, by using additional argument with [default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). I don't love this because it pollutes the interface of our function, but it works: +However, we can still merge the inner function into the main function, by using additional arguments with [default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). I don't love this because it pollutes the interface of our function, but it works: [View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381119504/) @@ -333,7 +333,7 @@ function filter( ### Using Other Built-Ins -The [`.flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/flatMap) method is used to map each array element to a list of values, rather than a single value as im `.map`. Although not explicitly designed as filtering behavior, it can be used to filter out array elements by returning an empty list! To handle arbitrary kinds of data in the input array, elements we keep are also wrapped in lists: +The [`.flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/flatMap) method is used to map each array element to a list of values, rather than a single value as in `.map`. Although not explicitly designed as filtering behavior, it can be used to filter out array elements by returning an empty list! To handle arbitrary kinds of data in the input array, elements we keep are also wrapped in lists: [View submission on LeetCode](https://leetcode.com/problems/filter-elements-from-array/submissions/1381120126/) From b6310017261ca79d6c10aa9e020ed9924d8dfe99 Mon Sep 17 00:00:00 2001 From: Miorel-Lucian Palii Date: Fri, 6 Sep 2024 23:17:25 -0700 Subject: [PATCH 3/3] Add files --- .../iterative-loops/using-classic-for.ts | 13 ++++++++++++ .../iterative-loops/using-for-each.ts | 12 +++++++++++ .../iterative-loops/using-for-of-entries.ts | 12 +++++++++++ .../iterative-loops/using-generators.ts | 13 ++++++++++++ .../solutions/recursive/inner-function.ts | 20 +++++++++++++++++++ .../single-function-with-default-arguments.ts | 14 +++++++++++++ .../using-built-in-filter/binding.js | 6 ++++++ .../using-built-in-filter/delegating.ts | 4 ++++ .../solutions/using-flat-map/delegating.ts | 5 +++++ .../using-reduce/elegant-but-quadratic.ts | 9 +++++++++ .../using-reduce/linear-contrarian.ts | 10 ++++++++++ .../linear-with-comma-operator.ts | 10 ++++++++++ .../solutions/using-reduce/linear.ts | 8 ++++++++ 13 files changed, 136 insertions(+) create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-classic-for.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-each.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-of-entries.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-generators.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/inner-function.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/single-function-with-default-arguments.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/binding.js create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/delegating.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-flat-map/delegating.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/elegant-but-quadratic.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-contrarian.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-with-comma-operator.ts create mode 100644 workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear.ts diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-classic-for.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-classic-for.ts new file mode 100644 index 00000000..51b59195 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-classic-for.ts @@ -0,0 +1,13 @@ +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] { + const res: T[] = []; + + for (let i = 0; i < arr.length; ++i) { + const element = arr[i]; + fn(element, i) && res.push(element); + } + + return res; +} diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-each.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-each.ts new file mode 100644 index 00000000..8a6a627c --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-each.ts @@ -0,0 +1,12 @@ +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] { + const res: T[] = []; + + arr.forEach((element, index) => { + fn(element, index) && res.push(element); + }); + + return res; +} diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-of-entries.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-of-entries.ts new file mode 100644 index 00000000..8d02388d --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-for-of-entries.ts @@ -0,0 +1,12 @@ +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] { + const res: T[] = []; + + for (const [index, element] of arr.entries()) { + fn(element, index) && res.push(element); + } + + return res; +} diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-generators.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-generators.ts new file mode 100644 index 00000000..4a50a893 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/iterative-loops/using-generators.ts @@ -0,0 +1,13 @@ +function* lazyFilter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): Generator { + for (const [index, element] of arr.entries()) { + fn(element, index) && (yield element); + } +} + +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => Array.from(lazyFilter(arr, fn)); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/inner-function.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/inner-function.ts new file mode 100644 index 00000000..53d8f806 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/inner-function.ts @@ -0,0 +1,20 @@ +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] { + const res: T[] = []; + + const doFilter = (index: number) => { + if (index === arr.length) { + return; + } + + const element = arr[index]; + fn(element, index) && res.push(element); + doFilter(index + 1); + }; + + doFilter(0); + + return res; +} diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/single-function-with-default-arguments.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/single-function-with-default-arguments.ts new file mode 100644 index 00000000..761b5457 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/recursive/single-function-with-default-arguments.ts @@ -0,0 +1,14 @@ +function filter( + arr: readonly T[], + fn: (element: T, index: number) => unknown, + index: number = 0, + res: T[] = [], +): T[] { + if (index === arr.length) { + return res; + } + + const element = arr[index]; + fn(element, index) && res.push(element); + return filter(arr, fn, index + 1, res); +} diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/binding.js b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/binding.js new file mode 100644 index 00000000..e7cb3e89 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/binding.js @@ -0,0 +1,6 @@ +/** + * @param {number[]} arr + * @param {Function} fn + * @return {number[]} + */ +const filter = Function.prototype.call.bind(Array.prototype.filter); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/delegating.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/delegating.ts new file mode 100644 index 00000000..b8813e35 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-built-in-filter/delegating.ts @@ -0,0 +1,4 @@ +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => arr.filter(fn); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-flat-map/delegating.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-flat-map/delegating.ts new file mode 100644 index 00000000..94c5cf31 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-flat-map/delegating.ts @@ -0,0 +1,5 @@ +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => + arr.flatMap((element, index) => (fn(element, index) ? [element] : [])); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/elegant-but-quadratic.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/elegant-but-quadratic.ts new file mode 100644 index 00000000..7782f4c9 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/elegant-but-quadratic.ts @@ -0,0 +1,9 @@ +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => + arr.reduce( + (res: T[], element, index) => + fn(element, index) ? [...res, element] : res, + [], + ); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-contrarian.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-contrarian.ts new file mode 100644 index 00000000..c97ac605 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-contrarian.ts @@ -0,0 +1,10 @@ +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => + arr + .reduceRight((res: T[], element, index) => { + fn(element, index) && res.push(element); + return res; + }, []) + .reverse(); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-with-comma-operator.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-with-comma-operator.ts new file mode 100644 index 00000000..45ff32dc --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear-with-comma-operator.ts @@ -0,0 +1,10 @@ +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => + arr.reduce( + (res: T[], element, index) => ( + fn(element, index) && res.push(element), res + ), + [], + ); diff --git a/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear.ts b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear.ts new file mode 100644 index 00000000..23ce7d27 --- /dev/null +++ b/workspaces/javascript-leetcode-month/problems/2634-filter-elements-from-array/solutions/using-reduce/linear.ts @@ -0,0 +1,8 @@ +const filter = ( + arr: readonly T[], + fn: (element: T, index: number) => unknown, +): T[] => + arr.reduce((res: T[], element, index) => { + fn(element, index) && res.push(element); + return res; + }, []);