web-dev-qa-db-ja.com

有効な単語を通じて1つの単語を別の単語に変換するアルゴリズム

私は edit-distance 問題のこのバリエーションに出くわしました:

ソースワードをターゲットワードに変換するアルゴリズムを設計します。例:頭から尾まで、各ステップで1文字だけを置き換えることができ、Wordが有効である必要があります。辞書が与えられます。

これは明らかに 編集距離 問題のバリエーションですが、編集距離では、Wordが有効かどうかは気にしません。では、この要件を追加して距離を編集するにはどうすればよいですか。

44
gameover

これはグラフの問題としてモデル化できます。単語をグラフのノードと見なすことができ、2つのノードが接続されているのは、長さが同じで1つの文字が異なる場合のみです。

辞書を前処理してこのグラフを作成できます。次のようになります。

   stack  jack
    |      |
    |      |
   smack  back -- pack -- pick

次に、WordからWordを表すノードへのマッピングを作成できます。これには、ハッシュテーブル、高さバランスBSTを使用できます...

上記のマッピングを配置したら、2つのグラフノード間にパスが存在するかどうかを確認するだけです。これは、BFSまたはDFSを使用して簡単に実行できます。

したがって、アルゴリズムを次のように要約できます。

preprocess the dictionary and create the graph.
Given the two inputs words w1 and w2
if length(w1) != length(w2)
 Not possible to convert
else
 n1 = get_node(w1)
 n2 = get_node(w2)

 if(path_exists(n1,n2))
   Possible and nodes in the path represent intermediary words
 else
   Not possible
48
codaddict

codaddictのグラフアプローチは有効ですが、各グラフの作成にはO(n ^ 2)時間かかります。ここで、nは指定された長さの単語数です。それが問題である場合は、 bk-tree をより効率的に構築できます。これにより、対象の単語の特定の編集距離(この場合は1)を持つすべての単語を見つけることができます。

14
Nick Johnson

辞書の各ノードがWordを表すグラフを作成します。対応する単語の編集距離が1の場合、2つのWordノード間にエッジを追加します。必要な変換の最小数は、ソースノードと宛先ノード間の最短パスの長さになります。

4
prasadvk

これは編集距離ではないと思います。

これはグラフを使用して行うことができると思います。辞書からグラフを作成し、お気に入りのグラフトラバーサルアルゴリズムを使用して目的地までナビゲートしてみてください。

2
Yuliy

単純に再帰的なバックトラッキングを使用することもできますが、これは最適なソリューションとはかけ離れています。

# Given two words of equal length that are in a dictionary, write a method to transform one Word into another Word by changing only
# one letter at a time.  The new Word you get in each step must be in the
# dictionary.

# def transform(english_words, start, end):

# transform(english_words, 'damp', 'like')
# ['damp', 'lamp', 'limp', 'Lime', 'like']
# ['damp', 'camp', 'came', 'lame', 'Lime', 'like']


def is_diff_one(str1, str2):
    if len(str1) != len(str2):
        return False

    count = 0
    for i in range(0, len(str1)):
        if str1[i] != str2[i]:
            count = count + 1

    if count == 1:
        return True

    return False


potential_ans = []


def transform(english_words, start, end, count):
    global potential_ans
    if count == 0:
        count = count + 1
        potential_ans = [start]

    if start == end:
        print potential_ans
        return potential_ans

    for w in english_words:
        if is_diff_one(w, start) and w not in potential_ans:
            potential_ans.append(w)
            transform(english_words, w, end, count)
            potential_ans[:-1]

    return None


english_words = set(['damp', 'camp', 'came', 'lame', 'Lime', 'like'])
transform(english_words, 'damp', 'lame', 0)
2

グラフやその他の複雑なデータ構造は必要ないと思います。私の考えは、辞書をHashSetとしてロードし、contains()メソッドを使用して、Wordが辞書に存在するかどうかを調べることです。

これをチェックしてくださいpseudocode私の考えを見るには:

Two words are given: START and STOP. 
//List is our "way" from words START to STOP, so, we add the original Word to it first.
    list.add(START);
//Finish to change the Word when START equals STOP.
    while(!START.equals(STOP))
//Change each letter at START to the letter to STOP one by one and check if such Word exists.
    for (int i = 0, i<STOP.length, i++){
        char temp = START[i];
        START[i] = STOP[i];
//If the Word exists add a new Word to the list of results. 
//And change another letter in the new Word with the next pass of the loop.
        if dictionary.contains(START)
           list.add(START)
//If the Word doesn't exist, leave it like it was and try to change another letter with the next pass of the loop.
        else START[i] = temp;}
    return list;

私が理解しているように、私のコードはそのように機能するはずです:

入力:DAMP、LIKE

出力:DAMP、LAMP、LIMP、Lime、LIKE

入力:戻る、PICK

出力:戻る、パック、ピック

0
Boris

これは、BFSを使用して問題を解決するC#コードです。

//use a hash set for a fast check if a Word is already in the dictionary
    static HashSet<string> Dictionary = new HashSet<string>();
    //dictionary used to find the parent in every node in the graph and to avoid traversing an already traversed node
    static Dictionary<string, string> parents = new Dictionary<string, string>();

    public static List<string> FindPath(List<string> input, string start, string end)
    {
        char[] allcharacters = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};

        foreach (string s in input)
            Dictionary.Add(s);
        List<string> currentFrontier = new List<string>();
        List<string> nextFrontier = new List<string>();
        currentFrontier.Add(start);
        while (currentFrontier.Count > 0)
        {
            foreach (string s in currentFrontier)
            {
                for (int i = 0; i < s.Length; i++)
                {
                    foreach (char c in allcharacters)
                    {
                        StringBuilder newWordBuilder = new StringBuilder(s);
                        newWordBuilder[i] = c;
                        string newWord = newWordBuilder.ToString();
                        if (Dictionary.Contains(newWord))
                        {
                            //avoid traversing a previously traversed node
                            if (!parents.Keys.Contains(newWord))
                            {
                                parents.Add(newWord.ToString(), s);
                                nextFrontier.Add(newWord);
                            }

                        }
                        if (newWord.ToString() == end)
                        {
                            return ExtractPath(start, end);

                        }
                    }
                }
            }
            currentFrontier.Clear();
            currentFrontier.Concat(nextFrontier);
            nextFrontier.Clear();
        }
        throw new ArgumentException("The given dictionary cannot be used to get a path from start to end");
    }

    private static List<string> ExtractPath(string start,string end)
    {
        List<string> path = new List<string>();
        string current = end;
        path.Add(end);
        while (current != start)
        {
            current = parents[current];
            path.Add(current);
        }
         path.Reverse();
         return path;
    }
0