case class DNA(strand: String) {
private def findInvalid(strand: String): Option[Either[String, Nothing]] =
strand
.find(!"ACTG".contains(_))
.map(chr => Left(f"invalid nucleotide '$chr'"))
def nucleotideCounts: Either[String, Map[Char, Int]] =
findInvalid(strand)
.getOrElse(Right("ACTG".map(chr => (chr, strand.count(_ == chr))).toMap))
}
This approach starts by defining a private validating method that checks for invalid characters.
It does this by calling the find() method on the input String, passing each character in the String
to be checked by the contains() method.
The underscore (_) wildcard is used to pass in the character, since it doesn't need to be named.
If the ACGT String contains all of the characters, it passes None to the map() method.
Otherwise, the first character that is not contained in ACGT is passed as a Some value to map().
If map() receives None, then it returns None from the validating method.
If map receives a Some, then map() converts it to a Some Left value which is returned from the method.
The nucleotideCounts() method calls the validating method. If there is any invalid character, then
the getOrElse() method returns the Left value from nucleotideCounts().
If getOrElse() receives None, then it maps each character in the ACGT String, passing each into
a lambda which uses the count() method to count the occurrences of the character in the
input String.
The resulting IndexedSeq is converted to a Map by the toMap() method, which is returned from
nucleotideCounts(), wrapped as a Right value.