From d24d3a63c84655dc4dfb742ea1f640a7500ba2f6 Mon Sep 17 00:00:00 2001 From: monoid Date: Sat, 20 Aug 2022 10:09:52 +0900 Subject: [PATCH] init 2 --- .eslintrc.js | 3 + .vscode/snippets.code-snippets | 21 +- src/auto-contract.ts | 19 + src/batching.ts | 39 + src/deploy-miner.ts | 45 + src/get-cylce-size.ts | 17 + src/hacknet-daemon.ts | 207 ++-- src/lib/batchbase.ts | 95 ++ src/lib/contract.ts | 229 ++++ src/lib/flag.ts | 1 + src/lib/formula.ts | 146 +++ src/lib/fuse.esm.d.ts | 295 +++++ src/lib/fuse.esm.js | 1780 +++++++++++++++++++++++++++ src/lib/hack.ts | 16 +- src/lib/log.ts | 13 + src/lib/printf.ts | 778 ++++++++++++ src/lib/servers.ts | 9 +- src/lib/solve/algorithmicTrader.ts | 117 ++ src/lib/solve/arrayJump.ts | 30 + src/lib/solve/arrayJump2.ts | 28 + src/lib/solve/compression.ts | 154 +++ src/lib/solve/findAllMathExpr.ts | 0 src/lib/solve/graph2color.ts | 82 ++ src/lib/solve/largestPrime.ts | 278 +++++ src/lib/solve/minsumTriangle.ts | 59 + src/lib/solve/shortestPathInGrid.ts | 161 +++ src/lib/solve/spiral.ts | 90 ++ src/lib/solve/submaxarr.ts | 58 + src/lib/solve/unsolved.ts | 1 + src/list-all-server.ts | 42 +- src/list-purchase-server.ts | 2 +- src/list-stock.ts | 63 + src/ls-contract.ts | 87 ++ src/lsnode.ts | 39 + src/purchase-server.ts | 13 +- src/server-status.ts | 56 +- src/stock-auto-sell-deamon.ts | 35 + src/stock-daemon.ts | 69 ++ src/unpack-script.ts | 7 + src/util/assert.ts | 8 +- src/util/range.ts | 3 +- 41 files changed, 5069 insertions(+), 126 deletions(-) create mode 100644 src/auto-contract.ts create mode 100644 src/batching.ts create mode 100644 src/deploy-miner.ts create mode 100644 src/get-cylce-size.ts create mode 100644 src/lib/batchbase.ts create mode 100644 src/lib/contract.ts create mode 100644 src/lib/formula.ts create mode 100644 src/lib/fuse.esm.d.ts create mode 100644 src/lib/fuse.esm.js create mode 100644 src/lib/log.ts create mode 100644 src/lib/printf.ts create mode 100644 src/lib/solve/algorithmicTrader.ts create mode 100644 src/lib/solve/arrayJump.ts create mode 100644 src/lib/solve/arrayJump2.ts create mode 100644 src/lib/solve/compression.ts create mode 100644 src/lib/solve/findAllMathExpr.ts create mode 100644 src/lib/solve/graph2color.ts create mode 100644 src/lib/solve/largestPrime.ts create mode 100644 src/lib/solve/minsumTriangle.ts create mode 100644 src/lib/solve/shortestPathInGrid.ts create mode 100644 src/lib/solve/spiral.ts create mode 100644 src/lib/solve/submaxarr.ts create mode 100644 src/lib/solve/unsolved.ts create mode 100644 src/list-stock.ts create mode 100644 src/ls-contract.ts create mode 100644 src/lsnode.ts create mode 100644 src/stock-auto-sell-deamon.ts create mode 100644 src/stock-daemon.ts create mode 100644 src/unpack-script.ts diff --git a/.eslintrc.js b/.eslintrc.js index e636593..6cb7660 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,6 +22,9 @@ module.exports = { "@typescript-eslint/explicit-module-boundary-types": ["off"], "@typescript-eslint/no-non-null-assertion": ["off"], "@typescript-eslint/no-use-before-define": ["off"], + + "@typescript-eslint/explicit-function-return-type":["off"], + "@typescript-eslint/no-parameter-properties": ["off"], "@typescript-eslint/no-unused-vars": [ "error", diff --git a/.vscode/snippets.code-snippets b/.vscode/snippets.code-snippets index a337168..e1385b2 100644 --- a/.vscode/snippets.code-snippets +++ b/.vscode/snippets.code-snippets @@ -15,10 +15,29 @@ "prefix": "autocomplete", "body": [ "// eslint-disable-next-line @typescript-eslint/no-unused-vars", - "export function autocomplete(data : ServerData, args : string[]) : string[] {", + "export function autocomplete(data : AutocompleteData, args : string[]) : string[] {", "\treturn [...data.servers]", "}" ], "description": "autocomplete" + }, + "template-advanced":{ + "scope": "typescript", + "prefix": "template-advanced", + "body": [ + "import { NS } from '@ns'", + "import { parse } from './lib/flag';", + "import { sprintf } from './lib/printf';", + "", + "export async function main(ns: NS) : Promise {", + " const flag = parse(ns.args.map(x=>x.toString()));", + " if(flag.h || flag.help){", + " const msg = ['run cmd']", + " msg.forEach(x=>ns.tprint(x));", + " return;", + " }", + "}" + ], + "description": "autocomplete" } } \ No newline at end of file diff --git a/src/auto-contract.ts b/src/auto-contract.ts new file mode 100644 index 0000000..8215cf3 --- /dev/null +++ b/src/auto-contract.ts @@ -0,0 +1,19 @@ +import { NS } from '@ns' +import { selectAllContract, isSolvable, solveContract } from './lib/contract' + +export async function main(ns : NS) : Promise { + const remoteList = selectAllContract(ns); + for (const contract of remoteList) { + if(isSolvable(ns,contract.filename,contract.hostname)){ + const [success, reward] = await solveContract(ns,contract.filename,contract.hostname); + if(success){ + ns.tprint(`solve ${contract.filename}`); + ns.tprint(reward); + } + else{ + ns.tprint("stop") + break; + } + } + } +} \ No newline at end of file diff --git a/src/batching.ts b/src/batching.ts new file mode 100644 index 0000000..933bb6b --- /dev/null +++ b/src/batching.ts @@ -0,0 +1,39 @@ +import { NS, AutocompleteData } from '@ns' +import { installBatchFilePack, isBatchFilePackInstalled, scpBatchFilePack } from './lib/batchbase'; +import { parse } from './lib/flag'; + +function help(ns:NS):void{ + ns.tprint("run cmd [target(hostname)] -h hostname") +} + +// eslint-disable-next-line require-await +export async function main(ns : NS) : Promise { + const flag = parse(ns.args.map(String)) + + const hostname: string|undefined = flag.hostname ?? flag.n; + if(!hostname){ + ns.tprint("hostname is not set"); + help(ns); + return; + } + if(flag._.length == 0){ + ns.tprint("target is not set"); + help(ns); + return; + } + const target = flag._[0].toString(); + + if(!isBatchFilePackInstalled(ns)){ + await installBatchFilePack(ns); + } + + await scpBatchFilePack(ns,hostname); + + const server = ns.getServer(target); + ns.tprint(); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function autocomplete(data : AutocompleteData, args : string[]) : string[] { + return [...data.servers] +} \ No newline at end of file diff --git a/src/deploy-miner.ts b/src/deploy-miner.ts new file mode 100644 index 0000000..b29f39d --- /dev/null +++ b/src/deploy-miner.ts @@ -0,0 +1,45 @@ +import { AutocompleteData, NS } from "@ns"; +import {parse} from "./lib/flag"; + +/** @param {NS} ns */ +export async function main(ns: NS): Promise { + const flag = parse(ns.args.map(String)); + if(flag._.length < 2){ + ns.tprint("argument required"); + ns.tprint("run cmd [src(hostname)] [dest(hostname)]"); + ns.exit(); + } + const src = String(flag._[0]); + const dest = String(flag._[1]); + + const th = flag.thread || false; + + ns.tprint(`scp /script/mining.js ${src}`); + await ns.scp("/script/mining.js",src); + let nThread; + if(th){ + nThread = th; + } + else { + nThread = calculateMaximumThread(ns, src, ns.getScriptRam("/script/mining.js", src)); + } + const pid = ns.exec("/script/mining.js",src,nThread,nThread,dest); + ns.tprint(pid," Process started."); +} + +/** + * @param {NS} ns + * @param {string} dst + * @param {number} amount the program ram + */ +export function calculateMaximumThread(ns:NS,host:string,amount:number):number{ + const maxRam = ns.getServerMaxRam(host); + const usedRam = ns.getServerUsedRam(host); + const restRam = maxRam - usedRam; + return Math.floor(restRam / amount); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function autocomplete(data: AutocompleteData,args: string[]): string[]{ + return [...data.servers]; +} \ No newline at end of file diff --git a/src/get-cylce-size.ts b/src/get-cylce-size.ts new file mode 100644 index 0000000..c2cf974 --- /dev/null +++ b/src/get-cylce-size.ts @@ -0,0 +1,17 @@ +import { NS } from '@ns' + +export async function main(ns : NS) : Promise { + const stat: number[] = []; + for (let i = 0; i < 100; i++) { + const start = performance.now(); + await ns.sleep(0); + const end = performance.now(); + stat.push(end- start); + } + const avg = stat.reduce((x,y)=>x+y)/stat.length; + const max = Math.max(...stat); + const min = Math.min(...stat); + ns.tprint(`avg : ${avg}`); + ns.tprint(`max : ${max}`); + ns.tprint(`min : ${min}`); +} \ No newline at end of file diff --git a/src/hacknet-daemon.ts b/src/hacknet-daemon.ts index 03004fa..8165dfe 100644 --- a/src/hacknet-daemon.ts +++ b/src/hacknet-daemon.ts @@ -1,25 +1,31 @@ -import {NS} from "@ns" +import { NS } from "@ns" import { parse } from "./lib/flag"; -import {range} from "./util/range"; +import { range } from "./util/range"; -class Account{ +class Account { value: number; - timer?: NodeJS.Timer; - constructor(value: number){ + constructor(value: number) { this.value = value; - this.timer = undefined; } - startIncrement(ns:NS, rate: number): void{ - this.timer = setInterval(()=>{ - this.value += ns.hacknet.getNodeStats().production * rate; - },1000); + /** + * + * @param ns NS + * @param rate reinvestment rate + * @param period the time since last update. it's millisecond unit. + */ + upgradeIncrement(ns: NS, rate: number, period: number): void { + const production = [...getIndexOfHackNode(ns)] + .map(x => ns.hacknet.getNodeStats(x).production) + .reduce((x, y) => x + y); + this.value += production * rate * (period / 1000); + } - stopIncrement(): void{ - clearInterval(this.timer) + formatedValue(ns: NS): string{ + return ns.nFormat(this.value,"$0.0000a"); } } -export function getNodeProduction(level: number, ram: number, cores: number, mult: number, bitMult:number): number{ +export function getNodeProduction(level: number, ram: number, cores: number, mult: number, bitMult: number): number { const moneyGainPerLevel = 1.5; const levelMult = level * moneyGainPerLevel; const ramMult = Math.pow(1.035, ram - 1); @@ -36,143 +42,194 @@ interface HacknetNodeComponent { type ComponentKind = "level" | "core" | "ram"; export interface CEInfo { - upgradeCost : HacknetNodeComponent; - production : HacknetNodeComponent; - costEffective : HacknetNodeComponent; + upgradeCost: HacknetNodeComponent; + production: HacknetNodeComponent; + costEffective: HacknetNodeComponent; +} +interface Factor { + cost: T; + production: T; + costEffective: T; } -function getCostEffective(ns:NS, nodeIndex: number, n:number): CEInfo{ - const coreUpgradeCost = ns.hacknet.getCoreUpgradeCost(nodeIndex,n); - const ramUpgradeCost = ns.hacknet.getRamUpgradeCost(nodeIndex,n); - const levelUpgradeCost = ns.hacknet.getLevelUpgradeCost(nodeIndex,n); +function CEInfoToFactor(ceinfo: CEInfo):HacknetNodeComponent>{ + return { + level:{ + cost:ceinfo.upgradeCost.level, + production:ceinfo.production.level, + costEffective:ceinfo.costEffective.level, + }, + core:{ + cost:ceinfo.upgradeCost.core, + production:ceinfo.production.core, + costEffective:ceinfo.costEffective.core, + }, + ram:{ + cost:ceinfo.upgradeCost.ram, + production:ceinfo.production.ram, + costEffective:ceinfo.costEffective.ram, + } + } +} + + +function getCostEffective(ns: NS, nodeIndex: number, n: number): CEInfo { + const coreUpgradeCost = ns.hacknet.getCoreUpgradeCost(nodeIndex, n); + const ramUpgradeCost = ns.hacknet.getRamUpgradeCost(nodeIndex, n); + const levelUpgradeCost = ns.hacknet.getLevelUpgradeCost(nodeIndex, n); const mult = ns.getHacknetMultipliers().production; const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney; const stat = ns.hacknet.getNodeStats(nodeIndex); - + const coreProduction = getNodeProduction(stat.level, stat.ram, stat.cores + n, mult, bitMult) - stat.production; const levelProduction = getNodeProduction(stat.level + n, stat.ram, stat.cores, mult, bitMult) - stat.production; - const ramProduction = getNodeProduction(stat.level, stat.ram * Math.pow(2,n), stat.cores, mult, bitMult) - stat.production; + const ramProduction = getNodeProduction(stat.level, stat.ram * Math.pow(2, n), stat.cores, mult, bitMult) - stat.production; return { - upgradeCost:{ + upgradeCost: { core: coreUpgradeCost, ram: ramUpgradeCost, level: levelUpgradeCost, }, - production:{ + production: { core: coreProduction, ram: ramProduction, level: levelProduction, }, - costEffective:{ - core: coreProduction/coreUpgradeCost, - ram: ramProduction/ramUpgradeCost, - level: levelProduction/levelUpgradeCost + costEffective: { + core: coreProduction / coreUpgradeCost, + ram: ramProduction / ramUpgradeCost, + level: levelProduction / levelUpgradeCost } } } -function createUpgradeAction(ns:NS, index: number, n: number, kind:ComponentKind):(account: Account)=>void{ - return (account: Account)=>{ + + +function createUpgradeAction(ns: NS, index: number, n: number, kind: ComponentKind): (account: Account) => void { + return (account: Account) => { switch (kind) { case "core": - account.value -= ns.hacknet.getCoreUpgradeCost(index,n); - ns.hacknet.upgradeCore(index,n) + account.value -= ns.hacknet.getCoreUpgradeCost(index, n); + ns.hacknet.upgradeCore(index, n) break; case "level": - account.value -= ns.hacknet.getLevelUpgradeCost(index,n); - ns.hacknet.upgradeLevel(index,n) + account.value -= ns.hacknet.getLevelUpgradeCost(index, n); + ns.hacknet.upgradeLevel(index, n) break; case "ram": - account.value -= ns.hacknet.getRamUpgradeCost(index,n); - ns.hacknet.upgradeRam(index,n) + account.value -= ns.hacknet.getRamUpgradeCost(index, n); + ns.hacknet.upgradeRam(index, n) break; } } } -function getMinCENode(ns:NS, index: number, n: number):[number,ComponentKind]{ - const {costEffective: ce} = getCostEffective(ns,index,n); - if(ce.core < ce.level){ - if(ce.core < ce.ram){ - return [ce.core, "core"]; +function getMaxCENode(ns: NS, index: number, n: number): [Factor, ComponentKind] { + const a = getCostEffective(ns, index, n); + const b = CEInfoToFactor(a); + const ce = a.costEffective; + if (ce.core > ce.level) { + if (ce.core > ce.ram) { + return [b.core, "core"]; } - else{ - return [ce.ram, "ram"]; + else { + return [b.ram, "ram"]; } } else { - if(ce.level < ce.ram){ - return [ce.level,"level"]; + if (ce.level > ce.ram) { + return [b.level, "level"]; } else { - return [ce.ram,"ram"]; + return [b.ram, "ram"]; } } } -function* getIndexOfHackNode(ns:NS): Generator{ - for (const it of range(ns.hacknet.numNodes())) { - yield it; +function* getIndexOfHackNode(ns: NS): Generator { + const numNodes = ns.hacknet.numNodes() + for (const it of range(numNodes)) { + yield it; } } -function getMinCEIndex(ns:NS, n: number):[number,number,ComponentKind]{ - let optimalC = Infinity; - let kind = ""; - let minIndex = 0; - for(const it of getIndexOfHackNode(ns)){ - const [c, k] = getMinCENode(ns,it,n); - if(c < optimalC){ +function getMaxCEIndex(ns: NS, n: number): [number, Factor, ComponentKind] { + let optimalC: Factor = { + cost:0, + production:0, + costEffective:0, + }; + let kind: ComponentKind = "core"; + let maxIndex = 0; + for (const it of getIndexOfHackNode(ns)) { + const [c, k] = getMaxCENode(ns, it, n); + if (c.costEffective > optimalC.costEffective) { optimalC = c; kind = k; - minIndex = it; + maxIndex = it; } } - return [minIndex,optimalC,kind]; + return [maxIndex, optimalC, kind]; } -function getBuyNodeCE(ns:NS):{cost:number;production:number;costEffective: number}{ +function getBuyNodeCE(ns: NS): Factor { const mult = ns.getHacknetMultipliers().production; const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney; const cost = ns.hacknet.maxNumNodes() > ns.hacknet.numNodes() ? ns.hacknet.getPurchaseNodeCost() : Infinity; - const production = getNodeProduction(1,1,1,mult,bitMult); + const production = getNodeProduction(1, 1, 1, mult, bitMult); return { cost, production, - costEffective: cost/production, + costEffective: production/cost, }; } -function cycle(ns:NS, account:Account): void{ +function cycle(ns: NS, account: Account): void { const chunkN = 1; - const [upgradeNodeIndex,upgradeNodeCE,kind] = getMinCEIndex(ns,chunkN); - const {costEffective: buyNew} = getBuyNodeCE(ns); - if(upgradeNodeCE < buyNew){ - const action = createUpgradeAction(ns,upgradeNodeIndex,chunkN,kind); - action(account); + const [upgradeNodeIndex, upgradeNodeCE, kind] = getMaxCEIndex(ns, chunkN); + const { costEffective: buyNewCE, cost: buyNewCost } = getBuyNodeCE(ns); + if (upgradeNodeCE.costEffective > buyNewCE) { + if(account.value > upgradeNodeCE.cost){ + ns.print(`upgrade ${kind} index ${upgradeNodeIndex} : CE ${upgradeNodeCE.costEffective}`); + const action = createUpgradeAction(ns, upgradeNodeIndex, chunkN, kind); + action(account); + ns.print(`budget ${account.formatedValue(ns)}`); + } } else { - ns.hacknet.purchaseNode() + if (account.value > buyNewCost) { + ns.print(`purchase node : CE ${buyNewCE}`); + account.value -= buyNewCost; + ns.hacknet.purchaseNode() + ns.print(`budget ${account.formatedValue(ns)}`); + } } } // eslint-disable-next-line require-await -export async function main(ns:NS):Promise{ +export async function main(ns: NS): Promise { ns.disableLog("ALL") - const flag = parse(ns.args); + const flag = parse(ns.args.map(String)); + if(flag.h || flag.help){ + ns.tprint(`run cmd [-i or --iter] [-r or --rate] [-b --budget]`); + ns.tprint(`-i : cycle iterating count`); + ns.tprint(`-r : reinvestment rate`); + ns.tprint(`-b : initial budget`); + return; + } const iter = parseInt(flag.i ?? flag.iter ?? "10"); const rate = parseFloat(flag.r ?? flag.rate ?? "0.5"); const budget = parseInt(flag.b ?? flag.budget ?? "0"); const account = new Account(budget); - account.startIncrement(ns,rate); ns.tail(); + const period = 1000; for (let i = 0; i < iter; i++) { - ns.clearLog() - ns.print(`budget ${account.value}`); - cycle(ns,account); - await ns.sleep(1000); + //ns.clearLog() + cycle(ns, account); + await ns.sleep(period); + account.upgradeIncrement(ns,rate,period); } } \ No newline at end of file diff --git a/src/lib/batchbase.ts b/src/lib/batchbase.ts new file mode 100644 index 0000000..7af3bf2 --- /dev/null +++ b/src/lib/batchbase.ts @@ -0,0 +1,95 @@ +import { NS } from "@ns" + +type FilePackEntry = { + path: string; + content: string; +}; + +type FilePack = FilePackEntry[]; + +const createScriptContent: (f: string) => string = (funcName: string) => `export async function main(ns) { + if(ns.args.length < 3){ + ns.print("[ERROR] args required"); + ns.print("run cmd [sleepDuration] [hostname] [effectStock]") + ns.exit(); + return; + } + const sleepDuration = parseInt(ns.args[0]); + const hostname = ns.args[1].toString(); + const stock = ns.args[2] == "true"; + await ns.sleep(sleepDuration); + await ns.${funcName}(hostname,{ + stock, + }); +}` + +async function unpackFileEntry(ns: NS, filePack: FilePackEntry): Promise { + await ns.write(filePack.path, filePack.content, "w"); +} + +export async function unpackFilePack(ns: NS, pack: FilePack): Promise { + for (const entry of pack) { + await unpackFileEntry(ns, entry); + } +} + +export const batchFilePack: FilePack = [ + { + path: "/tmp/hack.js", + content: createScriptContent("hack") + }, + { + path: "/tmp/weaken.js", + content: createScriptContent("weaken") + }, + { + path: "/tmp/grow.js", + content: createScriptContent("grow") + } +] + +export async function scpBatchFilePack(ns: NS,hostname :string): Promise { + const list = batchFilePack.map(entry=>entry.path); + await installBatchFilePack(ns); + await ns.scp(list,hostname); +} + +export async function installBatchFilePack(ns: NS): Promise { + await unpackFilePack(ns, batchFilePack); +} + +export function isBatchFilePackInstalled(ns: NS, hostname?: string): boolean { + return batchFilePack.every(entry => ns.fileExists(entry.path, hostname)) +} + +export interface ExecOption{ + hostname: string; + /** + * @default 1 + */ + numThread?: number; + /** + * @default 0 + */ + sleepDuration?: number; + /** + * target hostname to operate "hack", "weaken" or "grow" + */ + target: number; + stock?: boolean; +} + +function execBatchfile(ns: NS, path: string, option:ExecOption): number { + return ns.exec(path, option.hostname, option.numThread, + option.sleepDuration ?? 0, option.target, option.stock ?? false); +} + +export function execHack(ns: NS,option: ExecOption): number{ + return execBatchfile(ns,"/tmp/hack.js",option); +} +export function execGrow(ns: NS,option: ExecOption): number{ + return execBatchfile(ns,"/tmp/grow.js",option); +} +export function execWeaken(ns: NS,option: ExecOption): number{ + return execBatchfile(ns,"/tmp/weaken.js",option); +} \ No newline at end of file diff --git a/src/lib/contract.ts b/src/lib/contract.ts new file mode 100644 index 0000000..6e8e65e --- /dev/null +++ b/src/lib/contract.ts @@ -0,0 +1,229 @@ +import { NS } from "@ns"; + +import { subarrayMaxSolve } from "./solve/submaxarr"; +import { largestPrimeSolve } from "./solve/largestPrime"; +import { spiralSolve } from "./solve/spiral"; +import { arrayJumpSolve } from "./solve/arrayJump"; +import { arrayJump2Solve } from "./solve/arrayJump2"; +import { minimumTrianglePathSumSolve} from "./solve/minsumTriangle" +import { graph2coloringSolve } from "./solve/graph2color"; +import { shortestPathInGridSolve } from "./solve/shortestPathInGrid"; +import { algorithmicTradeIIISolve, algorithmicTradeIISolve, algorithmicTradeISolve, algorithmicTradeIVSolve } from "./solve/algorithmicTrader"; + +import {selectAllServerList} from "lib/servers"; +import { logFile } from "./log"; +import { SolveFailError } from "./solve/unsolved"; +import { RLECompressionSolve, decompressLZSolve, compressLZSolve } from "./solve/compression"; + +export { SolveFailError } from "./solve/unsolved"; + +export interface ContractSolver{ + name: string; + solve(n :any): string[] | number; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const ContractMAP: {[key:string]:ContractSolver} = { + /** + * A prime factor is a factor that is a prime number. + * What is the largest prime factor of the given number? + */ + "Find Largest Prime Factor": { + name: "Find Largest Prime Factor", + solve: largestPrimeSolve + }, + /** + * Given the following integer array, find the contiguous subarray + * (containing at least one number) which has the largest sum and return that sum. + * 'Sum' refers to the sum of all the numbers in the subarray. + */ + "Subarray with Maximum Sum": { + name: "Subarray with Maximum Sum", + solve: subarrayMaxSolve, + }, + /*"Total Ways to Sum": { + name: "Total Ways to Sum", + solve() { + notImplemented(); + } + }, + "Total Ways to Sum II": { + name: "Total Ways to Sum II", + solve() { + notImplemented(); + } + },*/ + "Spiralize Matrix": { + name: "Spiralize Matrix", + solve: spiralSolve, + }, + "Array Jumping Game": { + name: "Array Jumping Game", + solve: arrayJumpSolve, + }, + "Array Jumping Game II": { + name: "Array Jumping Game II", + solve: arrayJump2Solve + },/* + "Merge Overlapping Intervals": { + name: "Merge Overlapping Intervals", + solve() { + notImplemented(); + } + }, + "Generate IP Addresses": { + name: "Generate IP Addresses", + solve() { + notImplemented(); + } + },*/ + "Algorithmic Stock Trader I": { + name: "Algorithmic Stock Trader I", + solve: algorithmicTradeISolve, + }, + "Algorithmic Stock Trader II": { + name: "Algorithmic Stock Trader II", + solve: algorithmicTradeIISolve, + }, + "Algorithmic Stock Trader III": { + name: "Algorithmic Stock Trader III", + solve: algorithmicTradeIIISolve + }, + "Algorithmic Stock Trader IV": { + name: "Algorithmic Stock Trader IV", + solve: algorithmicTradeIVSolve + }, + "Minimum Path Sum in a Triangle": { + name: "Minimum Path Sum in a Triangle", + solve: minimumTrianglePathSumSolve, + },/* + "Unique Paths in a Grid I": { + name: "Unique Paths in a Grid I", + solve() { + notImplemented(); + } + }, + "Unique Paths in a Grid II": { + name: "Unique Paths in a Grid II", + solve() { + notImplemented(); + } + },*/ + "Shortest Path in a Grid": { + name: "Shortest Path in a Grid", + solve: shortestPathInGridSolve + },/* + "Sanitize Parentheses in Expression": { + name: "Sanitize Parentheses in Expression", + solve() { + notImplemented(); + } + }, + "Find All Valid Math Expressions": { + name: "Find All Valid Math Expressions", + solve() { + notImplemented(); + } + }, + "HammingCodes: Integer to Encoded Binary": { + name: "HammingCodes: Integer to Encoded Binary", + solve() { + notImplemented(); + } + }, + "HammingCodes: Encoded Binary to Integer": { + name: "HammingCodes: Encoded Binary to Integer", + solve() { + notImplemented(); + } + },*/ + "Proper 2-Coloring of a Graph": { + name: "Proper 2-Coloring of a Graph", + solve: graph2coloringSolve + }, + "Compression I: RLE Compression": { + name: "Compression I: RLE Compression", + solve: RLECompressionSolve, + }, + "Compression II: LZ Decompression": { + name: "Compression II: LZ Decompression", + solve: decompressLZSolve, + }, + "Compression III: LZ Compression": { + name: "Compression III: LZ Compression", + solve: compressLZSolve, + },/* + "Encryption I: Caesar Cipher": { + name: "Encryption I: Caesar Cipher", + solve() { + notImplemented(); + } + }, + "Encryption II: Vigenère Cipher": { + name: "Encryption II: Vigenère Cipher", + solve() { + notImplemented(); + } + }*/ +} + +export function isSolvable(ns:NS,filename: string, hostname: string): boolean { + const ct = ns.codingcontract; + const type = ct.getContractType(filename,hostname); + return (type in ContractMAP) +} + +export async function solveContract(ns:NS,filename: string, hostname: string): Promise<[boolean,string]>{ + const ct = ns.codingcontract; + const type = ct.getContractType(filename,hostname); + if(type in ContractMAP){ + const solver = ContractMAP[type]; + const data = ct.getData(filename,hostname); + let ans: number|string[]; + try { + ans = solver.solve(data); + } catch (error) { + if(error instanceof SolveFailError){ + ns.print(`[ERROR] failed to solve problem ${type}. throw SolveFailError`); + ns.toast(`failed to solve problem ${type}`,"error"); + await logFile(ns,type+"-Fail",data); + return [false,""]; + } + else{ + throw error; + } + } + const reward = ct.attempt(ans,filename,hostname,{returnReward:true}); + if(reward === ""){ + ns.print(`[ERROR] failed to solve problem ${type}`); + ns.toast(`failed to solve problem ${type}`,"error"); + await logFile(ns,type,[data,ans]); + return [false,""]; + } + else { + ns.print(`success to solve.\n${reward}`); + } + return [true,reward.toString()]; + } + return [false,""]; +} + +export interface RemoteFilePath{ + hostname: string; + filename: string; +} + +export function getContractList(ns: NS, hostname: string): RemoteFilePath[]{ + const ctFilenameList = ns.ls(hostname,".cct").filter(x=>x.endsWith(".cct")); + return ctFilenameList.map(filename=>({ + hostname: hostname, + filename: filename + })); +} + +export function selectAllContract(ns: NS): RemoteFilePath[] { + const serverList = selectAllServerList(ns); + const ctList = serverList.map(server=>getContractList(ns,server.hostname)) + .flat(); + return ctList; +} \ No newline at end of file diff --git a/src/lib/flag.ts b/src/lib/flag.ts index d7d08bc..c0c5315 100644 --- a/src/lib/flag.ts +++ b/src/lib/flag.ts @@ -89,6 +89,7 @@ function get(obj: Record, key: string): T | undefined { if (hasOwn(obj, key)) { return obj[key]; } + return undefined; } function getForce(obj: Record, key: string): T { diff --git a/src/lib/formula.ts b/src/lib/formula.ts new file mode 100644 index 0000000..c0808b4 --- /dev/null +++ b/src/lib/formula.ts @@ -0,0 +1,146 @@ +import { Server, Player } from "@ns"; + +const CONSTANTS = { + ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate + ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security) + ServerFortifyAmount: 0.002, // Amount by which server's security increases when its hacked/grown + ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened +} + +const BitNodeMultipliers = { + HackingLevelMultiplier: 1, + + ServerGrowthRate: 1, + ServerMaxMoney: 1, + ServerStartingMoney: 1, + ServerStartingSecurity: 1, + ServerWeakenRate: 1, + + HackExpGain: 1, + + ScriptHackMoney: 1, +} + +export function calculateIntelligenceBonus(intelligence: number, weight = 1): number { + return 1 + (weight * Math.pow(intelligence, 0.8)) / 600; + } + +export function calculateServerGrowth(server: Server, threads: number, p: Player, cores = 1): number { + const numServerGrowthCycles = Math.max(Math.floor(threads), 0); + + //Get adjusted growth rate, which accounts for server security + const growthRate = CONSTANTS.ServerBaseGrowthRate; + let adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty; + if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) { + adjGrowthRate = CONSTANTS.ServerMaxGrowthRate; + } + + //Calculate adjusted server growth rate based on parameters + const serverGrowthPercentage = server.serverGrowth / 100; + const numServerGrowthCyclesAdjusted = + numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate; + + //Apply serverGrowth for the calculated number of growth cycles + const coreBonus = 1 + (cores - 1) / 16; + return Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.mults.hacking_grow * coreBonus); +} + +/** + * Returns the chance the player has to successfully hack a server + */ +export function calculateHackingChance(server: Server, player: Player): number { + const hackFactor = 1.75; + const difficultyMult = (100 - server.hackDifficulty) / 100; + const skillMult = hackFactor * player.skills.hacking; + const skillChance = (skillMult - server.requiredHackingSkill) / skillMult; + const chance = + skillChance * + difficultyMult * + player.mults.hacking_chance * + calculateIntelligenceBonus(player.skills.intelligence, 1); + if (chance > 1) { + return 1; + } + if (chance < 0) { + return 0; + } + + return chance; +} + +/** + * Returns the amount of hacking experience the player will gain upon + * successfully hacking a server + */ +export function calculateHackingExpGain(server: Server, player: Player): number { + const baseExpGain = 3; + const diffFactor = 0.3; + if (server.baseDifficulty == null) { + server.baseDifficulty = server.hackDifficulty; + } + let expGain = baseExpGain; + expGain += server.baseDifficulty * diffFactor; + + return expGain * player.mults.hacking_exp * BitNodeMultipliers.HackExpGain; +} + +/** + * Returns the percentage of money that will be stolen from a server if + * it is successfully hacked (returns the decimal form, not the actual percent value) + */ +export function calculatePercentMoneyHacked(server: Server, player: Player): number { + // Adjust if needed for balancing. This is the divisor for the final calculation + const balanceFactor = 240; + + const difficultyMult = (100 - server.hackDifficulty) / 100; + const skillMult = (player.skills.hacking - (server.requiredHackingSkill - 1)) / player.skills.hacking; + const percentMoneyHacked = + (difficultyMult * skillMult * player.mults.hacking_money * BitNodeMultipliers.ScriptHackMoney) / balanceFactor; + if (percentMoneyHacked < 0) { + return 0; + } + if (percentMoneyHacked > 1) { + return 1; + } + + return percentMoneyHacked; +} + +/** + * Returns time it takes to complete a hack on a server, in seconds + */ +export function calculateHackingTime(server: Server, player: Player): number { + const difficultyMult = server.requiredHackingSkill * server.hackDifficulty; + + const baseDiff = 500; + const baseSkill = 50; + const diffFactor = 2.5; + let skillFactor = diffFactor * difficultyMult + baseDiff; + // tslint:disable-next-line + skillFactor /= player.skills.hacking + baseSkill; + + const hackTimeMultiplier = 5; + const hackingTime = + (hackTimeMultiplier * skillFactor) / + (player.mults.hacking_speed * calculateIntelligenceBonus(player.skills.intelligence, 1)); + + return hackingTime; +} + +/** + * Returns time it takes to complete a grow operation on a server, in seconds + */ +export function calculateGrowTime(server: Server, player: Player): number { + const growTimeMultiplier = 3.2; // Relative to hacking time. 16/5 = 3.2 + + return growTimeMultiplier * calculateHackingTime(server, player); +} + +/** + * Returns time it takes to complete a weaken operation on a server, in seconds + */ +export function calculateWeakenTime(server: Server, player: Player): number { + const weakenTimeMultiplier = 4; // Relative to hacking time + + return weakenTimeMultiplier * calculateHackingTime(server, player); +} \ No newline at end of file diff --git a/src/lib/fuse.esm.d.ts b/src/lib/fuse.esm.d.ts new file mode 100644 index 0000000..238f0fa --- /dev/null +++ b/src/lib/fuse.esm.d.ts @@ -0,0 +1,295 @@ +// Type definitions for Fuse.js v6.4.1 +// TypeScript v3.9.5 + +export default Fuse +export as namespace Fuse + +declare class Fuse { + constructor( + list: readonly T[], + options?: Fuse.IFuseOptions, + index?: FuseIndex + ) + /** + * Search function for the Fuse instance. + * + * ```typescript + * const list: MyType[] = [myType1, myType2, etc...] + + * const options: Fuse.IFuseOptions = { + * keys: ['key1', 'key2'] + * } + * + * const myFuse = new Fuse(list, options) + * let result = myFuse.search('pattern') + * ``` + * + * @param pattern The pattern to search + * @param options `Fuse.FuseSearchOptions` + * @returns An array of search results + */ + search( + pattern: string | Fuse.Expression, + options?: Fuse.FuseSearchOptions + ): Array> + + setCollection(docs: readonly T[], index?: FuseIndex): void + + /** + * Adds a doc to the end the list. + */ + add(doc: T): void + + /** + * Removes all documents from the list which the predicate returns truthy for, + * and returns an array of the removed docs. + * The predicate is invoked with two arguments: (doc, index). + */ + remove(predicate: (doc: T, idx: number) => boolean): T[] + + /** + * Removes the doc at the specified index. + */ + removeAt(idx: number): void + + /** + * Returns the generated Fuse index + */ + getIndex(): FuseIndex + + /** + * Return the current version. + */ + // eslint-disable-next-line @typescript-eslint/member-ordering + static version: string + + /** + * Use this method to pre-generate the index from the list, and pass it + * directly into the Fuse instance. + * + * _Note that Fuse will automatically index the table if one isn't provided + * during instantiation._ + * + * ```typescript + * const list: MyType[] = [myType1, myType2, etc...] + * + * const index = Fuse.createIndex( + * keys: ['key1', 'key2'] + * list: list + * ) + * + * const options: Fuse.IFuseOptions = { + * keys: ['key1', 'key2'] + * } + * + * const myFuse = new Fuse(list, options, index) + * ``` + * @param keys The keys to index + * @param list The list from which to create an index + * @param options? + * @returns An indexed list + */ + static createIndex( + keys: Fuse.FuseOptionKey[], + list: readonly U[], + options?: Fuse.FuseIndexOptions + ): FuseIndex + + static parseIndex( + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + index: any, + options?: Fuse.FuseIndexOptions + ): FuseIndex +} + +declare class FuseIndex { + constructor(options?: Fuse.FuseIndexOptions) + setSources(docs: readonly T[]): void + setKeys(keys: readonly string[]): void + setIndexRecords(records: Fuse.FuseIndexRecords): void + create(): void + add(doc: T): void + toJSON(): { + keys: readonly string[]; + collection: Fuse.FuseIndexRecords; + } +} + +declare namespace Fuse { + type FuseGetFunction = ( + obj: T, + path: string | string[] + ) => readonly string[] | string + + export type FuseIndexOptions = { + getFn: FuseGetFunction; + } + + // { + // title: { '$': "Old Man's War" }, + // 'author.firstName': { '$': 'Codenar' } + // } + // + // OR + // + // { + // tags: [ + // { $: 'nonfiction', idx: 0 }, + // { $: 'web development', idx: 1 }, + // ] + // } + export type FuseSortFunctionItem = { + [key: string]: { $: string } | Array<{ $: string; idx: number }>; + } + + // { + // score: 0.001, + // key: 'author.firstName', + // value: 'Codenar', + // indices: [ [ 0, 3 ] ] + // } + export type FuseSortFunctionMatch = { + score: number; + key: string; + value: string; + indices: Array; + } + + // { + // score: 0, + // key: 'tags', + // value: 'nonfiction', + // idx: 1, + // indices: [ [ 0, 9 ] ] + // } + export type FuseSortFunctionMatchList = FuseSortFunctionMatch & { + idx: number; + } + + export type FuseSortFunctionArg = { + idx: number; + item: FuseSortFunctionItem; + score: number; + matches?: Array; + } + + export type FuseSortFunction = ( + a: FuseSortFunctionArg, + b: FuseSortFunctionArg + ) => number + + // title: { + // '$': "Old Man's War", + // 'n': 0.5773502691896258 + // } + type RecordEntryObject = { + v: string; // The text value + n: number; // The field-length norm + } + + // 'author.tags.name': [{ + // 'v': 'pizza lover', + // 'i': 2, + // 'n: 0.7071067811865475 + // } + type RecordEntryArrayItem = ReadonlyArray + + // TODO: this makes it difficult to infer the type. Need to think more about this + type RecordEntry = { [key: string]: RecordEntryObject | RecordEntryArrayItem } + + // { + // i: 0, + // '$': { + // '0': { v: "Old Man's War", n: 0.5773502691896258 }, + // '1': { v: 'Codenar', n: 1 }, + // '2': [ + // { v: 'pizza lover', i: 2, n: 0.7071067811865475 }, + // { v: 'helo wold', i: 1, n: 0.7071067811865475 }, + // { v: 'hello world', i: 0, n: 0.7071067811865475 } + // ] + // } + // } + type FuseIndexObjectRecord = { + i: number; // The index of the record in the source list + $: RecordEntry; + } + + // { + // keys: null, + // list: [ + // { v: 'one', i: 0, n: 1 }, + // { v: 'two', i: 1, n: 1 }, + // { v: 'three', i: 2, n: 1 } + // ] + // } + type FuseIndexStringRecord = { + i: number; // The index of the record in the source list + v: string; // The text value + n: number; // The field-length norm + } + + type FuseIndexRecords = + | readonly FuseIndexObjectRecord[] + | readonly FuseIndexStringRecord[] + + // { + // name: 'title', + // weight: 0.7 + // } + export type FuseOptionKeyObject = { + name: string | string[]; + weight: number; + } + + export type FuseOptionKey = FuseOptionKeyObject | string | string[] + + export interface IFuseOptions { + isCaseSensitive?: boolean; + distance?: number; + findAllMatches?: boolean; + getFn?: FuseGetFunction; + ignoreLocation?: boolean; + ignoreFieldNorm?: boolean; + includeMatches?: boolean; + includeScore?: boolean; + keys?: FuseOptionKey[]; + location?: number; + minMatchCharLength?: number; + shouldSort?: boolean; + sortFn?: FuseSortFunction; + threshold?: number; + useExtendedSearch?: boolean; + } + + // Denotes the start/end indices of a match + // start end + // ↓ ↓ + type RangeTuple = [number, number] + + export type FuseResultMatch = { + indices: readonly RangeTuple[]; + key?: string; + refIndex?: number; + value?: string; + } + + export type FuseSearchOptions = { + limit: number; + } + + export type FuseResult = { + item: T; + refIndex: number; + score?: number; + matches?: readonly FuseResultMatch[]; + } + + export type Expression = + | { [key: string]: string } + | { + $path: readonly string[]; + $val: string; + } + | { $and?: Expression[] } + | { $or?: Expression[] } +} diff --git a/src/lib/fuse.esm.js b/src/lib/fuse.esm.js new file mode 100644 index 0000000..1e02dbd --- /dev/null +++ b/src/lib/fuse.esm.js @@ -0,0 +1,1780 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +function isArray(value) { + return !Array.isArray + ? getTag(value) === '[object Array]' + : Array.isArray(value) +} + +// Adapted from: https://github.com/lodash/lodash/blob/master/.internal/baseToString.js +const INFINITY = 1 / 0; +function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value + } + let result = value + ''; + return result == '0' && 1 / value == -INFINITY ? '-0' : result +} + +function toString(value) { + return value == null ? '' : baseToString(value) +} + +function isString(value) { + return typeof value === 'string' +} + +function isNumber(value) { + return typeof value === 'number' +} + +// Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js +function isBoolean(value) { + return ( + value === true || + value === false || + (isObjectLike(value) && getTag(value) == '[object Boolean]') + ) +} + +function isObject(value) { + return typeof value === 'object' +} + +// Checks if `value` is object-like. +function isObjectLike(value) { + return isObject(value) && value !== null +} + +function isDefined(value) { + return value !== undefined && value !== null +} + +function isBlank(value) { + return !value.trim().length +} + +// Gets the `toStringTag` of `value`. +// Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js +function getTag(value) { + return value == null + ? value === undefined + ? '[object Undefined]' + : '[object Null]' + : Object.prototype.toString.call(value) +} + +const EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available'; + +const INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; + +const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = (key) => + `Invalid value for key ${key}`; + +const PATTERN_LENGTH_TOO_LARGE = (max) => + `Pattern length exceeds max of ${max}.`; + +const MISSING_KEY_PROPERTY = (name) => `Missing ${name} property in key`; + +const INVALID_KEY_WEIGHT_VALUE = (key) => + `Property 'weight' in key '${key}' must be a positive integer`; + +const hasOwn = Object.prototype.hasOwnProperty; + +class KeyStore { + constructor(keys) { + this._keys = []; + this._keyMap = {}; + + let totalWeight = 0; + + keys.forEach((key) => { + let obj = createKey(key); + + totalWeight += obj.weight; + + this._keys.push(obj); + this._keyMap[obj.id] = obj; + + totalWeight += obj.weight; + }); + + // Normalize weights so that their sum is equal to 1 + this._keys.forEach((key) => { + key.weight /= totalWeight; + }); + } + get(keyId) { + return this._keyMap[keyId] + } + keys() { + return this._keys + } + toJSON() { + return JSON.stringify(this._keys) + } +} + +function createKey(key) { + let path = null; + let id = null; + let src = null; + let weight = 1; + let getFn = null; + + if (isString(key) || isArray(key)) { + src = key; + path = createKeyPath(key); + id = createKeyId(key); + } else { + if (!hasOwn.call(key, 'name')) { + throw new Error(MISSING_KEY_PROPERTY('name')) + } + + const name = key.name; + src = name; + + if (hasOwn.call(key, 'weight')) { + weight = key.weight; + + if (weight <= 0) { + throw new Error(INVALID_KEY_WEIGHT_VALUE(name)) + } + } + + path = createKeyPath(name); + id = createKeyId(name); + getFn = key.getFn; + } + + return { path, id, weight, src, getFn } +} + +function createKeyPath(key) { + return isArray(key) ? key : key.split('.') +} + +function createKeyId(key) { + return isArray(key) ? key.join('.') : key +} + +function get(obj, path) { + let list = []; + let arr = false; + + const deepGet = (obj, path, index) => { + if (!isDefined(obj)) { + return + } + if (!path[index]) { + // If there's no path left, we've arrived at the object we care about. + list.push(obj); + } else { + let key = path[index]; + + const value = obj[key]; + + if (!isDefined(value)) { + return + } + + // If we're at the last value in the path, and if it's a string/number/bool, + // add it to the list + if ( + index === path.length - 1 && + (isString(value) || isNumber(value) || isBoolean(value)) + ) { + list.push(toString(value)); + } else if (isArray(value)) { + arr = true; + // Search each item in the array. + for (let i = 0, len = value.length; i < len; i += 1) { + deepGet(value[i], path, index + 1); + } + } else if (path.length) { + // An object. Recurse further. + deepGet(value, path, index + 1); + } + } + }; + + // Backwards compatibility (since path used to be a string) + deepGet(obj, isString(path) ? path.split('.') : path, 0); + + return arr ? list : list[0] +} + +const MatchOptions = { + // Whether the matches should be included in the result set. When `true`, each record in the result + // set will include the indices of the matched characters. + // These can consequently be used for highlighting purposes. + includeMatches: false, + // When `true`, the matching function will continue to the end of a search pattern even if + // a perfect match has already been located in the string. + findAllMatches: false, + // Minimum number of characters that must be matched before a result is considered a match + minMatchCharLength: 1 +}; + +const BasicOptions = { + // When `true`, the algorithm continues searching to the end of the input even if a perfect + // match is found before the end of the same input. + isCaseSensitive: false, + // When true, the matching function will continue to the end of a search pattern even if + includeScore: false, + // List of properties that will be searched. This also supports nested properties. + keys: [], + // Whether to sort the result list, by score + shouldSort: true, + // Default sort function: sort by ascending score, ascending index + sortFn: (a, b) => + a.score === b.score ? (a.idx < b.idx ? -1 : 1) : a.score < b.score ? -1 : 1 +}; + +const FuzzyOptions = { + // Approximately where in the text is the pattern expected to be found? + location: 0, + // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match + // (of both letters and location), a threshold of '1.0' would match anything. + threshold: 0.6, + // Determines how close the match must be to the fuzzy location (specified above). + // An exact letter match which is 'distance' characters away from the fuzzy location + // would score as a complete mismatch. A distance of '0' requires the match be at + // the exact location specified, a threshold of '1000' would require a perfect match + // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. + distance: 100 +}; + +const AdvancedOptions = { + // When `true`, it enables the use of unix-like search commands + useExtendedSearch: false, + // The get function to use when fetching an object's properties. + // The default will search nested paths *ie foo.bar.baz* + getFn: get, + // When `true`, search will ignore `location` and `distance`, so it won't matter + // where in the string the pattern appears. + // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score + ignoreLocation: false, + // When `true`, the calculation for the relevance score (used for sorting) will + // ignore the field-length norm. + // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm + ignoreFieldNorm: false, + // The weight to determine how much field length norm effects scoring. + fieldNormWeight: 1 +}; + +var Config = { + ...BasicOptions, + ...MatchOptions, + ...FuzzyOptions, + ...AdvancedOptions +}; + +const SPACE = /[^ ]+/g; + +// Field-length norm: the shorter the field, the higher the weight. +// Set to 3 decimals to reduce index size. +function norm(weight = 1, mantissa = 3) { + const cache = new Map(); + const m = Math.pow(10, mantissa); + + return { + get(value) { + const numTokens = value.match(SPACE).length; + + if (cache.has(numTokens)) { + return cache.get(numTokens) + } + + // Default function is 1/sqrt(x), weight makes that variable + const norm = 1 / Math.pow(numTokens, 0.5 * weight); + + // In place of `toFixed(mantissa)`, for faster computation + const n = parseFloat(Math.round(norm * m) / m); + + cache.set(numTokens, n); + + return n + }, + clear() { + cache.clear(); + } + } +} + +class FuseIndex { + constructor({ + getFn = Config.getFn, + fieldNormWeight = Config.fieldNormWeight + } = {}) { + this.norm = norm(fieldNormWeight, 3); + this.getFn = getFn; + this.isCreated = false; + + this.setIndexRecords(); + } + setSources(docs = []) { + this.docs = docs; + } + setIndexRecords(records = []) { + this.records = records; + } + setKeys(keys = []) { + this.keys = keys; + this._keysMap = {}; + keys.forEach((key, idx) => { + this._keysMap[key.id] = idx; + }); + } + create() { + if (this.isCreated || !this.docs.length) { + return + } + + this.isCreated = true; + + // List is Array + if (isString(this.docs[0])) { + this.docs.forEach((doc, docIndex) => { + this._addString(doc, docIndex); + }); + } else { + // List is Array + this.docs.forEach((doc, docIndex) => { + this._addObject(doc, docIndex); + }); + } + + this.norm.clear(); + } + // Adds a doc to the end of the index + add(doc) { + const idx = this.size(); + + if (isString(doc)) { + this._addString(doc, idx); + } else { + this._addObject(doc, idx); + } + } + // Removes the doc at the specified index of the index + removeAt(idx) { + this.records.splice(idx, 1); + + // Change ref index of every subsquent doc + for (let i = idx, len = this.size(); i < len; i += 1) { + this.records[i].i -= 1; + } + } + getValueForItemAtKeyId(item, keyId) { + return item[this._keysMap[keyId]] + } + size() { + return this.records.length + } + _addString(doc, docIndex) { + if (!isDefined(doc) || isBlank(doc)) { + return + } + + let record = { + v: doc, + i: docIndex, + n: this.norm.get(doc) + }; + + this.records.push(record); + } + _addObject(doc, docIndex) { + let record = { i: docIndex, $: {} }; + + // Iterate over every key (i.e, path), and fetch the value at that key + this.keys.forEach((key, keyIndex) => { + let value = key.getFn ? key.getFn(doc) : this.getFn(doc, key.path); + + if (!isDefined(value)) { + return + } + + if (isArray(value)) { + let subRecords = []; + const stack = [{ nestedArrIndex: -1, value }]; + + while (stack.length) { + const { nestedArrIndex, value } = stack.pop(); + + if (!isDefined(value)) { + continue + } + + if (isString(value) && !isBlank(value)) { + let subRecord = { + v: value, + i: nestedArrIndex, + n: this.norm.get(value) + }; + + subRecords.push(subRecord); + } else if (isArray(value)) { + value.forEach((item, k) => { + stack.push({ + nestedArrIndex: k, + value: item + }); + }); + } else; + } + record.$[keyIndex] = subRecords; + } else if (isString(value) && !isBlank(value)) { + let subRecord = { + v: value, + n: this.norm.get(value) + }; + + record.$[keyIndex] = subRecord; + } + }); + + this.records.push(record); + } + toJSON() { + return { + keys: this.keys, + records: this.records + } + } +} + +function createIndex( + keys, + docs, + { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {} +) { + const myIndex = new FuseIndex({ getFn, fieldNormWeight }); + myIndex.setKeys(keys.map(createKey)); + myIndex.setSources(docs); + myIndex.create(); + return myIndex +} + +function parseIndex( + data, + { getFn = Config.getFn, fieldNormWeight = Config.fieldNormWeight } = {} +) { + const { keys, records } = data; + const myIndex = new FuseIndex({ getFn, fieldNormWeight }); + myIndex.setKeys(keys); + myIndex.setIndexRecords(records); + return myIndex +} + +function computeScore$1( + pattern, + { + errors = 0, + currentLocation = 0, + expectedLocation = 0, + distance = Config.distance, + ignoreLocation = Config.ignoreLocation + } = {} +) { + const accuracy = errors / pattern.length; + + if (ignoreLocation) { + return accuracy + } + + const proximity = Math.abs(expectedLocation - currentLocation); + + if (!distance) { + // Dodge divide by zero error. + return proximity ? 1.0 : accuracy + } + + return accuracy + proximity / distance +} + +function convertMaskToIndices( + matchmask = [], + minMatchCharLength = Config.minMatchCharLength +) { + let indices = []; + let start = -1; + let end = -1; + let i = 0; + + for (let len = matchmask.length; i < len; i += 1) { + let match = matchmask[i]; + if (match && start === -1) { + start = i; + } else if (!match && start !== -1) { + end = i - 1; + if (end - start + 1 >= minMatchCharLength) { + indices.push([start, end]); + } + start = -1; + } + } + + // (i-1 - start) + 1 => i - start + if (matchmask[i - 1] && i - start >= minMatchCharLength) { + indices.push([start, i - 1]); + } + + return indices +} + +// Machine word size +const MAX_BITS = 32; + +function search( + text, + pattern, + patternAlphabet, + { + location = Config.location, + distance = Config.distance, + threshold = Config.threshold, + findAllMatches = Config.findAllMatches, + minMatchCharLength = Config.minMatchCharLength, + includeMatches = Config.includeMatches, + ignoreLocation = Config.ignoreLocation + } = {} +) { + if (pattern.length > MAX_BITS) { + throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)) + } + + const patternLen = pattern.length; + // Set starting location at beginning text and initialize the alphabet. + const textLen = text.length; + // Handle the case when location > text.length + const expectedLocation = Math.max(0, Math.min(location, textLen)); + // Highest score beyond which we give up. + let currentThreshold = threshold; + // Is there a nearby exact match? (speedup) + let bestLocation = expectedLocation; + + // Performance: only computer matches when the minMatchCharLength > 1 + // OR if `includeMatches` is true. + const computeMatches = minMatchCharLength > 1 || includeMatches; + // A mask of the matches, used for building the indices + const matchMask = computeMatches ? Array(textLen) : []; + + let index; + + // Get all exact matches, here for speed up + while ((index = text.indexOf(pattern, bestLocation)) > -1) { + let score = computeScore$1(pattern, { + currentLocation: index, + expectedLocation, + distance, + ignoreLocation + }); + + currentThreshold = Math.min(score, currentThreshold); + bestLocation = index + patternLen; + + if (computeMatches) { + let i = 0; + while (i < patternLen) { + matchMask[index + i] = 1; + i += 1; + } + } + } + + // Reset the best location + bestLocation = -1; + + let lastBitArr = []; + let finalScore = 1; + let binMax = patternLen + textLen; + + const mask = 1 << (patternLen - 1); + + for (let i = 0; i < patternLen; i += 1) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from the match location we can stray + // at this error level. + let binMin = 0; + let binMid = binMax; + + while (binMin < binMid) { + const score = computeScore$1(pattern, { + errors: i, + currentLocation: expectedLocation + binMid, + expectedLocation, + distance, + ignoreLocation + }); + + if (score <= currentThreshold) { + binMin = binMid; + } else { + binMax = binMid; + } + + binMid = Math.floor((binMax - binMin) / 2 + binMin); + } + + // Use the result from this iteration as the maximum for the next. + binMax = binMid; + + let start = Math.max(1, expectedLocation - binMid + 1); + let finish = findAllMatches + ? textLen + : Math.min(expectedLocation + binMid, textLen) + patternLen; + + // Initialize the bit array + let bitArr = Array(finish + 2); + + bitArr[finish + 1] = (1 << i) - 1; + + for (let j = finish; j >= start; j -= 1) { + let currentLocation = j - 1; + let charMatch = patternAlphabet[text.charAt(currentLocation)]; + + if (computeMatches) { + // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) + matchMask[currentLocation] = +!!charMatch; + } + + // First pass: exact match + bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch; + + // Subsequent passes: fuzzy match + if (i) { + bitArr[j] |= + ((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1 | lastBitArr[j + 1]; + } + + if (bitArr[j] & mask) { + finalScore = computeScore$1(pattern, { + errors: i, + currentLocation, + expectedLocation, + distance, + ignoreLocation + }); + + // This match will almost certainly be better than any existing match. + // But check anyway. + if (finalScore <= currentThreshold) { + // Indeed it is + currentThreshold = finalScore; + bestLocation = currentLocation; + + // Already passed `loc`, downhill from here on in. + if (bestLocation <= expectedLocation) { + break + } + + // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. + start = Math.max(1, 2 * expectedLocation - bestLocation); + } + } + } + + // No hope for a (better) match at greater error levels. + const score = computeScore$1(pattern, { + errors: i + 1, + currentLocation: expectedLocation, + expectedLocation, + distance, + ignoreLocation + }); + + if (score > currentThreshold) { + break + } + + lastBitArr = bitArr; + } + + const result = { + isMatch: bestLocation >= 0, + // Count exact matches (those with a score of 0) to be "almost" exact + score: Math.max(0.001, finalScore) + }; + + if (computeMatches) { + const indices = convertMaskToIndices(matchMask, minMatchCharLength); + if (!indices.length) { + result.isMatch = false; + } else if (includeMatches) { + result.indices = indices; + } + } + + return result +} + +function createPatternAlphabet(pattern) { + let mask = {}; + + for (let i = 0, len = pattern.length; i < len; i += 1) { + const char = pattern.charAt(i); + mask[char] = (mask[char] || 0) | (1 << (len - i - 1)); + } + + return mask +} + +class BitapSearch { + constructor( + pattern, + { + location = Config.location, + threshold = Config.threshold, + distance = Config.distance, + includeMatches = Config.includeMatches, + findAllMatches = Config.findAllMatches, + minMatchCharLength = Config.minMatchCharLength, + isCaseSensitive = Config.isCaseSensitive, + ignoreLocation = Config.ignoreLocation + } = {} + ) { + this.options = { + location, + threshold, + distance, + includeMatches, + findAllMatches, + minMatchCharLength, + isCaseSensitive, + ignoreLocation + }; + + this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); + + this.chunks = []; + + if (!this.pattern.length) { + return + } + + const addChunk = (pattern, startIndex) => { + this.chunks.push({ + pattern, + alphabet: createPatternAlphabet(pattern), + startIndex + }); + }; + + const len = this.pattern.length; + + if (len > MAX_BITS) { + let i = 0; + const remainder = len % MAX_BITS; + const end = len - remainder; + + while (i < end) { + addChunk(this.pattern.substr(i, MAX_BITS), i); + i += MAX_BITS; + } + + if (remainder) { + const startIndex = len - MAX_BITS; + addChunk(this.pattern.substr(startIndex), startIndex); + } + } else { + addChunk(this.pattern, 0); + } + } + + searchIn(text) { + const { isCaseSensitive, includeMatches } = this.options; + + if (!isCaseSensitive) { + text = text.toLowerCase(); + } + + // Exact match + if (this.pattern === text) { + let result = { + isMatch: true, + score: 0 + }; + + if (includeMatches) { + result.indices = [[0, text.length - 1]]; + } + + return result + } + + // Otherwise, use Bitap algorithm + const { + location, + distance, + threshold, + findAllMatches, + minMatchCharLength, + ignoreLocation + } = this.options; + + let allIndices = []; + let totalScore = 0; + let hasMatches = false; + + this.chunks.forEach(({ pattern, alphabet, startIndex }) => { + const { isMatch, score, indices } = search(text, pattern, alphabet, { + location: location + startIndex, + distance, + threshold, + findAllMatches, + minMatchCharLength, + includeMatches, + ignoreLocation + }); + + if (isMatch) { + hasMatches = true; + } + + totalScore += score; + + if (isMatch && indices) { + allIndices = [...allIndices, ...indices]; + } + }); + + let result = { + isMatch: hasMatches, + score: hasMatches ? totalScore / this.chunks.length : 1 + }; + + if (hasMatches && includeMatches) { + result.indices = allIndices; + } + + return result + } +} + +class BaseMatch { + constructor(pattern) { + this.pattern = pattern; + } + static isMultiMatch(pattern) { + return getMatch(pattern, this.multiRegex) + } + static isSingleMatch(pattern) { + return getMatch(pattern, this.singleRegex) + } + search(/*text*/) { } +} + +function getMatch(pattern, exp) { + const matches = pattern.match(exp); + return matches ? matches[1] : null +} + +// Token: 'file + +class ExactMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'exact' + } + static get multiRegex() { + return /^="(.*)"$/ + } + static get singleRegex() { + return /^=(.*)$/ + } + search(text) { + const isMatch = text === this.pattern; + + return { + isMatch, + score: isMatch ? 0 : 1, + indices: [0, this.pattern.length - 1] + } + } +} + +// Token: !fire + +class InverseExactMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'inverse-exact' + } + static get multiRegex() { + return /^!"(.*)"$/ + } + static get singleRegex() { + return /^!(.*)$/ + } + search(text) { + const index = text.indexOf(this.pattern); + const isMatch = index === -1; + + return { + isMatch, + score: isMatch ? 0 : 1, + indices: [0, text.length - 1] + } + } +} + +// Token: ^file + +class PrefixExactMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'prefix-exact' + } + static get multiRegex() { + return /^\^"(.*)"$/ + } + static get singleRegex() { + return /^\^(.*)$/ + } + search(text) { + const isMatch = text.startsWith(this.pattern); + + return { + isMatch, + score: isMatch ? 0 : 1, + indices: [0, this.pattern.length - 1] + } + } +} + +// Token: !^fire + +class InversePrefixExactMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'inverse-prefix-exact' + } + static get multiRegex() { + return /^!\^"(.*)"$/ + } + static get singleRegex() { + return /^!\^(.*)$/ + } + search(text) { + const isMatch = !text.startsWith(this.pattern); + + return { + isMatch, + score: isMatch ? 0 : 1, + indices: [0, text.length - 1] + } + } +} + +// Token: .file$ + +class SuffixExactMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'suffix-exact' + } + static get multiRegex() { + return /^"(.*)"\$$/ + } + static get singleRegex() { + return /^(.*)\$$/ + } + search(text) { + const isMatch = text.endsWith(this.pattern); + + return { + isMatch, + score: isMatch ? 0 : 1, + indices: [text.length - this.pattern.length, text.length - 1] + } + } +} + +// Token: !.file$ + +class InverseSuffixExactMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'inverse-suffix-exact' + } + static get multiRegex() { + return /^!"(.*)"\$$/ + } + static get singleRegex() { + return /^!(.*)\$$/ + } + search(text) { + const isMatch = !text.endsWith(this.pattern); + return { + isMatch, + score: isMatch ? 0 : 1, + indices: [0, text.length - 1] + } + } +} + +class FuzzyMatch extends BaseMatch { + constructor( + pattern, + { + location = Config.location, + threshold = Config.threshold, + distance = Config.distance, + includeMatches = Config.includeMatches, + findAllMatches = Config.findAllMatches, + minMatchCharLength = Config.minMatchCharLength, + isCaseSensitive = Config.isCaseSensitive, + ignoreLocation = Config.ignoreLocation + } = {} + ) { + super(pattern); + this._bitapSearch = new BitapSearch(pattern, { + location, + threshold, + distance, + includeMatches, + findAllMatches, + minMatchCharLength, + isCaseSensitive, + ignoreLocation + }); + } + static get type() { + return 'fuzzy' + } + static get multiRegex() { + return /^"(.*)"$/ + } + static get singleRegex() { + return /^(.*)$/ + } + search(text) { + return this._bitapSearch.searchIn(text) + } +} + +// Token: 'file + +class IncludeMatch extends BaseMatch { + constructor(pattern) { + super(pattern); + } + static get type() { + return 'include' + } + static get multiRegex() { + return /^'"(.*)"$/ + } + static get singleRegex() { + return /^'(.*)$/ + } + search(text) { + let location = 0; + let index; + + const indices = []; + const patternLen = this.pattern.length; + + // Get all exact matches + while ((index = text.indexOf(this.pattern, location)) > -1) { + location = index + patternLen; + indices.push([index, location - 1]); + } + + const isMatch = !!indices.length; + + return { + isMatch, + score: isMatch ? 0 : 1, + indices + } + } +} + +// ❗Order is important. DO NOT CHANGE. +const searchers = [ + ExactMatch, + IncludeMatch, + PrefixExactMatch, + InversePrefixExactMatch, + InverseSuffixExactMatch, + SuffixExactMatch, + InverseExactMatch, + FuzzyMatch +]; + +const searchersLen = searchers.length; + +// Regex to split by spaces, but keep anything in quotes together +const SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/; +const OR_TOKEN = '|'; + +// Return a 2D array representation of the query, for simpler parsing. +// Example: +// "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]] +function parseQuery(pattern, options = {}) { + return pattern.split(OR_TOKEN).map((item) => { + let query = item + .trim() + .split(SPACE_RE) + .filter((item) => item && !!item.trim()); + + let results = []; + for (let i = 0, len = query.length; i < len; i += 1) { + const queryItem = query[i]; + + // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`) + let found = false; + let idx = -1; + while (!found && ++idx < searchersLen) { + const searcher = searchers[idx]; + let token = searcher.isMultiMatch(queryItem); + if (token) { + results.push(new searcher(token, options)); + found = true; + } + } + + if (found) { + continue + } + + // 2. Handle single query matches (i.e, once that are *not* quoted) + idx = -1; + while (++idx < searchersLen) { + const searcher = searchers[idx]; + let token = searcher.isSingleMatch(queryItem); + if (token) { + results.push(new searcher(token, options)); + break + } + } + } + + return results + }) +} + +// These extended matchers can return an array of matches, as opposed +// to a singl match +const MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]); + +/** + * Command-like searching + * ====================== + * + * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`, + * search in a given text. + * + * Search syntax: + * + * | Token | Match type | Description | + * | ----------- | -------------------------- | -------------------------------------- | + * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` | + * | `=scheme` | exact-match | Items that are `scheme` | + * | `'python` | include-match | Items that include `python` | + * | `!ruby` | inverse-exact-match | Items that do not include `ruby` | + * | `^java` | prefix-exact-match | Items that start with `java` | + * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` | + * | `.js$` | suffix-exact-match | Items that end with `.js` | + * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` | + * + * A single pipe character acts as an OR operator. For example, the following + * query matches entries that start with `core` and end with either`go`, `rb`, + * or`py`. + * + * ``` + * ^core go$ | rb$ | py$ + * ``` + */ +class ExtendedSearch { + constructor( + pattern, + { + isCaseSensitive = Config.isCaseSensitive, + includeMatches = Config.includeMatches, + minMatchCharLength = Config.minMatchCharLength, + ignoreLocation = Config.ignoreLocation, + findAllMatches = Config.findAllMatches, + location = Config.location, + threshold = Config.threshold, + distance = Config.distance + } = {} + ) { + this.query = null; + this.options = { + isCaseSensitive, + includeMatches, + minMatchCharLength, + findAllMatches, + ignoreLocation, + location, + threshold, + distance + }; + + this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); + this.query = parseQuery(this.pattern, this.options); + } + + static condition(_, options) { + return options.useExtendedSearch + } + + searchIn(text) { + const query = this.query; + + if (!query) { + return { + isMatch: false, + score: 1 + } + } + + const { includeMatches, isCaseSensitive } = this.options; + + text = isCaseSensitive ? text : text.toLowerCase(); + + let numMatches = 0; + let allIndices = []; + let totalScore = 0; + + // ORs + for (let i = 0, qLen = query.length; i < qLen; i += 1) { + const searchers = query[i]; + + // Reset indices + allIndices.length = 0; + numMatches = 0; + + // ANDs + for (let j = 0, pLen = searchers.length; j < pLen; j += 1) { + const searcher = searchers[j]; + const { isMatch, indices, score } = searcher.search(text); + + if (isMatch) { + numMatches += 1; + totalScore += score; + if (includeMatches) { + const type = searcher.constructor.type; + if (MultiMatchSet.has(type)) { + allIndices = [...allIndices, ...indices]; + } else { + allIndices.push(indices); + } + } + } else { + totalScore = 0; + numMatches = 0; + allIndices.length = 0; + break + } + } + + // OR condition, so if TRUE, return + if (numMatches) { + let result = { + isMatch: true, + score: totalScore / numMatches + }; + + if (includeMatches) { + result.indices = allIndices; + } + + return result + } + } + + // Nothing was matched + return { + isMatch: false, + score: 1 + } + } +} + +const registeredSearchers = []; + +function register(...args) { + registeredSearchers.push(...args); +} + +function createSearcher(pattern, options) { + for (let i = 0, len = registeredSearchers.length; i < len; i += 1) { + let searcherClass = registeredSearchers[i]; + if (searcherClass.condition(pattern, options)) { + return new searcherClass(pattern, options) + } + } + + return new BitapSearch(pattern, options) +} + +const LogicalOperator = { + AND: '$and', + OR: '$or' +}; + +const KeyType = { + PATH: '$path', + PATTERN: '$val' +}; + +const isExpression = (query) => + !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); + +const isPath = (query) => !!query[KeyType.PATH]; + +const isLeaf = (query) => + !isArray(query) && isObject(query) && !isExpression(query); + +const convertToExplicit = (query) => ({ + [LogicalOperator.AND]: Object.keys(query).map((key) => ({ + [key]: query[key] + })) +}); + +// When `auto` is `true`, the parse function will infer and initialize and add +// the appropriate `Searcher` instance +function parse(query, options, { auto = true } = {}) { + const next = (query) => { + let keys = Object.keys(query); + + const isQueryPath = isPath(query); + + if (!isQueryPath && keys.length > 1 && !isExpression(query)) { + return next(convertToExplicit(query)) + } + + if (isLeaf(query)) { + const key = isQueryPath ? query[KeyType.PATH] : keys[0]; + + const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; + + if (!isString(pattern)) { + throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)) + } + + const obj = { + keyId: createKeyId(key), + pattern + }; + + if (auto) { + obj.searcher = createSearcher(pattern, options); + } + + return obj + } + + let node = { + children: [], + operator: keys[0] + }; + + keys.forEach((key) => { + const value = query[key]; + + if (isArray(value)) { + value.forEach((item) => { + node.children.push(next(item)); + }); + } + }); + + return node + }; + + if (!isExpression(query)) { + query = convertToExplicit(query); + } + + return next(query) +} + +// Practical scoring function +function computeScore( + results, + { ignoreFieldNorm = Config.ignoreFieldNorm } +) { + results.forEach((result) => { + let totalScore = 1; + + result.matches.forEach(({ key, norm, score }) => { + const weight = key ? key.weight : null; + + totalScore *= Math.pow( + score === 0 && weight ? Number.EPSILON : score, + (weight || 1) * (ignoreFieldNorm ? 1 : norm) + ); + }); + + result.score = totalScore; + }); +} + +function transformMatches(result, data) { + const matches = result.matches; + data.matches = []; + + if (!isDefined(matches)) { + return + } + + matches.forEach((match) => { + if (!isDefined(match.indices) || !match.indices.length) { + return + } + + const { indices, value } = match; + + let obj = { + indices, + value + }; + + if (match.key) { + obj.key = match.key.src; + } + + if (match.idx > -1) { + obj.refIndex = match.idx; + } + + data.matches.push(obj); + }); +} + +function transformScore(result, data) { + data.score = result.score; +} + +function format( + results, + docs, + { + includeMatches = Config.includeMatches, + includeScore = Config.includeScore + } = {} +) { + const transformers = []; + + if (includeMatches) transformers.push(transformMatches); + if (includeScore) transformers.push(transformScore); + + return results.map((result) => { + const { idx } = result; + + const data = { + item: docs[idx], + refIndex: idx + }; + + if (transformers.length) { + transformers.forEach((transformer) => { + transformer(result, data); + }); + } + + return data + }) +} + +class Fuse { + constructor(docs, options = {}, index) { + this.options = { ...Config, ...options }; + + if ( + this.options.useExtendedSearch && + !true + ) { + throw new Error(EXTENDED_SEARCH_UNAVAILABLE) + } + + this._keyStore = new KeyStore(this.options.keys); + + this.setCollection(docs, index); + } + + setCollection(docs, index) { + this._docs = docs; + + if (index && !(index instanceof FuseIndex)) { + throw new Error(INCORRECT_INDEX_TYPE) + } + + this._myIndex = + index || + createIndex(this.options.keys, this._docs, { + getFn: this.options.getFn, + fieldNormWeight: this.options.fieldNormWeight + }); + } + + add(doc) { + if (!isDefined(doc)) { + return + } + + this._docs.push(doc); + this._myIndex.add(doc); + } + + remove(predicate = (/* doc, idx */) => false) { + const results = []; + + for (let i = 0, len = this._docs.length; i < len; i += 1) { + const doc = this._docs[i]; + if (predicate(doc, i)) { + this.removeAt(i); + i -= 1; + len -= 1; + + results.push(doc); + } + } + + return results + } + + removeAt(idx) { + this._docs.splice(idx, 1); + this._myIndex.removeAt(idx); + } + + getIndex() { + return this._myIndex + } + + search(query, { limit = -1 } = {}) { + const { + includeMatches, + includeScore, + shouldSort, + sortFn, + ignoreFieldNorm + } = this.options; + + let results = isString(query) + ? isString(this._docs[0]) + ? this._searchStringList(query) + : this._searchObjectList(query) + : this._searchLogical(query); + + computeScore(results, { ignoreFieldNorm }); + + if (shouldSort) { + results.sort(sortFn); + } + + if (isNumber(limit) && limit > -1) { + results = results.slice(0, limit); + } + + return format(results, this._docs, { + includeMatches, + includeScore + }) + } + + _searchStringList(query) { + const searcher = createSearcher(query, this.options); + const { records } = this._myIndex; + const results = []; + + // Iterate over every string in the index + records.forEach(({ v: text, i: idx, n: norm }) => { + if (!isDefined(text)) { + return + } + + const { isMatch, score, indices } = searcher.searchIn(text); + + if (isMatch) { + results.push({ + item: text, + idx, + matches: [{ score, value: text, norm, indices }] + }); + } + }); + + return results + } + + _searchLogical(query) { + + const expression = parse(query, this.options); + + const evaluate = (node, item, idx) => { + if (!node.children) { + const { keyId, searcher } = node; + + const matches = this._findMatches({ + key: this._keyStore.get(keyId), + value: this._myIndex.getValueForItemAtKeyId(item, keyId), + searcher + }); + + if (matches && matches.length) { + return [ + { + idx, + item, + matches + } + ] + } + + return [] + } + + const res = []; + for (let i = 0, len = node.children.length; i < len; i += 1) { + const child = node.children[i]; + const result = evaluate(child, item, idx); + if (result.length) { + res.push(...result); + } else if (node.operator === LogicalOperator.AND) { + return [] + } + } + return res + }; + + const records = this._myIndex.records; + const resultMap = {}; + const results = []; + + records.forEach(({ $: item, i: idx }) => { + if (isDefined(item)) { + let expResults = evaluate(expression, item, idx); + + if (expResults.length) { + // Dedupe when adding + if (!resultMap[idx]) { + resultMap[idx] = { idx, item, matches: [] }; + results.push(resultMap[idx]); + } + expResults.forEach(({ matches }) => { + resultMap[idx].matches.push(...matches); + }); + } + } + }); + + return results + } + + _searchObjectList(query) { + const searcher = createSearcher(query, this.options); + const { keys, records } = this._myIndex; + const results = []; + + // List is Array + records.forEach(({ $: item, i: idx }) => { + if (!isDefined(item)) { + return + } + + let matches = []; + + // Iterate over every key (i.e, path), and fetch the value at that key + keys.forEach((key, keyIndex) => { + matches.push( + ...this._findMatches({ + key, + value: item[keyIndex], + searcher + }) + ); + }); + + if (matches.length) { + results.push({ + idx, + item, + matches + }); + } + }); + + return results + } + _findMatches({ key, value, searcher }) { + if (!isDefined(value)) { + return [] + } + + let matches = []; + + if (isArray(value)) { + value.forEach(({ v: text, i: idx, n: norm }) => { + if (!isDefined(text)) { + return + } + + const { isMatch, score, indices } = searcher.searchIn(text); + + if (isMatch) { + matches.push({ + score, + key, + value: text, + idx, + norm, + indices + }); + } + }); + } else { + const { v: text, n: norm } = value; + + const { isMatch, score, indices } = searcher.searchIn(text); + + if (isMatch) { + matches.push({ score, key, value: text, norm, indices }); + } + } + + return matches + } +} + +Fuse.version = '6.6.2'; +Fuse.createIndex = createIndex; +Fuse.parseIndex = parseIndex; +Fuse.config = Config; + +{ + Fuse.parseQuery = parse; +} + +{ + register(ExtendedSearch); +} + +export { Fuse as default }; diff --git a/src/lib/hack.ts b/src/lib/hack.ts index 2e8ad8e..51940cb 100644 --- a/src/lib/hack.ts +++ b/src/lib/hack.ts @@ -1,4 +1,4 @@ -import { NS, Server } from '@ns' +import { NS, Server, AutocompleteData} from '@ns' import {selectAllServerList, ServerInfo} from "./servers" export type Hackability = "hackable" | "rooted" | "impossible" @@ -52,7 +52,9 @@ export const hacks: Hack[] = [ * @param {NS} ns */ export function hackablePorts(ns: NS): number { - return hacks.map(x=>(ns.fileExists(x.file) ? 1 : 0)).reduce((x,y)=>x+y) + function add(x:number,y:number):number{return x+y;} + return hacks.map(x=>(ns.fileExists(x.file) ? 1 : 0) as number) + .reduce(add); } export function isHackableServer(ns: NS, target: Server): boolean{ @@ -65,8 +67,8 @@ export function selectHackableServerList(ns: NS): ServerInfo[]{ }) } -export function hackServer(ns:NS,target:Server); -export function hackServer(ns:NS,target:string); +export function hackServer(ns:NS,target:Server):void; +export function hackServer(ns:NS,target:string):void; export function hackServer(ns:NS,target:string|Server):void{ const hostname = typeof target === "string" ? target : target.hostname hacks.forEach(x=>{ @@ -85,11 +87,11 @@ export async function main(ns: NS):Promise { ns.tprint("run cmd [target]") return } - const target = ns.args[0] - hackServer(target) + const target = String(ns.args[0]); + hackServer(ns,target) } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function autocomplete(data : ServerData, args : string[]) : string[] { +export function autocomplete(data : AutocompleteData, args : string[]) : string[] { return [...data.servers] } \ No newline at end of file diff --git a/src/lib/log.ts b/src/lib/log.ts new file mode 100644 index 0000000..e988a26 --- /dev/null +++ b/src/lib/log.ts @@ -0,0 +1,13 @@ +import {NS} from "@ns" + +export async function logFile(ns:NS,logFilename:string, data: unknown):Promise{ + logFilename = logFilename.replaceAll(" ","-"); + logFilename = `/log/${logFilename}${Date.now()}.txt`; + const loggedData = JSON.stringify(data); + if(ns.fileExists(logFilename)){ + await ns.write(logFilename,"\n"+loggedData,"a"); + } + else { + await ns.write(logFilename,loggedData,"w"); + } +} \ No newline at end of file diff --git a/src/lib/printf.ts b/src/lib/printf.ts new file mode 100644 index 0000000..f99f3c2 --- /dev/null +++ b/src/lib/printf.ts @@ -0,0 +1,778 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +/** + * This implementation is inspired by POSIX and Golang but does not port + * implementation code. */ + +enum State { + PASSTHROUGH, + PERCENT, + POSITIONAL, + PRECISION, + WIDTH, +} + +enum WorP { + WIDTH, + PRECISION, +} + +class Flags { + plus?: boolean; + dash?: boolean; + sharp?: boolean; + space?: boolean; + zero?: boolean; + lessthan?: boolean; + width = -1; + precision = -1; +} + +const min = Math.min; +const UNICODE_REPLACEMENT_CHARACTER = "\ufffd"; +const DEFAULT_PRECISION = 6; +const FLOAT_REGEXP = /(-?)(\d)\.?(\d*)e([+-])(\d+)/; + +enum F { + sign = 1, + mantissa, + fractional, + esign, + exponent, +} + +class Printf { + format: string; + args: unknown[]; + i: number; + + state: State = State.PASSTHROUGH; + verb = ""; + buf = ""; + argNum = 0; + flags: Flags = new Flags(); + + haveSeen: boolean[]; + + // barf, store precision and width errors for later processing ... + tmpError?: string; + + constructor(format: string, ...args: unknown[]) { + this.format = format; + this.args = args; + this.haveSeen = Array.from({ length: args.length }); + this.i = 0; + } + + doPrintf(): string { + for (; this.i < this.format.length; ++this.i) { + const c = this.format[this.i]; + switch (this.state) { + case State.PASSTHROUGH: + if (c === "%") { + this.state = State.PERCENT; + } else { + this.buf += c; + } + break; + case State.PERCENT: + if (c === "%") { + this.buf += c; + this.state = State.PASSTHROUGH; + } else { + this.handleFormat(); + } + break; + default: + throw Error("Should be unreachable, certainly a bug in the lib."); + } + } + // check for unhandled args + let extras = false; + let err = "%!(EXTRA"; + for (let i = 0; i !== this.haveSeen.length; ++i) { + if (!this.haveSeen[i]) { + extras = true; + err += ` '${typeof (this.args[i])}'`; + } + } + err += ")"; + if (extras) { + this.buf += err; + } + return this.buf; + } + + // %[]... + handleFormat(): void { + this.flags = new Flags(); + const flags = this.flags; + for (; this.i < this.format.length; ++this.i) { + const c = this.format[this.i]; + switch (this.state) { + case State.PERCENT: + switch (c) { + case "[": + this.handlePositional(); + this.state = State.POSITIONAL; + break; + case "+": + flags.plus = true; + break; + case "<": + flags.lessthan = true; + break; + case "-": + flags.dash = true; + flags.zero = false; // only left pad zeros, dash takes precedence + break; + case "#": + flags.sharp = true; + break; + case " ": + flags.space = true; + break; + case "0": + // only left pad zeros, dash takes precedence + flags.zero = !flags.dash; + break; + default: + if (("1" <= c && c <= "9") || c === "." || c === "*") { + if (c === ".") { + this.flags.precision = 0; + this.state = State.PRECISION; + this.i++; + } else { + this.state = State.WIDTH; + } + this.handleWidthAndPrecision(flags); + } else { + this.handleVerb(); + return; // always end in verb + } + } // switch c + break; + case State.POSITIONAL: + // TODO(bartlomieju): either a verb or * only verb for now + if (c === "*") { + const worp = this.flags.precision === -1 + ? WorP.WIDTH + : WorP.PRECISION; + this.handleWidthOrPrecisionRef(worp); + this.state = State.PERCENT; + break; + } else { + this.handleVerb(); + return; // always end in verb + } + default: + throw new Error(`Should not be here ${this.state}, library bug!`); + } // switch state + } + } + + /** + * Handle width or precision + * @param wOrP + */ + handleWidthOrPrecisionRef(wOrP: WorP): void { + if (this.argNum >= this.args.length) { + // handle Positional should have already taken care of it... + return; + } + const arg = this.args[this.argNum]; + this.haveSeen[this.argNum] = true; + if (typeof arg === "number") { + switch (wOrP) { + case WorP.WIDTH: + this.flags.width = arg; + break; + default: + this.flags.precision = arg; + } + } else { + const tmp = wOrP === WorP.WIDTH ? "WIDTH" : "PREC"; + this.tmpError = `%!(BAD ${tmp} '${this.args[this.argNum]}')`; + } + this.argNum++; + } + + /** + * Handle width and precision + * @param flags + */ + handleWidthAndPrecision(flags: Flags): void { + const fmt = this.format; + for (; this.i !== this.format.length; ++this.i) { + const c = fmt[this.i]; + switch (this.state) { + case State.WIDTH: + switch (c) { + case ".": + // initialize precision, %9.f -> precision=0 + this.flags.precision = 0; + this.state = State.PRECISION; + break; + case "*": + this.handleWidthOrPrecisionRef(WorP.WIDTH); + // force . or flag at this point + break; + default: { + const val = parseInt(c); + // most likely parseInt does something stupid that makes + // it unusable for this scenario ... + // if we encounter a non (number|*|.) we're done with prec & wid + if (isNaN(val)) { + this.i--; + this.state = State.PERCENT; + return; + } + flags.width = flags.width == -1 ? 0 : flags.width; + flags.width *= 10; + flags.width += val; + } + } // switch c + break; + case State.PRECISION: { + if (c === "*") { + this.handleWidthOrPrecisionRef(WorP.PRECISION); + break; + } + const val = parseInt(c); + if (isNaN(val)) { + // one too far, rewind + this.i--; + this.state = State.PERCENT; + return; + } + flags.precision *= 10; + flags.precision += val; + break; + } + default: + throw new Error("can't be here. bug."); + } // switch state + } + } + + /** Handle positional */ + handlePositional(): void { + if (this.format[this.i] !== "[") { + // sanity only + throw new Error("Can't happen? Bug."); + } + let positional = 0; + const format = this.format; + this.i++; + let err = false; + for (; this.i !== this.format.length; ++this.i) { + if (format[this.i] === "]") { + break; + } + positional *= 10; + const val = parseInt(format[this.i]); + if (isNaN(val)) { + //throw new Error( + // `invalid character in positional: ${format}[${format[this.i]}]` + //); + this.tmpError = "%!(BAD INDEX)"; + err = true; + } + positional += val; + } + if (positional - 1 >= this.args.length) { + this.tmpError = "%!(BAD INDEX)"; + err = true; + } + this.argNum = err ? this.argNum : positional - 1; + return; + } + + /** Handle less than */ + handleLessThan(): string { + // deno-lint-ignore no-explicit-any + const arg = this.args[this.argNum] as any; + if ((arg || {}).constructor.name !== "Array") { + throw new Error(`arg ${arg} is not an array. Todo better error handling`); + } + let str = "[ "; + for (let i = 0; i !== arg.length; ++i) { + if (i !== 0) str += ", "; + str += this._handleVerb(arg[i]); + } + return str + " ]"; + } + + /** Handle verb */ + handleVerb(): void { + const verb = this.format[this.i]; + this.verb = verb; + if (this.tmpError) { + this.buf += this.tmpError; + this.tmpError = undefined; + if (this.argNum < this.haveSeen.length) { + this.haveSeen[this.argNum] = true; // keep track of used args + } + } else if (this.args.length <= this.argNum) { + this.buf += `%!(MISSING '${verb}')`; + } else { + const arg = this.args[this.argNum]; // check out of range + this.haveSeen[this.argNum] = true; // keep track of used args + if (this.flags.lessthan) { + this.buf += this.handleLessThan(); + } else { + this.buf += this._handleVerb(arg); + } + } + this.argNum++; // if there is a further positional, it will reset. + this.state = State.PASSTHROUGH; + } + + // deno-lint-ignore no-explicit-any + _handleVerb(arg: any): string { + switch (this.verb) { + case "t": + return this.pad(arg.toString()); + case "b": + return this.fmtNumber(arg as number, 2); + case "c": + return this.fmtNumberCodePoint(arg as number); + case "d": + return this.fmtNumber(arg as number, 10); + case "o": + return this.fmtNumber(arg as number, 8); + case "x": + return this.fmtHex(arg); + case "X": + return this.fmtHex(arg, true); + case "e": + return this.fmtFloatE(arg as number); + case "E": + return this.fmtFloatE(arg as number, true); + case "f": + case "F": + return this.fmtFloatF(arg as number); + case "g": + return this.fmtFloatG(arg as number); + case "G": + return this.fmtFloatG(arg as number, true); + case "s": + return this.fmtString(arg as string); + case "T": + return this.fmtString(typeof arg); + case "v": + return this.fmtV(arg); + case "j": + return this.fmtJ(arg); + default: + return `%!(BAD VERB '${this.verb}')`; + } + } + + /** + * Pad a string + * @param s text to pad + */ + pad(s: string): string { + const padding = this.flags.zero ? "0" : " "; + + if (this.flags.dash) { + return s.padEnd(this.flags.width, padding); + } + + return s.padStart(this.flags.width, padding); + } + + /** + * Pad a number + * @param nStr + * @param neg + */ + padNum(nStr: string, neg: boolean): string { + let sign: string; + if (neg) { + sign = "-"; + } else if (this.flags.plus || this.flags.space) { + sign = this.flags.plus ? "+" : " "; + } else { + sign = ""; + } + const zero = this.flags.zero; + if (!zero) { + // sign comes in front of padding when padding w/ zero, + // in from of value if padding with spaces. + nStr = sign + nStr; + } + + const pad = zero ? "0" : " "; + const len = zero ? this.flags.width - sign.length : this.flags.width; + + if (this.flags.dash) { + nStr = nStr.padEnd(len, pad); + } else { + nStr = nStr.padStart(len, pad); + } + + if (zero) { + // see above + nStr = sign + nStr; + } + return nStr; + } + + /** + * Format a number + * @param n + * @param radix + * @param upcase + */ + fmtNumber(n: number, radix: number, upcase = false): string { + let num = Math.abs(n).toString(radix); + const prec = this.flags.precision; + if (prec !== -1) { + this.flags.zero = false; + num = n === 0 && prec === 0 ? "" : num; + while (num.length < prec) { + num = "0" + num; + } + } + let prefix = ""; + if (this.flags.sharp) { + switch (radix) { + case 2: + prefix += "0b"; + break; + case 8: + // don't annotate octal 0 with 0... + prefix += num.startsWith("0") ? "" : "0"; + break; + case 16: + prefix += "0x"; + break; + default: + throw new Error("cannot handle base: " + radix); + } + } + // don't add prefix in front of value truncated by precision=0, val=0 + num = num.length === 0 ? num : prefix + num; + if (upcase) { + num = num.toUpperCase(); + } + return this.padNum(num, n < 0); + } + + /** + * Format number with code points + * @param n + */ + fmtNumberCodePoint(n: number): string { + let s = ""; + try { + s = String.fromCodePoint(n); + } catch { + s = UNICODE_REPLACEMENT_CHARACTER; + } + return this.pad(s); + } + + /** + * Format special float + * @param n + */ + fmtFloatSpecial(n: number): string { + // formatting of NaN and Inf are pants-on-head + // stupid and more or less arbitrary. + + if (isNaN(n)) { + this.flags.zero = false; + return this.padNum("NaN", false); + } + if (n === Number.POSITIVE_INFINITY) { + this.flags.zero = false; + this.flags.plus = true; + return this.padNum("Inf", false); + } + if (n === Number.NEGATIVE_INFINITY) { + this.flags.zero = false; + return this.padNum("Inf", true); + } + return ""; + } + + /** + * Round fraction to precision + * @param fractional + * @param precision + * @returns tuple of fractional and round + */ + roundFractionToPrecision( + fractional: string, + precision: number, + ): [string, boolean] { + let round = false; + if (fractional.length > precision) { + fractional = "1" + fractional; // prepend a 1 in case of leading 0 + let tmp = parseInt(fractional.substr(0, precision + 2)) / 10; + tmp = Math.round(tmp); + fractional = Math.floor(tmp).toString(); + round = fractional[0] === "2"; + fractional = fractional.substr(1); // remove extra 1 + } else { + while (fractional.length < precision) { + fractional += "0"; + } + } + return [fractional, round]; + } + + /** + * Format float E + * @param n + * @param upcase + */ + fmtFloatE(n: number, upcase = false): string { + const special = this.fmtFloatSpecial(n); + if (special !== "") { + return special; + } + + const m = n.toExponential().match(FLOAT_REGEXP); + if (!m) { + throw Error("can't happen, bug"); + } + let fractional = m[F.fractional]; + const precision = this.flags.precision !== -1 + ? this.flags.precision + : DEFAULT_PRECISION; + let rounding = false; + [fractional, rounding] = this.roundFractionToPrecision( + fractional, + precision, + ); + + let e = m[F.exponent]; + let esign = m[F.esign]; + // scientific notation output with exponent padded to minlen 2 + let mantissa = parseInt(m[F.mantissa]); + if (rounding) { + mantissa += 1; + if (10 <= mantissa) { + mantissa = 1; + const r = parseInt(esign + e) + 1; + e = r.toString(); + esign = r < 0 ? "-" : "+"; + } + } + e = e.length == 1 ? "0" + e : e; + const val = `${mantissa}.${fractional}${upcase ? "E" : "e"}${esign}${e}`; + return this.padNum(val, n < 0); + } + + /** + * Format float F + * @param n + */ + fmtFloatF(n: number): string { + const special = this.fmtFloatSpecial(n); + if (special !== "") { + return special; + } + + // stupid helper that turns a number into a (potentially) + // VERY long string. + function expandNumber(n: number): string { + if (Number.isSafeInteger(n)) { + return n.toString() + "."; + } + + const t = n.toExponential().split("e"); + let m = t[0].replace(".", ""); + const e = parseInt(t[1]); + if (e < 0) { + let nStr = "0."; + for (let i = 0; i !== Math.abs(e) - 1; ++i) { + nStr += "0"; + } + return (nStr += m); + } else { + const splIdx = e + 1; + while (m.length < splIdx) { + m += "0"; + } + return m.substr(0, splIdx) + "." + m.substr(splIdx); + } + } + // avoiding sign makes padding easier + const val = expandNumber(Math.abs(n)) as string; + const arr = val.split("."); + let dig = arr[0]; + let fractional = arr[1]; + + const precision = this.flags.precision !== -1 + ? this.flags.precision + : DEFAULT_PRECISION; + let round = false; + [fractional, round] = this.roundFractionToPrecision(fractional, precision); + if (round) { + dig = (parseInt(dig) + 1).toString(); + } + return this.padNum(`${dig}.${fractional}`, n < 0); + } + + /** + * Format float G + * @param n + * @param upcase + */ + fmtFloatG(n: number, upcase = false): string { + const special = this.fmtFloatSpecial(n); + if (special !== "") { + return special; + } + + // The double argument representing a floating-point number shall be + // converted in the style f or e (or in the style F or E in + // the case of a G conversion specifier), depending on the + // value converted and the precision. Let P equal the + // precision if non-zero, 6 if the precision is omitted, or 1 + // if the precision is zero. Then, if a conversion with style E would + // have an exponent of X: + + // - If P > X>=-4, the conversion shall be with style f (or F ) + // and precision P -( X+1). + + // - Otherwise, the conversion shall be with style e (or E ) + // and precision P -1. + + // Finally, unless the '#' flag is used, any trailing zeros shall be + // removed from the fractional portion of the result and the + // decimal-point character shall be removed if there is no + // fractional portion remaining. + + // A double argument representing an infinity or NaN shall be + // converted in the style of an f or F conversion specifier. + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html + + let P = this.flags.precision !== -1 + ? this.flags.precision + : DEFAULT_PRECISION; + P = P === 0 ? 1 : P; + + const m = n.toExponential().match(FLOAT_REGEXP); + if (!m) { + throw Error("can't happen"); + } + + const X = parseInt(m[F.exponent]) * (m[F.esign] === "-" ? -1 : 1); + let nStr = ""; + if (P > X && X >= -4) { + this.flags.precision = P - (X + 1); + nStr = this.fmtFloatF(n); + if (!this.flags.sharp) { + nStr = nStr.replace(/\.?0*$/, ""); + } + } else { + this.flags.precision = P - 1; + nStr = this.fmtFloatE(n); + if (!this.flags.sharp) { + nStr = nStr.replace(/\.?0*e/, upcase ? "E" : "e"); + } + } + return nStr; + } + + /** + * Format string + * @param s + */ + fmtString(s: string): string { + if (this.flags.precision !== -1) { + s = s.substr(0, this.flags.precision); + } + return this.pad(s); + } + + /** + * Format hex + * @param val + * @param upper + */ + fmtHex(val: string | number, upper = false): string { + // allow others types ? + switch (typeof val) { + case "number": + return this.fmtNumber(val as number, 16, upper); + case "string": { + const sharp = this.flags.sharp && val.length !== 0; + let hex = sharp ? "0x" : ""; + const prec = this.flags.precision; + const end = prec !== -1 ? min(prec, val.length) : val.length; + for (let i = 0; i !== end; ++i) { + if (i !== 0 && this.flags.space) { + hex += sharp ? " 0x" : " "; + } + // TODO(bartlomieju): for now only taking into account the + // lower half of the codePoint, ie. as if a string + // is a list of 8bit values instead of UCS2 runes + const c = (val.charCodeAt(i) & 0xff).toString(16); + hex += c.length === 1 ? `0${c}` : c; + } + if (upper) { + hex = hex.toUpperCase(); + } + return this.pad(hex); + } + default: + throw new Error( + "currently only number and string are implemented for hex", + ); + } + } + + /** + * Format value + * @param val + */ + fmtV(val: Record): string { + if (this.flags.sharp) { + const options = this.flags.precision !== -1 + ? { depth: this.flags.precision } + : {}; + return this.pad(val.toString()); + } else { + const p = this.flags.precision; + return p === -1 ? val.toString() : val.toString().substr(0, p); + } + } + + /** + * Format JSON + * @param val + */ + fmtJ(val: unknown): string { + return JSON.stringify(val); + } +} + +/** + * Converts and format a variable number of `args` as is specified by `format`. + * `sprintf` returns the formatted string. + * + * @param format + * @param args + */ +export function sprintf(format: string, ...args: unknown[]): string { + const printf = new Printf(format, ...args); + return printf.doPrintf(); +} + +import {NS} from "@ns"; +/** + * Converts and format a variable number of `args` as is specified by `format`. + * `printf` writes the formatted string to standard output. + * @param format + * @param args + */ +export function printf(ns: NS, format: string, ...args: unknown[]): void { + const s = sprintf(format, ...args); + ns.tprint(s); +} diff --git a/src/lib/servers.ts b/src/lib/servers.ts index 0b29b2f..21e5e2c 100644 --- a/src/lib/servers.ts +++ b/src/lib/servers.ts @@ -8,8 +8,8 @@ export async function main(ns: NS): Promise { ns.tprint("run cmd [filePath]") ns.exit() } - const filePath = ns.args[0] - const collectedData = selectAllServerList() + const filePath = String(ns.args[0]) + const collectedData = selectAllServerList(ns) const buf = JSON.stringify(collectedData, undefined, 2) await ns.write(filePath, buf, "w") } @@ -24,14 +24,15 @@ export interface ServerInfo extends Server{ */ export function visitAllServerList(ns: NS, func:(data: ServerInfo)=>void):void { /** @type {string[]} */ - const queue = ["home"] + const queue: string[] = ["home"] /** @type {Set} */ const visited = new Set() //breadth first search while (queue.length > 0) { const vHost = queue.pop() - const data = getServersInfo(ns, vHost) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const data = getServersInfo(ns, vHost!) func(data) diff --git a/src/lib/solve/algorithmicTrader.ts b/src/lib/solve/algorithmicTrader.ts new file mode 100644 index 0000000..52fdbbc --- /dev/null +++ b/src/lib/solve/algorithmicTrader.ts @@ -0,0 +1,117 @@ +export function algorithmicTradeISolve(arr:number[]):number{ + const localMax = arr.map((v,i,ar)=>{ + return Math.max(...ar.slice(i)) - v; + }); + //console.log(localMax) + return Math.max(...localMax, 0); +} + +//console.log(algorithmicTrade1Solve([130,188,29,178,176,54,157,177,151,184,105,22,185,165,151,80,58,25])) +//163 + + +export function algorithmicTradeIISolve(arr:number[]):number{ + const memo: number[][] = (new Array(arr.length)).fill([]) + .map(_=>new Array(arr.length).fill(-1)); + const v = solve(arr,0,0); + return v; + function solve(arr:number[],i: number,sellChance: number):number{ + if(arr.length == i){ + return 0; + } + if(memo[i][sellChance] >= 0){ + return memo[i][sellChance]; + } + const result = []; + if(sellChance == 1){ + const sell = arr[i]; + result.push(sell + solve(arr,i+1,0)); + } + else { + const buy = arr[i]; + result.push(-buy + solve(arr,i+1, 1)); + } + result.push(solve(arr,i+1,sellChance)); + + const v = Math.max(...result); + memo[i][sellChance] = v; + return v; + } +} +//console.log(algorithmicTrade2Solve([17,172,46,80,159,16,159,182,60,27,114,75,176,106,5,30,130,87,22])) +//747 + +export function algorithmicTradeIIISolve(arr:number[]):number{ + const memo: number[][] = (new Array(arr.length)).fill([]) + .map(_=>new Array(5).fill(-1)); + const v = solve(arr,0,4); + console.log(memo) + return v; + function solve(arr:number[],i: number,transaction: number):number{ + if(arr.length == i){ + return 0; + } + if(transaction == 0){ + return 0; + } + if(memo[i][transaction] >= 0){ + return memo[i][transaction]; + } + const result = []; + const isBuy = transaction % 2 === 0; + if(isBuy){ + const buy = arr[i]; + result.push(-buy + solve(arr,i+1, transaction - 1)); + } + else { + const sell = arr[i]; + result.push(sell + solve(arr,i+1, transaction - 1)); + } + result.push(solve(arr,i+1,transaction)); + + const v = Math.max(...result); + memo[i][transaction] = v; + return v; + } +} + +//console.log(algorithmicTradeIIISolve([25,166,22,187,140,184,118,71,36,43,127,68,94,133,141])) +//306 + +export function algorithmicTradeIVSolve([transaction,arr]:[number,number[]]):number{ + const memo: number[][] = (new Array(arr.length)).fill([]) + .map(_=>new Array(transaction * 2 + 4).fill(-1)); + const v = solve(arr,0,transaction * 2); + console.log(memo) + return v; + function solve(arr:number[],i: number,transaction: number):number{ + if(arr.length == i){ + return 0; + } + if(transaction == 0){ + return 0; + } + if(memo[i][transaction - 1] >= 0){ + return memo[i][transaction - 1]; + } + const result = []; + const isBuy = transaction % 2 === 0; + if(isBuy){ + const buy = arr[i]; + result.push(-buy + solve(arr,i+1, transaction - 1)); + } + else { + const sell = arr[i]; + result.push(sell + solve(arr,i+1, transaction - 1)); + } + result.push(solve(arr,i+1,transaction)); + + const v = Math.max(...result); + memo[i][transaction - 1] = v; + return v; + } +} + +//const v = algorithmicTradeIVSolve([7, [86,97,78,46,113,13,79,42,153,96,93,116,64,190,5,76,66,121,149,177,56,33,75,107,99,106,117]]) +//console.log(v); +//649 \ No newline at end of file diff --git a/src/lib/solve/arrayJump.ts b/src/lib/solve/arrayJump.ts new file mode 100644 index 0000000..08a97c8 --- /dev/null +++ b/src/lib/solve/arrayJump.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +//Array Jumping Game + +export function isJumpPossible(arr:number[]){ + let possible = 0; + for (let i = 0; i <= possible && i < arr.length; i++) { + const element = arr[i]; + possible = Math.max(possible,i+element); + if(possible >= arr.length - 1) return true; + } + return false; +} + +export function arrayJumpSolve(arr:number[]): number{ + return isJumpPossible(arr) ? 1 : 0; +} + +export const testcases:Array<[number[],string]> = [ + [[5,4,7,0,0,0,0],"1"], + [[0,0,0,5,7],"0"], + [[1,1,1,1],"1"], + [[3,0,0,1],"1"], + [[2,0,1,0],"1"], + [[2,0,1,0,0],"0"] +] + +//for (const [data,ans] of testcases) { +// console.log(solve(data), ans); +//} \ No newline at end of file diff --git a/src/lib/solve/arrayJump2.ts b/src/lib/solve/arrayJump2.ts new file mode 100644 index 0000000..d9e175b --- /dev/null +++ b/src/lib/solve/arrayJump2.ts @@ -0,0 +1,28 @@ +export function arrayJump2(arr:number[]): number{ + const d = new Array(arr.length).fill(Infinity); + d[0] = 0; + for (let i = 0; i < arr.length; i++) { + const element = arr[i]; + for (let j = i+1; j <= element+i && j < arr.length; j++) { + d[j] = Math.min(d[j],d[i]+1); + } + } + console.log(d); + return d[arr.length - 1]; +} + +export function arrayJump2Solve(arr:number[]): number{ + return arrayJump2(arr); +} + +export const testcases: Array<[number[],number]> = [ + [[1,4,2,5,7,2,2,2,4,4,5,5,3,6,2,6,4,4,1,3], 5], + [[1,1,1,1],3], + [[3,1,1,1],1], +] + +//for (const testcase of testcases) { +// const [data,ans] = testcase; +// const step = arrayJump2(data); +// console.log(step,ans) +//} \ No newline at end of file diff --git a/src/lib/solve/compression.ts b/src/lib/solve/compression.ts new file mode 100644 index 0000000..34bb0fe --- /dev/null +++ b/src/lib/solve/compression.ts @@ -0,0 +1,154 @@ +/** + * Compression I: RLE Compression + * Run-length encoding (RLE) is a data compression technique which encodes data as + * a series of runs of a repeated single character. Runs are encoded as a length, + * followed by the character itself. Lengths are encoded as a single ASCII digit; + * runs of 10 characters or more are encoded by splitting them into multiple runs. + * + * You are given the following input string: + * iiiiiii225RHHuu2222fddgxxxxxxxxxxxxVVVVVVVVVVVVVooooNNDDDDDDDDDGGgqqpSSSy3FFFFFF + * Encode it using run-length encoding with the minimum possible output length. + * + * Examples: + * aaaaabccc -> 5a1b3c + * aAaAaA -> 1a1A1a1A1a1A + * 111112333 -> 511233 + * zzzzzzzzzzzzzzzzzzz -> 9z9z1z (or 9z8z2z, etc.) + */ + +function matchLength(str: string, start: number): number { + const char = str.charAt(start); + let i; + for (i = 0; start + i < str.length; i++) { + const element = str.charAt(i + start); + if (element !== char) { + return i; + } + } + return i; +} + +export function* RLECompression(str: string): Generator<[string, number]> { + let i = 0; + while (i < str.length) { + const char = str[i]; + let v = matchLength(str, i); + while (v > 9) { + yield [char, 9]; + v -= 9; + i += 9; + } + yield [char, v]; + i += v; + } + return; +} + +export function RLECompressionSolve(data: string): string[] { + return [[...RLECompression(data)].map(([ch, v]) => `${v}${ch}`).join("")] +} + +//const RLETestCase = [ +//["aaaaabccc", "5a1b3c"], +//["aAaAaA", "1a1A1a1A1a1A"], +//["111112333", "511233"], +//["zzzzzzzzzzzzzzzzzzz", "9z9z1z"], +//] +//for (const [data,ans] of RLETestCase) { +// const predict = [...RLECompression(data)].map(([ch,v])=>`${v}${ch}`).join("") +// console.log(predict,ans); +//} + +export function decompressLZSolve(data: string): string[] { + return [decompressLZ(data)]; +} + +// code from +// https://github.com/mirkoconsiglio/Bitburner-scripts/blob/master/contracts/contractor.js +// +function decompressLZ(str: string): string { + let decoded = '', type = 0, len, ref, pos, i = 0, j; + while (i < str.length) { + if (i > 0) type ^= 1; + len = parseInt(str[i]); + ref = parseInt(str[++i]); + if (len === 0) continue; + if (!isNaN(ref) && type === 1) { + i++; + for (j = 0; j < len; j++) decoded += decoded[decoded.length - ref]; + } else { + pos = i; + for (; i < len + pos; i++) decoded += str[i]; + } + } + return decoded; +} +export function compressLZSolve(str: string): string[] { + return [compressLZ(str)]; +} + +export function compressLZ(str: string): string { + // state [i][j] contains a backreference of offset i and length j + let cur_state = Array.from(Array(10), _ => Array(10)), new_state, tmp_state, result; + cur_state[0][1] = ''; // initial state is a literal of length 1 + for (let i = 1; i < str.length; i++) { + new_state = Array.from(Array(10), _ => Array(10)); + const c = str[i]; + // handle literals + for (let len = 1; len <= 9; len++) { + const input = cur_state[0][len]; + if (input === undefined) continue; + if (len < 9) set(new_state, 0, len + 1, input); // extend current literal + else set(new_state, 0, 1, input + '9' + str.substring(i - 9, i) + '0'); // start new literal + for (let offset = 1; offset <= Math.min(9, i); offset++) { // start new backreference + if (str[i - offset] === c) set(new_state, offset, 1, input + len + str.substring(i - len, i)); + } + } + // handle backreferences + for (let offset = 1; offset <= 9; offset++) { + for (let len = 1; len <= 9; len++) { + const input = cur_state[offset][len]; + if (input === undefined) continue; + if (str[i - offset] === c) { + if (len < 9) set(new_state, offset, len + 1, input); // extend current backreference + else set(new_state, offset, 1, input + '9' + offset + '0'); // start new backreference + } + set(new_state, 0, 1, input + len + offset); // start new literal + // end current backreference and start new backreference + for (let new_offset = 1; new_offset <= Math.min(9, i); new_offset++) { + if (str[i - new_offset] === c) set(new_state, new_offset, 1, input + len + offset + '0'); + } + } + } + tmp_state = new_state; + new_state = cur_state; + cur_state = tmp_state; + } + for (let len = 1; len <= 9; len++) { + let input = cur_state[0][len]; + if (input === undefined) continue; + input += len + str.substring(str.length - len, str.length); + // noinspection JSUnusedAssignment + if (result === undefined || input.length < result.length) result = input; + } + for (let offset = 1; offset <= 9; offset++) { + for (let len = 1; len <= 9; len++) { + let input = cur_state[offset][len]; + if (input === undefined) continue; + input += len + '' + offset; + if (result === undefined || input.length < result.length) result = input; + } + } + return result ?? ''; + +/** + * + * @param {string[][]} state + * @param {number} i + * @param {number} j + * @param {string} str + */ + function set(state: string[][], i:number, j:number, str:string): void { + if (state[i][j] === undefined || str.length < state[i][j].length) state[i][j] = str; + } +} \ No newline at end of file diff --git a/src/lib/solve/findAllMathExpr.ts b/src/lib/solve/findAllMathExpr.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/solve/graph2color.ts b/src/lib/solve/graph2color.ts new file mode 100644 index 0000000..aec5e1d --- /dev/null +++ b/src/lib/solve/graph2color.ts @@ -0,0 +1,82 @@ +// +// You are given the following data, representing a graph: +// [6,[[0,3],[0,4],[4,5],[0,1],[1,2],[1,4]]] +// Note that "graph", as used here, refers to the field of graph theory, +// and has no relation to statistics or plotting. The first element of +// the data represents the number of vertices in the graph. Each vertex +// is a unique number between 0 and 5. The next element of the data represents +// the edges of the graph. Two vertices u,v in a graph are said to be adjacent +// if there exists an edge [u,v]. Note that an edge [u,v] is the same as +// an edge [v,u], as order does not matter. You must construct a 2-coloring +// of the graph, meaning that you have to assign each vertex in the graph +// a "color", either 0 or 1, such that no two adjacent vertices have the +// same color. Submit your answer in the form of an array, where element i +// represents the color of vertex i. If it is impossible to construct a +// 2-coloring of the given graph, instead submit an empty array. +// + +/** + * input + */ + type Input = [number, + Array<[number,number]> +]; + +export function graph2coloring([nVertex,edges]:Input):number[]{ + const colors = new Array(nVertex).fill(null); + const queue = [0]; + colors[0] = false; + + while(queue.length > 0){ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const elem = queue.pop()!; + const neighbors = getNeighborhood(elem); + const chcolor = !colors[elem]; + for (const v of neighbors) { + //propagate color; + if(colors[v] === null){ + colors[v] = chcolor; + queue.push(v); + } + else if(colors[v] == !chcolor){ + return []; + } + } + } + return colors.map(x=> x ? 1 : 0); + function getNeighborhood(i: number): number[]{ + return edges.filter(([a,b])=> a==i || b==i) + .map(([a,b])=> a == i ? b : a); + } +} +export function graph2coloringSolve(input:Input):string[]{ + return graph2coloring(input).map(x=>x.toString()); +} +export type TestCase = [ + Input, + number[] +] + +export const testcases:TestCase[] = +[ + [ + [4, [[0, 2], [0, 3], [1, 2], [1, 3]]], + [0, 0, 1, 1] + ], + [ + [3, [[0, 1], [0, 2], [1, 2]]], + [] + ], + [ + [6,[[0,3],[0,4],[4,5],[0,1],[1,2],[1,4]]], + [] + ], + [ + [9,[[1,4],[3,6],[5,8],[0,1],[7,8],[2,8],[1,8],[2,6],[1,2],[4,6],[4,8]]], + [] + ] +] + +//for (const [data,ans] of testcases) { +// console.log(graph2coloringSolve(data), ans) +//} \ No newline at end of file diff --git a/src/lib/solve/largestPrime.ts b/src/lib/solve/largestPrime.ts new file mode 100644 index 0000000..5770e2d --- /dev/null +++ b/src/lib/solve/largestPrime.ts @@ -0,0 +1,278 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +const primeTable = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, + 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, + 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, + 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, + 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, + 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, + 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, + 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, + 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, + 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, + 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, + 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, + 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, + 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, + 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, + 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, + 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, + 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, + 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, + 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, + 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, + 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, + 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, + 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, + 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, + 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, + 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, + 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, + 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, + 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, + 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, + 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, + 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, + 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, + 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, + 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, + 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, + 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, + 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, + 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, + 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, + 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, + 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, + 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, + 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, + 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, + 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, + 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, + 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, + 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, + 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, + 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, + 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, + 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, + 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, + 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, + 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, + 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, + 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, + 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, + 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, + 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, + 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, + 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, + 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, + 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, + 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, + 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, + 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, + 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, + 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, + 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, + 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, + 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, + 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, + 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, + 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, + 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, + 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, + 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, + 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, + 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, + 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, + 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, + 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, + 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, + 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, + 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, + 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, + 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, + 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, + 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, + 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007 +] + +function getNthPrime(i: number) { + if (i < primeTable.length) { + return primeTable[i]; + } + for (let j = 0; j < i + 1 - primeTable.length; j++) { + makeNextPrime(); + } + return primeTable[i]; +} + +function makeNextPrime() { + const lastPrime = primeTable[primeTable.length - 1]; + let candidate = lastPrime + 2; + for (; ;) { + if (!primeTable.some(p => (candidate % p) == 0)) { + break; + } + candidate += 2; + } + primeTable.push(candidate); +} + +export function getLargestPrimeFactor(n: number) { + let rest = n; + let i = 0; + let largest = 0; + for (; ;) { + const end = Math.floor(Math.sqrt(rest)); + const p = getNthPrime(i); + if (p > end) { + break; + } + while (rest % p === 0) { + rest = Math.floor(rest / p); + largest = p; + } + i++; + } + if (largest < rest) largest = rest; + return largest; +} + +export function largestPrimeSolve(n: number): number { + return getLargestPrimeFactor(n) +} + +export const testcases:Array<[number,number]> = [ + [2, 2], + [3, 3], + [4, 2], + [5, 5], + [6, 3], + [7, 7], + [8, 2], + [9, 3], + [10, 5], + [11, 11], + [12, 3], + [13, 13], + [14, 7], + [15, 5], + [16, 2], + [17, 17], + [18, 3], + [19, 19], + [20, 5], + [21, 7], + [22, 11], + [23, 23], + [24, 3], + [25, 5], + [26, 13], + [27, 3], + [28, 7], + [29, 29], + [30, 5], + [31, 31], + [32, 2], + [33, 11], + [34, 17], + [35, 7], + [36, 3], + [37, 37], + [38, 19], + [39, 13], + [40, 5], + [41, 41], + [42, 7], + [43, 43], + [44, 11], + [45, 5], + [46, 23], + [47, 47], + [48, 3], + [49, 7], + [50, 5], + [51, 17], + [52, 13], + [53, 53], + [54, 3], + [55, 11], + [56, 7], + [57, 19], + [58, 29], + [59, 59], + [60, 5], + [61, 61], + [62, 31], + [63, 7], + [64, 2], + [65, 13], + [66, 11], + [67, 67], + [68, 17], + [69, 23], + [70, 7], + [71, 71], + [72, 3], + [73, 73], + [74, 37], + [75, 5], + [76, 19], + [77, 11], + [78, 13], + [79, 79], + [80, 5], + [81, 3], + [82, 41], + [83, 83], + [84, 7], + [85, 17], + [86, 43], + [87, 29], + [88, 11], + [89, 89], + [90, 5], + [91, 13], + [92, 23], + [93, 31], + [94, 47], + [95, 19], + [96, 3], + [97, 97], + [98, 7], + [99, 11], +] + +//if (import.meta.main) { +// for (const [data, ans] of testcases) { +// console.log(data,"\t",getLargestPrimeFactor(data),"\t", ans) +// } +//} \ No newline at end of file diff --git a/src/lib/solve/minsumTriangle.ts b/src/lib/solve/minsumTriangle.ts new file mode 100644 index 0000000..09dc08b --- /dev/null +++ b/src/lib/solve/minsumTriangle.ts @@ -0,0 +1,59 @@ +//Minimum Path Sum in a Triangle + +export function minimumTrianglePathSumSolve(triangle:number[][]):number{ + const minSum = [[triangle[0][0]]]; + for (let i = 1; i < triangle.length; i++) { + const arr = triangle[i]; + const tmp = []; + for (let j = 0; j < arr.length; j++) { + const cur = triangle[i][j]; + if(j - 1 < 0){ + tmp.push(minSum[i-1][j]+ cur); + } + else if(j >= triangle[i-1].length){ + tmp.push(minSum[i-1][j-1] + cur); + } + else { + tmp.push(Math.min(minSum[i-1][j-1],minSum[i-1][j])+ cur); + } + } + minSum.push(tmp); + } + return Math.min(...minSum[minSum.length - 1]); +} + +const case1 = [ + [2], + [3,4], + [6,5,7], + [4,1,8,3] +]; + +const case2 = [ + [3], + [8,5], + [3,5,4], + [2,6,3,4], + [9,6,5,5,3], + [6,1,6,5,2,4], + [6,7,7,1,1,8,6], + [7,6,5,9,3,9,2,2], + [6,3,4,6,9,2,8,8,5], + [1,4,6,3,6,5,5,1,9,2], + [1,6,3,6,6,7,2,9,6,6,1], + [1,3,9,8,3,7,1,7,4,5,6,6] +]; +interface TestCase{ + data: number[][]; + ans: number; +} +export const testcases: TestCase[] = [ + { + data:case1, + ans:6, + }, + { + data:case2, + ans:35 + } +]; \ No newline at end of file diff --git a/src/lib/solve/shortestPathInGrid.ts b/src/lib/solve/shortestPathInGrid.ts new file mode 100644 index 0000000..acf540b --- /dev/null +++ b/src/lib/solve/shortestPathInGrid.ts @@ -0,0 +1,161 @@ + + +/** + * You are located in the top-left corner of the following grid: + * + * [[0,0,0,0,0,0,0,0,1,0], + * [0,0,0,1,0,1,0,0,1,0], + * [0,0,0,1,0,0,0,0,0,0], + * [0,0,1,0,0,0,0,0,0,1], + * [0,0,0,0,1,0,0,0,1,0], + * [0,0,1,1,0,1,0,0,0,0], + * [1,1,0,0,0,0,0,0,1,0], + * [0,0,1,0,0,0,0,0,0,0], + * [0,1,1,1,0,0,1,0,0,1], + * [0,1,1,0,0,1,0,0,0,0], + * [1,0,0,1,0,1,0,0,0,0], + * [0,1,0,0,0,0,0,0,0,0]] + * + * You are trying to find the shortest path to the bottom-right + * corner of the grid, but there are obstacles on the grid that + * you cannot move onto. These obstacles are denoted by '1', wh + * ile empty spaces are denoted by 0. + * + * Determine the shortest path from start to finish, if one exi + * sts. The answer should be given as a string of UDLR characte + * rs, indicating the moves along the path + * + * NOTE: If there are multiple equally short paths, any of them + * is accepted as answer. If there is no path, the answer + * should be an empty string. + * NOTE: The data returned for this contract is an 2D array of + * numbers representing the grid. + * + * Examples: + * + * [[0,1,0,0,0], + * [0,0,0,1,0]] + * + * Answer: 'DRRURRD' + * + * [[0,1], + * [1,0]] + * + * Answer: '' + */ + +import { SolveFailError } from "./unsolved"; + +export function shortestPathInGrid(map: number[][]): string[] { + const distMap = makeDistanceMap(map, 0, 0); + if (!isFinite(distMap[map.length - 1][map[map.length - 1].length - 1])) { + return []; + } + const pathes = rewindMap(distMap, map, map[map.length - 1].length - 1, map.length - 1) + return reversePath(pathes); +} +export function shortestPathInGridSolve(map: number[][]): string[] { + return [shortestPathInGrid(map).join("")]; +} + +function copyMap(map: number[][], fillValue: T): T[][] { + return map.map(x => + x.map(_ => fillValue) + ); +} + +function makeDistanceMap(map: number[][], initPosX: number, initPosY: number): number[][] { + const distMap = copyMap(map, Infinity); + const queue: Array<[number, number]> = []; + queue.push([initPosX, initPosY]); + distMap[initPosY][initPosX] = 0; + while (queue.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const [x, y] = queue.pop()!; + const currentDist = distMap[y][x]; + //visit neighbor + const neighborhood: Array<[number, number]> = [[x + 1, y], [x, y + 1], [x, y - 1], [x - 1, y]] + for (const [nextX, nextY] of neighborhood) { + if (isVisitable(map, nextX, nextY)) { + if (distMap[nextY][nextX] > currentDist + 1) { + //set distance + distMap[nextY][nextX] = currentDist + 1; + //next nodes + queue.push([nextX, nextY]); + } + + } + } + } + return distMap; +} + +function isVisitable(map: number[][], x: number, y: number): boolean { + return 0 <= y && y < map.length && 0 <= x && x < map[y].length && map[y][x] === 0 +} + +type PathDir = "R" | "D" | "U" | "L"; + +function rewindMap(distMap: number[][], map: number[][], initPosX: number, initPosY: number): PathDir[] { + let x = initPosX, y = initPosY; + const ret: PathDir[] = []; + while (getMapDist([x, y]) > 0) { + //walk + const neighborhood: Array<[number, number]> = [[x + 1, y], [x, y + 1], [x, y - 1], [x - 1, y]] + const dirNames: PathDir[] = ["R", "D", "U", "L"]; + const candidate = neighborhood.map((x, i) => ({ p: x, index: i })) + .filter(({ p: [cx, cy] }) => isVisitable(map, cx, cy)) + if (candidate.length === 0) { + throw new SolveFailError("candidate 0"); + } + const next = candidate.reduce((v1, v2) => getMapDist(v1.p) < getMapDist(v2.p) ? v1 : v2) + x = next.p[0]; + y = next.p[1]; + ret.push(dirNames[next.index]); + } + return ret; + function getMapDist([x, y]: [number, number]): number { + return distMap[y][x]; + } +} + + +const REVERSE_TABLE: { [key in PathDir]: PathDir } = { + "R": "L", + "L": "R", + "D": "U", + "U": "D", +} +/** + * this method mutate array. + * @param path path to reverse + * @returns same reference of input array + */ +function reversePath(path: PathDir[]): PathDir[] { + return path.reverse().map(x => REVERSE_TABLE[x]); +} + + //const map = [ + // [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + // [0, 0, 0, 1, 0, 1, 0, 0, 1, 0], + // [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + // [0, 0, 1, 0, 0, 0, 0, 0, 0, 1], + // [0, 0, 0, 0, 1, 0, 0, 0, 1, 0], + // [0, 0, 1, 1, 0, 1, 0, 0, 0, 0], + // [1, 1, 0, 0, 0, 0, 0, 0, 1, 0], + // [0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + // [0, 1, 1, 1, 0, 0, 1, 0, 0, 1], + // [0, 1, 1, 0, 0, 1, 0, 0, 0, 0], + // [1, 0, 0, 1, 0, 1, 0, 0, 0, 0], + // [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] + //] + // + //import {sprintf} from "https://deno.land/std@0.152.0/fmt/printf.ts"; + //function sprintDistMap(distMap:number[][]){ + // return (distMap.map(x=>x.map(y=>isFinite(y)? y : "I") + // .map(y=>sprintf("%2s",y.toString())).join(",")).join("\n")); + //} + //const distMap = makeDistanceMap(map,0,0); + //console.log(sprintDistMap(distMap)); + //const pathes = rewindMap(distMap,map,map[map.length - 1].length - 1, map.length - 1) + //console.log(reversePath(pathes)); \ No newline at end of file diff --git a/src/lib/solve/spiral.ts b/src/lib/solve/spiral.ts new file mode 100644 index 0000000..a28dc14 --- /dev/null +++ b/src/lib/solve/spiral.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + + +enum Direction{ + RIGHT = 0, + DOWN = 1, + LEFT = 2, + UP = 3 +} + +export function spiral(arr: number[][]):number[] { + const w = arr[0].length; + const h = arr.length; + + let x = -1, y = 0; + let wdepth = w; + let hdepth = h - 1; + let dir:Direction = Direction.RIGHT; + const ret: number[] = []; + //ret.push(arr[y][x]); + for (;;) { + if(wdepth == 0) break; + //console.log(wdepth); + stepN(wdepth); + dir = turn90(dir); + if(hdepth == 0) break; + //console.log(hdepth); + stepN(hdepth); + dir = turn90(dir); + wdepth--; + hdepth--; + } + return ret; + function turn90(d: Direction){ + return (d + 1)% 4; + } + function stepN(n:number){ + for (let i = 0; i < n; i++) { + step(); + } + } + function step(){ + switch(dir){ + case Direction.RIGHT: + x++; + break; + case Direction.DOWN: + y++; + break; + case Direction.LEFT: + x--; + break; + case Direction.UP: + y--; + break; + } + ret.push(arr[y][x]); + } +} + +export function spiralSolve(arr:number[][]):string[]{ + return spiral(arr).map(x=>x.toString()); +} + +const case1: [number[][],number[]] = [[[1,2,3], +[4,5,6], +[7,8,9]], +[1, 2, 3, 6, 9, 8, 7, 4, 5] +]; + +const case2: [number[][],number[]] =[ + [[1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12],], + [1, 2, 3, 4, 8,12, 11, 10, 9, 5,6, 7] +]; + +const case3: [number[][],number[]] = [[ + [33,11,20,23,11,47,31,16,41,49], + [37,32,14,34,30,18,15,17,45,36], + [ 9,22,40,35,19,12,23,16,37, 5], +], +[33, 11, 20, 23, 11, 47, 31, 16, 41,49, 36, 5, 37, 16, 23, 12, 19, 35,40, 22, 9, 37, 32, 14, 34, 30, 18,15, 17, 45] +] + +export const testcases: Array<[number[][],number[]]> = [ + case1, + case2, + case3 +]; \ No newline at end of file diff --git a/src/lib/solve/submaxarr.ts b/src/lib/solve/submaxarr.ts new file mode 100644 index 0000000..dfcd332 --- /dev/null +++ b/src/lib/solve/submaxarr.ts @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +function* localmaxSum(arr: number[], first: number, last: number){ + const firstIndex = arr.findIndex(x=> x > 0); + if(firstIndex < 0) return; + let sum = 0; + let blockSum = 0; + for (let index = first; index <= last; index++) { + const element = arr[index]; + blockSum += element; + if(blockSum > 0){ + sum += blockSum; + blockSum = 0; + } + else if(sum + blockSum < 0){ + yield sum; + blockSum = 0; + sum = 0; + } + } + yield sum; +} +export function getMaximumlSumSubArray(arr:number[]): number{ + let maxsum = 0; + for(const g of localmaxSum(arr,0,arr.length - 1)){ + if(maxsum < g){ + maxsum = g; + } + } + if(maxsum === 0){ + return Math.max(...arr); + } + return maxsum; +} +export function subarrayMaxSolve(arr:number[]): number{ + return getMaximumlSumSubArray(arr); +} + +type TestCase = [number[],number]; + +export const testcases:TestCase[]=[ + [[1,5,-9,4],6], + [[9,4,4,0],17], + [[1,-90,5],5], + [[1,2,-50,6],6], + [[1,2,-90,1,4],5], + [[1,2,-90,1,1,1,1],4], + [[1,-90,91],91], + [[-1,-2,-4],-1], + [[-1,3,6],9], + [[5,-50,4,-1,3],6] +] + +//if(import.meta.main){ +// for (const [arr,s] of testcases) { +// console.log("predict: ",solve(arr)," actual: ", s); +// } +//} \ No newline at end of file diff --git a/src/lib/solve/unsolved.ts b/src/lib/solve/unsolved.ts new file mode 100644 index 0000000..ab4ff4d --- /dev/null +++ b/src/lib/solve/unsolved.ts @@ -0,0 +1 @@ +export class SolveFailError extends Error{} \ No newline at end of file diff --git a/src/list-all-server.ts b/src/list-all-server.ts index 7133109..ec98f19 100644 --- a/src/list-all-server.ts +++ b/src/list-all-server.ts @@ -1,10 +1,19 @@ import {NS} from "@ns" +import { parse } from "./lib/flag"; import {getHackability,getLockSymbol} from "./lib/hack"; +import { sprintf } from "./lib/printf"; + + +let detail = false; /** @param {NS} ns */ // eslint-disable-next-line require-await export async function main(ns: NS): Promise { - findServer(ns, 'home', 'home', 1); + const flag = parse(ns.args.map(String)); + detail = flag.d || flag.detail || false; + + const a = findServer(ns, 'home', 'home', 1); + ns.tprint("\n"+a.join("\n")); } /** @param {NS} ns @@ -12,16 +21,33 @@ export async function main(ns: NS): Promise { * @param {string} targetServer * @param {number} depth */ -function findServer(ns: NS, startServer: string, targetServer: string, depth: number): void { +function findServer(ns: NS, startServer: string, targetServer: string, depth: number): string[] { const servers = ns.scan(targetServer) .filter((server) => server !== startServer); - servers.forEach((server) => { + return servers.map((server, i, arr) => { const lock = getHackability(ns, server); const lock_symbol = getLockSymbol(lock); const info = ns.getServer(server); - ns.tprint(`😹${'>'.repeat(depth)} ${lock_symbol} ${server} ${info.backdoorInstalled ? '✅': '❌'}`); - if (lock !== "impossible") { - findServer(ns, targetServer, server, depth + 1); + const backdoorSymbol = info.backdoorInstalled ? '✅': '❌'; + let ret: string[] = []; + + const startSymbol = (i + 1 == arr.length) ? "└" : "├"; + const extendSymbol = (i + 1 == arr.length) ? " " : "│"; + //printf(ns, `😹${'>'.repeat(depth)} %s %s %s`,lock_symbol,server,info.backdoorInstalled ? '✅': '❌') + const fmsg = (`${startSymbol}${lock_symbol} ${backdoorSymbol} ${server}`); + ret.push(fmsg); + if(detail){ + const moneyAvailable = ns.nFormat(info.moneyAvailable,"$0.000a"); + const moneyMax = ns.nFormat(info.moneyMax,"$0.000a"); + ret.push(sprintf("%s └%s🛡️ %6.2f/%6.2f(%3d),💸 %10s/%10s",extendSymbol, + "-".repeat(20-depth), + info.hackDifficulty,info.minDifficulty,info.requiredHackingSkill, + moneyAvailable,moneyMax)); } - }); -} \ No newline at end of file + if (lock !== "impossible") { + const s = findServer(ns, targetServer, server, depth + 1).map(x=>`${extendSymbol}${x}`); + ret = ret.concat(s) + } + return ret; + }).flat(); +} diff --git a/src/list-purchase-server.ts b/src/list-purchase-server.ts index c3e3f67..d30bb9b 100644 --- a/src/list-purchase-server.ts +++ b/src/list-purchase-server.ts @@ -4,7 +4,7 @@ import {parse} from "lib/flag" /** @param {NS} ns */ // eslint-disable-next-line require-await export async function main(ns: NS):Promise { - const flag = parse(ns.args); + const flag = parse(ns.args.map(x=>String(x))); if(flag.help || flag.h){ ns.tprint(`* list all purchased server`); ns.exit(); diff --git a/src/list-stock.ts b/src/list-stock.ts new file mode 100644 index 0000000..3a8e223 --- /dev/null +++ b/src/list-stock.ts @@ -0,0 +1,63 @@ +import { NS } from '@ns' +import { parse } from './lib/flag'; +import { sprintf } from './lib/printf'; + +interface StockStatsticsInfo{ + stockName: string; + price: number; + forecast: number; + vaolatility: number; + expectation: number; + variation: number; + std:number; + askPrice:number; + bidPrice:number; + maxShare:number; +} + +export function getStockStatsticsList(ns:NS): StockStatsticsInfo[]{ + return ns.stock.getSymbols().map(x=>getStockStatstics(ns,x)); +} + +export function getStockStatstics(ns:NS, stockName:string):StockStatsticsInfo{ + const forecast = ns.stock.getForecast(stockName); + const val = ns.stock.getVolatility(stockName); + const price = ns.stock.getPrice(stockName); + const askPrice = ns.stock.getAskPrice(stockName); + const bidPrice = ns.stock.getBidPrice(stockName); + const maxShare = ns.stock.getMaxShares(stockName); + const priceVar = val * price; + const expectation = (2*forecast - 1) * priceVar; + const variation = 4*priceVar*(1-forecast)*forecast; + return { + stockName, + price, + forecast, + vaolatility: val, + expectation: expectation, + variation: variation, + std: Math.sqrt(variation), + askPrice, + bidPrice, + maxShare, + }; +} + +// eslint-disable-next-line require-await +export async function main(ns: NS) : Promise { + const flag = parse(ns.args.map(x=>x.toString())); + if(flag.h || flag.help){ + const msg = ['run cmd'] + msg.forEach(x=>ns.tprint(x)); + return; + } + const list = getStockStatsticsList(ns); + list.sort((a,b)=>b.expectation - a.expectation ); + list.forEach(info=>{ + ns.tprint(sprintf("%6s %8s %2.1f%% %8s %8s",info.stockName, + ns.nFormat(info.price,"$0.00a"), + info.forecast*100, + ns.nFormat(info.expectation,"$0.00a"), + ns.nFormat(info.std,"$0.00a"))) + }) +} \ No newline at end of file diff --git a/src/ls-contract.ts b/src/ls-contract.ts new file mode 100644 index 0000000..c59675a --- /dev/null +++ b/src/ls-contract.ts @@ -0,0 +1,87 @@ +import { NS, AutocompleteData } from '@ns' +import {selectAllContract, getContractList, RemoteFilePath} from "lib/contract"; +import { sprintf } from './lib/printf'; +import { parse } from './lib/flag'; +import Fuse from "lib/fuse.esm"; + +function searchFilename(ns: NS, filename: string, hostname?: string): RemoteFilePath | null{ + let p: RemoteFilePath[]; + if(hostname){ + p = getContractList(ns,hostname); + } + else{ + p = selectAllContract(ns); + } + const fuse = new Fuse(p, {includeScore:true, keys:["filename"]}); + const candiates = fuse.search(filename); + if(candiates.length === 0){ + return null; + } + const candiate = candiates[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if(candiate!.score! > 0.8){ + return null; + } + return candiate.item; +} + +// eslint-disable-next-line require-await +export async function main(ns : NS) : Promise { + const capi = ns.codingcontract; + if(ns.args.length === 0){ + const ctList = selectAllContract(ns); + ctList.map(ct=>{ + const type = capi.getContractType(ct.filename,ct.hostname); + return { + ...ct, + type + }; + }).sort((a,b)=>(a.type>b.type) ? 1 : -1) + .forEach(ct=>{ + const msg = sprintf("%17s %40s %s",ct.hostname,ct.filename,ct.type) + ns.tprint(msg); + }) + + return; + } + const flag = parse(ns.args.map(x=>x.toString())); + if(flag.h || flag.help){ + ns.tprint("HELP : ") + ns.tprint("run cmd [filename] [--host|-t]"); + return; + } + let hostFlag : string|undefined; + if(flag.host || flag.t){ + hostFlag = flag.host.toString() || flag.t.toString(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if(!ns.serverExists(hostFlag!)){ + ns.tprint("unexist hostname"); + return; + } + } + const inputFilename = flag._.toString(); + const p = searchFilename(ns,inputFilename, hostFlag); + if(p === null){ + ns.tprint(`could not file ${inputFilename}`); + return; + } + const {filename, hostname: target} = p; + + ns.tprint(`detail of ${target}:${filename}`); + + const msg = [ + `${filename}(${capi.getNumTriesRemaining(filename,target)}/10)`, + `${capi.getContractType(filename,target)}`, + `${capi.getDescription(filename,target).replaceAll(" "," ")}`, + `${JSON.stringify(capi.getData(filename,target))}` + ].join("\n"); + ns.tprint("\n",msg); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function autocomplete(data : AutocompleteData, args : string[]) : string[] { + if(args.length == 1){ + return [...data.servers] + } + return []; +} \ No newline at end of file diff --git a/src/lsnode.ts b/src/lsnode.ts new file mode 100644 index 0000000..72144b2 --- /dev/null +++ b/src/lsnode.ts @@ -0,0 +1,39 @@ +import { selectRootedServerList, ServerInfo } from "lib/servers"; +import { + calculateWeakenTime, + calculatePercentMoneyHacked, + calculateServerGrowth +} from "lib/formula"; +import { NS } from '@ns' +import { sprintf } from "./lib/printf"; + + +// eslint-disable-next-line require-await +export async function main(ns: NS): Promise { + const list = selectRootedServerList(ns); + const player = ns.getPlayer(); + const m = list.map(x => { + const optimalState: ServerInfo = {...x, + hackDifficulty: x.minDifficulty + } as ServerInfo; + ns.print(optimalState.minDifficulty," ", optimalState.hackDifficulty ,""); + const weakenTime = calculateWeakenTime(optimalState, player); + const earnMoney = calculatePercentMoneyHacked(optimalState, player); + //const growPercent = calculateServerGrowth() + return { + hostname: x.hostname, + info: x, + weakenTime, + earnMoney, + ce: earnMoney* x.moneyMax / weakenTime, + } + }); + m.sort((a,b)=>(b.ce-a.ce)); + + m.filter(x=>x.ce > 0).forEach(x=>{ + const msg = sprintf("%20s %8s %6.1fs %10.2f",x.hostname, + ns.nFormat(x.earnMoney * x.info.moneyMax,"$0.00a"),x.weakenTime, + x.ce*100); + ns.tprint(msg); + }) +} \ No newline at end of file diff --git a/src/purchase-server.ts b/src/purchase-server.ts index 7a22a69..5ecf241 100644 --- a/src/purchase-server.ts +++ b/src/purchase-server.ts @@ -5,7 +5,7 @@ import {range} from "./util/range"; /** @param {NS} ns */ export async function main(ns: NS): Promise { - const flag = parse(ns.args); + const flag = parse(ns.args.map(x=>String(x))); if(flag.h || flag.help){ ns.tprint("script : purchase server") ns.tprint(""); @@ -13,22 +13,23 @@ export async function main(ns: NS): Promise { } if(Boolean(flag.i) || Boolean(flag.interactive)){ const ramLimit = ns.getPurchasedServerMaxRam() - const choices = [...range(3,20)].map(x=>Math.pow(2,x)).filter(x=>x <= ramLimit).map((x)=>{ + const choices = [...range(3,21)].map(x=>Math.pow(2,x)).filter(x=>x <= ramLimit).map((x)=>{ const cost = ns.getPurchasedServerCost(x); return `${x}GB (${ns.nFormat(cost,"$0.000a")})` }); const choice = await ns.prompt("which server do you purchase?",{ type:"select", choices - }); + }) as string; if(choice === ""){ ns.tprint("canceled"); ns.exit(); return; } - const gb = parseInt(/^(\d+)GB/.exec(choice)[1]); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const gb = parseInt(/^(\d+)GB/.exec(choice)![1]); ns.tprint("you select ",gb,"GB"); - const hostname = await ns.prompt("name your server",{type:"text"}); + const hostname = await ns.prompt("name your server",{type:"text"}) as string; if(hostname === ""){ ns.tprint("canceled"); ns.exit(); @@ -39,7 +40,7 @@ export async function main(ns: NS): Promise { ns.nFormat(ns.getPurchasedServerCost(gb),"$0.000a")})?`, { type: "boolean" }); if (p) { - const l = ns.purchaseServer(hostname, ram) + const l = ns.purchaseServer(hostname, gb); ns.tprint(l, " purchased"); } ns.exit() diff --git a/src/server-status.ts b/src/server-status.ts index 664656a..e5108df 100644 --- a/src/server-status.ts +++ b/src/server-status.ts @@ -1,4 +1,4 @@ -import {NS} from "@ns"; +import {NS, AutocompleteData, Server} from "@ns"; import {hackablePorts, getHackability, getLockSymbol} from "./lib/hack"; import {parse} from "./lib/flag"; @@ -13,29 +13,33 @@ type ReportOption = { */ // eslint-disable-next-line require-await export async function main(ns: NS): Promise { - const flag = parse(ns.args); + const flag = parse(ns.args.map(x=>String(x))); if (flag._.length == 0) { ns.tprint("argumented required"); return; } - const hostname = flag._[0]; + const hostname = String(flag._[0]); const detail = Boolean(flag.d) || Boolean(flag.detail); - serverReport(ns, hostname,{ - detail: detail, - }); + const realtime = Boolean(flag.realtime); + if(realtime){ + ns.tail(); + for(;;){ + const server = ns.getServer(hostname); + ns.clearLog(); + const msg = makeDetailServerReport(ns,server); + ns.print(msg); + await ns.sleep(1000); + } + } + else{ + serverReport(ns, hostname,{ + detail: detail, + }); + } } -/** - * @param {NS} ns - * @param {string} hostname - */ -export function serverReport(ns: NS, hostname: string, options?:ReportOption):void { - options ??= {}; - const serverLock = getHackability(ns, hostname); - ns.tprint(`${getLockSymbol(serverLock)} ${hostname}`); - if(options.detail){ - const server = ns.getServer(hostname); - const msg = ['', - `hostname : ${server.hostname}(${server.ip})`, + +function makeDetailServerReport(ns:NS,server:Server): string{ + return [`hostname : ${server.hostname}(${server.ip})`, `🛡️${ns.nFormat(server.hackDifficulty,"0[.]000")}/${server.minDifficulty}(${server.baseDifficulty})`, `💸${ns.nFormat(server.moneyAvailable,"$0.000a")}/${ns.nFormat(server.moneyMax,"$0.000a")}`, `💾${server.ramUsed}GB/${server.maxRam}GB`, @@ -47,7 +51,19 @@ export function serverReport(ns: NS, hostname: string, options?:ReportOption):vo `required hacking skill\t: ${server.requiredHackingSkill}`, `ports \t\t\t: ${server.openPortCount}/${server.numOpenPortsRequired}` ].join("\n"); - ns.tprint(msg); +} + +/** + * @param {NS} ns + * @param {string} hostname + */ +export function serverReport(ns: NS, hostname: string, options?:ReportOption):void { + options ??= {}; + const serverLock = getHackability(ns, hostname); + ns.tprint(`${getLockSymbol(serverLock)} ${hostname}`); + if(options.detail){ + const server = ns.getServer(hostname); + ns.tprint("\n"+makeDetailServerReport(ns, server)); } else{ if (serverLock == "rooted") { @@ -74,6 +90,6 @@ export function serverHackStatus(ns: NS, server: string): string { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function autocomplete(data : ServerData, args : string[]) : string[] { +export function autocomplete(data : AutocompleteData, args : string[]) : string[] { return [...data.servers] } \ No newline at end of file diff --git a/src/stock-auto-sell-deamon.ts b/src/stock-auto-sell-deamon.ts new file mode 100644 index 0000000..dda365a --- /dev/null +++ b/src/stock-auto-sell-deamon.ts @@ -0,0 +1,35 @@ +import { NS } from '@ns' + +// eslint-disable-next-line require-await +export async function main(ns : NS) : Promise { + const api = ns.stock; + if(!api.hasWSEAccount() || !api.has4SDataTIXAPI()){ + ns.tprint("api need") + ns.tprint("purchase stock API!"); + return; + } + ns.print("start stock-auto-sell-daemon"); + ns.disableLog("ALL") + ns.tail(); + for(;;){ + for(const stock of api.getSymbols()){ + const p = api.getForecast(stock); + if(p < 0.5){ + const share = api.getPosition(stock)[0] + if(share > 0){ + ns.print(`forecast: ${p}, sell ${stock} amount of ${share}`); + const v = api.sellStock(stock, share); + if(v == 0){ + ns.print("failed to sell stock!"); + ns.toast("Failed To Sell Stock","error",6000); + } + else{ + ns.print(`avg sold price ${v}`); + ns.toast(`Sell ${stock} amount of ${ns.nFormat(share,"0.000a")}`,"info",6000); + } + } + } + } + await ns.sleep(6000); + } +} \ No newline at end of file diff --git a/src/stock-daemon.ts b/src/stock-daemon.ts new file mode 100644 index 0000000..43dc95c --- /dev/null +++ b/src/stock-daemon.ts @@ -0,0 +1,69 @@ +import { NS } from '@ns' + +// eslint-disable-next-line require-await +export async function main(ns : NS) : Promise { + const stock = ns.stock; + if(!stock.hasWSEAccount() || !stock.has4SDataTIXAPI()){ + ns.tprint("api need") + ns.tprint("purchase stock API!"); + return; + } + if(ns.args.length == 0){ + ns.tprint("argument need") + return; + } + ns.print("start stock-auto-sell-daemon"); + ns.disableLog("ALL") + ns.tail(); + + const account = ns.args[0]; + const tradableStocks = stock.getSymbols(); + const p = tradableStocks.map(st=>{ + const forecast = stock.getForecast(st); + const val = stock.getVolatility(st); + const price = stock.getPrice(st); + const priceVar = val * price; + const expectation = (2*forecast - 1) * priceVar; + const variation = 4*priceVar*(1-forecast)*forecast; + return { + stockName: st, + expectation: expectation, + variation: variation + }; + }); + +} + +//// eslint-disable-next-line require-await +//export async function main(ns : NS) : Promise { +// const api = ns.stock; +// if(!api.hasWSEAccount() || !api.has4SDataTIXAPI()){ +// ns.tprint("api need") +// ns.tprint("purchase stock API!"); +// return; +// } +// ns.print("start stock-auto-sell-daemon"); +// ns.disableLog("ALL") +// ns.tail(); +// for(;;){ +// for(const stock of api.getSymbols()){ +// const p = api.getForecast(stock); +// if(p < 0.5){ +// const share = api.getPosition(stock)[0] +// if(share > 0){ +// ns.print(`forecast: ${p}, sell ${stock} amount of ${share}`); +// const v = api.sellStock(stock, share); +// if(v == 0){ +// ns.print("failed to sell stock!"); +// ns.toast("Failed To Sell Stock","error",6000); +// } +// else{ +// ns.print(`avg sold price ${v}`); +// ns.toast(`Sell ${stock} amount of ${ns.nFormat(share,"0.000a")}`,"info",6000); +// } +// } +// } +// } +// await ns.sleep(6000); +// } +//} \ No newline at end of file diff --git a/src/unpack-script.ts b/src/unpack-script.ts new file mode 100644 index 0000000..5ce8491 --- /dev/null +++ b/src/unpack-script.ts @@ -0,0 +1,7 @@ +import { NS } from '@ns' +import { installBatchFilePack } from './lib/batchbase'; + +// eslint-disable-next-line require-await +export async function main(ns : NS) : Promise { + await installBatchFilePack(ns); +} \ No newline at end of file diff --git a/src/util/assert.ts b/src/util/assert.ts index d6ba9e8..1fc0797 100644 --- a/src/util/assert.ts +++ b/src/util/assert.ts @@ -1,5 +1,3 @@ -import assert from "assert"; - export class AssertionError extends Error{ constructor(msg:string){ super(msg); @@ -11,4 +9,10 @@ export function assert(expr: unknown, msg=""): asserts expr{ if(!expr){ throw new AssertionError(msg) } +} + +export class NotImplementError extends Error{} + +export function notImplemented(): never { + throw new NotImplementError(); } \ No newline at end of file diff --git a/src/util/range.ts b/src/util/range.ts index 377437c..a9e1630 100644 --- a/src/util/range.ts +++ b/src/util/range.ts @@ -1,5 +1,4 @@ -export function* range(end: number): Generator; -export function* range(begin:number,end:number): Generator; + export function* range(arg1: number, arg2?: number): Generator{ const begin = arg2 ? arg1 : 0; const end = arg2 ?? arg1;