簡體   English   中英

在隨機字母字符串中插入字符,但保持成為單詞的可能性,沒有龐大的數據結構是否可能?

[英]Insert character inside a string of random letters but maintain possibility to become word, is it possible without immense data-structure?

我有一dictionary按字母順序排列的dictionary (約20萬個單詞)。 我只需要知道在string的任何位置插入了char后, string是否仍然可以變成單詞。 換句話說:我需要知道在單詞中特定位置插入的哪些字符,仍然可以稍后使之成為帶有插入詞的單詞。 字符串中字母的順序(彼此之前或之后)應保持不變。

我只是認為如果不使用一些巨大的數據結構或放棄准確性,下降性能(不到半秒)就不可能實現。 但是,即使您要犧牲精度,我也不知道有什么好的方法可以使我以非常非常高的精度(幾乎找到的所有可能的校正確實是可能的)獲得良好的精度,並且同時達到某種程度的平衡。 我想知道其他人是否有辦法。 我認為這是必要的:

  • 以100%的准確性和精確度訪問dictionary中的每個單詞,或在速度,准確性和精確度之間進行折衷。
  • 檢查您訪問的字有char正確的順序來匹配字符串acters。
  • 檢查這個單詞是否適合附加字母

有誰知道如何在速度和准確性之間取得良好的結合? 現在,我有一個數據結構,可以很快地找到某個詞,因此,我認為作為最后的選擇,只是用隨機位置上的隨機字母查詢數據庫,並詢問該數據結構是否仍然可以之后再說,但這感覺就像是一種不平衡的方式,沒有固定的時間。

他們用這種方式來表達這個問題,就像您建議的答案一樣,如果不是這么多話,它應該包括一個概率解決方案,例如Bloom過濾器。

但是,我認為確定性解決方案是可行的,因為有必要嘗試實現和優化該確定性要求(少於0.5秒,合理的內存使用),而不是為了一個不完美的概率解決方案。

假設您有一個字符串,並且想在該字符串中找到所有可能的單個字符插入,這些插入會產生可以轉換為有效單詞的字符串,並進一步插入該字符,則如果該字符串的長度為n個字符,則存在n + 1個可能的插入位置和26個可能的字符可以在每個位置插入(假設未加重英文字母),因此對於9個字符長的字符串將有260個可能的插入。 對於這些中的每一個,您都需要檢查它們是否是有效詞,或者可以通過進一步插入變成有效詞。 乘以詞典中的200K條目,即轉換為5200萬個測試,每個測試都由“此字符串是否按此順序出現在此詞典條目中”組成。 如果我們能夠找到一種“盡早”完成大多數測試的方法,那么這在現代台式機或智能手機上似乎是可以實現的。

在偽代碼中,基本算法是:

List findPossibleInsertions(String currentString)
{
    List list = {};
    for(int pos = 0; pos < currentString.length + 1; pos++)
    {
        for(char c = 'a'; c <= 'z'; c++)
        {
            String insertedString = insert c into currentString before pos;
            if(stringIsImpossible(insertedString))
                continue;   // high level test whether the string could be turned into a valid word

            int64 stringMask = computeStringMask(insertedString);

            // the string is not impossible according to the test, but we need to verify that it is actually possible:
            for(String s in Dictionary)
            {
                // check if the string could be turned into s via insertions using a simple mask check to potentially exclude it (but not 100% confirm it):
                if((s.mask & stringMask) != stringMask)
                    continue;   // it's not possible to turn insertedString into s via insertions

                if(s.length < insertedString.length)
                    continue; // can't insert chars to make a shorter string

                // confirm that is it possible:
                if(canMakeStringViaInsertions(insertedString, s)
                {
                    list.add(insertedString); // this is a valid insertion, add to the list
                    break;
                }
            }
        }
    }
}

剩下3個任務

  • 查找高級檢查,以確保給定的字符串可能無法用於創建任何進一步插入的有效單詞
  • 計算一個掩碼,該掩碼可用於測試是否可以通過插入來擴展給定的字符串以創建給定的世界,從而允許假陽性但沒有假陰性
  • 明確測試是否可以通過插入擴展給定的字符串以創建給定的單詞,不允許假陽性或陰性

對於第一個任務,我們可以使用預先計算的位掩碼來存儲是否可以在有效字中出現某些字符序列(可能在其中的任何一個之間添加了額外的字符)。 要存儲5個字符的序列,我們需要26 * 26 * 26 * 26 * 26 = 11881376位或1485172字節。 鑒於這將大致等於存儲20萬個單詞所需的存儲量(假設平均單詞長度為5.1個字符,加上一個終止的null,再加上每個單詞有4個字節的偏移量),我認為這並不重要作為“巨大”。

為3個字符,4個字符和5個字符的每種組合存儲一個位域。

將位域設置為全零,然后通過字典。 以5個字符為例,對於每個單詞,請選擇每個可能的5個字符序列,其中序列中的每個字符都位於單詞中序列中先前的字符之前。 例如,單詞“ pencil”給出以下5個字符序列:

"encil"
"pncil"
"pecil"
"penil"
"pencl"
"penci"

使用以下公式將這些5個字符的組合分別添加到位域中:

index = ((s[0]-'a')*(26^4)) + ((s[1]-'a')*(26^3)) + ((s[2]-'a')*(26^2)) + ((s[3]-'a')*26) + (s[4]-'a');
bitfield[index] = 1;

如果將字典中所有單詞的所有可能的5個字符組成的序列添加到位域,則表示如果字符串中出現5個字符的序列,但未在位域中設置其位,則表示它不是可以通過在字符串中插入char來在字典中創建任何單詞,因為字典中沒有按這5個char順序出現的條目。 因此,無論您添加什么字符,都不會產生有效的單詞。

可以對4個字符和3個字符的位域重復相同的過程。

要檢查是否可以使用位域將字符串擴展為字典中的有效單詞,請使用以下函數:

boolean stringIsImpossible(String s)
{
    // test against 5 char bitfield:
    for(i = 0; i <= s.length - 5; i++)
    {
        index = ((s[i]-'a')*(26^4)) + ((s[i+1]-'a')*(26^3)) + ((s[i+2]-'a')*(26^2)) + ((s[i+3]-'a')*26) + (s[i+4]-'a');
        if(5charBitmask[index] == 0)
            return true;
    }
    if(s.length > 4)
        return false;
    // test against 4 char bitfield:
    for(i = 0; i <= s.length - 4; i++)
    {
        index = ((s[i]-'a')*(26^3)) + ((s[i+1]-'a')*(26^2)) + ((s[i+2]-'a')*26) + (s[i+3]-'a');
        if(4charBitmask[index] == 0)
            return true;
    }
    if(s.length > 3)
        return false;
    // test against 3 char bitfield:
    for(i = 0; i <= s.length - 3; i++)
    {
        index = ((s[i]-'a')*(26^2)) + ((s[i+1]-'a')*26) + (s[i+2]-'a');
        if(3charBitmask[index] == 0)
            return true;
    }
    return false;
}

對於第二項任務,有必要為每個詞典單詞創建一個位掩碼,該位掩碼可以輕松地用於測試是否可以通過添加字母從現有單詞字符串創建該位掩碼。 這意味着它需要以相同順序包含字符串中的所有字母。 從邏輯上講,如果它不包含字符串中的所有字母,那么它既不能包含字符串中的所有字母又不能包含它們的相同順序。 因此,如果單詞包含字母“ a”,則將位0設置為1,如果單詞包含“ b”,則將位1設置,如果單詞包含“ c”,則將位2設置為1,則可以創建位掩碼。我們試圖查看是否可以通過在字符串中插入字符來制成帶有單詞的位掩碼的字符串,如果結果不等於字符串的位掩碼,則無法從中獲得單詞,因為不是所有的字母字典單詞中存在一個字符串。

此外,我們可以根據某些字母是否在某些其他字母之后出現來在掩碼中設置額外的位。 例如,如果字符串中有字母“ g”,我們可以設置一個位,在此之后的某個時候,則有字母“ t”。 如果在字符串中設置了該位,但在目標字中未設置該位,則無法從字符串中生成該字。 還可以重用位來處理多個字母組合。 例如,如果有一個“ g”后跟一個“ t”,或者有一個“ d”后跟一個“ j”,則可以設置一個位。減少了碰撞的可能性,因為在有一個“ g”后跟一個“ t”,則將設置“ g”和“ t”位,因此與帶有“ d”后跟“ j”的單詞匹配,共享位上可能會發生沖突,但很可能不會設置單個的“ d”和“ j”位。 只要沒有假陰性,就可以接受一些假陽性。

用於計算字符串掩碼的函數可能類似於以下幾行:

int64 computeStringMask(String s)
{
    int64 mask = 0;
    // add individual letters to bitmask:
    for(int i = 0; i < s.length; i++)
    {
        mask |= 1 << (s[i]-'a');
    }
    // add "followed by" letter combinations to bitmask:
    for(int i = 0; i < s.length-1; i++)
    {
        for(int j = i+1; j < s.length; j++)
        {
            mask |= 1 << (((((s[i]-'a') * 26) + (s[j]-'a')) % 37) + 26);
        }
    }
    return mask;
}

需要為字典中的每個字符串計算並存儲此掩碼。

第三項任務:要測試給定的字符串是否可以擴展以創建給定的單詞,只需檢查單詞是否以正確的順序包含字符串中的每個字符:

boolean canMakeStringViaInsertions(s, word)
{
    int i = 0; j = 0;
    while(word[j] != 0)
    {
        if(s[i] == word[j])
        {
            // match!
            i++;
            if(s[i] == 0)
                return true;   // all chars have matched
        }
        j++;
    }
    return false;
}

findPossibleInsertions()函數的進一步優化是將字典划分為多個塊,並為塊中的每個單詞計算字符串掩碼,或者對它們進行或運算。 如果從字符串計算出的掩碼相對於塊掩碼測試為負,則無需測試塊中的任何單詞。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM