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

doc (collections): Add contribtution guide, some principles and extend README #1110

Merged
merged 6 commits into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
34 changes: 34 additions & 0 deletions collections/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Contribution Guide

First, thank you for being interested in contributing! We hope this page helps as a guide. When in doubt, look at the files
in this folder, read through issues and PRs regarding the `collections` module or visit the
[#deno-std channel on our Discord](https://discord.gg/Ub9bRxHv) and ask.

## Philosophy

Currently, those are the general ideas behind how the module is implemented:

- Provide a clear, specific toolbox and vocabulary for common tasks with `Array`s and `Record`s
- Optimize each specific tasks for maximum runtime and memory efficiency
- Offer only pure functions, never mutate given arguments, always return a new value
LionC marked this conversation as resolved.
Show resolved Hide resolved
- Accept and return `Array`s and `Record`s - we are optimizing for the most common use cases and
leave more general approaches to the [ES iterator helpers proposal](https://github.com/tc39/proposal-iterator-helpers)
for now
- Some implementations for common tasks are pretty trivial - this is fine. It still provides a readable vocabulary to
express a common task, is as optimized as possible and helps newcomers. Be aware that it might not be everyones style
to use small functions and we do not want to change that, just offer a way to do so if desired
- All functions can be imported in isolation to reduce bundle size if important
- All functions are implemented in Typescript

## Contribution Checklist

If you want to post a PR, this checklis might help you to speed up the process by solving most common review comments upfront.

- Did you support importing from `mod.ts` as well as your functions file?
- Have you made sure to allocate as little memory and do as little steps in your algorithm as possible?
- Did you add/adapt JSDoc comments, add/adapt an example, made sure that the example uses the `ts` markdown tag and `assertEquals` to show the result?
- Did you add/adapt your functions documentation in `README.md`?
- Did you make sure **not** to mutate the arguments passe to your function?
- Did you add tests ensuring no mutation, empty input and corner cases?
- Are your types flat, meaning there is no unnecessary alias in them that makes your users "go to type definition" twice
to understand what to pass?
149 changes: 130 additions & 19 deletions collections/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
# std/collections

This module includes utilities around collection types like `Array`, `Record`
and `Set`.
This module includes pure functions for specific common tasks around collection types
like `Array` and `Record`. This module is heavily inspired by `kotlin`s stdlib.

# Usage
- All provided functions are **pure**, which also means that hey do **not mutate** your inputs,
**returning a new value** instead.
- All functions are importable on their own by referencing their snake_case named file (e.g. `collections/sort_by.ts`)

## chunked
If you want to contribute or undestand why this is done the way it is, see the [contribution guide](CONTRIBUTING.md).

## Usage

### associateBy

Transforms the given array into a Record, extracting the key of each element using the given selector.
If the selector produces the same key for multiple elements, the latest one will be used (overriding the
ones before it).

```ts
const users = [
{ id: 'a2e', userName: 'Anna' },
{ id: '5f8', userName: 'Arnold' },
{ id: 'd2c', userName: 'Kim' },
];
const usersById = associateBy(users, (it) => it.id);

assertEquals(usersById, {
'a2e': { id: 'a2e', userName: 'Anna' },
'5f8': { id: '5f8', userName: 'Arnold' },
'd2c': { id: 'd2c', userName: 'Kim' },
});
```


### chunked

Splits the given array into chunks of the given size and returns them.

Expand All @@ -30,7 +58,7 @@ console.assert(
);
```

## deepMerge
### deepMerge

Merges the two given Records, recursively merging any nested Records with the
second collection overriding the first in case of conflict
Expand All @@ -49,7 +77,7 @@ const b = { foo: { bar: true } };
assertEquals(deepMerge(a, b), { foo: { bar: true } });
```

## distinctBy
### distinctBy

Returns all elements in the given array that produce a distinct value using the
given selector, preserving order by first occurence.
Expand All @@ -61,7 +89,7 @@ const exampleNamesByFirstLetter = distinctBy(names, (it) => it.charAt(0));
console.assert(exampleNamesByFirstLetter === ["Anna", "Kim"]);
```

## distinct
### distinct

Returns all distinct elements in the given array, preserving order by first
occurence.
Expand All @@ -73,7 +101,7 @@ const distinctNumbers = distinct(numbers);
console.assert(distinctNumbers === [3, 2, 5]);
```

## filterEntries
### filterEntries

Returns a new record with all entries of the given record except the ones that
do not match the given predicate.
Expand All @@ -95,7 +123,7 @@ console.assert(
);
```

## filterKeys
### filterKeys

Returns a new record with all entries of the given record except the ones that
have a key that does not match the given predicate.
Expand All @@ -116,7 +144,7 @@ console.assert(
);
```

## filterValues
### filterValues

Returns a new record with all entries of the given record except the ones that
have a value that does not match the given predicate.
Expand All @@ -137,7 +165,7 @@ console.assert(
);
```

## findLast
### findLast

Returns the last element in the given array matching the given predicate.

Expand All @@ -148,7 +176,7 @@ const lastEvenNumber = findLast(numbers, (it) => it % 2 === 0);
console.assert(lastEvenNumber === 2);
```

## findLastIndex
### findLastIndex

Returns the index of the last element in the given array matching the given
predicate.
Expand All @@ -160,7 +188,7 @@ const lastIndexEvenNumber = findLastIndex(numbers, (it) => it % 2 === 0);
console.assert(lastIndexEvenNumber === 6);
```

## groupBy
### groupBy

Applies the given selector to each element in the given array, returning a
Record containing the results as keys and all values that produced that key as
Expand All @@ -182,7 +210,7 @@ console.assert(
);
```

## intersect
### intersect

Returns all distinct elements that appear at least once in each of the given
arrays.
Expand All @@ -195,7 +223,7 @@ const commonInterests = intersectTest(lisaInterests, kimInterests);
console.assert(commonInterests === ["Cooking", "Music"]);
```

## mapEntries
### mapEntries

Applies the given transformer to all entries in the given record and returns a
new record containing the results.
Expand All @@ -217,7 +245,7 @@ console.assert(
);
```

## mapKeys
### mapKeys

Applies the given transformer to all keys in the given record's entries and
returns a new record containing the transformed entries.
Expand All @@ -237,7 +265,24 @@ console.assert(
);
```

## mapValues
### mapNotNullish

Returns a new array, containing all elements in the given array transformed using the given transformer, except the ones
that were transformed to `null` or `undefined`.

```typescript
const people = [
{ middleName: null },
{ middleName: 'William' },
{ middleName: undefined },
{ middleName: 'Martha' },
];
const foundMiddleNames = mapNotNullish(people, (it) => it.middleName);

assertEquals(foundMiddleNames, [ 'William', 'Martha' ]);
```

### mapValues

Applies the given transformer to all valuesin the given record and returns a new
record containing the resulting keys associated to the last value that produced
Expand All @@ -258,7 +303,7 @@ console.assert(
);
```

## partition
### partition

Returns a tuple of two arrays with the first one containing all elements in the
given array that match the given predicate and the second one containing all
Expand All @@ -272,7 +317,7 @@ console.assert(even === [6, 8]);
console.assert(odd === [5, 7, 9]);
```

## permutations
### permutations

Builds all possible orders of all elements in the given array Ignores equality
of elements, meaning this will always reutrn the same number of permutations for
Expand All @@ -289,3 +334,69 @@ console.assert(
],
);
```

### sortBy

Returns all elements in the given collection, sorted by their result using the given selector

```ts
const people = [
{ name: 'Anna', age: 34 },
{ name: 'Kim', age: 42 },
{ name: 'John', age: 23 },
];
const sortedByAge = sortBy(people, (it) => it.age);

assertEquals(sortedByAge, [
{ name: 'John', age: 23 },
{ name: 'Anna', age: 34 },
{ name: 'Kim', age: 42 },
]);
```

### union

Returns all distinct elements that appear in any of the given arrays

```ts
const soupIngredients = [ 'Pepper', 'Carrots', 'Leek' ];
const saladIngredients = [ 'Carrots', 'Radicchio', 'Pepper' ];
const shoppingList = union(soupIngredients, saladIngredients);

assertEquals(shoppingList, [ 'Pepper', 'Carrots', 'Leek', 'Radicchio' ]);
```

### unzip

Builds two separate arrays from the given array of 2-tuples, with the first returned array holding all first
tuple elements and the second one holding all the second elements

```ts
const parents = [
[ 'Maria', 'Jeff' ],
[ 'Anna', 'Kim' ],
[ 'John', 'Leroy' ],
] as [string, string][];

const [ moms, dads ] = unzip(parents);

assertEquals(moms, [ 'Maria', 'Anna', 'John' ]);
assertEquals(moms, [ 'Jeff', 'Kim', 'Leroy' ]);
```

### zip

Builds 2-tuples of elements from the given array with matching indices, stopping when the smaller array's end is reached

```ts
const numbers = [ 1, 2, 3, 4 ];
const letters = [ 'a', 'b', 'c', 'd' ];
const pairs = zip(numbers, letters);

console.assert(pairs === [
[ 1, 'a' ],
[ 2, 'b' ],
[ 3, 'c' ],
[ 4, 'd' ],
]);
```