-
-
Notifications
You must be signed in to change notification settings - Fork 4k
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
perf(collection): optimisations #10552
Open
Renegade334
wants to merge
11
commits into
discordjs:main
Choose a base branch
from
Renegade334:collection-optimisations
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
621df06
perf: `merge()`: deduplicate boolean checks
Renegade334 3ff27da
perf: `toSorted()`: remove redundant closure
Renegade334 8a7ce37
perf: `last[Key]()`: order of operations
Renegade334 788de50
perf: `{at,keyAt}()`: manually iterate to target
Renegade334 b13d9b2
perf: `first[Key]()`: avoid `Array.from()`
Renegade334 9052bf4
perf: `map()`: avoid `Array.from()`
Renegade334 9ed4d3c
perf: `random[Key]()`: avoid `Array.from()`
Renegade334 64c1e9e
test: `.{at,keyAt}()` indices
Renegade334 7bffad5
perf: `last[Key]()`: use `.at()`/`.keyAt()` for single element
Renegade334 efdbc7f
perf: `first[Key]()`: use iterable-to-array if returning all
Renegade334 b93437c
perf: `random[Key]()`: use `{at,keyAt}()` for single value
Renegade334 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -85,9 +85,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; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -102,9 +109,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; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -117,11 +131,11 @@ 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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -134,11 +148,11 @@ 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); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -148,10 +162,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 { | ||||||||||||||||||||||||||
if (index < this.size * -1) return undefined; | ||||||||||||||||||||||||||
index += this.size; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const iter = this.values(); | ||||||||||||||||||||||||||
for (let skip = 0; skip < index; skip++) { | ||||||||||||||||||||||||||
iter.next(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return iter.next().value!; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -161,10 +186,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 { | ||||||||||||||||||||||||||
if (index < this.size * -1) return undefined; | ||||||||||||||||||||||||||
index += this.size; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Comment on lines
+191
to
+196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const iter = this.keys(); | ||||||||||||||||||||||||||
for (let skip = 0; skip < index; skip++) { | ||||||||||||||||||||||||||
iter.next(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return iter.next().value!; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -176,13 +212,18 @@ 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()]; | ||||||||||||||||||||||||||
// eslint-disable-next-line unicorn/no-new-array | ||||||||||||||||||||||||||
const results: Value[] = new Array(amount); | ||||||||||||||||||||||||||
for (let index = 0; index < amount; index++) { | ||||||||||||||||||||||||||
results[index] = values.splice(Math.floor(Math.random() * values.length), 1)[0]!; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return results; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -194,13 +235,18 @@ 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()]; | ||||||||||||||||||||||||||
// eslint-disable-next-line unicorn/no-new-array | ||||||||||||||||||||||||||
const results: Key[] = new Array(amount); | ||||||||||||||||||||||||||
for (let index = 0; index < amount; index++) { | ||||||||||||||||||||||||||
results[index] = keys.splice(Math.floor(Math.random() * keys.length), 1)[0]!; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return results; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -511,10 +557,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; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -959,12 +1009,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); | ||||||||||||||||||||||||||
|
@@ -995,8 +1047,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() { | ||||||||||||||||||||||||||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better readability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think making one addition is faster then multiplication + addition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's 14 8% faster for negative indexes