extract word matching

This commit is contained in:
myrmidex 2026-08-22 17:52:17 +02:00
parent bc99731e62
commit 47141cf4c0
5 changed files with 114 additions and 39 deletions

View file

@ -2,43 +2,25 @@
require __DIR__ . '/../autoload.php';
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Myrmidex\AnagramFinder\ArgHandler;
use Myrmidex\AnagramFinder\Matcher;
use Myrmidex\AnagramFinder\Output;
const DICTIONARY = __DIR__ . '/../assets/words.txt';
echo "ANAGRAM FINDER\n";
Output::line("ANAGRAM FINDER");
$words = LazyCollection::make(function () {
$fh = fopen(DICTIONARY, 'rb');
try {
while (($line = fgets($fh)) !== false) {
yield rtrim($line, "\r\n");
$args = ArgHandler::make($argv);
$anagram = $args->get(1) ?: readline("Enter the anagram: ");
$minLength = $args->get(2) ?: 3;
$top10 = $args->hasOption('top');
Output::line();
Output::line("Results for your anagram: " . $anagram);
$matches = Matcher::findWords($anagram, $minLength);
if ($top10) {
$matches = $matches->slice(0, 10);
}
} finally {
fclose($fh);
}
});
$matches->each(fn (string $word) => Output::line($word . " (" . strlen($word) . ")"));
function output($message)
{
$message = $message . PHP_EOL;
print($message);
flush();
// ob_flush();
}
$anagramBase = readline("Enter the anagram: ");
echo "\n";
echo "Results for your anagram: " . $anagramBase . "\n";
$anagramChars = new Collection(explode($anagramBase, ""));
echo $words
->filter(fn (string $word) => ctype_alpha($word))
->filter(fn (string $word) => strtolower($word) === $word)
->map(fn (string $word) => new Collection(explode($word, "")))
->filter(fn (Collection $wordChars) => $wordChars->count() === $anagramChars->intersect($wordChars)->count())
->count();
// ->map(fn (Collection $wordChars) => $wordChars->join(""))
// ->each(fn (string $word) => output($word));

33
src/ArgHandler.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace Myrmidex\AnagramFinder;
use Illuminate\Support\Collection;
class ArgHandler
{
private Collection $args;
public function __construct(array $argv)
{
$this->args = new Collection($argv);
}
public static function make(array $argv): self
{
return new self($argv);
}
public function get(int $index)
{
return $this->args
->reject(fn ($word) => substr($word, 0, 2) === '--')
->get($index);
}
public function hasOption(string $option) {
return $this->args
->filter(fn ($word) => $word === '--' . $option)
->isNotEmpty();
}
}

View file

@ -3,15 +3,49 @@
namespace Myrmidex\AnagramFinder;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
class Matcher
{
public static function findWords(string $anagram, int $minLength = 3): Collection
{
$matches = self::getWordList()
->filter(fn (string $word) => strlen($word) >= $minLength)
->filter(fn (string $word) => ctype_alpha($word))
->filter(fn (string $word) => $word === strtolower($word))
->filter(fn (string $word) => self::check($anagram, $word))
->toArray();
return new Collection($matches)
->sort(fn ($a, $b) => strlen($b) - strlen($a));
}
public static function check(string $anagram, string $word): bool
{
$available = (new Collection(str_split($anagram)))->countBy();
$pool = array_count_values(str_split($anagram));
return (new Collection(str_split($word)))
->countBy()
->every(fn (int $needed, string $letter) => $available->get($letter, 0) >= $needed);
foreach (str_split($word) as $char) {
if (($pool[$char] ?? 0) === 0) {
return false;
}
$pool[$char]--;
}
return true;
}
private static function getWordList(): LazyCollection
{
return LazyCollection::make(function () {
$fh = fopen(DICTIONARY, 'rb');
try {
while (($line = fgets($fh)) !== false) {
yield rtrim($line, "\r\n");
}
} finally {
fclose($fh);
}
});
}
}

12
src/Output.php Normal file
View file

@ -0,0 +1,12 @@
<?php
namespace Myrmidex\AnagramFinder;
class Output
{
public static function line(string $message = ''): void
{
print($message . PHP_EOL);
flush();
}
}

View file

@ -2,12 +2,26 @@
namespace Myrmidex\AnagramFinder\Tests\Unit;
use Myrmidex\AnagramFinder\Matcher;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
final class MatcherTest extends TestCase
{
public function testCheck()
#[DataProvider('checkProvider')]
public function testCheck(string $anagram, string $word, bool $isExpectedMatch)
{
return;
$this->assertEquals($isExpectedMatch, Matcher::check($anagram, $word));
}
public static function checkProvider(): array
{
return [
'valid1' => [ "eamstoxil", "steam", true ],
'valid2' => [ "wordneoxs", "downers", true ],
'valid3' => [ "ioevpmoef", "movie", true ],
'invalid_basic' => [ "eamstoxil", "smear", false],
'invalid_double_e' => [ "eamstexil", "smear", false],
];
}
}