extract word matching
This commit is contained in:
parent
bc99731e62
commit
47141cf4c0
5 changed files with 114 additions and 39 deletions
|
|
@ -2,43 +2,25 @@
|
||||||
|
|
||||||
require __DIR__ . '/../autoload.php';
|
require __DIR__ . '/../autoload.php';
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Myrmidex\AnagramFinder\ArgHandler;
|
||||||
use Illuminate\Support\LazyCollection;
|
use Myrmidex\AnagramFinder\Matcher;
|
||||||
|
use Myrmidex\AnagramFinder\Output;
|
||||||
|
|
||||||
const DICTIONARY = __DIR__ . '/../assets/words.txt';
|
const DICTIONARY = __DIR__ . '/../assets/words.txt';
|
||||||
|
|
||||||
echo "ANAGRAM FINDER\n";
|
Output::line("ANAGRAM FINDER");
|
||||||
|
|
||||||
$words = LazyCollection::make(function () {
|
$args = ArgHandler::make($argv);
|
||||||
$fh = fopen(DICTIONARY, 'rb');
|
$anagram = $args->get(1) ?: readline("Enter the anagram: ");
|
||||||
try {
|
$minLength = $args->get(2) ?: 3;
|
||||||
while (($line = fgets($fh)) !== false) {
|
$top10 = $args->hasOption('top');
|
||||||
yield rtrim($line, "\r\n");
|
|
||||||
|
Output::line();
|
||||||
|
Output::line("Results for your anagram: " . $anagram);
|
||||||
|
|
||||||
|
$matches = Matcher::findWords($anagram, $minLength);
|
||||||
|
if ($top10) {
|
||||||
|
$matches = $matches->slice(0, 10);
|
||||||
}
|
}
|
||||||
} finally {
|
$matches->each(fn (string $word) => Output::line($word . " (" . strlen($word) . ")"));
|
||||||
fclose($fh);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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
33
src/ArgHandler.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,15 +3,49 @@
|
||||||
namespace Myrmidex\AnagramFinder;
|
namespace Myrmidex\AnagramFinder;
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\LazyCollection;
|
||||||
|
|
||||||
class Matcher
|
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
|
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)))
|
foreach (str_split($word) as $char) {
|
||||||
->countBy()
|
if (($pool[$char] ?? 0) === 0) {
|
||||||
->every(fn (int $needed, string $letter) => $available->get($letter, 0) >= $needed);
|
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
12
src/Output.php
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Myrmidex\AnagramFinder;
|
||||||
|
|
||||||
|
class Output
|
||||||
|
{
|
||||||
|
public static function line(string $message = ''): void
|
||||||
|
{
|
||||||
|
print($message . PHP_EOL);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,26 @@
|
||||||
|
|
||||||
namespace Myrmidex\AnagramFinder\Tests\Unit;
|
namespace Myrmidex\AnagramFinder\Tests\Unit;
|
||||||
|
|
||||||
|
use Myrmidex\AnagramFinder\Matcher;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class MatcherTest extends 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],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue