Lint and format
This commit is contained in:
10
match_collector/.prettierrc
Normal file
10
match_collector/.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -1,336 +1,374 @@
|
||||
function sameArrays(array1 : Array<any>, array2 : Array<any>) {
|
||||
if(array1.length != array2.length) return false;
|
||||
for(let e of array1) {
|
||||
if(!array2.includes(e)) return false;
|
||||
}
|
||||
return true;
|
||||
function sameArrays(array1: Array<any>, array2: Array<any>) {
|
||||
if (array1.length != array2.length) return false
|
||||
for (const e of array1) {
|
||||
if (!array2.includes(e)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { ItemTree, treeInit, treeMerge, treeCutBranches, treeSort } from "./item_tree";
|
||||
import { MongoClient } from 'mongodb'
|
||||
import { ItemTree, treeInit, treeMerge, treeCutBranches, treeSort } from './item_tree'
|
||||
const itemDict = new Map()
|
||||
|
||||
async function itemList() {
|
||||
const response = await fetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/items.json")
|
||||
const list = await response.json()
|
||||
return list
|
||||
const response = await fetch(
|
||||
'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/items.json'
|
||||
)
|
||||
const list = await response.json()
|
||||
return list
|
||||
}
|
||||
|
||||
function arrayRemovePercentage(array: Array<{count:number}>, totalGames:number, percentage: number) {
|
||||
let toRemove : Array<{count:number}> = []
|
||||
for(let item of array) {
|
||||
if((item.count/totalGames) < percentage) {
|
||||
toRemove.push(item)
|
||||
}
|
||||
}
|
||||
for(let tr of toRemove) {
|
||||
array.splice(array.indexOf(tr), 1)
|
||||
function arrayRemovePercentage(
|
||||
array: Array<{ count: number }>,
|
||||
totalGames: number,
|
||||
percentage: number
|
||||
) {
|
||||
const toRemove: Array<{ count: number }> = []
|
||||
for (const item of array) {
|
||||
if (item.count / totalGames < percentage) {
|
||||
toRemove.push(item)
|
||||
}
|
||||
}
|
||||
for (const tr of toRemove) {
|
||||
array.splice(array.indexOf(tr), 1)
|
||||
}
|
||||
}
|
||||
|
||||
type Rune = {
|
||||
count: number
|
||||
primaryStyle: number
|
||||
secondaryStyle: number
|
||||
selections: Array<number>
|
||||
pickrate?: number
|
||||
};
|
||||
count: number
|
||||
primaryStyle: number
|
||||
secondaryStyle: number
|
||||
selections: Array<number>
|
||||
pickrate?: number
|
||||
}
|
||||
type Builds = {
|
||||
tree: ItemTree
|
||||
start: Array<{data: number, count: number}>
|
||||
bootsFirst: number
|
||||
boots: Array<{data: number, count: number}>
|
||||
lateGame: Array<{data: number, count: number}>
|
||||
suppItems?: Array<{data: number, count: number}>
|
||||
tree: ItemTree
|
||||
start: Array<{ data: number; count: number }>
|
||||
bootsFirst: number
|
||||
boots: Array<{ data: number; count: number }>
|
||||
lateGame: Array<{ data: number; count: number }>
|
||||
suppItems?: Array<{ data: number; count: number }>
|
||||
}
|
||||
type Champion = {
|
||||
id: Number
|
||||
name: String
|
||||
alias: String
|
||||
id: number
|
||||
name: string
|
||||
alias: string
|
||||
}
|
||||
type LaneData = {
|
||||
data: string
|
||||
count: number
|
||||
winningMatches: number
|
||||
losingMatches: number
|
||||
winrate: number
|
||||
pickrate: number
|
||||
runes: Array<Rune>
|
||||
builds: Builds
|
||||
data: string
|
||||
count: number
|
||||
winningMatches: number
|
||||
losingMatches: number
|
||||
winrate: number
|
||||
pickrate: number
|
||||
runes: Array<Rune>
|
||||
builds: Builds
|
||||
}
|
||||
type ChampionData = {
|
||||
champion: Champion
|
||||
winningMatches: number
|
||||
losingMatches: number
|
||||
lanes: Array<LaneData>
|
||||
champion: Champion
|
||||
winningMatches: number
|
||||
losingMatches: number
|
||||
lanes: Array<LaneData>
|
||||
}
|
||||
|
||||
function handleParticipantRunes(participant, runes: Array<Rune>) {
|
||||
const primaryStyle = participant.perks.styles[0].style
|
||||
const secondaryStyle = participant.perks.styles[1].style
|
||||
const selections : Array<number> = []
|
||||
for(let style of participant.perks.styles) {
|
||||
for(let perk of style.selections) {
|
||||
selections.push(perk.perk)
|
||||
}
|
||||
const primaryStyle = participant.perks.styles[0].style
|
||||
const secondaryStyle = participant.perks.styles[1].style
|
||||
const selections: Array<number> = []
|
||||
for (const style of participant.perks.styles) {
|
||||
for (const perk of style.selections) {
|
||||
selections.push(perk.perk)
|
||||
}
|
||||
const gameRunes : Rune = {count:1, primaryStyle: primaryStyle, secondaryStyle: secondaryStyle, selections: selections};
|
||||
let addRunes = true;
|
||||
for(let rune of runes) {
|
||||
if(rune.primaryStyle == gameRunes.primaryStyle
|
||||
&& rune.secondaryStyle == gameRunes.secondaryStyle
|
||||
&& sameArrays(rune.selections, gameRunes.selections)) {
|
||||
rune.count++; addRunes = false; break;
|
||||
}
|
||||
}
|
||||
const gameRunes: Rune = {
|
||||
count: 1,
|
||||
primaryStyle: primaryStyle,
|
||||
secondaryStyle: secondaryStyle,
|
||||
selections: selections
|
||||
}
|
||||
let addRunes = true
|
||||
for (const rune of runes) {
|
||||
if (
|
||||
rune.primaryStyle == gameRunes.primaryStyle &&
|
||||
rune.secondaryStyle == gameRunes.secondaryStyle &&
|
||||
sameArrays(rune.selections, gameRunes.selections)
|
||||
) {
|
||||
rune.count++
|
||||
addRunes = false
|
||||
break
|
||||
}
|
||||
if(addRunes) runes.push(gameRunes)
|
||||
}
|
||||
if (addRunes) runes.push(gameRunes)
|
||||
}
|
||||
|
||||
function handleMatchItems(timeline, participant: any, participantIndex : number, builds: Builds) {
|
||||
const items : Array<number> = []
|
||||
for(let frame of timeline.info.frames) {
|
||||
for(let event of frame.events) {
|
||||
if(event.participantId != participantIndex) continue;
|
||||
if(event.type == "ITEM_UNDO") {
|
||||
if(items.length > 0 && items[items.length - 1] == event.beforeId) {
|
||||
items.pop()
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let itemInfo = itemDict.get(event.itemId)
|
||||
// Handle bounty of worlds destroy as upgrade
|
||||
if(event.type == "ITEM_DESTROYED") {
|
||||
if(event.itemId == 3867) {
|
||||
let suppItem : number = itemInfo.to.find((x:number) =>
|
||||
x == participant.item0
|
||||
|| x == participant.item1
|
||||
|| x == participant.item2
|
||||
|| x == participant.item3
|
||||
|| x == participant.item4
|
||||
|| x == participant.item5
|
||||
|| x == participant.item6 )
|
||||
if(suppItem != undefined) {
|
||||
const already = builds.suppItems.find((x) => x.data == suppItem)
|
||||
if(already == undefined) builds.suppItems.push({count:1, data: suppItem})
|
||||
else already.count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if(event.type != "ITEM_PURCHASED") continue;
|
||||
|
||||
// Handle boots upgrades
|
||||
if(itemInfo.requiredBuffCurrencyName == "Feats_NoxianBootPurchaseBuff"
|
||||
|| itemInfo.requiredBuffCurrencyName == "Feats_SpecialQuestBootBuff") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle boots differently
|
||||
if(itemInfo.categories.includes("Boots")){
|
||||
if(itemInfo.to.length == 0 || (itemInfo.to[0] >= 3171 && itemInfo.to[0] <= 3176)) {
|
||||
// Check for bootsFirst
|
||||
if(items.length < 2) {
|
||||
builds.bootsFirst += 1
|
||||
}
|
||||
|
||||
// Add to boots
|
||||
const already = builds.boots.find((x) => x.data == event.itemId)
|
||||
if(already == undefined) builds.boots.push({count:1, data:event.itemId})
|
||||
else already.count += 1
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if item should be included
|
||||
if(itemInfo.categories.includes("Consumable")) continue;
|
||||
if(itemInfo.categories.includes("Trinket")) continue;
|
||||
|
||||
// Ignore zephyr
|
||||
if(event.itemId == 3172) continue;
|
||||
|
||||
// Ignore Cull as not-first item
|
||||
if(event.itemId == 1083 && items.length >= 1) continue;
|
||||
|
||||
// Ignore non-final items, except when first item bought
|
||||
if(itemInfo.to.length != 0 && items.length >= 1) continue;
|
||||
|
||||
items.push(event.itemId)
|
||||
function handleMatchItems(timeline, participant: any, participantIndex: number, builds: Builds) {
|
||||
const items: Array<number> = []
|
||||
for (const frame of timeline.info.frames) {
|
||||
for (const event of frame.events) {
|
||||
if (event.participantId != participantIndex) continue
|
||||
if (event.type == 'ITEM_UNDO') {
|
||||
if (items.length > 0 && items[items.length - 1] == event.beforeId) {
|
||||
items.pop()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const itemInfo = itemDict.get(event.itemId)
|
||||
// Handle bounty of worlds destroy as upgrade
|
||||
if (event.type == 'ITEM_DESTROYED') {
|
||||
if (event.itemId == 3867) {
|
||||
const suppItem: number = itemInfo.to.find(
|
||||
(x: number) =>
|
||||
x == participant.item0 ||
|
||||
x == participant.item1 ||
|
||||
x == participant.item2 ||
|
||||
x == participant.item3 ||
|
||||
x == participant.item4 ||
|
||||
x == participant.item5 ||
|
||||
x == participant.item6
|
||||
)
|
||||
if (suppItem != undefined) {
|
||||
const already = builds.suppItems.find(x => x.data == suppItem)
|
||||
if (already == undefined) builds.suppItems.push({ count: 1, data: suppItem })
|
||||
else already.count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.type != 'ITEM_PURCHASED') continue
|
||||
|
||||
// Handle boots upgrades
|
||||
if (
|
||||
itemInfo.requiredBuffCurrencyName == 'Feats_NoxianBootPurchaseBuff' ||
|
||||
itemInfo.requiredBuffCurrencyName == 'Feats_SpecialQuestBootBuff'
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle boots differently
|
||||
if (itemInfo.categories.includes('Boots')) {
|
||||
if (itemInfo.to.length == 0 || (itemInfo.to[0] >= 3171 && itemInfo.to[0] <= 3176)) {
|
||||
// Check for bootsFirst
|
||||
if (items.length < 2) {
|
||||
builds.bootsFirst += 1
|
||||
}
|
||||
|
||||
// Add to boots
|
||||
const already = builds.boots.find(x => x.data == event.itemId)
|
||||
if (already == undefined) builds.boots.push({ count: 1, data: event.itemId })
|
||||
else already.count += 1
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if item should be included
|
||||
if (itemInfo.categories.includes('Consumable')) continue
|
||||
if (itemInfo.categories.includes('Trinket')) continue
|
||||
|
||||
// Ignore zephyr
|
||||
if (event.itemId == 3172) continue
|
||||
|
||||
// Ignore Cull as not-first item
|
||||
if (event.itemId == 1083 && items.length >= 1) continue
|
||||
|
||||
// Ignore non-final items, except when first item bought
|
||||
if (itemInfo.to.length != 0 && items.length >= 1) continue
|
||||
|
||||
items.push(event.itemId)
|
||||
}
|
||||
|
||||
// Core items
|
||||
treeMerge(builds.tree, items.slice(1, 4))
|
||||
|
||||
// Start items
|
||||
if(items.length >= 1) {
|
||||
const already = builds.start.find((x) => x.data == items[0])
|
||||
if(already == undefined) builds.start.push({count:1, data:items[0]})
|
||||
else already.count += 1
|
||||
}
|
||||
|
||||
// Late game items
|
||||
for(let item of items.slice(3)) {
|
||||
const already = builds.lateGame.find((x) => x.data == item)
|
||||
if(already == undefined) builds.lateGame.push({count:1, data:item})
|
||||
else already.count += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Core items
|
||||
treeMerge(builds.tree, items.slice(1, 4))
|
||||
|
||||
// Start items
|
||||
if (items.length >= 1) {
|
||||
const already = builds.start.find(x => x.data == items[0])
|
||||
if (already == undefined) builds.start.push({ count: 1, data: items[0] })
|
||||
else already.count += 1
|
||||
}
|
||||
|
||||
// Late game items
|
||||
for (const item of items.slice(3)) {
|
||||
const already = builds.lateGame.find(x => x.data == item)
|
||||
if (already == undefined) builds.lateGame.push({ count: 1, data: item })
|
||||
else already.count += 1
|
||||
}
|
||||
}
|
||||
|
||||
function handleMatch(match: any, champions : Map<number, ChampionData>) {
|
||||
let participantIndex = 0;
|
||||
for(let participant of match.info.participants) {
|
||||
participantIndex += 1
|
||||
const championId = participant.championId
|
||||
const champion = champions.get(championId)
|
||||
function handleMatch(match: any, champions: Map<number, ChampionData>) {
|
||||
let participantIndex = 0
|
||||
for (const participant of match.info.participants) {
|
||||
participantIndex += 1
|
||||
const championId = participant.championId
|
||||
const champion = champions.get(championId)
|
||||
|
||||
// Lanes
|
||||
let lane = champion.lanes.find((x) => x.data == participant.teamPosition)
|
||||
if(lane == undefined) {
|
||||
const builds : Builds = {tree:treeInit(), start: [], bootsFirst: 0, boots: [], lateGame: [], suppItems: []}
|
||||
lane = {count:1, data: participant.teamPosition, runes:[], builds:builds, winningMatches: 0, losingMatches: 0, winrate: 0, pickrate: 0}
|
||||
champion.lanes.push(lane)
|
||||
}
|
||||
else lane.count += 1
|
||||
// Lanes
|
||||
let lane = champion.lanes.find(x => x.data == participant.teamPosition)
|
||||
if (lane == undefined) {
|
||||
const builds: Builds = {
|
||||
tree: treeInit(),
|
||||
start: [],
|
||||
bootsFirst: 0,
|
||||
boots: [],
|
||||
lateGame: [],
|
||||
suppItems: []
|
||||
}
|
||||
lane = {
|
||||
count: 1,
|
||||
data: participant.teamPosition,
|
||||
runes: [],
|
||||
builds: builds,
|
||||
winningMatches: 0,
|
||||
losingMatches: 0,
|
||||
winrate: 0,
|
||||
pickrate: 0
|
||||
}
|
||||
champion.lanes.push(lane)
|
||||
} else lane.count += 1
|
||||
|
||||
// Winrate
|
||||
if(participant.win) {
|
||||
champion.winningMatches++;
|
||||
lane.winningMatches++;
|
||||
}
|
||||
else {
|
||||
champion.losingMatches++;
|
||||
lane.losingMatches++;
|
||||
}
|
||||
|
||||
// Runes
|
||||
handleParticipantRunes(participant, lane.runes)
|
||||
|
||||
// Items
|
||||
handleMatchItems(match.timeline, participant, participantIndex, lane.builds)
|
||||
// Winrate
|
||||
if (participant.win) {
|
||||
champion.winningMatches++
|
||||
lane.winningMatches++
|
||||
} else {
|
||||
champion.losingMatches++
|
||||
lane.losingMatches++
|
||||
}
|
||||
|
||||
// Runes
|
||||
handleParticipantRunes(participant, lane.runes)
|
||||
|
||||
// Items
|
||||
handleMatchItems(match.timeline, participant, participantIndex, lane.builds)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMatchList(client: MongoClient, patch: string, champions: Map<number, ChampionData>) {
|
||||
const database = client.db("matches");
|
||||
const matches = database.collection(patch)
|
||||
const allMatches = matches.find()
|
||||
const totalMatches: number = await matches.countDocuments()
|
||||
async function handleMatchList(
|
||||
client: MongoClient,
|
||||
patch: string,
|
||||
champions: Map<number, ChampionData>
|
||||
) {
|
||||
const database = client.db('matches')
|
||||
const matches = database.collection(patch)
|
||||
const allMatches = matches.find()
|
||||
const totalMatches: number = await matches.countDocuments()
|
||||
|
||||
let currentMatch = 0;
|
||||
for await (let match of allMatches) {
|
||||
process.stdout.write("\rComputing champion stats, game entry " + currentMatch + "/" + totalMatches + " ... ")
|
||||
currentMatch += 1;
|
||||
handleMatch(match, champions)
|
||||
}
|
||||
|
||||
return totalMatches
|
||||
let currentMatch = 0
|
||||
for await (const match of allMatches) {
|
||||
process.stdout.write(
|
||||
'\rComputing champion stats, game entry ' + currentMatch + '/' + totalMatches + ' ... '
|
||||
)
|
||||
currentMatch += 1
|
||||
handleMatch(match, champions)
|
||||
}
|
||||
|
||||
return totalMatches
|
||||
}
|
||||
|
||||
async function finalizeChampionStats(champion: ChampionData, totalMatches: number) {
|
||||
let totalChampionMatches = champion.winningMatches + champion.losingMatches;
|
||||
const totalChampionMatches = champion.winningMatches + champion.losingMatches
|
||||
|
||||
arrayRemovePercentage(champion.lanes, totalChampionMatches, 0.2)
|
||||
champion.lanes.sort((a, b) => b.count - a.count)
|
||||
arrayRemovePercentage(champion.lanes, totalChampionMatches, 0.2)
|
||||
champion.lanes.sort((a, b) => b.count - a.count)
|
||||
|
||||
// Filter runes to keep 3 most played
|
||||
for(let lane of champion.lanes) {
|
||||
const runes = lane.runes
|
||||
|
||||
runes.sort((a, b) => b.count - a.count)
|
||||
if(runes.length > 3)
|
||||
runes.splice(3, runes.length - 3)
|
||||
// Compute runes pickrate
|
||||
for(let rune of runes)
|
||||
rune.pickrate = rune.count / lane.count;
|
||||
}
|
||||
// Filter runes to keep 3 most played
|
||||
for (const lane of champion.lanes) {
|
||||
const runes = lane.runes
|
||||
|
||||
for(let lane of champion.lanes) {
|
||||
const builds = lane.builds
|
||||
runes.sort((a, b) => b.count - a.count)
|
||||
if (runes.length > 3) runes.splice(3, runes.length - 3)
|
||||
// Compute runes pickrate
|
||||
for (const rune of runes) rune.pickrate = rune.count / lane.count
|
||||
}
|
||||
|
||||
// Cut item tree branches to keep only 4 branches every time and with percentage threshold
|
||||
builds.tree.count = lane.count;
|
||||
treeCutBranches(builds.tree, 4, 0.05)
|
||||
treeSort(builds.tree)
|
||||
for (const lane of champion.lanes) {
|
||||
const builds = lane.builds
|
||||
|
||||
// Cut item start, to only 4 and with percentage threshold
|
||||
arrayRemovePercentage(builds.start, lane.count, 0.05)
|
||||
builds.start.sort((a, b) => b.count - a.count)
|
||||
if(builds.start.length > 4)
|
||||
builds.start.splice(4, builds.start.length - 4)
|
||||
|
||||
// Remove boots that are not within percentage threshold
|
||||
arrayRemovePercentage(builds.boots, lane.count, 0.05)
|
||||
builds.boots.sort((a, b) => b.count - a.count)
|
||||
|
||||
builds.bootsFirst /= lane.count
|
||||
|
||||
// Cut supp items below 2 and percentage threshold
|
||||
arrayRemovePercentage(builds.suppItems, lane.count, 0.05)
|
||||
builds.suppItems.sort((a, b) => b.count - a.count)
|
||||
if(builds.suppItems.length > 2)
|
||||
builds.suppItems.splice(2, builds.suppItems.length - 2)
|
||||
// Cut item tree branches to keep only 4 branches every time and with percentage threshold
|
||||
builds.tree.count = lane.count
|
||||
treeCutBranches(builds.tree, 4, 0.05)
|
||||
treeSort(builds.tree)
|
||||
|
||||
// Delete supp items if empty
|
||||
if(builds.suppItems.length == 0) delete builds.suppItems
|
||||
// Cut item start, to only 4 and with percentage threshold
|
||||
arrayRemovePercentage(builds.start, lane.count, 0.05)
|
||||
builds.start.sort((a, b) => b.count - a.count)
|
||||
if (builds.start.length > 4) builds.start.splice(4, builds.start.length - 4)
|
||||
|
||||
builds.lateGame.sort((a, b) => b.count - a.count)
|
||||
}
|
||||
// Remove boots that are not within percentage threshold
|
||||
arrayRemovePercentage(builds.boots, lane.count, 0.05)
|
||||
builds.boots.sort((a, b) => b.count - a.count)
|
||||
|
||||
for(let lane of champion.lanes) {
|
||||
lane.winrate = lane.winningMatches / lane.count
|
||||
lane.pickrate = lane.count / totalMatches
|
||||
}
|
||||
builds.bootsFirst /= lane.count
|
||||
|
||||
return {name: champion.champion.name,
|
||||
alias: champion.champion.alias.toLowerCase(),
|
||||
id: champion.champion.id,
|
||||
lanes: champion.lanes,
|
||||
winrate: champion.winningMatches / totalChampionMatches,
|
||||
gameCount: totalChampionMatches,
|
||||
pickrate: totalChampionMatches/totalMatches,
|
||||
};
|
||||
// Cut supp items below 2 and percentage threshold
|
||||
arrayRemovePercentage(builds.suppItems, lane.count, 0.05)
|
||||
builds.suppItems.sort((a, b) => b.count - a.count)
|
||||
if (builds.suppItems.length > 2) builds.suppItems.splice(2, builds.suppItems.length - 2)
|
||||
|
||||
// Delete supp items if empty
|
||||
if (builds.suppItems.length == 0) delete builds.suppItems
|
||||
|
||||
builds.lateGame.sort((a, b) => b.count - a.count)
|
||||
}
|
||||
|
||||
for (const lane of champion.lanes) {
|
||||
lane.winrate = lane.winningMatches / lane.count
|
||||
lane.pickrate = lane.count / totalMatches
|
||||
}
|
||||
|
||||
return {
|
||||
name: champion.champion.name,
|
||||
alias: champion.champion.alias.toLowerCase(),
|
||||
id: champion.champion.id,
|
||||
lanes: champion.lanes,
|
||||
winrate: champion.winningMatches / totalChampionMatches,
|
||||
gameCount: totalChampionMatches,
|
||||
pickrate: totalChampionMatches / totalMatches
|
||||
}
|
||||
}
|
||||
|
||||
async function championList() {
|
||||
const response = await fetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json");
|
||||
const list = await response.json()
|
||||
return list.slice(1)
|
||||
const response = await fetch(
|
||||
'https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json'
|
||||
)
|
||||
const list = await response.json()
|
||||
return list.slice(1)
|
||||
}
|
||||
|
||||
async function makeChampionsStats(client: MongoClient, patch : string) {
|
||||
var globalItems = await itemList()
|
||||
for(let item of globalItems) {
|
||||
itemDict.set(item.id, item)
|
||||
}
|
||||
async function makeChampionsStats(client: MongoClient, patch: string) {
|
||||
const globalItems = await itemList()
|
||||
for (const item of globalItems) {
|
||||
itemDict.set(item.id, item)
|
||||
}
|
||||
|
||||
const list = await championList()
|
||||
console.log("Generating stats for " + list.length + " champions")
|
||||
const list = await championList()
|
||||
console.log('Generating stats for ' + list.length + ' champions')
|
||||
|
||||
// Pre-generate list of champions
|
||||
const champions: Map<number, ChampionData> = new Map()
|
||||
for(let champion of list) {
|
||||
champions.set(champion.id, {
|
||||
champion: {id: champion.id, name: champion.name, alias: champion.alias},
|
||||
winningMatches: 0,
|
||||
losingMatches: 0,
|
||||
lanes: []
|
||||
})
|
||||
}
|
||||
// Pre-generate list of champions
|
||||
const champions: Map<number, ChampionData> = new Map()
|
||||
for (const champion of list) {
|
||||
champions.set(champion.id, {
|
||||
champion: { id: champion.id, name: champion.name, alias: champion.alias },
|
||||
winningMatches: 0,
|
||||
losingMatches: 0,
|
||||
lanes: []
|
||||
})
|
||||
}
|
||||
|
||||
// Loop through all matches to generate stats
|
||||
const totalMatches = await handleMatchList(client, patch, champions)
|
||||
// Loop through all matches to generate stats
|
||||
const totalMatches = await handleMatchList(client, patch, champions)
|
||||
|
||||
// Finalize and save stats for every champion
|
||||
const database = client.db("champions")
|
||||
const collection = database.collection(patch)
|
||||
for(let champion of list) {
|
||||
const championInfo = await finalizeChampionStats(champions.get(champion.id), totalMatches)
|
||||
await collection.updateOne({id: champion.id}, {$set: championInfo}, { upsert: true })
|
||||
}
|
||||
// Finalize and save stats for every champion
|
||||
const database = client.db('champions')
|
||||
const collection = database.collection(patch)
|
||||
for (const champion of list) {
|
||||
const championInfo = await finalizeChampionStats(champions.get(champion.id), totalMatches)
|
||||
await collection.updateOne({ id: champion.id }, { $set: championInfo }, { upsert: true })
|
||||
}
|
||||
|
||||
// Create alias-index for better key-find
|
||||
await collection.createIndex({alias:1})
|
||||
// Create alias-index for better key-find
|
||||
await collection.createIndex({ alias: 1 })
|
||||
}
|
||||
|
||||
export default {makeChampionsStats}
|
||||
export default { makeChampionsStats }
|
||||
|
||||
16
match_collector/eslint.config.js
Normal file
16
match_collector/eslint.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'eslint/config'
|
||||
import js from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import prettier from 'eslint-config-prettier'
|
||||
|
||||
export default defineConfig([
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
prettier,
|
||||
{
|
||||
rules: {
|
||||
semi: 'off',
|
||||
'prefer-const': 'error'
|
||||
}
|
||||
}
|
||||
])
|
||||
@@ -1,207 +1,218 @@
|
||||
const base = "https://euw1.api.riotgames.com"
|
||||
const base = 'https://euw1.api.riotgames.com'
|
||||
const api_key = process.env.RIOT_API_KEY
|
||||
const sleep_minutes = 12
|
||||
|
||||
import { MongoClient } from 'mongodb'
|
||||
|
||||
import champion_stat from "./champion_stat"
|
||||
import champion_stat from './champion_stat'
|
||||
|
||||
main()
|
||||
|
||||
async function main() {
|
||||
// Check if we're in development mode with pre-loaded data
|
||||
if (process.env.NODE_ENV === 'development' && process.env.USE_IMPORTED_DATA === 'true') {
|
||||
console.log("MatchCollector - Development mode with pre-loaded data");
|
||||
await runWithPreloadedData();
|
||||
return;
|
||||
// Check if we're in development mode with pre-loaded data
|
||||
if (process.env.NODE_ENV === 'development' && process.env.USE_IMPORTED_DATA === 'true') {
|
||||
console.log('MatchCollector - Development mode with pre-loaded data')
|
||||
await runWithPreloadedData()
|
||||
return
|
||||
}
|
||||
|
||||
// Original production mode
|
||||
console.log('MatchCollector - Hello !')
|
||||
const client = await connectToDatabase()
|
||||
const [latestPatch, latestPatchTime] = await fetchLatestPatchDate(client)
|
||||
console.log(
|
||||
'Connected to database, latest patch ' + latestPatch + ' was epoch: ' + latestPatchTime
|
||||
)
|
||||
|
||||
const alreadySeenGameList = await alreadySeenGames(client, latestPatch)
|
||||
console.log('We already have ' + alreadySeenGameList.length + ' matches for this patch !')
|
||||
|
||||
console.log('Using RIOT_API_KEY: ' + api_key)
|
||||
if (api_key != null && api_key != undefined && api_key != '') {
|
||||
const challengerLeague = await fetchChallengerLeague()
|
||||
console.log('ChallengerLeague: got ' + challengerLeague.entries.length + ' entries')
|
||||
|
||||
const gameList = []
|
||||
let i = 0
|
||||
for (const challenger of challengerLeague.entries) {
|
||||
console.log('Entry ' + i + '/' + challengerLeague.entries.length + ' ...')
|
||||
const puuid = challenger.puuid
|
||||
const challengerGameList = await summonerGameList(puuid, latestPatchTime)
|
||||
for (const game of challengerGameList) {
|
||||
if (!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
||||
gameList.push(game)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// Original production mode
|
||||
console.log("MatchCollector - Hello !");
|
||||
const client = await connectToDatabase();
|
||||
const [latestPatch, latestPatchTime] = await fetchLatestPatchDate(client);
|
||||
console.log("Connected to database, latest patch " + latestPatch + " was epoch: " + latestPatchTime)
|
||||
|
||||
const alreadySeenGameList = await alreadySeenGames(client, latestPatch);
|
||||
console.log("We already have " + alreadySeenGameList.length + " matches for this patch !")
|
||||
|
||||
console.log("Using RIOT_API_KEY: " + api_key)
|
||||
if(api_key != null && api_key != undefined && api_key != "") {
|
||||
const challengerLeague = await fetchChallengerLeague();
|
||||
console.log("ChallengerLeague: got " + challengerLeague.entries.length + " entries");
|
||||
|
||||
const gameList = [];
|
||||
let i = 0;
|
||||
for(let challenger of challengerLeague.entries) {
|
||||
console.log("Entry " + i + "/" + challengerLeague.entries.length + " ...")
|
||||
const puuid = challenger.puuid;
|
||||
const challengerGameList = await summonerGameList(puuid, latestPatchTime);
|
||||
for(let game of challengerGameList) {
|
||||
if(!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
||||
gameList.push(game)
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
console.log("Games: got " + gameList.length + " entries");
|
||||
i = 0;
|
||||
for(let game of gameList) {
|
||||
console.log("Entry " + i + "/" + gameList.length + " ...")
|
||||
const gameMatch = await match(game)
|
||||
const gameTimeline = await matchTimeline(game)
|
||||
gameMatch.timeline = gameTimeline
|
||||
await saveMatch(client, gameMatch, latestPatch)
|
||||
i++;
|
||||
}
|
||||
console.log('Games: got ' + gameList.length + ' entries')
|
||||
i = 0
|
||||
for (const game of gameList) {
|
||||
console.log('Entry ' + i + '/' + gameList.length + ' ...')
|
||||
const gameMatch = await match(game)
|
||||
const gameTimeline = await matchTimeline(game)
|
||||
gameMatch.timeline = gameTimeline
|
||||
await saveMatch(client, gameMatch, latestPatch)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Generating stats...");
|
||||
await champion_stat.makeChampionsStats(client, latestPatch)
|
||||
console.log('Generating stats...')
|
||||
await champion_stat.makeChampionsStats(client, latestPatch)
|
||||
|
||||
console.log("All done. Closing client.");
|
||||
await client.close()
|
||||
console.log('All done. Closing client.')
|
||||
await client.close()
|
||||
}
|
||||
|
||||
async function handleRateLimit(url : URL) : Promise<Response> {
|
||||
let response = await fetch(url)
|
||||
if(response.status == 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, sleep_minutes * 60 * 1000))
|
||||
response = await handleRateLimit(url)
|
||||
}
|
||||
async function handleRateLimit(url: URL): Promise<Response> {
|
||||
let response = await fetch(url)
|
||||
if (response.status == 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, sleep_minutes * 60 * 1000))
|
||||
response = await handleRateLimit(url)
|
||||
}
|
||||
|
||||
return response
|
||||
return response
|
||||
}
|
||||
|
||||
function handleError(response : Response) {
|
||||
if(!response.ok) {
|
||||
console.log("Error during fetch(" + response.url + "): STATUS " + response.status + " (" + response.statusText + ")");
|
||||
process.exit(1);
|
||||
}
|
||||
function handleError(response: Response) {
|
||||
if (!response.ok) {
|
||||
console.log(
|
||||
'Error during fetch(' +
|
||||
response.url +
|
||||
'): STATUS ' +
|
||||
response.status +
|
||||
' (' +
|
||||
response.statusText +
|
||||
')'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function connectToDatabase() {
|
||||
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||
let uri = `mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_HOST}`
|
||||
if(process.env.MONGO_URI != undefined && process.env.MONGO_URI != null && process.env.MONGO_URI != "") {
|
||||
uri = process.env.MONGO_URI
|
||||
}
|
||||
const client = new MongoClient(uri)
|
||||
await client.connect()
|
||||
return client
|
||||
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||
let uri = `mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_HOST}`
|
||||
if (
|
||||
process.env.MONGO_URI != undefined &&
|
||||
process.env.MONGO_URI != null &&
|
||||
process.env.MONGO_URI != ''
|
||||
) {
|
||||
uri = process.env.MONGO_URI
|
||||
}
|
||||
const client = new MongoClient(uri)
|
||||
await client.connect()
|
||||
return client
|
||||
}
|
||||
|
||||
async function fetchLatestPatchDate(client) {
|
||||
const database = client.db("patches");
|
||||
const patches = database.collection("patches");
|
||||
const latestPatch = await patches.find().limit(1).sort({date:-1}).next()
|
||||
return [latestPatch.patch, Math.floor(latestPatch.date.valueOf() / 1000)]
|
||||
const database = client.db('patches')
|
||||
const patches = database.collection('patches')
|
||||
const latestPatch = await patches.find().limit(1).sort({ date: -1 }).next()
|
||||
return [latestPatch.patch, Math.floor(latestPatch.date.valueOf() / 1000)]
|
||||
}
|
||||
|
||||
async function fetchChallengerLeague() {
|
||||
const queue = "RANKED_SOLO_5x5"
|
||||
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
const queue = 'RANKED_SOLO_5x5'
|
||||
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const challengerLeagueResponse = await handleRateLimit(new URL(url));
|
||||
|
||||
handleError(challengerLeagueResponse)
|
||||
const challengerLeagueResponse = await handleRateLimit(new URL(url))
|
||||
|
||||
const challengerLeague = await challengerLeagueResponse.json();
|
||||
return challengerLeague;
|
||||
handleError(challengerLeagueResponse)
|
||||
|
||||
const challengerLeague = await challengerLeagueResponse.json()
|
||||
return challengerLeague
|
||||
}
|
||||
|
||||
async function summonerGameList(puuid, startTime) {
|
||||
const base = "https://europe.api.riotgames.com"
|
||||
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`;
|
||||
const url = `${base}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
||||
const base = 'https://europe.api.riotgames.com'
|
||||
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`
|
||||
const url = `${base}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
||||
|
||||
const gameListResponse = await handleRateLimit(new URL(url));
|
||||
handleError(gameListResponse)
|
||||
const gameList = await gameListResponse.json();
|
||||
const gameListResponse = await handleRateLimit(new URL(url))
|
||||
handleError(gameListResponse)
|
||||
const gameList = await gameListResponse.json()
|
||||
|
||||
return gameList;
|
||||
return gameList
|
||||
}
|
||||
|
||||
async function match(matchId) {
|
||||
const base = "https://europe.api.riotgames.com"
|
||||
const endpoint = `/lol/match/v5/matches/${matchId}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
const base = 'https://europe.api.riotgames.com'
|
||||
const endpoint = `/lol/match/v5/matches/${matchId}`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const matchResponse = await handleRateLimit(new URL(url))
|
||||
handleError(matchResponse)
|
||||
const match = await matchResponse.json();
|
||||
const matchResponse = await handleRateLimit(new URL(url))
|
||||
handleError(matchResponse)
|
||||
const match = await matchResponse.json()
|
||||
|
||||
return match;
|
||||
return match
|
||||
}
|
||||
|
||||
async function matchTimeline(matchId) {
|
||||
const base = "https://europe.api.riotgames.com"
|
||||
const endpoint = `/lol/match/v5/matches/${matchId}/timeline`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
const base = 'https://europe.api.riotgames.com'
|
||||
const endpoint = `/lol/match/v5/matches/${matchId}/timeline`
|
||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
||||
|
||||
const timelineResponse = await handleRateLimit(new URL(url))
|
||||
handleError(timelineResponse)
|
||||
const timeline = await timelineResponse.json();
|
||||
const timelineResponse = await handleRateLimit(new URL(url))
|
||||
handleError(timelineResponse)
|
||||
const timeline = await timelineResponse.json()
|
||||
|
||||
return timeline
|
||||
return timeline
|
||||
}
|
||||
|
||||
async function alreadySeenGames(client, latestPatch) {
|
||||
const database = client.db("matches")
|
||||
const matches = database.collection(latestPatch)
|
||||
const database = client.db('matches')
|
||||
const matches = database.collection(latestPatch)
|
||||
|
||||
const alreadySeen = await matches.distinct("metadata.matchId")
|
||||
return alreadySeen
|
||||
const alreadySeen = await matches.distinct('metadata.matchId')
|
||||
return alreadySeen
|
||||
}
|
||||
|
||||
async function saveMatch(client, match, latestPatch) {
|
||||
const database = client.db("matches")
|
||||
const matches = database.collection(latestPatch)
|
||||
await matches.insertOne(match)
|
||||
const database = client.db('matches')
|
||||
const matches = database.collection(latestPatch)
|
||||
await matches.insertOne(match)
|
||||
}
|
||||
|
||||
/**
|
||||
* Development mode function that generates stats from pre-loaded data
|
||||
*/
|
||||
async function runWithPreloadedData() {
|
||||
console.log("Using pre-loaded match data for development");
|
||||
console.log('Using pre-loaded match data for development')
|
||||
|
||||
const client = await connectToDatabase();
|
||||
try {
|
||||
const [latestPatch] = await fetchLatestPatchDate(client);
|
||||
console.log(`Latest patch: ${latestPatch}`);
|
||||
const client = await connectToDatabase()
|
||||
try {
|
||||
const [latestPatch] = await fetchLatestPatchDate(client)
|
||||
console.log(`Latest patch: ${latestPatch}`)
|
||||
|
||||
// Check if we have matches for this patch
|
||||
const matchesDb = client.db("matches");
|
||||
const collections = await matchesDb.listCollections().toArray();
|
||||
const patchCollections = collections
|
||||
.map(c => c.name)
|
||||
.filter(name => name === latestPatch);
|
||||
// Check if we have matches for this patch
|
||||
const matchesDb = client.db('matches')
|
||||
const collections = await matchesDb.listCollections().toArray()
|
||||
const patchCollections = collections.map(c => c.name).filter(name => name === latestPatch)
|
||||
|
||||
if (patchCollections.length === 0) {
|
||||
console.error(`❌ No match data found for patch ${latestPatch}`);
|
||||
console.log("💡 Please run the data import script first:");
|
||||
console.log(" node dev/scripts/setup-db.js");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${patchCollections.length} match collection(s)`);
|
||||
|
||||
// Generate stats for each patch with data
|
||||
for (const patch of patchCollections) {
|
||||
console.log(`Generating stats for patch ${patch}...`);
|
||||
await champion_stat.makeChampionsStats(client, patch);
|
||||
console.log(`Stats generated for patch ${patch}`);
|
||||
}
|
||||
|
||||
console.log("🎉 All stats generated successfully!");
|
||||
console.log("🚀 Your development database is ready for frontend testing!");
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Error in development mode:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await client.close();
|
||||
if (patchCollections.length === 0) {
|
||||
console.error(`❌ No match data found for patch ${latestPatch}`)
|
||||
console.log('💡 Please run the data import script first:')
|
||||
console.log(' node dev/scripts/setup-db.js')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Found ${patchCollections.length} match collection(s)`)
|
||||
|
||||
// Generate stats for each patch with data
|
||||
for (const patch of patchCollections) {
|
||||
console.log(`Generating stats for patch ${patch}...`)
|
||||
await champion_stat.makeChampionsStats(client, patch)
|
||||
console.log(`Stats generated for patch ${patch}`)
|
||||
}
|
||||
|
||||
console.log('🎉 All stats generated successfully!')
|
||||
console.log('🚀 Your development database is ready for frontend testing!')
|
||||
} catch (error) {
|
||||
console.error('❌ Error in development mode:', error)
|
||||
throw error
|
||||
} finally {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,91 @@
|
||||
type ItemTree = {
|
||||
data: any
|
||||
count: number
|
||||
children: Array<ItemTree>
|
||||
};
|
||||
|
||||
function treeInit() : ItemTree {
|
||||
return {data:undefined, count:0, children:[]}
|
||||
data: any
|
||||
count: number
|
||||
children: Array<ItemTree>
|
||||
}
|
||||
|
||||
function treeInit(): ItemTree {
|
||||
return { data: undefined, count: 0, children: [] }
|
||||
}
|
||||
|
||||
function treeNode(data : number, count : number) : ItemTree {
|
||||
return {data:data, count:count, children:[]}
|
||||
function treeNode(data: number, count: number): ItemTree {
|
||||
return { data: data, count: count, children: [] }
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
* 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(let node of itemtree.children) {
|
||||
if(node.data == item) {
|
||||
node.count += 1;
|
||||
next = node;
|
||||
break;
|
||||
}
|
||||
// Try to find an existing node in this tree level with same item
|
||||
for (const node of itemtree.children) {
|
||||
if (node.data == item) {
|
||||
node.count += 1
|
||||
next = node
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, add item node at this level
|
||||
if(next == null) {
|
||||
next = treeNode(item, count)
|
||||
itemtree.children.push(next)
|
||||
}
|
||||
// If not found, add item node at this level
|
||||
if (next == null) {
|
||||
next = treeNode(item, count)
|
||||
itemtree.children.push(next)
|
||||
}
|
||||
|
||||
return next;
|
||||
return next
|
||||
}
|
||||
|
||||
/*
|
||||
* Merge a full build path with an existing item tree
|
||||
*/
|
||||
function treeMerge(itemtree : ItemTree, items : Array<number>) {
|
||||
let current = itemtree;
|
||||
/*
|
||||
* Merge a full build path with an existing item tree
|
||||
*/
|
||||
function treeMerge(itemtree: ItemTree, items: Array<number>) {
|
||||
let current = itemtree
|
||||
|
||||
for(let item of items) {
|
||||
current = nodeMerge(current, {data: item, count:1, children:[]})
|
||||
}
|
||||
for (const item of items) {
|
||||
current = nodeMerge(current, { data: item, count: 1, children: [] })
|
||||
}
|
||||
}
|
||||
|
||||
function treeCutBranches(itemtree : ItemTree, thresholdCount : number, thresholdPerc : number) {
|
||||
// Remove branches that are above threshold count
|
||||
while(itemtree.children.length > thresholdCount) {
|
||||
let leastUsedBranch = itemtree.children.reduce((a, b) => Math.min(a.count, b.count) == a.count ? a : b, {data:undefined, count: +Infinity, children: []})
|
||||
itemtree.children.splice(itemtree.children.indexOf(leastUsedBranch), 1)
|
||||
}
|
||||
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: [] }
|
||||
)
|
||||
itemtree.children.splice(itemtree.children.indexOf(leastUsedBranch), 1)
|
||||
}
|
||||
|
||||
// Remove branches that are of too low usage
|
||||
let toRemove : Array<ItemTree> = []
|
||||
for(let child of itemtree.children) {
|
||||
if((child.count/itemtree.count) < thresholdPerc) {
|
||||
toRemove.push(child)
|
||||
}
|
||||
}
|
||||
for(let tr of toRemove) {
|
||||
itemtree.children.splice(itemtree.children.indexOf(tr), 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))
|
||||
itemtree.children.map(x => treeCutBranches(x, thresholdCount, thresholdPerc))
|
||||
}
|
||||
|
||||
function treeMergeTree(itemtree1: ItemTree, itemtree2: ItemTree) {
|
||||
for(let child of itemtree2.children) {
|
||||
let node = nodeMerge(itemtree1, child)
|
||||
treeMergeTree(node, child)
|
||||
}
|
||||
for (const child of itemtree2.children) {
|
||||
const node = nodeMerge(itemtree1, child)
|
||||
treeMergeTree(node, child)
|
||||
}
|
||||
}
|
||||
|
||||
function treeSort(itemtree: ItemTree) {
|
||||
itemtree.children.sort((a, b) => b.count - a.count)
|
||||
itemtree.children.sort((a, b) => b.count - a.count)
|
||||
|
||||
for(let item of itemtree.children) {
|
||||
treeSort(item)
|
||||
}
|
||||
for (const item of itemtree.children) {
|
||||
treeSort(item)
|
||||
}
|
||||
}
|
||||
|
||||
export {ItemTree, treeMerge, treeInit, treeCutBranches, treeSort}
|
||||
export { ItemTree, treeMerge, treeInit, treeCutBranches, treeSort }
|
||||
|
||||
1430
match_collector/package-lock.json
generated
1430
match_collector/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,11 @@
|
||||
"main": "index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
@@ -13,7 +17,15 @@
|
||||
"mongodb": "^6.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/node": "^22.9.1",
|
||||
"tsx": "^4.19.2"
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.53.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.8.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.53.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
},
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user