Exercism v3 launches on Sept 1st 2021. Learn more! ๐Ÿš€๐Ÿš€๐Ÿš€
Avatar of rootulp

rootulp's solution

to Robot Name in the TypeScript Track

Published at Jun 04 2021 · 0 comments
Instructions
Test suite
Solution

Manage robot factory settings.

When robots come off the factory floor, they have no name.

The first time you boot them up, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811.

Every once in a while we need to reset a robot to its factory settings, which means that their name gets wiped. The next time you ask, it will respond with a new random name.

The names must be random: they should not follow a predictable sequence. Random names means a risk of collisions. Your solution must ensure that every existing robot has a unique name.

Setup

Go through the setup instructions for TypeScript to install the necessary dependencies:

https://exercism.io/tracks/typescript/installation

Requirements

Install assignment dependencies:

$ yarn install

Making the test suite pass

Execute the tests with:

$ yarn test

In the test suites all tests but the first have been skipped.

Once you get a test passing, you can enable the next one by changing xit to it.

Source

A debugging session with Paul Blackwell at gSchool.

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

robot-name.test.ts

import Robot from './robot-name'

const areSequential = (name1: string, name2: string): boolean => {
  const alpha1 = name1.substr(0, 2)
  const alpha2 = name2.substr(0, 2)
  const num1 = +name1.substr(2, 3)
  const num2 = +name2.substr(2, 3)

  const numDiff = num2 - num1
  const alphaDiff =
    (alpha2.charCodeAt(0) - alpha1.charCodeAt(0)) * 26 +
    (alpha2.charCodeAt(1) - alpha1.charCodeAt(1))

  const totalDiff = alphaDiff * 1000 + numDiff

  return Math.abs(totalDiff) <= 1
}

const NAME_RE = /^[A-Z]{2}\d{3}$/
const TOTAL_NUMBER_OF_NAMES =
  26 * // A-Z
  26 * // A-Z
  10 * // 0-9
  10 * // 0-9
  10 // 0-9

describe('Robot', () => {
  let robot: Robot

  beforeEach(() => {
    robot = new Robot()
  })

  afterEach(() => {
    Robot.releaseNames()
  })

  it('has a name', () => {
    expect(robot.name).toMatch(NAME_RE)
  })

  xit('name is the same each time', () => {
    expect(robot.name).toEqual(robot.name)
  })

  xit('different robots have different names', () => {
    const differentRobot = new Robot()
    expect(differentRobot.name).not.toEqual(robot.name)
  })

  xit('is able to reset the name', () => {
    const originalName = robot.name

    robot.resetName()
    const newName = robot.name

    expect(newName).toMatch(NAME_RE)
    expect(originalName).not.toEqual(newName)
  })

  xit('should set a unique name after reset', () => {
    const NUMBER_OF_ROBOTS = 10000
    const usedNames = new Set()

    usedNames.add(robot.name)
    for (let i = 0; i < NUMBER_OF_ROBOTS; i++) {
      robot.resetName()
      usedNames.add(robot.name)
    }

    expect(usedNames.size).toEqual(NUMBER_OF_ROBOTS + 1)
  })

  xit('new names should not be sequential', () => {
    const name1 = robot.name
    const name2 = new Robot().name
    const name3 = new Robot().name
    expect(areSequential(name1, name1)).toBe(true)
    expect(areSequential(name1, name2)).toBe(false)
    expect(areSequential(name2, name3)).toBe(false)
  })

  xit('names from reset should not be sequential', () => {
    const name1 = robot.name
    robot.resetName()
    const name2 = robot.name
    robot.resetName()
    const name3 = robot.name
    expect(areSequential(name1, name2)).toBe(false)
    expect(areSequential(name2, name3)).toBe(false)
    expect(areSequential(name3, name3)).toBe(true)
  })

  // This test is optional.
  xit('all the names can be generated', () => {
    const usedNames = new Set()
    usedNames.add(robot.name)

    for (let i = 0; i < TOTAL_NUMBER_OF_NAMES - 1; i += 1) {
      const newRobot = new Robot()
      usedNames.add(newRobot.name)
    }

    expect(usedNames.size).toEqual(TOTAL_NUMBER_OF_NAMES)
  })
})
export default class Robot {
  private _name: string;
  private static existingNames = new Set();
  constructor() {
    this._name = this.generateName();
    Robot.existingNames.add(this._name);
  }

  public get name(): string {
    return this._name;
  }

  public resetName(): void {
    const name = this.generateName();
    if (Robot.existingNames.has(name)) {
      this.resetName();
    } else {
      this._name = name;
      Robot.existingNames.add(this._name);
    }
  }

  public static releaseNames(): void {
    Robot.existingNames = new Set();
  }

  private generateName(): string {
    return [
      this.randomLetter(),
      this.randomLetter(),
      this.randomNumber(),
      this.randomNumber(),
      this.randomNumber(),
    ].join("")
  }

  private randomLetter(): string {
    const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return ALPHABET.charAt(Math.floor(Math.random() * 26));
  }

  private randomNumber(): string {
    return Math.floor(Math.random() * 10).toString();
  }
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other peopleโ€™s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?