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: add selectFirst function #52

Merged
merged 1 commit into from
Jun 30, 2024
Merged
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
4 changes: 2 additions & 2 deletions .github/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ Scan through the [existing issues](https://github.com/aleclarson/radashi/issues)

## You want to write code?

- To get started, run `yarn` in the project's root directory to install the dependencies.
- You can run the unit tests with `yarn test`. They require Node v16+. You can run `nvm use` in the root directory to change to the correct Node version. The tests should pass (duh) and maintain 100% code coverage.
- To get started, run `pnpm i` in the project's root directory to install the dependencies.
- You can run the unit tests with `pnpm test`. They require Node v16+. You can run `nvm use` in the root directory to change to the correct Node version. The tests should pass (duh) and maintain 100% code coverage.
- To get familiar with the existing code I would recommend looking through the docs and the codebase in parallel. For each function in the docs, find the implementation in the source and skim over the code.
- When coding, try not to create internal APIs (any function or module that is not exported to be used by the client).
- Also, try not to use functions from other Radashi modules. This is a softer rule but we want to make it easy for readers to understand a function without having to navigate the codebase. As a utility library users should ideally be able to copy/paste a function from Radashi into their project. Most Radashi functions should be easy to write in isolation.
Expand Down
1 change: 1 addition & 0 deletions docs/array/select.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description: Filter and map an array
## Basic usage

Applies a filter and a map operation at once and in one pass.
If the filter is omitted, returns all non-nullish mapped values.

```ts
import * as _ from 'radashi'
Expand Down
38 changes: 38 additions & 0 deletions docs/array/selectFirst.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: selectFirst
group: 'Array'
description: Array find + map
---

## Basic usage

Returns the mapped value for the first element that satisfies the specified condition--else undefined.
If the filter is omitted, returns the first non-nullish mapped value.

```ts
import * as _ from 'radashi'

const fish = [
{
name: 'Marlin',
weight: 105,
source: 'ocean',
},
{
name: 'Bass',
weight: 8,
source: 'lake',
},
{
name: 'Trout',
weight: 13,
source: 'lake',
},
]

_.selectFirst(
fish,
f => f.weight,
f => f.source === 'lake',
) // => 8
```
3 changes: 2 additions & 1 deletion src/array/select.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Select performs a filter and a mapper inside of a reduce, only
* iterating the list one time.
* iterating the list one time. If condition is omitted, will
* select all mapped values that are non-nullish.
*
* ```ts
* select(
Expand Down
29 changes: 29 additions & 0 deletions src/array/selectFirst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Select performs a find + map operation, short-circuiting on the first
* element that satisfies the prescribed condition. If condition is omitted,
* will select the first mapped value which is non-nullish.
*
* ```ts
* selectFirst(
* [1, 2, 3, 4],
* x => x * x,
* x => x > 2
* )
* // => 9
* ```
*/
export function selectFirst<T, U>(
array: readonly T[],
mapper: (item: T, index: number) => U,
condition?: (item: T, index: number) => boolean,
): U | undefined {
if (!array) {
return undefined
}
let foundIndex = -1
const found = array.find((item, index) => {
foundIndex = index
return condition ? condition(item, index) : mapper(item, index) != null
})
return found === undefined ? undefined : mapper(found, foundIndex)
}
2 changes: 1 addition & 1 deletion src/array/tests/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('select function', () => {
)
expect(result).toEqual(['c2', 'd3'])
})
test('works without a condition callback', () => {
test('works without a condition callback, filtering nullish mapped values', () => {
const list = [{ a: 1 }, { b: 2 }, { a: 3 }, { a: null }, { a: undefined }]
const result = _.select(list, obj => obj.a)
expect(result).toEqual([1, 3])
Expand Down
60 changes: 60 additions & 0 deletions src/array/tests/selectFirst.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as _ from 'radashi'

const cast = <T = any[]>(value: any): T => value

describe('selectFirst function', () => {
test('does not fail on bad input', () => {
expect(
_.selectFirst(
cast(null),
x => x,
x => x,
),
).toBeUndefined()
expect(
_.selectFirst(
cast(undefined),
x => x,
x => x,
),
).toBeUndefined()
})
test('returns mapped result of first value that meets the condition', () => {
const list = [
{ group: 'a', word: 'hello' },
{ group: 'b', word: 'bye' },
{ group: 'a', word: 'oh' },
{ group: 'b', word: 'hey' },
{ group: 'c', word: 'ok' },
]
const result = _.selectFirst(
list,
x => x.word,
x => x.group === 'b',
)
expect(result).toEqual('bye')
})
test('does not fail on empty input list', () => {
const list: any[] = []
const result = _.selectFirst(
list,
(x: any) => x.word,
x => x.group === 'a',
)
expect(result).toBeUndefined()
})
test('works with index', () => {
const letters = ['a', 'b', 'c', 'd']
const result = _.selectFirst(
letters,
(l, idx) => `${l}${idx}`,
(_, idx) => idx > 1,
)
expect(result).toEqual('c2')
})
test('works without a condition callback, filtering nullish mapped values', () => {
const list = [{ a: null }, { a: undefined }, { b: 2 }, { a: 1 }, { a: 3 }]
const result = _.selectFirst(list, el => el.a)
expect(result).toEqual(1)
})
})
1 change: 1 addition & 0 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './array/range'
export * from './array/replace'
export * from './array/replaceOrAppend'
export * from './array/select'
export * from './array/selectFirst'
export * from './array/shift'
export * from './array/sift'
export * from './array/sort'
Expand Down