Skip to content

Commit

Permalink
Merge branch 'main' into replace-emoji-name-placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
cobaltt7 authored Nov 10, 2024
2 parents 4e90578 + c973106 commit 7b5efd2
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 64 deletions.
20 changes: 18 additions & 2 deletions packages/collection/__tests__/collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,20 @@ describe('at() tests', () => {
expect(coll.at(0)).toStrictEqual(1);
});

test('positive non-integer index', () => {
expect(coll.at(1.5)).toStrictEqual(2);
});

test('negative index', () => {
expect(coll.at(-1)).toStrictEqual(3);
});

test('negative non-integer index', () => {
expect(coll.at(-2.5)).toStrictEqual(2);
});

test('invalid positive index', () => {
expect(coll.at(4)).toBeUndefined();
expect(coll.at(3)).toBeUndefined();
});

test('invalid negative index', () => {
Expand Down Expand Up @@ -432,12 +440,20 @@ describe('keyAt() tests', () => {
expect(coll.keyAt(0)).toStrictEqual('a');
});

test('positive non-integer index', () => {
expect(coll.keyAt(1.5)).toStrictEqual('b');
});

test('negative index', () => {
expect(coll.keyAt(-1)).toStrictEqual('c');
});

test('negative non-integer index', () => {
expect(coll.keyAt(-2.5)).toStrictEqual('b');
});

test('invalid positive index', () => {
expect(coll.keyAt(4)).toBeUndefined();
expect(coll.keyAt(3)).toBeUndefined();
});

test('invalid negative index', () => {
Expand Down
166 changes: 104 additions & 62 deletions packages/collection/src/collection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
/* eslint-disable no-param-reassign */
/**
* @internal
*/
export interface CollectionConstructor {
new (): Collection<unknown, unknown>;
new <Key, Value>(entries?: readonly (readonly [Key, Value])[] | null): Collection<Key, Value>;
new <Key, Value>(iterable: Iterable<readonly [Key, Value]>): Collection<Key, Value>;
readonly prototype: Collection<unknown, unknown>;
readonly [Symbol.species]: CollectionConstructor;
}

/**
* Represents an immutable version of a collection
Expand All @@ -19,13 +9,13 @@ export type ReadonlyCollection<Key, Value> = Omit<
> &
ReadonlyMap<Key, Value>;

/**
* Separate interface for the constructor so that emitted js does not have a constructor that overwrites itself
*
* @internal
*/
export interface Collection<Key, Value> extends Map<Key, Value> {
constructor: CollectionConstructor;
export interface Collection<Key, Value> {
/**
* Ambient declaration to allow `this.constructor[@@species]` in class methods.
*
* @internal
*/
constructor: typeof Collection & { readonly [Symbol.species]: typeof Collection };
}

/**
Expand Down Expand Up @@ -85,9 +75,16 @@ export class Collection<Key, Value> extends Map<Key, Value> {
public first(amount?: number): Value | Value[] | undefined {
if (amount === undefined) return this.values().next().value;
if (amount < 0) return this.last(amount * -1);
amount = Math.min(this.size, amount);
if (amount >= this.size) return [...this.values()];

const iter = this.values();
return Array.from({ length: amount }, (): Value => iter.next().value!);
// eslint-disable-next-line unicorn/no-new-array
const results: Value[] = new Array(amount);
for (let index = 0; index < amount; index++) {
results[index] = iter.next().value!;
}

return results;
}

/**
Expand All @@ -102,9 +99,16 @@ export class Collection<Key, Value> extends Map<Key, Value> {
public firstKey(amount?: number): Key | Key[] | undefined {
if (amount === undefined) return this.keys().next().value;
if (amount < 0) return this.lastKey(amount * -1);
amount = Math.min(this.size, amount);
if (amount >= this.size) return [...this.keys()];

const iter = this.keys();
return Array.from({ length: amount }, (): Key => iter.next().value!);
// eslint-disable-next-line unicorn/no-new-array
const results: Key[] = new Array(amount);
for (let index = 0; index < amount; index++) {
results[index] = iter.next().value!;
}

return results;
}

/**
Expand All @@ -117,11 +121,12 @@ export class Collection<Key, Value> extends Map<Key, Value> {
public last(): Value | undefined;
public last(amount: number): Value[];
public last(amount?: number): Value | Value[] | undefined {
const arr = [...this.values()];
if (amount === undefined) return arr[arr.length - 1];
if (amount < 0) return this.first(amount * -1);
if (amount === undefined) return this.at(-1);
if (!amount) return [];
return arr.slice(-amount);
if (amount < 0) return this.first(amount * -1);

const arr = [...this.values()];
return arr.slice(amount * -1);
}

/**
Expand All @@ -134,11 +139,12 @@ export class Collection<Key, Value> extends Map<Key, Value> {
public lastKey(): Key | undefined;
public lastKey(amount: number): Key[];
public lastKey(amount?: number): Key | Key[] | undefined {
const arr = [...this.keys()];
if (amount === undefined) return arr[arr.length - 1];
if (amount < 0) return this.firstKey(amount * -1);
if (amount === undefined) return this.keyAt(-1);
if (!amount) return [];
return arr.slice(-amount);
if (amount < 0) return this.firstKey(amount * -1);

const arr = [...this.keys()];
return arr.slice(amount * -1);
}

/**
Expand All @@ -148,10 +154,21 @@ export class Collection<Key, Value> extends Map<Key, Value> {
*
* @param index - The index of the element to obtain
*/
public at(index: number) {
index = Math.floor(index);
const arr = [...this.values()];
return arr.at(index);
public at(index: number): Value | undefined {
index = Math.trunc(index);
if (index >= 0) {
if (index >= this.size) return undefined;
} else {
index += this.size;
if (index < 0) return undefined;
}

const iter = this.values();
for (let skip = 0; skip < index; skip++) {
iter.next();
}

return iter.next().value!;
}

/**
Expand All @@ -161,10 +178,21 @@ export class Collection<Key, Value> extends Map<Key, Value> {
*
* @param index - The index of the key to obtain
*/
public keyAt(index: number) {
index = Math.floor(index);
const arr = [...this.keys()];
return arr.at(index);
public keyAt(index: number): Key | undefined {
index = Math.trunc(index);
if (index >= 0) {
if (index >= this.size) return undefined;
} else {
index += this.size;
if (index < 0) return undefined;
}

const iter = this.keys();
for (let skip = 0; skip < index; skip++) {
iter.next();
}

return iter.next().value!;
}

/**
Expand All @@ -176,13 +204,17 @@ export class Collection<Key, Value> extends Map<Key, Value> {
public random(): Value | undefined;
public random(amount: number): Value[];
public random(amount?: number): Value | Value[] | undefined {
const arr = [...this.values()];
if (amount === undefined) return arr[Math.floor(Math.random() * arr.length)];
if (!arr.length || !amount) return [];
return Array.from(
{ length: Math.min(amount, arr.length) },
(): Value => arr.splice(Math.floor(Math.random() * arr.length), 1)[0]!,
);
if (amount === undefined) return this.at(Math.floor(Math.random() * this.size));
amount = Math.min(this.size, amount);
if (!amount) return [];

const values = [...this.values()];
for (let sourceIndex = 0; sourceIndex < amount; sourceIndex++) {
const targetIndex = sourceIndex + Math.floor(Math.random() * (values.length - sourceIndex));
[values[sourceIndex], values[targetIndex]] = [values[targetIndex]!, values[sourceIndex]!];
}

return values.slice(0, amount);
}

/**
Expand All @@ -194,13 +226,17 @@ export class Collection<Key, Value> extends Map<Key, Value> {
public randomKey(): Key | undefined;
public randomKey(amount: number): Key[];
public randomKey(amount?: number): Key | Key[] | undefined {
const arr = [...this.keys()];
if (amount === undefined) return arr[Math.floor(Math.random() * arr.length)];
if (!arr.length || !amount) return [];
return Array.from(
{ length: Math.min(amount, arr.length) },
(): Key => arr.splice(Math.floor(Math.random() * arr.length), 1)[0]!,
);
if (amount === undefined) return this.keyAt(Math.floor(Math.random() * this.size));
amount = Math.min(this.size, amount);
if (!amount) return [];

const keys = [...this.keys()];
for (let sourceIndex = 0; sourceIndex < amount; sourceIndex++) {
const targetIndex = sourceIndex + Math.floor(Math.random() * (keys.length - sourceIndex));
[keys[sourceIndex], keys[targetIndex]] = [keys[targetIndex]!, keys[sourceIndex]!];
}

return keys.slice(0, amount);
}

/**
Expand Down Expand Up @@ -511,10 +547,14 @@ export class Collection<Key, Value> extends Map<Key, Value> {
if (typeof fn !== 'function') throw new TypeError(`${fn} is not a function`);
if (thisArg !== undefined) fn = fn.bind(thisArg);
const iter = this.entries();
return Array.from({ length: this.size }, (): NewValue => {
// eslint-disable-next-line unicorn/no-new-array
const results: NewValue[] = new Array(this.size);
for (let index = 0; index < this.size; index++) {
const [key, value] = iter.next().value!;
return fn(value, key, this);
});
results[index] = fn(value, key, this);
}

return results;
}

/**
Expand Down Expand Up @@ -959,12 +999,14 @@ export class Collection<Key, Value> extends Map<Key, Value> {
const hasInSelf = this.has(key);
const hasInOther = other.has(key);

if (hasInSelf && hasInOther) {
const result = whenInBoth(this.get(key)!, other.get(key)!, key);
if (result.keep) coll.set(key, result.value);
} else if (hasInSelf) {
const result = whenInSelf(this.get(key)!, key);
if (result.keep) coll.set(key, result.value);
if (hasInSelf) {
if (hasInOther) {
const result = whenInBoth(this.get(key)!, other.get(key)!, key);
if (result.keep) coll.set(key, result.value);
} else {
const result = whenInSelf(this.get(key)!, key);
if (result.keep) coll.set(key, result.value);
}
} else if (hasInOther) {
const result = whenInOther(other.get(key)!, key);
if (result.keep) coll.set(key, result.value);
Expand Down Expand Up @@ -995,8 +1037,8 @@ export class Collection<Key, Value> extends Map<Key, Value> {
* collection.sorted((userA, userB) => userA.createdTimestamp - userB.createdTimestamp);
* ```
*/
public toSorted(compareFunction: Comparator<Key, Value> = Collection.defaultSort) {
return new this.constructor[Symbol.species](this).sort((av, bv, ak, bk) => compareFunction(av, bv, ak, bk));
public toSorted(compareFunction: Comparator<Key, Value> = Collection.defaultSort): Collection<Key, Value> {
return new this.constructor[Symbol.species](this).sort(compareFunction);
}

public toJSON() {
Expand Down

0 comments on commit 7b5efd2

Please sign in to comment.