1P5: Kelime Değiştirici


20

Bu, İlk Periyodik Premier Programlama Bulmaca Push'un bir parçası olarak yazılmıştır .

Oyun

Aynı uzunlukta bir başlangıç ​​ve bitiş kelimesi sağlanır. Oyunun amacı, başlangıç ​​kelimesindeki bir harfi farklı bir geçerli kelime oluşturmak için değiştirerek, en az sayıda adım kullanarak bitiş kelimesine ulaşılana kadar bu adımı tekrarlamaktır. Örneğin, TREE ve FLED kelimeleri göz önüne alındığında, çıktı şöyle olur:

TREE
FREE
FLEE
FLED
2

Özellikler

  • OWL veya SOWPODS için Wikipedia makaleleri , kelime listeleri kadar faydalı bir başlangıç ​​noktası olabilir.
  • Program başlangıç ​​ve bitiş kelimelerini seçmenin iki yolunu desteklemelidir:
    1. Kullanıcı tarafından komut satırı, stdin veya seçtiğiniz dil için uygun olan herhangi bir yöntemle belirtilir (ne yaptığınızı belirtin).
    2. Dosyadan rastgele 2 kelime seçme.
  • Başlangıç ​​ve bitiş sözcüklerinin yanı sıra tüm ara sözcükler aynı uzunlukta olmalıdır.
  • Her adım kendi hattına yazdırılmalıdır.
  • Çıktınızın son satırı, başlangıç ​​ve bitiş sözcükleri arasında geçiş yapmak için gereken ara adım sayısı olmalıdır.
  • Başlangıç ​​ve bitiş sözcükleri arasında bir eşleşme bulunamazsa, çıktı 3 satırdan oluşmalıdır: başlangıç ​​sözcüğü, bitiş sözcüğü ve OY sözcüğü.
  • Yanıtınıza çözümünüz için Büyük O Notasyonunu ekleyin
  • Programınızın ürettiği adımları göstermek için lütfen 10 benzersiz başlangıç ​​ve bitiş kelime çiftini (çıktıları ile birlikte) ekleyin. (Yerden tasarruf etmek için, programınızın bunları tek tek satırlarda çıkarması gerekirken, her satır arasında yeni satırları boşluklarla ve virgüllerle değiştirmek için bunları tek bir satırda birleştirebilirsiniz.

Hedefler / Kazanma Kriterleri

  • Bir hafta sonra en kısa ara adımları üreten en hızlı / en iyi Big O çözümü kazanacaktır.
  • Big O kriterlerinden bir kravat çıkarsa, en kısa kod kazanacaktır.
  • Hala bir kravat varsa, en hızlı ve en kısa revizyonuna ulaşan ilk çözüm kazanacaktır.

Testler / Numune Çıkışı

DIVE
DIME
DAME
NAME
2

PEACE
PLACE
PLATE
SLATE
2

HOUSE
HORSE
GORSE
GORGE
2

POLE
POSE
POST
PAST
FAST
3

onaylama

Çıktıyı doğrulamak için kullanılabilecek bir komut dosyası üzerinde çalışıyorum.

Olacak:

  1. Her kelimenin geçerli olduğundan emin olun.
  2. Her kelimenin bir önceki kelimeden tam olarak 1 harf farklı olduğundan emin olun.

O olmaz:

  1. En kısa adımların kullanıldığını kontrol edin.

Bunu yazdıktan sonra, elbette bu yazıyı güncelleyeceğim. (:


4
Bu 3 işlemlerini gerçekleştirmesini almak için bana garip görünüyor HOUSEiçin GORGEben 2 ara sözler vardır farkında 2. gibi mantıklı mı yani bildirilmektedir ancak operasyonların # daha sezgisel olacaktır.
Matthew

4
@Peter, sowpods wikipedia sayfasına göre 13 harften daha uzun ~ 15k kelime var
gnibbler

4
Hepsini bilmek istemiyorum, ama bulmacanın aslında bir Adı var, Lewis Carroll tarafından icat edildi en.wikipedia.org/wiki/Word_ladder
st0le

1
Soruda kararlaştırılamaz bir hedefiniz var: The fastest/best Big O solution producing the shortest interim steps after one week will win.Garanti edemeyeceğiniz için, en hızlı çözümün bu arada en az adımı kullanan çözüm olduğunu, bir çözüm daha az adım kullanıyor ancak daha sonra hedefe ulaşırsa bir tercih sunmalısınız.
kullanıcı bilinmiyor

2
Sadece onaylamak istiyorum BATve CATsıfır adım atacağım, değil mi?
st0le

Yanıtlar:


9

Uzunluk bir kriter olarak listelendiğinden, burada 1681 karakterdeki golf edilmiş sürüm (muhtemelen% 10 oranında iyileştirilebilir):

import java.io.*;import java.util.*;public class W{public static void main(String[]
a)throws Exception{int n=a.length<1?5:a[0].length(),p,q;String f,t,l;S w=new S();Scanner
s=new Scanner(new
File("sowpods"));while(s.hasNext()){f=s.next();if(f.length()==n)w.add(f);}if(a.length<1){String[]x=w.toArray(new
String[0]);Random
r=new Random();q=x.length;p=r.nextInt(q);q=r.nextInt(q-1);f=x[p];t=x[p>q?q:q+1];}else{f=a[0];t=a[1];}H<S>
A=new H(),B=new H(),C=new H();for(String W:w){A.put(W,new
S());for(p=0;p<n;p++){char[]c=W.toCharArray();c[p]='.';l=new
String(c);A.get(W).add(l);S z=B.get(l);if(z==null)B.put(l,z=new
S());z.add(W);}}for(String W:A.keySet()){C.put(W,w=new S());for(String
L:A.get(W))for(String b:B.get(L))if(b!=W)w.add(b);}N m,o,ñ;H<N> N=new H();N.put(f,m=new
N(f,t));N.put(t,o=new N(t,t));m.k=0;N[]H=new
N[3];H[0]=m;p=H[0].h;while(0<1){if(H[0]==null){if(H[1]==H[2])break;H[0]=H[1];H[1]=H[2];H[2]=null;p++;continue;}if(p>=o.k-1)break;m=H[0];H[0]=m.x();if(H[0]==m)H[0]=null;for(String
v:C.get(m.s)){ñ=N.get(v);if(ñ==null)N.put(v,ñ=new N(v,t));if(m.k+1<ñ.k){if(ñ.k<ñ.I){q=ñ.k+ñ.h-p;N
Ñ=ñ.x();if(H[q]==ñ)H[q]=Ñ==ñ?null:Ñ;}ñ.b=m;ñ.k=m.k+1;q=ñ.k+ñ.h-p;if(H[q]==null)H[q]=ñ;else{ñ.n=H[q];ñ.p=ñ.n.p;ñ.n.p=ñ.p.n=ñ;}}}}if(o.b==null)System.out.println(f+"\n"+t+"\nOY");else{String[]P=new
String[o.k+2];P[o.k+1]=o.k-1+"";m=o;for(q=m.k;q>=0;q--){P[q]=m.s;m=m.b;}for(String
W:P)System.out.println(W);}}}class N{String s;int k,h,I=(1<<30)-1;N b,p,n;N(String S,String
d){s=S;for(k=0;k<d.length();k++)if(d.charAt(k)!=S.charAt(k))h++;k=I;p=n=this;}N
x(){N r=n;n.p=p;p.n=n;n=p=this;return r;}}class S extends HashSet<String>{}class H<V>extends
HashMap<String,V>{}

Paket adlarını ve yöntemlerini kullanan ve sınıfları yalnızca takma adlara uyarmayan veya genişletmeyen ungolfed sürümü:

package com.akshor.pjt33;

import java.io.*;
import java.util.*;

// WordLadder partially golfed and with reduced dependencies
//
// Variables used in complexity analysis:
// n is the word length
// V is the number of words (vertex count of the graph)
// E is the number of edges
// hash is the cost of a hash insert / lookup - I will assume it's constant, but without completely brushing it under the carpet
public class WordLadder2
{
    private Map<String, Set<String>> wordsToWords = new HashMap<String, Set<String>>();

    // Initialisation cost: O(V * n * (n + hash) + E * hash)
    private WordLadder2(Set<String> words)
    {
        Map<String, Set<String>> wordsToLinks = new HashMap<String, Set<String>>();
        Map<String, Set<String>> linksToWords = new HashMap<String, Set<String>>();

        // Cost: O(Vn * (n + hash))
        for (String word : words)
        {
            // Cost: O(n*(n + hash))
            for (int i = 0; i < word.length(); i++)
            {
                // Cost: O(n + hash)
                char[] ch = word.toCharArray();
                ch[i] = '.';
                String link = new String(ch).intern();
                add(wordsToLinks, word, link);
                add(linksToWords, link, word);
            }
        }

        // Cost: O(V * n * hash + E * hash)
        for (Map.Entry<String, Set<String>> from : wordsToLinks.entrySet()) {
            String src = from.getKey();
            wordsToWords.put(src, new HashSet<String>());
            for (String link : from.getValue()) {
                Set<String> to = linksToWords.get(link);
                for (String snk : to) {
                    // Note: equality test is safe here. Cost is O(hash)
                    if (snk != src) add(wordsToWords, src, snk);
                }
            }
        }
    }

    public static void main(String[] args) throws IOException
    {
        // Cost: O(filelength + num_words * hash)
        Map<Integer, Set<String>> wordsByLength = new HashMap<Integer, Set<String>>();
        BufferedReader br = new BufferedReader(new FileReader("sowpods"), 8192);
        String line;
        while ((line = br.readLine()) != null) add(wordsByLength, line.length(), line);

        if (args.length == 2) {
            String from = args[0].toUpperCase();
            String to = args[1].toUpperCase();
            new WordLadder2(wordsByLength.get(from.length())).findPath(from, to);
        }
        else {
            // 5-letter words are the most interesting.
            String[] _5 = wordsByLength.get(5).toArray(new String[0]);
            Random rnd = new Random();
            int f = rnd.nextInt(_5.length), g = rnd.nextInt(_5.length - 1);
            if (g >= f) g++;
            new WordLadder2(wordsByLength.get(5)).findPath(_5[f], _5[g]);
        }
    }

    // O(E * hash)
    private void findPath(String start, String dest) {
        Node startNode = new Node(start, dest);
        startNode.cost = 0; startNode.backpointer = startNode;

        Node endNode = new Node(dest, dest);

        // Node lookup
        Map<String, Node> nodes = new HashMap<String, Node>();
        nodes.put(start, startNode);
        nodes.put(dest, endNode);

        // Heap
        Node[] heap = new Node[3];
        heap[0] = startNode;
        int base = heap[0].heuristic;

        // O(E * hash)
        while (true) {
            if (heap[0] == null) {
                if (heap[1] == heap[2]) break;
                heap[0] = heap[1]; heap[1] = heap[2]; heap[2] = null; base++;
                continue;
            }

            // If the lowest cost isn't at least 1 less than the current cost for the destination,
            // it can't improve the best path to the destination.
            if (base >= endNode.cost - 1) break;

            // Get the cheapest node from the heap.
            Node v0 = heap[0];
            heap[0] = v0.remove();
            if (heap[0] == v0) heap[0] = null;

            // Relax the edges from v0.
            int g_v0 = v0.cost;
            // O(hash * #neighbours)
            for (String v1Str : wordsToWords.get(v0.key))
            {
                Node v1 = nodes.get(v1Str);
                if (v1 == null) {
                    v1 = new Node(v1Str, dest);
                    nodes.put(v1Str, v1);
                }

                // If it's an improvement, use it.
                if (g_v0 + 1 < v1.cost)
                {
                    // Update the heap.
                    if (v1.cost < Node.INFINITY)
                    {
                        int bucket = v1.cost + v1.heuristic - base;
                        Node t = v1.remove();
                        if (heap[bucket] == v1) heap[bucket] = t == v1 ? null : t;
                    }

                    // Next update the backpointer and the costs map.
                    v1.backpointer = v0;
                    v1.cost = g_v0 + 1;

                    int bucket = v1.cost + v1.heuristic - base;
                    if (heap[bucket] == null) {
                        heap[bucket] = v1;
                    }
                    else {
                        v1.next = heap[bucket];
                        v1.prev = v1.next.prev;
                        v1.next.prev = v1.prev.next = v1;
                    }
                }
            }
        }

        if (endNode.backpointer == null) {
            System.out.println(start);
            System.out.println(dest);
            System.out.println("OY");
        }
        else {
            String[] path = new String[endNode.cost + 1];
            Node t = endNode;
            for (int i = t.cost; i >= 0; i--) {
                path[i] = t.key;
                t = t.backpointer;
            }
            for (String str : path) System.out.println(str);
            System.out.println(path.length - 2);
        }
    }

    private static <K, V> void add(Map<K, Set<V>> map, K key, V value) {
        Set<V> vals = map.get(key);
        if (vals == null) map.put(key, vals = new HashSet<V>());
        vals.add(value);
    }

    private static class Node
    {
        public static int INFINITY = Integer.MAX_VALUE >> 1;

        public String key;
        public int cost;
        public int heuristic;
        public Node backpointer;

        public Node prev = this;
        public Node next = this;

        public Node(String key, String dest) {
            this.key = key;
            cost = INFINITY;
            for (int i = 0; i < dest.length(); i++) if (dest.charAt(i) != key.charAt(i)) heuristic++;
        }

        public Node remove() {
            Node rv = next;
            next.prev = prev;
            prev.next = next;
            next = prev = this;
            return rv;
        }
    }
}

Gördüğünüz gibi işletme maliyeti analizi O(filelength + num_words * hash + V * n * (n + hash) + E * hash). Bir karma tablo ekleme / aramanın sabit zaman olduğu varsayımı kabul ederseniz, bu O(filelength + V n^2 + E). SOWPODS'taki grafiklerin belirli istatistikleri, çoğu için O(V n^2)gerçekten egemen olduğu anlamına gelir .O(E)n

Örnek çıktılar:

IDOLA, IDOLS, IDYLS, ODYLS, ODALS, OVALS, OVELS, OVENS, EVENS, ETENS, STENS, SKENS, SKINS, SPINS, Omurga, 13

WICCA, PROSY, OY

BRINY, BRINS, TRINS, TAIN, TARNS, İPLİK, YAWNS, YAWPS, YAPPS, 7

GALES, GASES, GASTS, GESTS, GESTE, GESSE, DESSE, 5

SURES, DURES, DUNES, DINES, DINGS, DINGY, 4

LICHT, LIGHT, BIGHT, BIGOT, BIGOS, BIROS, GIROS, KIZ, GURNS, GUANS, GUANA, RUANA, 10

SARGE, SERGE, SERRE, SERRS, SEERS, DEERS, BOYALAR, OYERS, OVERS, OVELS, OVALS, ODALS, ODYLS, IDYLS, 12

KEIRS, SEIRS, SEERS, BIRA, BRERS, BRERE, BREME, CREME, CREPE, 7

Bu, en uzun yolu olan 6 çiftten biridir:

KAZANÇ, EN ÇOK, DÜNYA, SAIREST, SAIDEST, SADDEST, MADDEST, MIDDEST, MILDEST, WILDEST, WILIEST, WANIEST, WANEST, CANIEST, KANTEST, YARIŞMA, KONFEST, KONFRE, KONFERS, CONKERS, PİŞİRİCİLER, COOPERS, COPPERS, POPPERS KANEPELER, HAŞHAŞ, KÖPEKLER, MOPSIES, MOUSIES, MOUSS, POUSSES, PLUSS, PLISSES, PRISSES, PRES, PRES, UREASES, UNEASES, UNCASED, UNCASED, UNBASED, UNBATED, UNMED, UNMETE, UNW, INWEDED, ENWED, ENWEDED ENDEKSLER, HİNDİSTANLAR, KİRALIKLAR, TEŞVİKLER, İNCESLER, INFESTS, INFECTS, INJECTS, 56

Ve en kötü durumda çözünür 8 harfli çiftlerden biri:

PÜSKÜRTME, UNROBING, BOŞALTMA, UNCOPING, UNCAPING, UNCAGING, ENCAGING, ENRAGING, ENRACING, STROYING, STROKING, STOPING, STOPOM, STUM CRIMPING, CRISPING, CRISPINS, CRISPENS, CRISPERS, CRIMPERS, CRAMPERS, CLAMPERS, CLASPERS, CLASHERS, SLASHERS, SLATERS, SMITHERS, SMOTHERS, SOOTHERS, SOUTHERS, MOUTHERS, MOUCHERS, MOCHCHERS, COCHERS, COCHERS, COCHERS, CHCHERS LUNCHERS, LYNCHERS, LYNCHETS, LINCHETS, 52

Şimdi, sorunun tüm gereksinimlerini ortadan kaldırdığımı düşünüyorum, tartışmam.

Bir CompSci için soru, köşeleri kelimeler olan ve kenarları bir harften farklı kelimeleri birbirine bağlayan bir grafik G'deki en kısa yola indirgenir. Grafiği verimli bir şekilde oluşturmak önemsiz değildir - aslında karmaşıklığı O'ya (V n hash + E) azaltmak için tekrar gözden geçirmem gereken bir fikrim var. Benim yaptığım yol, (bir joker karakterli kelimelere karşılık gelen) fazladan köşe noktaları ekleyen ve söz konusu grafiğe homeomorfik bir grafik oluşturmayı içerir. 3'ten fazla kenarı olan bir joker düğümün grafikteki kenar sayısını azaltması temelinde, G açısından azaltmak yerine bu grafiği kullanmayı düşündüm ve sanırım bir golf açısından yapmalıydım. standart en kötü durum çalışma süresi en kısa yol algoritmalarıdır O(V heap-op + E).

Ancak, yaptığım ilk şey, farklı kelime uzunlukları için G grafiklerinin bazı analizlerini yapmaktı ve 5 veya daha fazla harfli kelimeler için son derece seyrek olduklarını keşfettim. 5 harfli grafik 12478 köşeye ve 40759 kenara sahiptir; bağlantı düğümleri eklemek grafiği daha da kötüleştirir. 8 harfe kadar olan zamana kadar düğümlerden daha az kenar vardır ve kelimelerin 3 / 7'si "uzak" dır. Bu yüzden optimizasyon fikrini gerçekten yararlı bulmadım.

Yararlı olan fikir, yığını incelemekti. Dürüst olmak gerekirse, geçmişte orta derecede egzotik yığınlar uyguladığımı söyleyebilirim, ancak hiçbiri bu kadar egzotik değil. A-yıldız kullanıyorum (C kullandığım öbek göz önüne alındığında hiçbir fayda sağlamadığı için) hedeften farklı harflerin açık sezgisel taramalarını kullanıyorum ve biraz analiz, herhangi bir zamanda 3'ten fazla farklı önceliğin olmadığını gösteriyor yığın. Önceliği (maliyet + sezgisel) olan bir düğümü patlattığımda ve komşularına baktığımda, düşündüğüm üç durum var: 1) komşunun maliyeti maliyet + 1; komşunun sezgiselliği sezgisel-1'dir (çünkü değiştirdiği harf "doğru" olur); 2) maliyet + 1 ve sezgisel + 0 (çünkü değiştirdiği harf "yanlış" dan "hala yanlış" a gider; 3) maliyet + 1 ve sezgisel + 1 (çünkü değiştirdiği harf "doğru" dan "yanlış" a gider). Bu yüzden komşuyu rahatlatırsam, aynı öncelik, öncelik + 1 veya öncelik + 2'ye ekleyeceğim. Sonuç olarak yığın için 3-elemanlı bağlantılı listeler dizisini kullanabilirsiniz.

Karma aramaların sabit olduğu varsayımım hakkında bir not eklemeliyim. Çok iyi diyebilirsiniz, ama karma hesaplamalar ne olacak? Cevap şu: Onları amorti ediyorum: java.lang.Stringönbelleğe alıyor hashCode(), bu yüzden hesaplama karmaları harcanan toplam süre O(V n^2)(grafiği oluştururken).

Karmaşıklığı etkileyen başka bir değişiklik daha var, ancak bunun bir optimizasyon olup olmadığı sorusu, istatistiklerle ilgili varsayımlarınıza bağlıdır. (IMO "en iyi Big O çözümünü" bir kriter olarak koymak bir hatadır, çünkü basit bir nedenden dolayı en iyi karmaşıklık yoktur: tek bir değişken yoktur). Bu değişiklik grafik oluşturma adımını etkiler. Yukarıdaki kodda:

        Map<String, Set<String>> wordsToLinks = new HashMap<String, Set<String>>();
        Map<String, Set<String>> linksToWords = new HashMap<String, Set<String>>();

        // Cost: O(Vn * (n + hash))
        for (String word : words)
        {
            // Cost: O(n*(n + hash))
            for (int i = 0; i < word.length(); i++)
            {
                // Cost: O(n + hash)
                char[] ch = word.toCharArray();
                ch[i] = '.';
                String link = new String(ch).intern();
                add(wordsToLinks, word, link);
                add(linksToWords, link, word);
            }
        }

        // Cost: O(V * n * hash + E * hash)
        for (Map.Entry<String, Set<String>> from : wordsToLinks.entrySet()) {
            String src = from.getKey();
            wordsToWords.put(src, new HashSet<String>());
            for (String link : from.getValue()) {
                Set<String> to = linksToWords.get(link);
                for (String snk : to) {
                    // Note: equality test is safe here. Cost is O(hash)
                    if (snk != src) add(wordsToWords, src, snk);
                }
            }
        }

İşte O(V * n * (n + hash) + E * hash). Ancak O(V * n^2)bölüm, her bağlantı için yeni bir n karakter dizesi oluşturmak ve ardından hash kodunu hesaplamaktan gelir. Bir yardımcı sınıfla bu önlenebilir:

    private static class Link
    {
        private String str;
        private int hash;
        private int missingIdx;

        public Link(String str, int hash, int missingIdx) {
            this.str = str;
            this.hash = hash;
            this.missingIdx = missingIdx;
        }

        @Override
        public int hashCode() { return hash; }

        @Override
        public boolean equals(Object obj) {
            Link l = (Link)obj; // Unsafe, but I know the contexts where I'm using this class...
            if (this == l) return true; // Essential
            if (hash != l.hash || missingIdx != l.missingIdx) return false;
            for (int i = 0; i < str.length(); i++) {
                if (i != missingIdx && str.charAt(i) != l.str.charAt(i)) return false;
            }
            return true;
        }
    }

Sonra grafik üretiminin ilk yarısı

        Map<String, Set<Link>> wordsToLinks = new HashMap<String, Set<Link>>();
        Map<Link, Set<String>> linksToWords = new HashMap<Link, Set<String>>();

        // Cost: O(V * n * hash)
        for (String word : words)
        {
            // apidoc: The hash code for a String object is computed as
            // s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
            // Cost: O(n * hash)
            int hashCode = word.hashCode();
            int pow = 1;
            for (int j = word.length() - 1; j >= 0; j--) {
                Link link = new Link(word, hashCode - word.charAt(j) * pow, j);
                add(wordsToLinks, word, link);
                add(linksToWords, link, word);
                pow *= 31;
            }
        }

Hashcode yapısını kullanarak içindeki bağlantıları oluşturabiliriz O(V * n). Bununla birlikte, bunun bir vuruntu etkisi vardır. Karma aramaların sürekli olduğu varsayımımın doğasında var olan şey, nesnelerin eşitlik için karşılaştırılmasının ucuz olduğu bir varsayımdır. Ancak, Link'in eşitlik testi O(n)en kötü durumdadır. En kötü durum, farklı kelimelerden üretilen iki eşit bağlantı arasında bir karma çarpışmamız olduğunda - yani O(E)grafik üretiminin ikinci yarısındaki zamanlar. Bunun dışında, eşit olmayan bağlantılar arasındaki karma bir çarpışma olayı haricinde iyiyiz. Bu yüzden işlem ettik O(V * n^2)için O(E * n * hash). İstatistiklerle ilgili önceki noktama bakın.


Ben 8192 BufferedReader (SunVM üzerinde) için varsayılan arabellek boyutu olduğuna inanıyorum
st0le

@ st0le, golfçü versiyonda bu parametreyi atladım ve ungolfed olana zarar vermez.
Peter Taylor

5

Java

Karmaşıklık : ?? (CompSci Derecem yok, bu yüzden bu konuda yardım için minnettar olurum.)

Giriş : Komut satırına Çift sözcük (isterseniz 1 çiftten fazla) girin. Komut satırı belirtilmezse 2 farklı rastgele kelime seçilir.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

public class M {

    // for memoization
    private static Map<String, List<String>> memoEdits = new HashMap<String, List<String>>(); 
    private static Set<String> dict;

    private static List<String> edits(String word, Set<String> dict) {
        if(memoEdits.containsKey(word))
            return memoEdits.get(word);

        List<String> editsList = new LinkedList<String>();
        char[] letters = word.toCharArray();
        for(int i = 0; i < letters.length; i++) {
            char hold = letters[i];
            for(char ch = 'A'; ch <= 'Z'; ch++) {
                if(ch != hold) {
                    letters[i] = ch;
                    String nWord = new String(letters);
                    if(dict.contains(nWord)) {
                        editsList.add(nWord);
                    }
                }
            }
            letters[i] = hold;
        }
        memoEdits.put(word, editsList);
        return editsList;
    }

    private static Map<String, String> bfs(String wordFrom, String wordTo,
                                           Set<String> dict) {
        Set<String> visited = new HashSet<String>();
        List<String> queue = new LinkedList<String>();
        Map<String, String> pred = new HashMap<String, String>();
        queue.add(wordFrom);
        while(!queue.isEmpty()) {
            String word = queue.remove(0);
            if(word.equals(wordTo))
                break;

            for(String nWord: edits(word, dict)) {
                if(!visited.contains(nWord)) {
                    queue.add(nWord);
                    visited.add(nWord);
                    pred.put(nWord, word);
                }
            }
        }
        return pred;
    }

    public static void printPath(String wordTo, String wordFrom) {
        int c = 0;
        Map<String, String> pred = bfs(wordFrom, wordTo, dict);
        do {
            System.out.println(wordTo);
            c++;
            wordTo = pred.get(wordTo);
        }
        while(wordTo != null && !wordFrom.equals(wordTo));
        System.out.println(wordFrom);
        if(wordTo != null)
            System.out.println(c - 1);
        else
            System.out.println("OY");
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
        BufferedReader scan = new BufferedReader(new FileReader(new File("c:\\332609\\dict.txt")),
                                                 40 * 1024);
        String line;
        dict = new HashSet<String>(); //the dictionary (1 word per line)
        while((line = scan.readLine()) != null) {
            dict.add(line);
        }
        scan.close();
        if(args.length == 0) { // No Command line Arguments? Pick 2 random
                               // words.
            Random r = new Random(System.currentTimeMillis());
            String[] words = dict.toArray(new String[dict.size()]);
            int x = r.nextInt(words.length), y = r.nextInt(words.length);
            while(x == y) //same word? that's not fun...
                y = r.nextInt(words.length);
            printPath(words[x], words[y]);
        }
        else { // Arguments provided, search for path pairwise
            for(int i = 0; i < args.length; i += 2) {
                if(i + 1 < args.length)
                    printPath(args[i], args[i + 1]);
            }
        }
    }
}

Daha hızlı sonuçlar için Memoization kullandım. Sözlük yolu sabit kodlanmıştır.
st0le

@Joey, eskiden vardı ama artık değil. Artık her seferinde artan ve eklediği statik bir alanı var System.nanoTime().
Peter Taylor

@Joey, aah, tamam, ama şimdilik bırakacağım, revizyonlarımı artırmak istemiyorum: P
st0le

oh, btw, işteyim ve bu scrabble web siteleri görünüşte engellendi, bu yüzden sözlüklere erişimim yok ... yarın sabah en iyi 10 kelimeyi üretecek. Şerefe!
st0le

İki yönlü bir bfs yaparak (hesaplama) karmaşıklığını azaltabilirsiniz, yani her iki taraftan arama yapın ve diğer taraftan ziyaret edilen bir düğümle karşılaştığınızda durun.
Nabb

3

unix'te c

Dijkstra algoritmasını kullanma.

Kodun büyük bir kısmı, tutunmaya hizmet eden bir kostüm n-ary ağacı uygulamasıdır

  • IO dosyasının yavaş olduğu varsayımı üzerine giriş listesi (giriş dosyasının kaç kez okunduğunu (argüman olmadan iki kez, diğer durumlar için bir kez) en aza indirir
  • Kısmi ağaçlar onları inşa ederken.
  • Son yol.

Herkes görmeye ilgilenen nasıl çalıştığını muhtemelen okumalı findPath, processve processOne(ve bunlarla ilişkili yorum). Ve belki buildPathve buildPartialPath. Gerisi defter tutma ve iskele. Test ve geliştirme sırasında kullanılan ancak "üretim" sürümünde kullanılmayan çeşitli rutinler yerinde bırakılmıştır.

Ben kullanıyorum /usr/share/dict/wordso rastgele tamamen yönetmesine izin bir oluşturur böylece çok uzun, ezoterik girişler var benim Mac OS 10.5 kutu, üzerinde çok ait OYs.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getline.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>

const char*wordfile="/usr/share/dict/words";
/* const char*wordfile="./testwords.txt"; */
const long double RANDOM_MAX = (2LL<<31)-1;

typedef struct node_t {
  char*word;
  struct node_t*kids;
  struct node_t*next;
} node;


/* Return a pointer to a newly allocated node. If word is non-NULL, 
 * call setWordNode;
 */
node*newNode(char*word){
  node*n=malloc(sizeof(node));
  n->word=NULL;
  n->kids=NULL;
  n->next=NULL;
  if (word) n->word = strdup(word);
  return n;
}
/* We can use the "next" links to treat these as a simple linked list,
 * and further can make it a stack or queue by
 *
 * * pop()/deQueu() from the head
 * * push() onto the head
 * * enQueue at the back
 */
void push(node*n, node**list){
  if (list==NULL){
    fprintf(stderr,"Active operation on a NULL list! Exiting\n");
    exit(5);
  }
  n->next = (*list);
  (*list) = n;
}
void enQueue(node*n, node**list){
  if (list==NULL){
    fprintf(stderr,"Active operation on a NULL list! Exiting\n");
    exit(5);
  }
  if ( *list==NULL ) {
    *list=n;
  } else {
    enQueue(n,&((*list)->next));
  }
}
node*pop(node**list){
  node*temp=NULL;
  if (list==NULL){
    fprintf(stderr,"Active operation on a NULL list! Exiting\n");
    exit(5);
  }
  temp = *list;
  if (temp != NULL) {
    (*list) = temp->next;
    temp->next=NULL;
  }
  return temp;
}
node*deQueue(node**list){ /* Alias for pop */
  return pop(list);
}

/* return a pointer to a node in tree matching word or NULL if none */
node* isInTree(char*word, node*tree){
  node*isInNext=NULL;
  node*isInKids=NULL;
  if (tree==NULL || word==NULL) return NULL;
  if (tree->word && (0 == strcasecmp(word,tree->word))) return tree;
  /* prefer to find the target at shallow levels so check the siblings
     before the kids */
  if (tree->next && (isInNext=isInTree(word,tree->next))) return isInNext;
  if (tree->kids && (isInKids=isInTree(word,tree->kids))) return isInKids;
  return NULL;
}

node* freeTree(node*t){
  if (t==NULL) return NULL;
  if (t->word) {free(t->word); t->word=NULL;}
  if (t->next) t->next=freeTree(t->next);
  if (t->kids) t->kids=freeTree(t->kids);
  free(t);
  return NULL;
}

void printTree(node*t, int indent){
  int i;
  if (t==NULL) return;
  for (i=0; i<indent; i++) printf("\t"); printf("%s\n",t->word);
  printTree(t->kids,indent+1);
  printTree(t->next,indent);
}

/* count the letters of difference between two strings */
int countDiff(const char*w1, const char*w2){
  int count=0;
  if (w1==NULL || w2==NULL) return -1;
  while ( (*w1)!='\0' && (*w2)!='\0' ) {
    if ( (*w1)!=(*w2) ) count++;
    w1++;
    w2++;
  }
  return count;
}

node*buildPartialPath(char*stop, node*tree){
  node*list=NULL;
  while ( (tree != NULL) && 
      (tree->word != NULL) && 
      (0 != strcasecmp(tree->word,stop)) ) {
    node*kid=tree->kids;
    node*newN = newNode(tree->word);
    push(newN,&list);
    newN=NULL;
    /* walk over all all kids not leading to stop */
    while ( kid && 
        (strcasecmp(kid->word,stop)!=0) &&
        !isInTree(stop,kid->kids) ) {
      kid=kid->next;
    }
    if (kid==NULL) {
      /* Assuming a preconditions where isInTree(stop,tree), we should
       * not be able to get here...
       */
      fprintf(stderr,"Unpossible!\n");
      exit(7);
    } 
    /* Here we've found a node that either *is* the target or leads to it */
    if (strcasecmp(stop,kid->word) == 0) {
      break;
    }
    tree = kid;
  }
  return list; 
}
/* build a node list path 
 *
 * We can walk down each tree, identfying nodes as we go
 */
node*buildPath(char*pivot,node*frontTree,node*backTree){
  node*front=buildPartialPath(pivot,frontTree);
  node*back=buildPartialPath(pivot,backTree);
  /* weld them together with pivot in between 
  *
  * The front list is in reverse order, the back list in order
  */
  node*thePath=NULL;
  while (front != NULL) {
    node*n=pop(&front);
    push(n,&thePath);
  }
  if (pivot != NULL) {
    node*n=newNode(pivot);
    enQueue(n,&thePath);
  }
  while (back != NULL) {
    node*n=pop(&back);
    enQueue(n,&thePath);
  }
  return thePath;
}

/* Add new child nodes to the single node in ts named by word. Also
 * queue these new word in q
 * 
 * Find node N matching word in ts
 * For tword in wordList
 *    if (tword is one change from word) AND (tword not in ts)
 *        add tword to N.kids
 *        add tword to q
 *        if tword in to
 *           return tword
 * return NULL
 */
char* processOne(char *word, node**q, node**ts, node**to, node*wordList){
  if ( word==NULL || q==NULL || ts==NULL || to==NULL || wordList==NULL ) {
    fprintf(stderr,"ProcessOne called with NULL argument! Exiting.\n");
    exit(9);
  }
  char*result=NULL;
  /* There should be a node in ts matching the leading node of q, find it */
  node*here = isInTree(word,*ts);
  /* Now test each word in the list as a possible child of HERE */
  while (wordList != NULL) {
    char *tword=wordList->word;
    if ((1==countDiff(word,tword)) && !isInTree(tword,*ts)) {
      /* Queue this up as a child AND for further processing */
      node*newN=newNode(tword);
      enQueue(newN,&(here->kids));
      newN=newNode(tword);
      enQueue(newN,q);
      /* This might be our pivot */
      if ( isInTree(tword,*to) ) {
    /* we have found a node that is in both trees */
    result=strdup(tword);
    return result;
      }
    }
    wordList=wordList->next;
  }
  return result;
}

/* Add new child nodes to ts for all the words in q */
char* process(node**q, node**ts, node**to, node*wordList){
  node*tq=NULL;
  char*pivot=NULL;
  if ( q==NULL || ts==NULL || to==NULL || wordList==NULL ) {
    fprintf(stderr,"Process called with NULL argument! Exiting.\n");
    exit(9);
  }
  while (*q && (pivot=processOne((*q)->word,&tq,ts,to,wordList))==NULL) {
    freeTree(deQueue(q));
  }
  freeTree(*q); 
  *q=tq;
  return pivot;
}

/* Find a path between w1 and w2 using wordList by dijkstra's
 * algorithm
 *
 * Use a breadth-first extensions of the trees alternating between
 * trees.
 */
node* findPath(char*w1, char*w2, node*wordList){
  node*thePath=NULL; /* our resulting path */
  char*pivot=NULL; /* The node we find that matches */
  /* trees of existing nodes */
  node*t1=newNode(w1); 
  node*t2=newNode(w2);
  /* queues of nodes to work on */
  node*q1=newNode(w1);
  node*q2=newNode(w2);

  /* work each queue all the way through alternating until a word is
     found in both lists */
  while( (q1!=NULL) && ((pivot = process(&q1,&t1,&t2,wordList)) == NULL) &&
     (q2!=NULL) && ((pivot = process(&q2,&t2,&t1,wordList)) == NULL) )
    /* no loop body */ ;


  /* one way or another we are done with the queues here */
  q1=freeTree(q1);
  q2=freeTree(q2);
  /* now construct the path */
  if (pivot!=NULL) thePath=buildPath(pivot,t1,t2);
  /* clean up after ourselves */
  t1=freeTree(t1);
  t2=freeTree(t2);

  return thePath;
}

/* Convert a non-const string to UPPERCASE in place */
void upcase(char *s){
  while (s && *s) {
    *s = toupper(*s);
    s++;
  }
}

/* Walks the input file stuffing lines of the given length into a list */
node*getListWithLength(const char*fname, int len){
  int l=-1;
  size_t n=0;
  node*list=NULL;
  char *line=NULL;
  /* open the word file */
  FILE*f = fopen(fname,"r");
  if (NULL==f){
    fprintf(stderr,"Could not open word file '%s'. Exiting.\n",fname);
    exit(3);
  }
  /* walk the file, trying each word in turn */
  while ( !feof(f) && ((l = getline(&line,&n,f)) != -1) ) {
    /* strip trailing whitespace */
    char*temp=line;
    strsep(&temp," \t\n");
    if (strlen(line) == len) {
      node*newN = newNode(line);
      upcase(newN->word);
      push(newN,&list);
    }
  }
  fclose(f);
  return list;
}

/* Assumes that filename points to a file containing exactly one
 * word per line with no other whitespace.
 * It will return a randomly selected word from filename.
 *
 * If veto is non-NULL, only non-matching words of the same length
 * wll be considered.
 */
char*getRandomWordFile(const char*fname, const char*veto){
  int l=-1, count=1;
  size_t n=0;
  char *word=NULL;
  char *line=NULL;
  /* open the word file */
  FILE*f = fopen(fname,"r");
  if (NULL==f){
    fprintf(stderr,"Could not open word file '%s'. Exiting.\n",fname);
    exit(3);
  }
  /* walk the file, trying each word in turn */
  while ( !feof(f) && ((l = getline(&line,&n,f)) != -1) ) {
    /* strip trailing whitespace */
    char*temp=line;
    strsep(&temp," \t\n");
    if (strlen(line) < 2) continue; /* Single letters are too easy! */
    if ( (veto==NULL) || /* no veto means chose from all */ 
     ( 
      ( strlen(line) == strlen(veto) )  && /* veto means match length */
      ( 0 != strcasecmp(veto,line) )       /* but don't match word */ 
       ) ) { 
      /* This word is worthy of consideration. Select it with random
         chance (1/count) then increment count */
      if ( (word==NULL) || (random() < RANDOM_MAX/count) ) {
    if (word) free(word);
    word=strdup(line);
      }
      count++;
    }
  }
  fclose(f);
  upcase(word);
  return word;
}

void usage(int argc, char**argv){
  fprintf(stderr,"%s [ <startWord> [ <endWord> ]]:\n\n",argv[0]);
  fprintf(stderr,
      "\tFind the shortest transformation from one word to another\n");
  fprintf(stderr,
      "\tchanging only one letter at a time and always maintaining a\n");
  fprintf(stderr,
      "\tword that exists in the word file.\n\n");
  fprintf(stderr,
      "\tIf startWord is not passed, chose at random from '%s'\n",
      wordfile);
  fprintf(stderr,
      "\tIf endWord is not passed, chose at random from '%s'\n",
      wordfile);
  fprintf(stderr,
      "\tconsistent with the length of startWord\n");
  exit(2);
}

int main(int argc, char**argv){
  char *startWord=NULL;
  char *endWord=NULL;

  /* intialize OS services */
  srandom(time(0)+getpid());
  /* process command line */
  switch (argc) {
  case 3:
    endWord = strdup(argv[2]);
    upcase(endWord);
  case 2:
    startWord = strdup(argv[1]);
    upcase(startWord);
  case 1:
    if (NULL==startWord) startWord = getRandomWordFile(wordfile,NULL);
    if (NULL==endWord)   endWord   = getRandomWordFile(wordfile,startWord);
    break;
  default:
    usage(argc,argv);
    break;
  }
  /* need to check this in case the user screwed up */
  if ( !startWord || ! endWord || strlen(startWord) != strlen(endWord) ) {
    fprintf(stderr,"Words '%s' and '%s' are not the same length! Exiting\n",
        startWord,endWord);
    exit(1);
  }
  /* Get a list of all the words having the right length */
  node*wordList=getListWithLength(wordfile,strlen(startWord));
  /* Launch into the path finder*/
  node *theList=findPath(startWord,endWord,wordList);
  /* Print the resulting path */
  if (theList) {
    int count=-2;
    while (theList) {
      printf("%s\n",theList->word);
      theList=theList->next;
      count++;
    }
    printf("%d\n",count);
  } else {
    /* No path found case */
    printf("%s %s OY\n",startWord,endWord);
  }
  return 0;
}

Bazı çıktılar:

$ ./changeword dive name
DIVE
DIME
DAME
NAME
2
$ ./changeword house gorge
HOUSE
HORSE
GORSE
GORGE
2
$ ./changeword stop read
STOP
STEP
SEEP
SEED
REED
READ
4
$ ./changeword peace slate
PEACE
PLACE
PLATE
SLATE
2
$ ./changeword pole fast  
POLE
POSE
POST
PAST
FAST
3
$ ./changeword          
QUINTIPED LINEARITY OY
$ ./changeword sneaky   
SNEAKY WAXILY OY
$ ./changeword TRICKY
TRICKY
PRICKY
PRINKY
PRANKY
TRANKY
TWANKY
SWANKY
SWANNY
SHANNY
SHANTY
SCANTY
SCATTY
SCOTTY
SPOTTY
SPOUTY
STOUTY
STOUTH
STOUSH
SLOUSH
SLOOSH
SWOOSH
19
$ ./changeword router outlet
ROUTER
ROTTER
RUTTER
RUTHER
OUTHER
OUTLER
OUTLET
5
$ ./changeword 
IDIOM
IDISM
IDIST
ODIST
OVIST
OVEST
OVERT
AVERT
APERT
APART
SPART
SPARY
SEARY
DEARY
DECRY
DECAY
DECAN
DEDAN
SEDAN
17

Karmaşıklık analizi önemsiz değildir. Arama iki taraflı, yinelemeli bir derinlemedir.

  • İncelenen her düğüm için tüm kelime listesini (doğru uzunluktaki kelimelerle sınırlı olsa da) yürüyorum. Listenin uzunluğunu arayın W.
  • Minimum adım sayısı, S_min = (<number of different letter>-1)eğer sadece bir harf varsa, değişikliği 0 ara adımda puanladığımız içindir. Maksimum değeri ölçmek zordur, yukarıdaki TRICKY - SWOOSH bölümüne bakın. Ağacın her yarım olacaktır S/2-1içinS/2
  • Ağacın dallanma davranışını analiz etmedim, ama diyorum B.

Yani asgari işlem sayısı ortada 2 * (S/2)^B * W, gerçekten iyi değil.


Belki bu benim safım, ama tasarımınızda veya uygulamanızda kenar ağırlıkları gerektiren hiçbir şey görmüyorum. Dijkstra'nın aslında ağırlıksız grafikler için çalışmasına rağmen (kenar ağırlığı her zaman "1" dir), O(|V|+|E|)bunun yerine sınırlarınızı iyileştirmek için burada basit bir ilk önce yapılan arama geçerli olmaz O(|E|+|V| log |V|)mı?
MrGomez
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.