Skip to content

feat(collection): add findSingle #1166

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

Merged
merged 10 commits into from
Sep 12, 2021
19 changes: 19 additions & 0 deletions collections/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,25 @@ console.assert(
);
```

### single

Returns the only element in the given collection matching the given predicate.
Returns undefined if there is none.
Copy link
Member

Choose a reason for hiding this comment

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

I think we should mention more explicitly about returning undefined when there are multiple matches

and it might be good to have another example code block which has multiple matches

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your review.
I added an example and description for returning undefined cases in this commit (7472844 6c74b61).

Would you please confirm that this explanation is enough?


```ts
import { single } from "https://deno.land/std@$STD_VERSION/collections/mod.ts";
import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";

const bookings = [
{ month: 'January', active: false },
{ month: 'March', active: false },
{ month: 'June', active: true },
];
const activeBooking = single(bookings, (it) => it.active);

assertEquals(activeBooking, { month: 'June', active: true });
```

### sortBy

Returns all elements in the given collection, sorted by their result using the
Expand Down
1 change: 1 addition & 0 deletions collections/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from "./map_not_nullish.ts";
export * from "./map_values.ts";
export * from "./partition.ts";
export * from "./permutations.ts";
export * from "./single.ts";
export * from "./sum_of.ts";
export * from "./max_of.ts";
export * from "./sort_by.ts";
Expand Down
37 changes: 37 additions & 0 deletions collections/single.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

/**
* Returns the only element in the given collection matching the given predicate. Returns undefined if there is none.
*
* Example:
*
* ```ts
* import { single } from "./single.ts";
* import { assertEquals } from "../testing/asserts.ts";
*
* const bookings = [
* { month: 'January', active: false },
* { month: 'March', active: false },
* { month: 'June', active: true },
* ];
* const activeBooking = single(bookings, (it) => it.active);
*
* assertEquals(activeBooking, { month: 'June', active: true });
* ```
*/
export function single<T>(
array: readonly T[],
predicate: (el: T) => boolean = (_) => true,
): T | undefined {
let match: T | undefined = undefined;
for (const element of array) {
if (predicate(element)) {
if (match !== undefined) {
return undefined;
}
match = element;
}
}

return match;
}
136 changes: 136 additions & 0 deletions collections/single_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

import { assertEquals } from "../testing/asserts.ts";
import { single } from "./single.ts";

function singleTest<I>(
input: [Array<I>, (element: I) => boolean],
expected: I | undefined,
message?: string,
) {
const actual = single(...input);
assertEquals(actual, expected, message);
}

function singleDefaultPredicatorTest<I>(
input: Array<I>,
expected: I | undefined,
message?: string,
) {
const actual = single(input);
assertEquals(actual, expected, message);
}

Deno.test({
name: "[collections/single] no mutation",
fn() {
const array = [1, 2, 3];
single(array, (it) => it % 2 === 0);

assertEquals(array, [1, 2, 3]);
},
});

Deno.test({
name: "[collections/single] empty input",
fn() {
singleTest(
[[], (_) => true],
undefined,
);
},
});

Deno.test({
name: "[collections/single] only one element",
fn() {
singleTest(
[[42], (_it) => true],
42,
);
singleTest(
[["foo"], (_it) => true],
"foo",
);
singleTest(
[[null], (_it) => true],
null,
);
singleTest(
[[undefined], (_it) => true],
undefined,
);
},
});

Deno.test({
name: "[collections/single] no matches",
fn() {
singleTest(
[[9, 11, 13], (it) => it % 2 === 0],
undefined,
);
singleTest(
[["foo", "bar"], (it) => it.startsWith("z")],
undefined,
);
singleTest(
[[{ done: false }], (it) => it.done],
undefined,
);
},
});

Deno.test({
name: "[collections/single] only match",
fn() {
singleTest(
[[9, 12, 13], (it) => it % 2 === 0],
12,
);
singleTest(
[["zap", "foo", "bar"], (it) => it.startsWith("z")],
"zap",
);
singleTest(
[[{ done: false }, { done: true }], (it) => it.done],
{ done: true },
);
},
});

Deno.test({
name: "[collections/single] multiple matches",
fn() {
singleTest(
[[9, 12, 13, 14], (it) => it % 2 === 0],
undefined,
);
singleTest(
[["zap", "foo", "bar", "zee"], (it) => it.startsWith("z")],
undefined,
);
},
});

Deno.test({
name: "[collections/single] default predicator",
fn() {
singleDefaultPredicatorTest(
[42],
42,
);
singleDefaultPredicatorTest(
["foo"],
"foo",
);
singleDefaultPredicatorTest(
[9, 11, 13],
undefined,
);
singleDefaultPredicatorTest(
["foo", "bar"],
undefined,
);
},
});