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
23 changes: 23 additions & 0 deletions collections/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,29 @@ assertEquals(
);
```

### findSingle

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

```ts
import { findSingle } 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 = findSingle(bookings, (it) => it.active);
const inactiveBooking = findSingle(bookings, (it) => !it.active);

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

=======

# slidingWindows

Generates sliding views of the given array of the given size and returns a new
Expand Down
41 changes: 41 additions & 0 deletions collections/find_single.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

/**
* Returns an element if and only if that element is the only one matching the given condition. Returns `undefined` otherwise.
*
* Example:
*
* ```ts
* import { findSingle } from "./find_single.ts";
* import { assertEquals } from "../testing/asserts.ts";
*
* const bookings = [
* { month: 'January', active: false },
* { month: 'March', active: false },
* { month: 'June', active: true },
* ];
* const activeBooking = findSingle(bookings, (it) => it.active);
* const inactiveBooking = findSingle(bookings, (it) => !it.active);
*
* assertEquals(activeBooking, { month: "June", active: true });
* assertEquals(inactiveBooking, undefined); // there are two applicable items
* ```
*/
export function findSingle<T>(
array: readonly T[],
predicate: (el: T) => boolean = (_) => true,
): T | undefined {
let match: T | undefined = undefined;
let found = false;
for (const element of array) {
if (predicate(element)) {
if (found) {
return undefined;
}
found = true;
match = element;
}
}

return match;
}
136 changes: 136 additions & 0 deletions collections/find_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 { findSingle } from "./find_single.ts";

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

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

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

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

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

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

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

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

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

Deno.test({
name: "[collections/findSingle] default predicator",
fn() {
singleDefaultPredicatorTest(
[42],
42,
);
singleDefaultPredicatorTest(
["foo"],
"foo",
);
singleDefaultPredicatorTest(
[9, 11, 13],
undefined,
);
singleDefaultPredicatorTest(
["foo", "bar"],
undefined,
);
},
});
1 change: 1 addition & 0 deletions collections/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from "./map_not_nullish.ts";
export * from "./map_values.ts";
export * from "./partition.ts";
export * from "./permutations.ts";
export * from "./find_single.ts";
export * from "./sliding_windows.ts";
export * from "./sum_of.ts";
export * from "./max_by.ts";
Expand Down