Solve each challenge before you open the solution. Then compare, and think about edge cases.
Challenge 1: palindrome
Write isPalindrome(string $text): bool that ignores case, spaces and punctuation. "A man, a plan, a canal: Panama" should be true.
Show a possible solution
<?php
function isPalindrome(string $text): bool
{
$clean = preg_replace('/[^a-z0-9]/', "", strtolower($text)) ?? "";
return $clean !== "" && $clean === strrev($clean);
}
var_dump(isPalindrome("A man, a plan, a canal: Panama"));
var_dump(isPalindrome("Hello"));
var_dump(isPalindrome(" "));
bool(true)
bool(false)
bool(false)
Challenge 2: anagrams
Write areAnagrams(string $a, string $b): bool.
Show a possible solution
<?php
function areAnagrams(string $a, string $b): bool
{
$normalise = function (string $s): array {
$letters = str_split(strtolower(preg_replace('/\s+/', "", $s) ?? ""));
sort($letters);
return $letters;
};
return $normalise($a) === $normalise($b);
}
var_dump(areAnagrams("Listen", "Silent"));
var_dump(areAnagrams("Hello", "World"));
bool(true)
bool(false)
Challenge 3: word frequency
Return the three most common words of a text (ignore case and punctuation). Break ties alphabetically.
Show a possible solution
<?php
function topWords(string $text, int $limit = 3): array
{
preg_match_all('/[a-z0-9\']+/', strtolower($text), $m);
$counts = array_count_values($m[0]);
uksort($counts, fn($a, $b) => $counts[$b] <=> $counts[$a] ?: strcmp($a, $b));
return array_slice($counts, 0, $limit, true);
}
print_r(topWords("The cat and the hat and the bat. The end!"));
Array
(
[the] => 4
[and] => 2
[bat] => 1
)
Challenge 4: flatten a nested array
Write flatten(array $data): array for any depth.
Show a possible solution
<?php
function flatten(array $data): array
{
$result = [];
array_walk_recursive($data, function ($value) use (&$result) {
$result[] = $value;
});
return $result;
}
echo implode(",", flatten([1, [2, [3, [4]], 5], [[6]]]));
1,2,3,4,5,6
Challenge 5: two numbers that sum to a target
Given an array and a target, return the positions of two numbers that add up to it, in one pass.
Show a possible solution
<?php
function twoSum(array $numbers, int $target): ?array
{
$seen = [];
foreach ($numbers as $i => $n) {
$need = $target - $n;
if (isset($seen[$need])) {
return [$seen[$need], $i];
}
$seen[$n] = $i;
}
return null;
}
echo json_encode(twoSum([2, 7, 11, 15], 9)) . " ";
var_dump(twoSum([1, 2, 3], 100));
[0,1] NULL
The array $seen is a hash lookup, so the whole search takes one pass instead of two nested loops.
Challenge 6: a small validator function
Write validate(array $data, array $rules): array where rules are "required", "email" and "min:N". Return an array of errors.
Show a possible solution
<?php
function validate(array $data, array $rules): array
{
$errors = [];
foreach ($rules as $field => $list) {
$value = trim((string) ($data[$field] ?? ""));
foreach ($list as $rule) {
if ($rule === "required" && $value === "") {
$errors[$field] = "$field is required";
} elseif ($rule === "email" && $value !== "" && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field] = "$field must be an email";
} elseif (str_starts_with($rule, "min:") && $value !== "" && mb_strlen($value) < (int) substr($rule, 4)) {
$errors[$field] = "$field is too short";
}
if (isset($errors[$field])) {
break;
}
}
}
return $errors;
}
print_r(validate(["name" => "A", "email" => "x"], ["name" => ["required", "min:2"], "email" => ["required", "email"], "age" => ["required"]]));
Array
(
[name] => name is too short
[email] => email must be an email
[age] => age is required
)
Challenge 7: bank account class
Create Account with deposit(), withdraw() and a balance(). Withdrawing more than the balance throws an exception. Deposits must be positive.
Show a possible solution
<?php
final class Account
{
private int $cents = 0;
public function deposit(int $cents): void
{
if ($cents <= 0) {
throw new InvalidArgumentException("Deposit must be positive.");
}
$this->cents += $cents;
}
public function withdraw(int $cents): void
{
if ($cents > $this->cents) {
throw new RuntimeException("Insufficient funds.");
}
$this->cents -= $cents;
}
public function balance(): string
{
return number_format($this->cents / 100, 2);
}
}
$a = new Account();
$a->deposit(10050);
try {
$a->withdraw(20000);
} catch (RuntimeException $e) {
echo $e->getMessage() . " ";
}
echo $a->balance();
Insufficient funds. 100.50
Money is stored as integer cents, not floats.
Challenge 8: FizzBuzz with match
Print 1 to 15 on one line, separated by spaces, using match(true).
Show a possible solution
<?php
$out = [];
foreach (range(1, 15) as $n) {
$out[] = match (true) {
$n % 15 === 0 => "FizzBuzz",
$n % 3 === 0 => "Fizz",
$n % 5 === 0 => "Buzz",
default => (string) $n,
};
}
echo implode(" ", $out);
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz