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(collection): add findSingle #1166

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

### single

Returns the only element in the given collection matching the given predicate.
Returns undefined if there is none or multiple matches for the predicate.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

alternative:

Returns the element if and only if a single matching element exists to the given condition; otherwise, it returns undefined.

Copy link
Contributor

@LionC LionC Aug 27, 2021

Choose a reason for hiding this comment

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

"to the given condition" does not make sense to me (not a native speaker though). Maybe slightly modify to

Returns an element if and only if that element is the only one matching the given condition.

Returns `undefined` otherwise.

?

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 comment! Fixed it! 1fa2dbb


```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);
const inactiveBooking = single(bookings, (it) => !it.active);

assertEquals(activeBooking, { month: "June", active: true });
assertEquals(inactiveBooking, undefined); // there are two applicable items
```

### 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
39 changes: 39 additions & 0 deletions collections/single.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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 or multiple matches for the predicate.
*
* 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);
* const inactiveBooking = single(bookings, (it) => !it.active);
*
* assertEquals(activeBooking, { month: "June", active: true });
* assertEquals(inactiveBooking, undefined); // there are two applicable items
* ```
*/
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,
);
},
});