Files
buildpath/match_collector/item_tree.ts
Valentin Haudiquet dae65c8fa2
All checks were successful
pipeline / lint-and-format (push) Successful in 4m44s
pipeline / build-and-push-images (push) Successful in 4m7s
Allow collecting data from EUNE, NA, KR on top of EUW
- match_collector: query API and build collections for each platform
- match_collector: aggregate champion stats of each platform in one collection with platform annotations
- frontend: replace stats to count matches in platform-specific collections
- frontend: replace "EUW Challengers" with all supported platforms
- dev: adapted scripts to count match in platforms
2026-04-17 16:25:19 +02:00

280 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
type GoldAdvantageTag = 'ahead' | 'behind' | 'even'
type PlatformCounts = {
euw: number
eun: number
na: number
kr: number
}
type ItemTree = {
data: number | undefined
count: number
children: Array<ItemTree>
// Gold advantage tracking
boughtWhen: {
aheadCount: number
behindCount: number
evenCount: number
meanGold: number
}
// Platform tracking
platformCount: PlatformCounts
}
function initPlatformCounts(): PlatformCounts {
return { euw: 0, eun: 0, na: 0, kr: 0 }
}
function treeInit(): ItemTree {
return {
data: undefined,
count: 0,
children: [],
boughtWhen: { aheadCount: 0, behindCount: 0, evenCount: 0, meanGold: 0 },
platformCount: initPlatformCounts()
}
}
/*
* Merge a node with an item tree
*/
function nodeMerge(itemtree: ItemTree, node: ItemTree) {
const item = node.data
const count = node.count
let next: ItemTree | null = null
// Try to find an existing node in this tree level with same item
for (const child of itemtree.children) {
if (child.data == item) {
child.count += 1
child.boughtWhen.aheadCount += node.boughtWhen.aheadCount
child.boughtWhen.evenCount += node.boughtWhen.evenCount
child.boughtWhen.behindCount += node.boughtWhen.behindCount
// Merge platform counts
child.platformCount.euw += node.platformCount.euw
child.platformCount.eun += node.platformCount.eun
child.platformCount.na += node.platformCount.na
child.platformCount.kr += node.platformCount.kr
next = child
break
}
}
// If not found, add item node at this level
if (next == null && item !== undefined) {
next = {
data: item,
count: count,
children: [],
boughtWhen: { ...node.boughtWhen },
platformCount: { ...node.platformCount }
}
itemtree.children.push(next)
}
return next!
}
/*
* Merge a full build path with an existing item tree
*/
function treeMerge(
itemtree: ItemTree,
items: Array<{ itemId: number; goldAdvantage: GoldAdvantageTag; platform?: string }>
) {
let current = itemtree
for (const item of items) {
const platformKey = item.platform ? item.platform.toLowerCase() : null
current = nodeMerge(current, {
data: item.itemId,
count: 1,
boughtWhen: {
aheadCount: item.goldAdvantage == 'ahead' ? 1 : 0,
evenCount: item.goldAdvantage == 'even' ? 1 : 0,
behindCount: item.goldAdvantage == 'behind' ? 1 : 0,
meanGold: 0
},
children: [],
platformCount: {
euw: platformKey === 'euw1' ? 1 : 0,
eun: platformKey === 'eun1' ? 1 : 0,
na: platformKey === 'na1' ? 1 : 0,
kr: platformKey === 'kr' ? 1 : 0
}
})
}
}
function treeCutBranches(itemtree: ItemTree, thresholdCount: number, thresholdPerc: number) {
// Remove branches that are above threshold count
while (itemtree.children.length > thresholdCount) {
const leastUsedBranch = itemtree.children.reduce(
(a, b) => (Math.min(a.count, b.count) == a.count ? a : b),
{
data: undefined,
count: +Infinity,
children: [],
boughtWhen: { aheadCount: 0, behindCount: 0, evenCount: 0, meanGold: 0 },
platformCount: initPlatformCounts()
}
)
itemtree.children.splice(itemtree.children.indexOf(leastUsedBranch), 1)
}
// Remove branches that are of too low usage
const toRemove: Array<ItemTree> = []
for (const child of itemtree.children) {
if (child.count / itemtree.count < thresholdPerc) {
toRemove.push(child)
}
}
for (const tr of toRemove) {
itemtree.children.splice(itemtree.children.indexOf(tr), 1)
}
itemtree.children.map(x => treeCutBranches(x, thresholdCount, thresholdPerc))
}
function treeSort(itemtree: ItemTree) {
itemtree.children.sort((a, b) => b.count - a.count)
for (const item of itemtree.children) {
treeSort(item)
}
}
/*
* Deep clone an ItemTree
*/
function treeClone(tree: ItemTree): ItemTree {
return {
data: tree.data,
count: tree.count,
children: tree.children.map(child => treeClone(child)),
boughtWhen: {
aheadCount: tree.boughtWhen.aheadCount,
behindCount: tree.boughtWhen.behindCount,
evenCount: tree.boughtWhen.evenCount,
meanGold: tree.boughtWhen.meanGold
},
platformCount: {
euw: tree.platformCount.euw,
eun: tree.platformCount.eun,
na: tree.platformCount.na,
kr: tree.platformCount.kr
}
}
}
/*
* Merge two ItemTrees into one
*/
function treeMergeTree(t1: ItemTree, t2: ItemTree): ItemTree {
// Merge counts for the root
t1.count += t2.count
// Merge platform counts
t1.platformCount.euw += t2.platformCount.euw
t1.platformCount.eun += t2.platformCount.eun
t1.platformCount.na += t2.platformCount.na
t1.platformCount.kr += t2.platformCount.kr
// Merge boughtWhen
t1.boughtWhen.aheadCount += t2.boughtWhen.aheadCount
t1.boughtWhen.evenCount += t2.boughtWhen.evenCount
t1.boughtWhen.behindCount += t2.boughtWhen.behindCount
// Merge children from t2 into t1
for (const child2 of t2.children) {
// Find matching child in t1 (same data value)
const matchingChild = t1.children.find(child1 => child1.data === child2.data)
if (matchingChild) {
// Recursively merge matching children
treeMergeTree(matchingChild, child2)
} else {
// Add a deep copy of child2 to t1
t1.children.push(treeClone(child2))
}
}
return t1
}
/*
* Flatten an ItemTree into a Set of item numbers
*/
function treeToSet(itemtree: ItemTree): Set<number> {
const items: Set<number> = new Set()
function traverse(node: ItemTree) {
if (node.data !== undefined) {
items.add(node.data)
}
for (const child of node.children) {
traverse(child)
}
}
traverse(itemtree)
return items
}
/*
* Calculate similarity between two trees as item sets.
* Returns a number between 0 and 1, where 1 means identical and 0 means completely different.
* Uses Jaccard similarity: |A ∩ B| / |A B|
* Sets included in one another will have similarity close to 1.
*/
function areTreeSimilars(t1: ItemTree, t2: ItemTree): number {
const set1 = treeToSet(t1)
const set2 = treeToSet(t2)
// Handle empty sets
if (set1.size === 0 && set2.size === 0) {
return 1.0
}
// Calculate intersection
const intersection = new Set<number>()
for (const item of Array.from(set1)) {
if (set2.has(item)) {
intersection.add(item)
}
}
// Calculate union
const union = new Set<number>()
for (const item of Array.from(set1)) {
union.add(item)
}
for (const item of Array.from(set2)) {
union.add(item)
}
// Jaccard similarity: |intersection| / |union|
const similarity = intersection.size / Math.min(set1.size, set2.size)
// Ensure result is between 0 and 1
return Math.max(0, Math.min(1, similarity))
}
export {
ItemTree,
PlatformCounts,
GoldAdvantageTag,
treeMerge,
treeInit,
treeCutBranches,
treeSort,
treeMergeTree,
areTreeSimilars
}