Files
SnowLuma/.pnpm-store/v11/files/02/a7583a7f4db81b9696aa991dedad41608d415af4fbeb182cfd0d98b760ce2aff19e910ec66edc5764c2af11487c909b6b436f5dc79bee56f17f71c022730c7
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Canceled after 0s
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

67 lines
1.9 KiB
Plaintext

import utils from '../../scoring/utils'
import Options from '../../Options'
import { MatchEstimated, MatchExtended, OptionsGraphEntry } from '../../types'
interface EstimatePossiblePatternsOptions {
token: string
turns: number
}
const calcAverageDegree = (graph: OptionsGraphEntry) => {
let average = 0
Object.keys(graph).forEach((key) => {
const neighbors = graph[key]
average += neighbors.filter((entry) => !!entry).length
})
average /= Object.entries(graph).length
return average
}
const estimatePossiblePatterns = (
graphEntry: OptionsGraphEntry,
{ token, turns }: EstimatePossiblePatternsOptions,
) => {
const startingPosition = Object.keys(graphEntry).length
const averageDegree = calcAverageDegree(graphEntry)
let guesses = 0
const tokenLength = token.length
// # estimate the number of possible patterns w/ tokenLength or less with turns or less.
for (let i = 2; i <= tokenLength; i += 1) {
const possibleTurns = Math.min(turns, i - 1)
for (let j = 1; j <= possibleTurns; j += 1) {
guesses += utils.nCk(i - 1, j - 1) * startingPosition * averageDegree ** j
}
}
return guesses
}
export default (
{ graph, token, shiftedCount, turns }: MatchExtended | MatchEstimated,
options: Options,
) => {
let guesses = estimatePossiblePatterns(options.graphs[graph], {
token,
turns,
})
// add extra guesses for shifted keys. (% instead of 5, A instead of a.)
// math is similar to extra guesses of l33t substitutions in dictionary matches.
if (shiftedCount) {
const unShiftedCount = token.length - shiftedCount
if (shiftedCount === 0 || unShiftedCount === 0) {
guesses *= 2
} else {
let shiftedVariations = 0
for (let i = 1; i <= Math.min(shiftedCount, unShiftedCount); i += 1) {
shiftedVariations += utils.nCk(
(shiftedCount as number) + unShiftedCount,
i,
)
}
guesses *= shiftedVariations
}
}
return Math.round(guesses)
}