DEV Community

Discussion on: Daily Challenge #296 - Years to Centuries

Collapse
 
willsmart profile image
willsmart • Edited

Here's a typescript implementation, and a ✨verbose-and-pretty✨ reference to check it against.
It's funny, but I take much more care with these little easy tasks than I used to. There's always more nuance than you expect and the implementations end up sitting at the bottom of your library being used for everything.

(btw, I tend to use ~~ as an alternative to of Math.floor because it's more succinct, and most often a little quicker)

function suffixForOrdinal(ordinal: number): string {
  return (~~(ordinal / 10) % 10 !== 1 && ['th', 'st', 'nd', 'rd'][ordinal % 10]) || 'th';
}

function nameOfCentury(yearOrdinal: string | number): string {
  const centuryOrdinal = ~~((+yearOrdinal + 99) / 100);
  return `${centuryOrdinal}${suffixForOrdinal(centuryOrdinal)}`;
}

I'll check my working by comparing with a naive implementation that's more readable and hopefully reliable out of the gate...

// Stringifying the ordinal is an easier way to get individual the parts of the year...
function suffixForOrdinal_slowButSure(ordinal: number): string {
  const ordinalString = String(ordinal),
    tens = +ordinalString.slice(-2, -1),
    ones = +ordinalString.slice(-1);
  if (tens == 1) return 'th'; // i.e. 11th, not 11st
  if (ones == 1) return 'st'; // 81st
  if (ones == 2) return 'nd'; // 82nd
  if (ones == 3) return 'rd'; // 83rd
  return 'th'; // anything else is 'th'. 45th
}

function nameOfCentury_slowButSure(yearOrdinal: string | number): string {
  // When extracting the century we need to be working with the index, not ordinal.
  // That's why the weirdness about 2000 -> 20th, 2001 -> 21st
  //    (The year 2000 as an ordinal has index 1999)
  const yearIndex = +yearOrdinal - 1,
    yearIndexString = String(yearIndex),
    centuryIndex = +yearIndexString.slice(0, -2) || 0,
    centuryOrdinal = centuryIndex + 1;
  return `${centuryOrdinal}${suffixForOrdinal_slowButSure(centuryOrdinal)}`;
}


// Now do some 'testing' by comparing the implementations
// This ensures that either both are right or both are wrong,
//   I'm pretty sure that the verbose one is as right as any static fixtures I could make.
(function hareVsTortoise(startYearOrdinal = 1, endYearOrdinal = 100000) {
  for (let yearOrdinal = startYearOrdinal; yearOrdinal <= endYearOrdinal; yearOrdinal++) {
    if (nameOfCentury(yearOrdinal) !== nameOfCentury_slowButSure(yearOrdinal)) {
      console.error(
        `Conflicting answers for year ${yearOrdinal}:\n      Hare: "${nameOfCentury(
          yearOrdinal
        )}"\n  Tortoise: "${nameOfCentury_slowButSure(yearOrdinal)}"`
      );
      return;
    }
  }
  console.log(`Checked ${endYearOrdinal + 1 - startYearOrdinal} years, all seems good`);
})();

/*-->
Checked 100000 years, all seems good
[1, 99, 100, 101, 200, 300, 400, 1000, 1100, 1200, 1300, 1400, 1900, 2000, 2001, 2020, 10000, 10100].forEach(year => {
  console.log(`Year ${year} --> ${nameOfCentury(year)}`);
});

/*-->
Year 1 --> 1st
Year 99 --> 1st
Year 100 --> 1st
Year 101 --> 2nd
Year 200 --> 2nd
Year 300 --> 3rd
Year 400 --> 4th
Year 1000 --> 10th
Year 1100 --> 11th
Year 1200 --> 12th
Year 1300 --> 13th
Year 1400 --> 14th
Year 1900 --> 19th
Year 2000 --> 20th
Year 2001 --> 21st
Year 2020 --> 21st
Year 10000 --> 100th
Year 10100 --> 101st