Randomly add to used names

Robot Name
Robot Name in Scala
import scala.annotation.tailrec
import scala.util.Random

class Robot() {
  import Robot.addName
  var name: String = addName
  def reset(): Unit = name = addName
}

object Robot {
  private val randy = Random
  private var savedNames = scala.collection.mutable.Set[String]()

  private object CharType extends Enumeration {
    type CharType = Value
    val Letter, Number = Value
  }
  import CharType._

  private def getNextRandom(base: Int, high: Int): Int =
    base + randy.nextInt(high)

  private def genChar(charType: CharType): Char = {
    charType match {
      case Letter => (getNextRandom(65, 26)).toChar
      case Number => (getNextRandom(48, 10)).toChar
    }
  }

  @tailrec
  private def nameMe(chars: List[Char]): String = {
    chars.length match {
      case len if len < 2 => nameMe(genChar(Letter) :: chars)
      case len if len < 5 => nameMe(genChar(Number) :: chars)
      case _              => chars.reverse.mkString("")
    }
  }

  @tailrec
  private def addName: String = {
    val temp = nameMe(List[Char]())
    temp match {
      case name if savedNames contains name => addName
      case name =>
        savedNames += name
        name
    }
  }
}

There are many possible variations of randomly adding to used names.

This approach uses a mutable Set for storing the names.

An Enumeration is defined for determining if a letter or digit is being generated.

The getNextRandom() method is passed in the lower bound for an ASCII value as the first argument. The value will be 65 for A or 40 for 0. The second argument is a value for generating a random number from 0 up but not including the second argument. The randomly generated number from the nextInt() method is added to the lower bound and returned from the method as a character, from either A through Z or 0 through 9.

The genChar() method passes the appropriate bounds to getNextRandom() for either a letter or a number.

The nameMe() method passes in the appropriate Enumeraton value to genChar() based on the length of the name being built. The name starts as an empty List and is built with the cons operator (::) until it reaches all two letters and three digits. The nameMe() method calls itself, passing in the list of name characters until it is complete, at which point it reverse()s the characters and uses mkString() to return them as a String.

It is annotated with the @tailrec annotation to verify that the method can be compiled with tail call optimization.

A tail call is a particular form of recursion where the last call in the method is a call to the same method and nothing else.

In other words, if the last call in recurMe() is recurMe(arg1, arg2) + 1, the + 1 makes the recursion non-tail recursive.

If the last call in recurMe() is recurMe(arg1, arg2, acc + 1), then the recursion is a tail call, because only the method is being called with no other operation being peformed on it.

The addName() method is also tail recursive. It will keep calling itself until a name is generated which hasn't been generated before, at which point the newly generated name is added to the Set of used names and is returned from the method.

Other variations of this approach could generate the numeric part of the name as one number instead of three separate digits. This particular variation of generating three separate digits, even with the collision against used names, can finish all the tests, including generating all 676,000 unique names, in about five seconds.

Interestingly, when the code is refactored to generate the name with one number instead of three digits, it takes about eight seconds to finish all the tests, including generating all 676,000 unique names, which perhaps better demonstrates the disadvantage of collision:

private def genChar(): Char = (65 + randy.nextInt(26)).toChar

private def genNumber(): String = f"${randy.nextInt(1000)}%03d"

@tailrec
private def nameMe(chars: List[Char]): String = {
  chars.length match {
    case len if len < 2 => nameMe(genChar() :: chars)
    case _              => chars.mkString("", "", genNumber())
  }
}
16th Aug 2024 · Found it useful?