选择一个有效的数据结构来寻找韵律

Choosing an efficient data structure to find rhymes

本文关键字:数据结构 寻找 有效 一个 选择      更新时间:2023-10-16

我一直在开发一个程序,该程序可以读取整个词典,并利用CMU的WordNet将每个单词拆分为其发音。

目标是利用字典找到给定单词的最佳押韵和头韵,给定我们需要找到的单词中的音节数量及其词性。

我决定使用std::map<std::string, vector<Sound> >std::multimap<int, std::string>,其中映射将字典中的每个单词映射到其在向量中的发音,并且多映射是从一个函数返回的,该函数查找与给定单词押韵的所有单词。

int是对应单词的音节数,string保存单词。

我一直在研究效率,但似乎没有比O(n)更高效的了。我找到所有与给定单词押韵的单词的方法是

vector<string> *rhymingWords = new vector<string>;
for (iterator it : map<std::string, vector<Sound> >) {
    if(rhymingSyllables(word, it.first) >= 1 && it.first != word) {
        rhymingWords->push_back(it.first);
    }
}
return rhymingWords;

当我找到一个单词的最佳押韵(一个与给定单词押韵最多的单词)时,我会进行

vector<string> rhymes = *getAllRhymes(rhymesWith);
int x = 0;
for (string s : rhymes) {
    if (countSyllables(s) == numberOfSyllables) {
        int a = rhymingSyllables(s, rhymesWith);
        if (a > x) {
            maxRhymes = thisRhyme;
            bestRhyme = s;
        }
    }
}
return bestRhyme;

缺点是就字典中的单词数量而言O(n)访问时间。我在想把它降到O(log n)的想法,但似乎每次都会走到死胡同。我曾考虑过使用树状结构,但无法确定具体细节。

有什么建议吗?谢谢

rhymingSyllables功能是这样实现的:

int syllableCount = 0;
if((soundMap.count(word1) == 0) || (soundMap.count(word2) == 0)) {
    return 0;
}
vector<Sound> &firstSounds = soundMap.at(word1), &secondSounds = soundMap.at(word2);
for(int i = firstSounds.size() - 1, j = secondSounds.size() - 1; i >= 0 && j >= 0; --i, --j){
    if(firstSounds[i] != secondSounds[j]) return syllableCount;
    else if(firstSounds[i].isVowel()) ++syllableCount;
}
return syllableCount;

p.S。vector<Sound>是单词的发音,其中Sound是包含英语词素的每个不同发音的类:即,AA vowel AE vowel AH vowel AO vowel AW vowel AY vowel B stop CH affricate D stop DH fricative EH vowel ER vowel EY vowel F fricative G stop HH aspirate IH vowel IY vowel JH affricate K stop L liquid M nasal N nasal NG nasal OW vowel OY vowel P stop R liquid S fricative SH fricative T stop TH fricative UH vowel UW vowel V fricative W semivowel Y semivowel Z fricative ZH fricative

也许你可以对押韵过程中匹配的词素进行分组,并不是比较词素的向量,而是比较相关组的向量。然后你可以对字典进行一次排序,得到对数搜索时间。

在研究了押韵音节的实现之后,似乎可以将单词转换为声音,然后将任何元音相互匹配,并且只有在其他声音相同的情况下才匹配。因此,应用上面的建议,你可以引入一个额外的辅助音"anyVowel",然后在构建词典的过程中,将每个单词转换为其声音,用"anyVowell"替换所有元音,并将该表示推送到词典中。一旦你完成了对字典的分类。当你想搜索一个单词的押韵时,将其转换为相同的表示形式,并在字典上进行二进制搜索,首先按最后一个音作为关键字,然后按前一个音,依此类推。这将给你m*log(n)最坏情况下的复杂度,其中n是字典大小,m是单词长度,但通常它会终止得更快。

你也可以利用这样一个事实,即为了获得最佳押韵,你只考虑具有特定音节数的单词,并为每个音节数保留一个单独的词典。然后你们计算出单词中的音节数,并在合适的字典中搜索。渐进地说,它不会给你任何增益,但它提供的加速可能对你的应用程序有用。

我一直在考虑这个问题,我可能会提出一种算法的方法。

我可能会先拿字典,把它分成多个桶或批次。其中,每个批次表示每个单词的音节数。要存储到不同存储桶中的向量的遍历应该是线性的,因为您正在遍历一个大的字符串向量。从这里开始,由于第一个桶将包含1个音节的所有单词,所以目前没有什么可做的,所以你可以跳到第二个桶,之后的每个桶都需要提取每个单词并将每个单词的音节分开。因此,如果你有25个桶,你知道前几个和后几个桶不会容纳太多单词,他们的时间不应该很长,应该先完成,然而,中间有3-5或3-6个音节长度的桶将是最大的,所以如果它们的大小超过一定量,你可以在一个单独的线程上运行这些桶中的每一个,并让它们并行运行。现在,一旦你完成了;每个bucket应该返回一个std::vector<std::shared_ptr<Word>>,其中您的结构可能如下所示:

 enum SpeechSound {
     SS_AA,
     SS_AE,
     SS_...
     SS_ZH
 };
 enum SpeechSoundType {
     ASPIRATE,
     ...
     VOWEL
 };
 struct SyllableMorpheme  {
     SpeechSound sound;
     SpeechSoundType type;         
 };
 class Word {
 public:
 private:
     std::string m_strWord;
     // These Two Containers Should Match In Size! One String For Each
     // Syllable & One Matching Struct From Above Containing Two Enums.
     std::vector<std::string> m_vSyllables
     std::vector<SyllableMorpheme> m_vMorphemes;
 public:
     explicit Word( const std::string& word );
     std::string getWord() const;
     std::string getSyllable( unsigned index ) const;
     unsigned getSyllableCount() const;
     SyllableMorpheme getMorhpeme( unsigned index ) const;
     bool operator==( const ClassObj& other ) const;
     bool operator!=( const ClassObj& other ) const;
 private:
     Word( const Word& c ); // Not Implemented
     Word& operator=( const Word& other ) const; // Not Implemented
 };

这一次,您将拥有这些类对象的共享指针的新桶或向量。然后,您可以轻松地编写一个函数来遍历每个bucket,甚至多个bucket,因为bucket将具有相同的签名,只有不同数量的音节。回想起每个bucket应该已经按照字母顺序进行了排序,因为我们只根据音节数添加它们,并且从未更改从字典中读取的顺序。

有了这个,你可以在检查匹配音节和词素时很容易地比较两个单词是否相等。这些都包含在CCD_ 13中。所以你也不必担心记忆会被清理掉。

其思想是尽可能多地使用线性搜索、分离和比较;但是,如果你的容器太大,那么创建bucket并在多个线程中并行运行,或者如果它能满足你的需求,可以使用哈希表。

这个类结构的另一种可能性是,如果你想或需要的话,你甚至可以在以后添加更多的内容,比如另一个std::vector用于它的定义,另一个std::vector<string>用于它的词性{名词、动词等}。你甚至可以添加其他vector<string>用于同音词、同音词,甚至可以添加vector<string>用于与它押韵的所有单词的列表。

现在,对于你寻找最佳匹配押韵的特定任务,你可能会发现有些单词最终可能会有一个单词列表,这些单词都被认为是最佳匹配或最适合的!因此,您不想存储或返回单个字符串,而是希望存储或返回一个字符串向量!

案例示例:

To Too Two Blue Blew Hue Hew Knew New,  
Bare Bear Care Air Ayre Heir Fair Fare There Their They're
Plain, Plane, Rain, Reign, Main, Mane, Maine

是的,这些都是单音节押韵词,但正如你所看到的,在很多情况下,有多个有效答案,而不仅仅是一个最佳匹配。这确实需要考虑。