UNPKG

bit-crush

Version:
115 lines (90 loc) 3.24 kB
import CharGenerator from './CharGenerator' import generateOutput from './generateOutput' import { getShortestString, countSubstring } from './utils' enum STRATEGY { NONE, MAX, MIN } function findCandidates (code: string, strategy: STRATEGY) { let candidates: Array<ICandidate> = [] let maxSavings = 0 let stop = false for (let length = 2; !stop; length++) { let hasWords = false for (let i = 0; i < code.length - length; i++) { const word = code.substring(i, i + length) const wordOccurences = countSubstring(word, code) if (wordOccurences > 1) { hasWords = true candidates = candidates.filter(candidate => { return word.indexOf(candidate.word) === -1 || candidate.wordOccurences > wordOccurences }) const savings = (wordOccurences * length) - (wordOccurences + length + 2) if (savings > 0 && savings >= maxSavings * (strategy === STRATEGY.NONE ? 1 : 0.8)) { candidates.push({ word, wordOccurences, savings }) } maxSavings = Math.max(maxSavings, savings) } } if (!hasWords) { stop = true } } return candidates.sort((candidateA, candidateB) => candidateB.savings - candidateA.savings) } function crush (code: string, strategy: STRATEGY) { const charGenerator = new CharGenerator(code) let swaps: Array<ISwap> = [] let leftover = code let previousOutput = null let stop = false while (!stop) { const char = charGenerator.getNextChar() const candidates = findCandidates(leftover, strategy) if (candidates.length === 0) break const max = candidates[0].savings const candidatesWithMax = candidates.filter(candidate => { return candidate.savings >= 0.8 * max }) let index: number if (strategy === STRATEGY.NONE) { index = 0 } if (strategy === STRATEGY.MAX) { let maxLength = 0 candidatesWithMax.forEach((candidate, i) => { if (candidate.word.length > maxLength) { index = i maxLength = candidate.word.length } }) } if (strategy === STRATEGY.MIN) { let minLength = Infinity candidatesWithMax.forEach((candidate, i) => { if (candidate.word.length < minLength) { index = i minLength = candidate.word.length } }) } const word = candidates[index].word const newLeftover = leftover.split(word).join(char) + char + word const newSwaps = [...swaps] newSwaps.push({ from: word, to: char }) const output = generateOutput(newSwaps, newLeftover) if (previousOutput === null || previousOutput.length > output.length) { previousOutput = output leftover = newLeftover swaps = newSwaps } else { stop = true } } const output = generateOutput(swaps, leftover) return code.length < output.length ? code : output } export default function compress (code: string) { const strategies = [STRATEGY.NONE, STRATEGY.MAX, STRATEGY.MIN] const codes = strategies.map(strategy => crush(code, strategy)) return getShortestString(codes) }