Skip to content

Commit

Permalink
[optimize] rename to uniqueBy() (fix #10, fix #13)
Browse files Browse the repository at this point in the history
[fix] 0, -0 & NaN cases (fix #5, fix #12)
[optimize] update Polyfill packages
  • Loading branch information
TechQuery committed Jul 28, 2020
1 parent b543bd0 commit 0df27fa
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 30 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.editorconfig
*.spec.*
.vscode/
18 changes: 10 additions & 8 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Array deduplication proposal

Stage: 1

ECMAScript proposal for Deduplicating method of Array.

![Proposal Stage-1](https://img.shields.io/badge/Proposal-Stage--1-red)

[![NPM](https://nodei.co/npm/array-unique-proposal.png?downloads=true&downloadRank=true&stars=true)][1]

## Motivation

**Deduplication** is one of the most common requirements in Data processing, especially in large Web Apps nowadays.

`[...new Set(array)]` in ECMAScript 6 isn't enough for **Non-primitive values**, and now, we may need a `Array.prototype.unique()`.
`[...new Set(array)]` in ECMAScript 6 isn't enough for **Non-primitive values**, and now, we may need a `Array.prototype.uniqueBy()`.

## Core features

While `Array.prototype.unique()` invoked with:
While `Array.prototype.uniqueBy()` invoked with:

1. no parameter, it'll work as `[...new Set(array)]`;

Expand All @@ -26,25 +26,27 @@ Notice:

- the **Returned value** is a new array, no mutation happens in the original array
- **Empty/nullish items** are treated as nullish values
- `0` & `-0` are treated as the same
- All `NaN`s are treated as the same

## Typical cases

```JavaScript
[1, 2, 3, 3, 2, 1].unique(); // [1, 2, 3]
[1, 2, 3, 3, 2, 1].uniqueBy(); // [1, 2, 3]

const data = [
{ id: 1, uid: 10000 },
{ id: 2, uid: 10000 },
{ id: 3, uid: 10001 }
];

data.unique('uid');
data.uniqueBy('uid');
// [
// { id: 2, uid: 10000 },
// { id: 1, uid: 10000 },
// { id: 3, uid: 10001 }
// ]

data.unique(({ id, uid }) => `${id}-${uid}`);
data.uniqueBy(({ id, uid }) => `${id}-${uid}`);
// [
// { id: 1, uid: 10000 },
// { id: 2, uid: 10000 },
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "array-unique-proposal",
"version": "0.2.1",
"version": "0.3.0",
"description": "ECMAScript proposal for Deduplicating method of Array",
"keywords": [
"JavaScript",
Expand Down Expand Up @@ -29,13 +29,13 @@
"prepublishOnly": "npm test && npm run build"
},
"devDependencies": {
"@types/jest": "^26.0.4",
"@types/jest": "^26.0.7",
"husky": "^4.2.5",
"jest": "^26.1.0",
"lint-staged": "^10.2.11",
"prettier": "^2.0.5",
"ts-jest": "^26.1.2",
"typescript": "^3.9.6"
"ts-jest": "^26.1.4",
"typescript": "^3.9.7"
},
"prettier": {
"singleQuote": true,
Expand Down
41 changes: 28 additions & 13 deletions polyfill/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import '.';

describe('Array.prototype.unique()', () => {
describe('Array.prototype.uniqueBy()', () => {
it('should return new Array', () => {
const origin = [];

expect(origin.unique()).not.toBe(origin);
expect(origin.uniqueBy()).not.toBe(origin);
});

it('should compare items using "!==" while 0 parameter passed in', () => {
expect([1, 2, 3, 3, 2, 1].unique()).toEqual([1, 2, 3]);
expect([1, 2, 3, 3, 2, 1].uniqueBy()).toEqual([1, 2, 3]);
});

const symbol = Symbol.for('test');
Expand All @@ -31,11 +31,10 @@ describe('Array.prototype.unique()', () => {
];

it('should compare items using "!==" by a String or Symbol key', () => {
expect(origin.unique('uid')).toEqual([
expect(origin.uniqueBy('uid')).toEqual([
{
id: 2,
uid: 10000,
[symbol]: 'example'
id: 1,
uid: 10000
},
{
id: 3,
Expand All @@ -44,30 +43,46 @@ describe('Array.prototype.unique()', () => {
}
]);

expect(origin.unique(symbol)).toEqual([
expect(origin.uniqueBy(symbol)).toEqual([
{
id: 1,
uid: 10000
},
{
id: 3,
uid: 10001,
id: 2,
uid: 10000,
[symbol]: 'example'
}
]);
});

it('should compare items using "!==" by Returned values of a Custom callback', () => {
expect(origin.unique(item => `${item.uid}${item[symbol]}`)).toEqual(
expect(origin.uniqueBy(item => `${item.uid}${item[symbol]}`)).toEqual(
origin
);
});

it('should treat an empty/nullish item as a nullish value', () => {
expect([1, , 2, undefined, null, 1].unique()).toEqual([1, , 2, null]);
expect([1, , 2, undefined, null, 1].uniqueBy()).toEqual([1, , 2, null]);

expect(
[{ id: 1 }, , { id: 2 }, undefined, null, { id: 1 }].unique('id')
[{ id: 1 }, , { id: 2 }, undefined, null, { id: 1 }].uniqueBy('id')
).toEqual([{ id: 1 }, , { id: 2 }, null]);
});

it('should treat 0 & -0 as the same', () => {
expect([0, -0].uniqueBy()).toEqual([0]);

expect([{ count: 0 }, { count: -0 }].uniqueBy('count')).toEqual([
{ count: 0 }
]);
});

it('should treat all NaNs as the same', () => {
expect([NaN, NaN].uniqueBy()).toEqual([NaN]);

expect([{ count: NaN }, { count: NaN }].uniqueBy('count')).toEqual([
{ count: NaN }
]);
});
});
13 changes: 8 additions & 5 deletions polyfill/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ type Indexer<T> = number | keyof T | symbol;
type ValueResolver<T> = Indexer<T> | Resolver<T>;

interface Array<T> {
unique(valueResolver?: ValueResolver<T>): T[];
uniqueBy(valueResolver?: ValueResolver<T>): T[];
}

if (typeof Array.prototype.unique !== 'function')
Object.defineProperty(Array.prototype, 'unique', {
if (typeof Array.prototype.uniqueBy !== 'function')
Object.defineProperty(Array.prototype, 'uniqueBy', {
writable: true,
configurable: true,
value: function <T>(this: T[], valueResolver?: ValueResolver<T>) {
Expand All @@ -22,8 +22,11 @@ if (typeof Array.prototype.unique !== 'function')
? (item: Record<Indexer<T>, any>) => item?.[key] ?? item
: valueResolver;

for (const item of this)
map.set((valueResolver as Resolver<T>)(item), item);
for (const item of this) {
const key = (valueResolver as Resolver<T>)(item);

if (!map.has(key)) map.set(key, item);
}

return [...map.values()];
}
Expand Down

0 comments on commit 0df27fa

Please sign in to comment.