如何使用 Perl 从一组字母中生成单词列表?
Posted
技术标签:
【中文标题】如何使用 Perl 从一组字母中生成单词列表?【英文标题】:How can I generate a list of words from a group of letters using Perl? 【发布时间】:2012-02-24 16:42:39 【问题描述】:我正在寻找一个模块、正则表达式或任何其他可能适用于这个问题的东西。
如何以编程方式解析字符串并创建已知的英语 &|鉴于我有一个字典表,我可以根据它检查算法随机化的每个排列是否匹配?
给定一组字符:EBLAIDL KDIOIDSI ADHFWB
程序应该返回:BLADE
AID
KID
KIDS
FIDDLE
HOLA
等......
我还希望能够定义最小和最大字长以及音节数
输入长度无所谓,只能是字母,标点无所谓。
感谢您的帮助
编辑输入字符串中的字母可以重复使用。
例如,如果输入是:ABLED
,那么输出可能包含:BALL
或 BLEED
【问题讨论】:
音节数?这将是一个艰难的...我很想看看你想出什么! Syllabification 在西班牙语中是微不足道的,但在英语中却相当困难。现有的模块做得不太好;我写了自己的版本,效果更好,但现在我不能动手。 castellano 有一个Lingua::ES::Syllabify
模块,由 Al·ber·to Mon·te·ro A·sen·jo 编写。
应该ABL
返回BALL
吗?还是每个字母只能使用一次?
@ikegami - 好问题,字母可以重复使用。生病更新我的问题,谢谢
【参考方案1】:
您没有指定,所以我假设输入中的每个字母只能使用一次。
[你已经指定输入中的字母可以多次使用,但我将把这篇文章留在这里,以防有人发现它有用。]
有效地做到这一点的关键是对单词中的字母进行排序。
abracadabra => AAAAABBCDRR
abroad => AABDOR
drab => ABDR
那么很明显,“drab”在“abracadabra”中。
abracadabra => AAAAABBCDRR
drab => A B DR
“国外”不是。
abracadabra => AAAAABBCD RR
abroad => AA B DOR
我们将排序后的字母称为“签名”。如果您可以从“A”的签名中删除字母以获得“B”的签名,则单词“B”在单词“A”中。使用正则表达式模式很容易检查。
sig('drab') =~ /^A?A?A?A?A?B?B?C?D?R?R?\z/
或者,如果我们为了效率而消除不必要的回溯,我们会得到
sig('drab') =~ /^A?+A?+A?+A?+A?+B?+B?+C?+D?+R?+R?+\z/
既然我们知道了我们想要什么模式,那就只是构建它的问题。
use strict;
use warnings;
use feature qw( say );
sub sig join '', sort grep /^\pL\z/, split //, uc $_[0]
my $key = shift(@ARGV);
my $pat = sig($key);
$pat =~ s/.\K/?+/sg;
my $re = qr/^(?:$pat)\z/s;
my $shortest = 9**9**9;
my $longest = 0;
my $count = 0;
while (my $word = <>)
chomp($word);
next if !length($word); # My dictionary starts with a blank line!!
next if sig($word) !~ /$re/;
say $word;
++$count;
$shortest = length($word) if length($word) < $shortest;
$longest = length($word) if length($word) > $longest;
say "Words: $count";
if ($count)
say "Shortest: $shortest";
say "Longest: $longest";
例子:
$ perl script.pl EBLAIDL /usr/share/dict/words
A
Abe
Abel
Al
...
libel
lid
lie
lied
Words: 117
Shortest: 1
Longest: 6
【讨论】:
【参考方案2】:嗯,正则表达式相当简单......然后你只需要遍历字典中的单词。 EG,假设是标准的 linux:
# perl -n -e 'print if (/^[EBLAIDL]+$/);' /usr/share/dict/words
将快速返回该文件中包含这些且仅包含这些字母的所有单词。
A
AA
AAA
AAAA
AAAAAA
AAAL
AAE
AAEE
AAII
AB
...
不过,正如您所见,您需要一个有价值的字典文件 有。特别是我的 Fedora 系统上的 /usr/share/dict/words 包含一堆单词,所有 As 可能是也可能不是 你想要的东西。所以请仔细选择您的字典文件。
对于最小和最大长度,您也可以快速获得:
$min = 9999;
$max = -1;
while(<>)
if (/[EBLAIDL]+$/)
print;
chomp;
if (length($_) > $max)
$max = length($_);
$maxword = $_;
if (length($_) < $min)
$min = length($_);
$minword = $_;
print "longest: $maxword\n";
print "shortest: $minword\n";
将产生:
ZI
ZMRI
ZWEI
longest: TANSTAAFL
shortest: A
如上面的 cmets 所述,将单词分解成碎片并计算音节是非常特定于语言的。
【讨论】:
不幸的是,您假设您有无限数量的字符 ([EBLAIDL]+
),我猜这不是 OP 想要的。例如,使用字母 A、B 和 N 您可以创建“BAN”,但不能创建“BANANA”
如何调整此脚本以使用 mysql 数据库表作为其字典?我应该将表格导出到平面文件中吗?如果可以的话,我宁愿查询表并将数组中存储的结果用作字典。有什么想法吗?
不是从文件while (<>)
读取,而是从数据库中获取一行。 DBI 是 Perl 的标准数据库。
@CheeseConQueso:你的数据库是什么?根据您的数据库是什么,您甚至不需要 perl 来执行此操作。例如 PostgreSQL 可以简单地做select * from words where word similar to '[EBLAIDL]+'
@CheeseConQueso:对于 mysql:select * from words where word regexp '^[EBLAIDL]+$'
【参考方案3】:
我能想到的唯一方法是解析所有可能的字母组合,并将它们与字典进行比较。将它们与字典进行比较的最快方法是将字典转换为哈希。这样,您可以快速查找该词是否为有效词。
为了安全起见,我通过将字典单词中的所有字母小写然后删除所有非字母字符来键入我的字典。对于该值,我将存储实际的字典单词。例如:
cant => "can't",
google => "Google",
这样,我可以显示正确拼写的单词。
我发现Math::Combinatorics 看起来不错,但并没有按照我希望的方式工作。你给它一个字母列表,它会以你指定的字母数量返回这些字母的所有组合。因此,我认为我所要做的就是将字母转换为单个字母的列表,然后简单地遍历所有可能的组合!
不...这给了我所有无序的组合。然后我要做的是对每个组合,列出这些字母的所有可能排列。呸!太棒了!耶!
所以,臭名昭著的循环循环。实际上,三个循环。 * 外部循环只是将所有组合数从 1 倒数到单词中的字母数。 * 下一个查找每个字母组的所有无序组合。 * 最后,最后一个获取所有无序组合并返回这些组合的排列列表。
现在,我终于可以将这些字母排列与我的词典进行比较了。令人惊讶的是,考虑到它必须将 235,886 个单词字典转换为哈希,然后循环通过三层循环来查找所有可能的字母数量的所有组合的所有排列,该程序的运行速度比我预期的要快得多。整个程序不到两秒就跑完了。
#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);
use autodie;
use Data::Dumper;
use Math::Combinatorics;
use constant
LETTERS => "EBLAIDL",
DICTIONARY => "/usr/share/dict/words",
;
#
# Create Dictionary Hash
#
open my $dict_fh, "<", DICTIONARY;
my %dictionary;
foreach my $word (<$dict_fh>)
chomp $word;
(my $key = $word) =~ s/[^[:alpha:]]//;
$dictionarylc $key = $word;
#
# Now take the letters and create a Perl list of them.
#
my @letter_list = split // => LETTERS;
my %valid_word_hash;
#
# Outer Loop: This is a range from one letter combinations to the
# maximum letters combination
#
foreach my $num_of_letters (1..scalar @letter_list)
#
# Now we generate a reference to a list of lists of all letter
# combinations of $num_of_letters long. From there, we need to
# take the Permutations of all those letters.
#
foreach my $letter_list_ref (combine($num_of_letters, @letter_list))
my @letter_list = @$letter_list_ref;
# For each combination of letters $num_of_letters long,
# we now generate a permeation of all of those letter
# combinations.
#
foreach my $word_letters_ref (permute(@letter_list))
my $word = join "" => @$word_letters_ref;
#
# This $word is just a possible candidate for a word.
# We now have to compare it to the words in the dictionary
# to verify it's a word
#
$word = lc $word;
if (exists $dictionary$word)
my $dictionary_word = $dictionary$word;
$valid_word_hash$word = $dictionary_word;
#
# I got lazy here... Just dumping out the list of actual words.
# You need to go through this list to find your longest and
# shortest words. Number of syllables? That's trickier, you could
# see if you can divide on CVC and CVVC divides where C = consonant
# and V = vowel.
#
say join "\n", sort keys %valid_word_hash;
运行这个程序产生:
$ ./test.pl | column
a al balei bile del i lai
ab alb bali bill delia iba laid
abdiel albe ball billa dell ibad lea
abe albi balled billed della id lead
abed ale balli blad di ida leal
abel alible be blade dial ide led
abide all bea blae dib idea leda
abie alle bead d die ideal lei
able allie beal da dieb idle leila
ad allied bed dab dill ie lelia
ade b beid dae e ila li
adib ba bel dail ea ill liable
adiel bad bela dal ed l libel
ae bade beld dale el la lid
ai bae belial dali elb lab lida
aid bail bell dalle eld label lide
aide bal bella de eli labile lie
aiel bald bid deal elia lad lied
ail baldie bide deb ell lade lila
aile bale bield debi ella ladle lile
【讨论】:
组合在定义上是无序的。排列按定义排序。您需要一个排列函数,允许您提供 $num_letters @ikegami - 是的,我意识到这些函数在数学上是正确定义的。我希望有一个函数可以返回所有可能性,而无需经过每个组合的排列。顺便说一句,您对所有字典单词进行排序并比较正则表达式的概念很有趣。 不同之处在于我需要为字典中的每个条目做一些工作,因为您的所有工作都是预先完成的。对于大型词典,您的将胜出。顺便说一句,我认为 trie 比 hash 更好。 另一种方法是创建 dictionary 的 trie。这将加快进行多次搜索。【参考方案4】:如果您创建一个包含 26 个字母的单独表格可能会有所帮助。然后,您将构建一个查询,该查询将在第二个数据库中搜索您定义的任何字母。查询确保每个结果都是唯一的,这一点很重要。
因此,您有一个包含您的单词的表格,并且您与另一个包含所有字母表的表格存在多对多关系。您将查询第二个表并使结果独一无二。您可以对字母的数量采用类似的方法。
您可以对字母和音节的数量使用相同的方法。因此,您将进行一个查询,该查询将加入您想要的所有信息。在数据库上放置正确的索引以提高性能,利用适当的缓存,如果涉及到,您可以并行化搜索。
【讨论】:
以上是关于如何使用 Perl 从一组字母中生成单词列表?的主要内容,如果未能解决你的问题,请参考以下文章
如何确定可以从一袋字母和一袋单词python中组成的单词的数量和集合