opened 05:02PM - 26 Apr 25 UTC
The test suite for the `say` exercise in the Scala track appears to be out of sy…nc with the canonical data specified in `exercism/problem-specifications`.
**Canonical Data:** [https://github.com/exercism/problem-specifications/blob/main/exercises/say/canonical-data.json](https://github.com/exercism/problem-specifications/blob/main/exercises/say/canonical-data.json)
**Problem:**
The current test suite in the Scala track seems to be missing specific test cases present in the canonical data, particularly those testing multiples of hundreds, thousands, millions, etc., where the remainder after division is zero.
For example, the canonical data includes (among others) a test case for the input `200` which expects the output `"two hundred"`:
```json
{
"uuid": "2f061132-54bc-4fd4-b5df-0a3b778959b9",
"description": "two hundred",
"property": "say",
"input": {
"number": 200
},
"expected": "two hundred"
}
```
Other similar cases involve inputs like `1000`, `1000000`, `2000`, `987000`, etc., which test the correct handling of zero remainders during the number-to-word conversion.
**Impact:**
The absence of these test cases allows solutions with a common bug to pass the Scala track tests. This bug typically occurs when recursively building the string for numbers like 200 or 2000. A naive implementation might incorrectly append " zero" (e.g., producing `"two hundred zero"` instead of `"two hundred"`).
**Example Buggy Solution (Passes current Scala tests, but fails canonical tests):**
The following solution demonstrates the bug related to handling zero remainders. It incorrectly constructs strings like `"two hundred zero"` but might pass the existing Scala test suite because the specific test cases (like input `200`) are missing.
```scala
object Say:
private val MaxPronounceableNumber = 999_999_999_999L
private val Billion = 1_000_000_000L
private val Million = 1_000_000
private val Thousand = 1_000
def inEnglish(number: Long): Option[String] =
Option(sayNumber(number))
private def sayNumber(number: Long): String =
// BUG is in this nested function: Doesn't handle remainder == 0 correctly
def say(divisor: Long, name: String): String =
sayNumber(number / divisor) + " " + name + " " + sayNumber(number % divisor)
number match
case x if x < 0 || MaxPronounceableNumber < x => null
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case 3 => "three"
case 4 => "four"
case 5 => "five"
case 6 => "six"
case 7 => "seven"
case 8 => "eight"
case 9 => "nine"
case 10 => "ten"
case 11 => "eleven"
case 12 => "twelve"
case 13 => "thirteen"
case 14 => "fourteen"
case 15 => "fifteen"
case 16 => "sixteen"
case 17 => "seventeen"
case 18 => "eighteen"
case 19 => "nineteen"
case 20 => "twenty"
case 30 => "thirty"
case 40 => "forty"
case 50 => "fifty"
case 60 => "sixty"
case 70 => "seventy"
case 80 => "eighty"
case 90 => "ninety"
case n if n < 100 => sayNumber(n / 10 * 10) + "-" + sayNumber(n % 10)
// Note: Specific cases like 100, 1000 work fine due to explicit matches,
// but the recursive calls using 'say' fail for other multiples.
case 100 => "one hundred"
case n if n < Thousand => say(100, "hundred") // Fails for 200, 300 etc.
case Thousand => "one thousand"
case n if n < Million => say(Thousand, "thousand") // Fails for 2000, 3000 etc.
case Million => "one million"
case n if n < Billion => say(Million, "million")
case Billion => "one billion"
case _ => say(Billion, "billion")
end Say
```
**Proposed Action:**
Update the Scala track's test suite for the `say` exercise (`SayTest.scala`) to align fully with the test cases defined in the canonical data file. This will ensure more robust testing and prevent incorrect solutions from passing.