Skip to content

Commit

Permalink
Add Say exercise to track (#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
vnkmpf authored Aug 30, 2023
1 parent 62ea9d4 commit 643adb1
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 3
},
{
"slug": "say",
"name": "Say",
"uuid": "56873c13-5efd-462f-810f-1884d0c776a4",
"practices": [],
"prerequisites": [],
"difficulty": 2
}
]
},
Expand Down
50 changes: 50 additions & 0 deletions exercises/practice/say/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Description

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.
If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`.

## 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.
10 changes: 10 additions & 0 deletions exercises/practice/say/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.",
"authors": ["joohan"],
"contributors": [],
"files": {
"solution": ["Say.php"],
"test": ["SayTest.php"],
"example": [".meta/example.php"]
}
}
112 changes: 112 additions & 0 deletions exercises/practice/say/.meta/example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

function say(int $number): string
{
if ($number < 0 || $number > 999_999_999_999) {
throw new \InvalidArgumentException('Input out of range');
}

if (0 === $number) {
return 'zero';
}

$out = [];
$segments = [
[1_000_000_000, 'billion'],
[1_000_000, 'million'],
[1_000, 'thousand'],
[1, ''],
];

foreach ($segments as $segment) {
$word = getSegmentWord(intdiv($number, $segment[0]));
if ($word !== '') {
$out[] = sprintf('%s %s', $word, $segment[1]);
}
$number %= $segment[0];
}

return rtrim(implode(' ', $out), ' -');
}

function getSegmentWord(int $number): string
{
$word = '';

if ($number >= 100) {
$word .= getWord(intdiv($number, 100)) . ' hundred ';
$number %= 100;
}

if ($number > 20) {
return sprintf(
'%s%s-%s',
$word,
getWord(intdiv($number, 10) * 10),
getWord($number % 10),
);
}

return $word . getWord($number);
}

function getWord(int $number): string
{
return match ($number) {
1 => 'one',
2 => 'two',
3 => 'three',
4 => 'four',
5 => 'five',
6 => 'six',
7 => 'seven',
8 => 'eight',
9 => 'nine',

11 => 'eleven',
12 => 'twelve',
13 => 'thirteen',
14 => 'fourteen',
15 => 'fifteen',
16 => 'sixteen',
17 => 'seventeen',
18 => 'eighteen',
19 => 'nineteen',

10 => 'ten',
20 => 'twenty',
30 => 'thirty',
40 => 'forty',
50 => 'fifty',
60 => 'sixty',
70 => 'seventy',
80 => 'eighty',
90 => 'ninety',

default => '',
};
}
30 changes: 30 additions & 0 deletions exercises/practice/say/Say.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

function say(int $number): string
{
throw new \BadFunctionCallException("Implement the say function");
}
141 changes: 141 additions & 0 deletions exercises/practice/say/SayTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

class SayTest extends PHPUnit\Framework\TestCase
{
public static function setUpBeforeClass(): void
{
require_once 'Say.php';
}

public function testZero(): void
{
$this->assertEquals('zero', say(0));
}

public function testOne(): void
{
$this->assertEquals('one', say(1));
}

public function testFourteen(): void
{
$this->assertEquals('fourteen', say(14));
}

public function testTwenty(): void
{
$this->assertEquals('twenty', say(20));
}

public function testTwentyTwo(): void
{
$this->assertEquals('twenty-two', say(22));
}

public function testThirty(): void
{
$this->assertEquals('thirty', say(30));
}

public function testNinetyNine(): void
{
$this->assertEquals('ninety-nine', say(99));
}

public function testOneHundred(): void
{
$this->assertEquals('one hundred', say(100));
}

public function testOneHundredTwentyThree(): void
{
$this->assertEquals('one hundred twenty-three', say(123));
}

public function testTwoHundred(): void
{
$this->assertEquals('two hundred', say(200));
}

public function testNineHundredNinetyNine(): void
{
$this->assertEquals('nine hundred ninety-nine', say(999));
}

public function testOneThousand(): void
{
$this->assertEquals('one thousand', say(1000));
}

public function testOneThousandTwoHundredThirtyFour(): void
{
$this->assertEquals('one thousand two hundred thirty-four', say(1234));
}

public function testOneMillion(): void
{
$this->assertEquals('one million', say(1_000_000));
}

public function testOneMillionTwoThousandThreeHundredFortyFive(): void
{
$this->assertEquals(
'one million two thousand three hundred forty-five',
say(1_002_345),
);
}

public function testOneBillion(): void
{
$this->assertEquals('one billion', say(1_000_000_000));
}

public function testABigNumber(): void
{
$this->assertEquals(
'nine hundred eighty-seven billion six hundred fifty-four million '
. 'three hundred twenty-one thousand one hundred twenty-three',
say(987_654_321_123),
);
}

public function testNumbersBelowZeroAreOutOfRange(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Input out of range');

say(-1);
}

public function testNumbersAbove999999999999AreOutOfRange(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Input out of range');

say(1_000_000_000_000);
}
}

0 comments on commit 643adb1

Please sign in to comment.