portunus
Version:
Converts from decimal numbers to roman numerals and the other way around.
145 lines (118 loc) • 5.28 kB
text/typescript
type PortunusInput = string | number;
type PortunusConvertedValue = string | number;
interface PortunusOutput {
error?: string;
convertedValue?: PortunusConvertedValue;
}
const romanNumeralToDecimalValueMap: { [numeral: string]: number } = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000,
};
const decimalValueToRomanNumeralMap: { [decimal: number]: string } = Object.keys(romanNumeralToDecimalValueMap)
.reduce((acc, currentValue) => {
return {
[romanNumeralToDecimalValueMap[currentValue]]: currentValue,
...acc,
};
}, {});
const sortedNumeralCollection: string[] = Object.keys(romanNumeralToDecimalValueMap)
.sort((a, b) => romanNumeralToDecimalValueMap[b] - romanNumeralToDecimalValueMap[a]);
const sortedDecimalCollection: number[] = Object.keys(decimalValueToRomanNumeralMap)
.map(d => parseInt(d, 10))
.sort((a, b) => b - a);
function isPortunusInput(input: any): input is PortunusInput {
return typeof input === 'string' || typeof input === 'number';
}
export function portunus(numeral: string): PortunusOutput;
export function portunus(integer: number): PortunusOutput;
export function portunus(input: PortunusInput): PortunusOutput {
if (!isPortunusInput(input)) {
return {
error: `Provided input (${input}) not a valid portunus input.`,
};
}
if (typeof input === 'string') {
return { convertedValue: portunusPeonNumeralToDecimal(input.toUpperCase()) };
}
if (typeof input === 'number') {
return { convertedValue: portunusPeonDecimalToNumeral(input) };
}
return {
error: `Something went wrong, perhaps input type check failed? I expect a number or string as input and got: ${typeof input}`,
};
}
function portunusPeonNumeralToDecimal(numeral: string): number {
const numeralCollection: string[] = [].slice.call(numeral);
return numeralCollection.reduce((acc, currentValue, currentIndex) => {
const decimalValueForCurrentNumeral: number = romanNumeralToDecimalValueMap[currentValue];
const previousNumeral: string = numeralCollection[currentIndex - 1];
const decimalValueForPreviousNumeral: number = romanNumeralToDecimalValueMap[previousNumeral];
const subtractiveNotationPresent: boolean = !!decimalValueForPreviousNumeral && (decimalValueForPreviousNumeral < decimalValueForCurrentNumeral);
let runningTally: number = acc + decimalValueForCurrentNumeral;
if (subtractiveNotationPresent) {
runningTally -= (decimalValueForPreviousNumeral * 2);
}
return runningTally;
}, 0);
}
function portunusPeonDecimalToNumeral(integer: number): string {
const numeralWithoutSubtractiveNotation: string = sortedDecimalCollection
.reduce((acc, currentDecimal) => {
let numeral: string = decimalValueToRomanNumeralMap[currentDecimal];
let occurencesOfNumeral: number = Math.floor(integer / currentDecimal);
integer -= (currentDecimal * occurencesOfNumeral);
return `${acc}${numeral.repeat(occurencesOfNumeral)}`;
}, '');
const numeralWithoutSubtractiveNotationCollection: string[] = [].slice.call(numeralWithoutSubtractiveNotation);
let numeralWithSubtractiveNotationCollection: string[] = [];
for (let i: number = 0; i < numeralWithoutSubtractiveNotationCollection.length; ++i) {
const numeral: string = numeralWithoutSubtractiveNotationCollection[i];
const numeralRankIndex: number = sortedNumeralCollection.indexOf(numeral);
const nextLowestRankingNumeralIndex: number = numeralRankIndex + 1;
const nextLowestRankingNumeral: string = sortedNumeralCollection[nextLowestRankingNumeralIndex];
const nextHighestRankingNumeralIndex: number = numeralRankIndex - 1;
const nextHighestRankingNumeral: string = sortedNumeralCollection[nextHighestRankingNumeralIndex];
const nextFourNumerals: string[] = [1, 2, 3, 4].map(indexOffset => numeralWithoutSubtractiveNotationCollection[i + indexOffset]);
const nextThreeNumerals: string[] = nextFourNumerals.slice(0, 3);
/*console.log({
numeralWithSubtractiveNotationCollection,
numeral,
numeralRankIndex,
nextLowestRankingNumeral,
nextLowestRankingNumeralIndex,
nextHighestRankingNumeral,
nextHighestRankingNumeralIndex,
nextFourNumerals,
nextThreeNumerals,
});*/
if (!!nextHighestRankingNumeral) {
if (nextThreeNumerals.every(n => sortedNumeralCollection.indexOf(n) === numeralRankIndex)) {
/*console.log({
msg: 'here in next three',
nextThreeNumerals,
numeralWithoutSubtractiveNotation,
});*/
numeralWithSubtractiveNotationCollection.push(`${numeral}${nextHighestRankingNumeral}`)
i += 3;
continue;
}
if (nextFourNumerals.every(n => sortedNumeralCollection.indexOf(n) === nextLowestRankingNumeralIndex)) {
/*console.log({
msg: 'here in next four',
nextFourNumerals,
numeralWithoutSubtractiveNotation,
});*/
numeralWithSubtractiveNotationCollection.push(`${nextLowestRankingNumeral}${nextHighestRankingNumeral}`);
i += 4;
continue;
}
}
numeralWithSubtractiveNotationCollection.push(numeral);
};
return numeralWithSubtractiveNotationCollection.join('');
}