@nftgo/gorarity
Version:
An algorithm to calculate rarity of NFT(how special it is), based on Jaccard Distance.
103 lines (102 loc) • 4.26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JaccardDistanceScoringHandler = void 0;
const utils_1 = require("../utils");
const MAX_RARITY_SCORE = 100;
class JaccardDistanceScoringHandler {
/**
* @description Calculate the score of each token.
* @param collection
* @param tokens
* @returns The scores of each token, which has the same order with tokens.
*/
scoreTokens(collection, tokens) {
/**
* We use traitKeys Array index to represent a unique trait,
* so we can use the index set to identify which traits a token has.
*/
const traitKeys = [];
const traitKeyMapTraitKeysIndex = new Map();
const tokenIds = [];
const indexesList = [];
for (const token of tokens) {
const indexes = [];
Array.from(token.metadata.stringTraits.values()).forEach((trait) => {
const traitKey = `${trait.name}-${trait.value}`;
let index = traitKeyMapTraitKeysIndex.get(traitKey);
if (index === undefined) {
index = traitKeys.length;
traitKeyMapTraitKeysIndex.set(traitKey, index);
traitKeys.push(traitKey);
}
indexes.push(index);
});
tokenIds.push(token.tokenIdentifier.tokenId);
indexesList.push(indexes);
}
return this.calcScores(tokenIds, indexesList);
}
/**
* @description Calculate the score of each token.
* The token score is defined as the sum of Jaccard Distance between this token and all other token.
* @param tokenIds Indicate all tokens using tokenId.
* @param indexesList Indicate the token traits using indexes, and the indexes list has the same order with tokenIds.
* @returns The scores of each token, which has the same order with tokenIds.
*/
calcScores(tokenIds, indexesList) {
let maxScore = -1;
let minScore = -1;
const scores = [];
for (let i = 0; i < tokenIds.length; i++) {
let score = 0;
for (let j = 0; j < tokenIds.length; j++) {
const dist = this.calcDistance(indexesList[i], indexesList[j]);
score += dist;
}
if (maxScore === -1 || score > maxScore)
maxScore = score;
if (minScore === -1 || score < minScore)
minScore = score;
scores.push(score);
}
return this.normalizeScores(scores, maxScore, minScore);
}
/**
* @description We use the Jaccard Distance as the algorithm for calculating the difference between two sets,
* you can get more information from here: https://en.wikipedia.org/wiki/Jaccard_index.
* @param traitsA The traits set of token A.
* @param traitsB The traits set of token B.
* @returns Distance. The distance indicate the difference between two sets. The bigger the distance, the more different between two sets.
*/
calcDistance(traitsA, traitsB) {
if (traitsA.length === 0 && traitsB.length === 0)
return 0;
let intersection = 0;
for (const a of traitsA) {
for (const b of traitsB) {
if (a === b)
intersection++;
}
}
const union = traitsA.length + traitsB.length - intersection;
return (union - intersection) / union;
}
normalizeScores(scores, maxScore, minScore) {
const range = maxScore - minScore;
if (range < 0)
throw new Error('negative rarity range');
const normalizedScores = [];
for (let i = 0; i < scores.length; i++) {
if (range === 0) {
// Set each element of scores as zero when they are the same
normalizedScores[i] = 0;
}
else {
normalizedScores[i] = (MAX_RARITY_SCORE * (scores[i] - minScore)) / range;
normalizedScores[i] = (0, utils_1.round)(normalizedScores[i], 2);
}
}
return normalizedScores;
}
}
exports.JaccardDistanceScoringHandler = JaccardDistanceScoringHandler;