# felixyz's solution

## to Say in the Swift Track

Published at Apr 14 2020 · 0 comments
Instructions
Test suite
Solution

Given a number from 0 to 999,999,999,999, spell out that number in English.

## Step 1

Handle the basic case of 0 through 99.

If the input to the program is `22`, then the output should be `'twenty-two'`.

Your program should complain loudly if given a number outside the blessed range.

Some good test cases for this program are:

• 0
• 14
• 50
• 98
• -1
• 100

### Extension

If you're on a Mac, shell out to Mac OS X's `say` program to talk out loud.

## Step 2

Implement breaking a number up into chunks of thousands.

So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0.

The program must also report any values that are out of range.

## Step 3

Now handle inserting the appropriate scale word between those chunks.

So `1234567890` should yield `'1 billion 234 million 567 thousand 890'`

The program must also report any values that are out of range. It's fine to stop at "trillion".

## Step 4

Put it all together to get nothing but plain English.

`12345` should give `twelve thousand three hundred forty-five`.

The program must also report any values that are out of range.

### Extensions

Use and (correctly) when spelling out the number in English:

• 14 becomes "fourteen".
• 100 becomes "one hundred".
• 120 becomes "one hundred and twenty".
• 1002 becomes "one thousand and two".
• 1323 becomes "one thousand three hundred and twenty-three".

## Setup

Go through the project setup instructions for Xcode using Swift:

http://exercism.io/languages/swift
http://exercism.io/languages/swift/tests

Notably from the source directory:

`swift test` runs tests
`swift package generate-xcodeproj` creates an Xcode project

## Source

A variation on JavaRanch CattleDrive, exercise 4a http://www.javaranch.com/say.jsp

## Submitting Incomplete Solutions

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

### LinuxMain.swift

``````import XCTest
@testable import SayTests

XCTMain([
testCase(SayTests.allTests),
])``````

### SayTests.swift

``````import XCTest
@testable import Say

class SayTests: XCTestCase {

func testZero() {
XCTAssertEqual("zero", Say.say(0))
}

func testOne() {
XCTAssertEqual("one", Say.say(1))
}

func testFourteen() {
XCTAssertEqual("fourteen", Say.say(14))
}

func testTwenty() {
XCTAssertEqual("twenty", Say.say(20))
}

func testTwentyTwo() {
XCTAssertEqual("twenty-two", Say.say(22))
}

func testOneHundred() {
XCTAssertEqual("one hundred", Say.say(100))
}

func testOneHundredTwentyThree() {
XCTAssertEqual("one hundred twenty-three", Say.say(123))
}

func testOneThousand() {
XCTAssertEqual("one thousand", Say.say(1_000))
}

func testOneThousandTwoHundredThirtyFour() {
XCTAssertEqual("one thousand two hundred thirty-four", Say.say(1_234))
}

func testOneMillion() {
XCTAssertEqual("one million", Say.say(1_000_000))
}

func testOneMillionTwoThousandThreeHundredFortyFive() {
XCTAssertEqual("one million two thousand three hundred forty-five", Say.say(1_002_345))
}

func testOneBillion() {
XCTAssertEqual("one billion", Say.say(1_000_000_000))
}

func testABigNumber() {
XCTAssertEqual("nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three", Say.say(987_654_321_123))
}

func testNumbersBelowZeroAreOutOfRange() {
XCTAssertNil(Say.say(-1))
}

func testNumbersAbove999999999999AreOutOfRange() {
XCTAssertNil(Say.say(1_000_000_000_000))
}

static var allTests: [(String, (SayTests) -> () throws -> Void)] {
return [
("testZero", testZero),
("testOne", testOne),
("testFourteen", testFourteen),
("testTwenty", testTwenty),
("testTwentyTwo", testTwentyTwo),
("testOneHundred", testOneHundred),
("testOneHundredTwentyThree", testOneHundredTwentyThree),
("testOneThousand", testOneThousand),
("testOneThousandTwoHundredThirtyFour", testOneThousandTwoHundredThirtyFour),
("testOneMillion", testOneMillion),
("testOneMillionTwoThousandThreeHundredFortyFive", testOneMillionTwoThousandThreeHundredFortyFive),
("testOneBillion", testOneBillion),
("testABigNumber", testABigNumber),
("testNumbersBelowZeroAreOutOfRange", testNumbersBelowZeroAreOutOfRange),
("testNumbersAbove999999999999AreOutOfRange", testNumbersAbove999999999999AreOutOfRange),
]
}
}``````
``````struct Say {
static let firstTwenty: [Int: String] = [
1: "one", 2: "two", 3: "three", 4: "four", 5: "five",
6: "six", 7: "seven", 8: "eight", 9: "nine", 10: "ten",
11: "eleven", 12: "twelve", 13: "thirteen", 14: "fourteen", 15: "fifteen",
16: "sixteen", 17: "seventeen", 18: "eighteen", 19: "nineteen"
]

static let tens: [Int: String] = [
20: "twenty", 30: "thirty", 40: "forty", 50: "fifty",
60: "sixty", 70: "seventy", 80: "eighty", 90: "twenty"
]

static let magnitudes = [1_000_000_000, 1_000_000, 1000, 100]

static let magnitudeNames: [Int: String] = [
1_000_000_000: "billion",
1_000_000: "million",
1000: "thousand",
100: "hundred"
]

static func say(_ number: Int) -> String? {
if number < 0 || number >= 1_000_000_000_000 { return nil }
if number == 0 { return "zero" }

let (counts, remainder) = countsAndRemainder(number: number, magnitudes: magnitudes)

var segments = magnitudes.reduce([String]()) { segments, magnitude in
let count = counts[magnitude]!
return count == 0 ? segments : segments + [numToText(count), magnitudeNames[magnitude]!]
}

if remainder > 0 { segments.append(numToText(remainder)) }

return segments.joined(separator: " ")
}

static func countsAndRemainder(number: Int, magnitudes: [Int]) -> ([Int: Int], Int) {
let initial = ([Int: Int](), number)

return magnitudes.reduce(initial) { acc, magnitude in
var (counts, num) = acc
let (remainder, count) = countForMagnitude(num, magnitude)

counts[magnitude] = count
return (counts, remainder)
}
}

static func countForMagnitude(_ num: Int, _ magnitude: Int) -> (Int, Int) {
var count = 0
var num = num

while num >= magnitude {
count += 1
num -= magnitude
}

return (num, count)
}

static func numToText(_ num: Int) -> String {
assert(num > 0 && num < 1000)

var num = num
var hundreds = 0

while num >= 100 {
hundreds += 1
num -= 100
}

var text = ""

if num < 20 {
text = num > 0 ? firstTwenty[num]! : ""
} else {
var tens = 20
num -= 20
while num > 10 {
tens += 10
num -= 10
}

text = self.tens[tens]!
if num > 0 {
text = "\(text)-\(firstTwenty[num]!)"
}
}

if hundreds > 0 {
text = numToText(hundreds) + " hundred " + text
}

return text
}
}``````