UNPKG

romans

Version:

A small, no-dependency lib for converting to and from roman numerals

293 lines (246 loc) 9.16 kB
const original = require('./romans') const optimized = require('./romans-optimized') const memoized = require('./romans-memoized') // Test data const TEST_NUMBERS = [ // Common values 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000, // Complex values 444, 999, 1994, 2023, 3999, // Random values ...Array.from({ length: 50 }, () => Math.floor(Math.random() * 3999) + 1) ] const TEST_ROMANS = TEST_NUMBERS.map(n => original.romanize(n)) const ITERATIONS = { SMALL: 1000, MEDIUM: 10000, LARGE: 100000, XLARGE: 1000000 } function benchmark(name, fn, iterations = ITERATIONS.MEDIUM) { // Warmup for (let i = 0; i < 100; i++) fn() const start = process.hrtime.bigint() for (let i = 0; i < iterations; i++) { fn() } const end = process.hrtime.bigint() const durationMs = Number(end - start) / 1000000 const opsPerSec = (iterations / durationMs) * 1000 return { name, duration: durationMs, opsPerSec, iterations } } function compareRomanize() { console.log('\n=== ROMANIZE COMPARISON ===') const tests = [ { name: 'Single value (1994)', iterations: ITERATIONS.XLARGE, fn: (impl) => () => impl.romanize(1994) }, { name: 'Test numbers array', iterations: ITERATIONS.LARGE, fn: (impl) => () => TEST_NUMBERS.forEach(n => impl.romanize(n)) }, { name: 'Sequential 1-1000', iterations: ITERATIONS.SMALL, fn: (impl) => () => { for (let i = 1; i <= 1000; i++) { impl.romanize(i) } } }, { name: 'Random values', iterations: ITERATIONS.MEDIUM, fn: (impl) => () => { const num = Math.floor(Math.random() * 3999) + 1 impl.romanize(num) } } ] tests.forEach(test => { console.log(`\n${test.name}:`) const originalResult = benchmark('Original', test.fn(original), test.iterations) const optimizedResult = benchmark('Optimized', test.fn(optimized), test.iterations) const memoizedResult = benchmark('Memoized', test.fn(memoized), test.iterations) console.log(` Original: ${originalResult.opsPerSec.toLocaleString()} ops/sec`) console.log(` Optimized: ${optimizedResult.opsPerSec.toLocaleString()} ops/sec (${(optimizedResult.opsPerSec / originalResult.opsPerSec).toFixed(1)}x)`) console.log(` Memoized: ${memoizedResult.opsPerSec.toLocaleString()} ops/sec (${(memoizedResult.opsPerSec / originalResult.opsPerSec).toFixed(1)}x)`) }) } function compareDeromanize() { console.log('\n=== DEROMANIZE COMPARISON ===') const tests = [ { name: 'Single value (MCMXCIV)', iterations: ITERATIONS.XLARGE, fn: (impl) => () => impl.deromanize('MCMXCIV') }, { name: 'Test romans array', iterations: ITERATIONS.LARGE, fn: (impl) => () => TEST_ROMANS.forEach(r => impl.deromanize(r)) }, { name: 'Complex romans', iterations: ITERATIONS.LARGE, fn: (impl) => () => { const complexRomans = ['MMMCMXCIX', 'CDXLIV', 'CMXCIX', 'DCCCXC'] complexRomans.forEach(r => impl.deromanize(r)) } }, { name: 'Random romans', iterations: ITERATIONS.MEDIUM, fn: (impl) => () => { const randomRoman = TEST_ROMANS[Math.floor(Math.random() * TEST_ROMANS.length)] impl.deromanize(randomRoman) } } ] tests.forEach(test => { console.log(`\n${test.name}:`) const originalResult = benchmark('Original', test.fn(original), test.iterations) const optimizedResult = benchmark('Optimized', test.fn(optimized), test.iterations) const memoizedResult = benchmark('Memoized', test.fn(memoized), test.iterations) console.log(` Original: ${originalResult.opsPerSec.toLocaleString()} ops/sec`) console.log(` Optimized: ${optimizedResult.opsPerSec.toLocaleString()} ops/sec (${(optimizedResult.opsPerSec / originalResult.opsPerSec).toFixed(1)}x)`) console.log(` Memoized: ${memoizedResult.opsPerSec.toLocaleString()} ops/sec (${(memoizedResult.opsPerSec / originalResult.opsPerSec).toFixed(1)}x)`) }) } function compareRoundTrip() { console.log('\n=== ROUND-TRIP COMPARISON ===') const tests = [ { name: 'Test numbers round-trip', iterations: ITERATIONS.MEDIUM, fn: (impl) => () => TEST_NUMBERS.forEach(n => { const roman = impl.romanize(n) impl.deromanize(roman) }) }, { name: 'Sequential 1-500 round-trip', iterations: ITERATIONS.SMALL, fn: (impl) => () => { for (let i = 1; i <= 500; i++) { const roman = impl.romanize(i) impl.deromanize(roman) } } } ] tests.forEach(test => { console.log(`\n${test.name}:`) const originalResult = benchmark('Original', test.fn(original), test.iterations) const optimizedResult = benchmark('Optimized', test.fn(optimized), test.iterations) const memoizedResult = benchmark('Memoized', test.fn(memoized), test.iterations) console.log(` Original: ${originalResult.opsPerSec.toLocaleString()} ops/sec`) console.log(` Optimized: ${optimizedResult.opsPerSec.toLocaleString()} ops/sec (${(optimizedResult.opsPerSec / originalResult.opsPerSec).toFixed(1)}x)`) console.log(` Memoized: ${memoizedResult.opsPerSec.toLocaleString()} ops/sec (${(memoizedResult.opsPerSec / originalResult.opsPerSec).toFixed(1)}x)`) }) } function memoryComparison() { console.log('\n=== MEMORY COMPARISON ===') function measureMemory(impl, name) { global.gc && global.gc() const before = process.memoryUsage() // Generate conversions const results = [] for (let i = 0; i < 50000; i++) { const num = Math.floor(Math.random() * 3999) + 1 results.push(impl.romanize(num)) } for (let i = 0; i < results.length; i++) { impl.deromanize(results[i]) } global.gc && global.gc() const after = process.memoryUsage() const heapDiff = (after.heapUsed - before.heapUsed) / 1024 / 1024 console.log(` ${name}: ${heapDiff.toFixed(2)} MB heap increase`) return heapDiff } const originalMem = measureMemory(original, 'Original') const optimizedMem = measureMemory(optimized, 'Optimized') const memoizedMem = measureMemory(memoized, 'Memoized') console.log(`\nMemory efficiency:`) console.log(` Optimized vs Original: ${(optimizedMem / originalMem).toFixed(2)}x`) console.log(` Memoized vs Original: ${(memoizedMem / originalMem).toFixed(2)}x`) } function validateCorrectness() { console.log('\n=== CORRECTNESS VALIDATION ===') let errors = 0 // Test all numbers 1-3999 for (let i = 1; i < 4000; i++) { const originalRoman = original.romanize(i) const optimizedRoman = optimized.romanize(i) const memoizedRoman = memoized.romanize(i) if (originalRoman !== optimizedRoman) { console.error(`Romanize mismatch at ${i}: ${originalRoman} vs ${optimizedRoman}`) errors++ } if (originalRoman !== memoizedRoman) { console.error(`Romanize mismatch at ${i}: ${originalRoman} vs ${memoizedRoman}`) errors++ } const originalDecimal = original.deromanize(originalRoman) const optimizedDecimal = optimized.deromanize(originalRoman) const memoizedDecimal = memoized.deromanize(originalRoman) if (originalDecimal !== optimizedDecimal) { console.error(`Deromanize mismatch at ${originalRoman}: ${originalDecimal} vs ${optimizedDecimal}`) errors++ } if (originalDecimal !== memoizedDecimal) { console.error(`Deromanize mismatch at ${originalRoman}: ${originalDecimal} vs ${memoizedDecimal}`) errors++ } } console.log(`Validation complete: ${errors} errors found`) if (errors === 0) { console.log('✅ All implementations produce identical results') } return errors === 0 } function main() { console.log('Romans Library Optimization Comparison') console.log('======================================') console.log(`Node.js ${process.version}`) console.log(`Platform: ${process.platform} ${process.arch}`) // Validate correctness first if (!validateCorrectness()) { console.error('❌ Correctness validation failed! Stopping benchmark.') return } compareRomanize() compareDeromanize() compareRoundTrip() memoryComparison() // Show cache stats for memoized version if (memoized.getCacheStats) { const stats = memoized.getCacheStats() console.log(`\nMemoized cache stats:`) console.log(` Romanize cache: ${stats.romanizeSize} entries`) console.log(` Deromanize cache: ${stats.deromanizeSize} entries`) } console.log('\n=== SUMMARY ===') console.log('🚀 Optimized version: Uses lookup tables for O(1) conversions') console.log('💾 Memoized version: Uses LRU cache for frequently accessed values') console.log('📊 Run with --expose-gc for accurate memory measurements') } if (require.main === module) { main() } module.exports = { compareRomanize, compareDeromanize, compareRoundTrip, memoryComparison, validateCorrectness }