Bir girişteki döngüsel sözcük sayısını sayma


9

Döngüsel Kelimeler

Sorun bildirimi

Döngüsel bir kelimeyi daire içinde yazılmış bir kelime olarak düşünebiliriz. Döngüsel bir kelimeyi temsil etmek için, rastgele bir başlangıç ​​konumu seçer ve karakterleri saat yönünde okuruz. Dolayısıyla, "resim" ve "turepik" aynı döngüsel kelimenin temsilleridir.

Size her öğesi döngüsel bir kelimenin temsili olan bir String [] kelimesi verilir. Temsil edilen farklı döngüsel kelimelerin sayısını döndürür.

En hızlı kazançlar (Big O, burada n = bir dizgedeki karakter sayısı)


3
Kodunuzu eleştiri arıyorsanız o zaman gidilecek yer codereview.stackexchange.com.
Peter Taylor

Güzel. Ben meydan okuma vurgulamak için düzenleyecek ve eleştiri bölümünü kod incelemeye taşıyacağım. Teşekkürler Peter.
eggonlegs

1
Kazanan kriterler nedir? En kısa kod (Kod Golf) veya başka bir şey? Girdi ve çıktı biçiminde herhangi bir sınırlama var mı? Bir fonksiyon mu yoksa tam bir program mı yazmamız gerekiyor? Java'da olması gerekiyor mu?
ugoren

1
@eggonlegs big-O - belirttiniz, ancak hangi parametreye göre? Dizideki dizelerin sayısı? Dize karşılaştırması O (1) 'den mi? Veya dizedeki karakter sayısı veya toplam karakter sayısı? Ya da başka bir şey?
Howard

1
@dude, kesinlikle 4 mü?
Peter Taylor

Yanıtlar:


4

piton

İşte benim çözümüm. Bence hala O (n 2 ) olabilir, ama bence ortalama durum bundan çok daha iyi.

Temel olarak her dizeyi normalleştirerek çalışır, böylece herhangi bir döndürme aynı forma sahip olur. Örneğin:

'amazing' -> 'mazinga'
'mazinga' -> 'mazinga'
'azingam' -> 'mazinga'
'zingama' -> 'mazinga'
'ingamaz' -> 'mazinga'
'ngamazi' -> 'mazinga'
'gamazin' -> 'mazinga'

Normalleştirme, minimum karakteri (karakter koduyla) arayarak ve karakter son konumda olacak şekilde döndürerek yapılır. Bu karakter bir kereden fazla ortaya çıkarsa, her tekrardan sonraki karakterler kullanılır. Bu, her döngüsel kelimeye bir haritada anahtar olarak kullanılabilen kanonik bir gösterim verir.

Normalleştirme n 2 (dize her karakteri örneğin aynıdır kötü durumda aaaaaa), ancak çoğu zaman yalnızca birkaç olaylar olacaksa oluyor ve çalışma süresi daha yakın olacaktır n.

Dizüstü bilgisayarımda (çift çekirdekli Intel Atom @ 1.66GHz ve 1GB ram), bunu /usr/share/dict/words(ortalama 9.5 karakter uzunluğunda 234.937 kelime ) çalıştırmak yaklaşık 7.6 saniye sürer.

#!/usr/bin/python

import sys

def normalize(string):
   # the minimum character in the string
   c = min(string) # O(n) operation
   indices = [] # here we will store all the indices where c occurs
   i = -1       # initialize the search index
   while True: # finding all indexes where c occurs is again O(n)
      i = string.find(c, i+1)
      if i == -1:
         break
      else:
         indices.append(i)
   if len(indices) == 1: # if it only occurs once, then we're done
      i = indices[0]
      return string[i:] + string[:i]
   else:
      i = map(lambda x:(x,x), indices)
      for _ in range(len(string)):                       # go over the whole string O(n)
         i = map(lambda x:((x[0]+1)%len(string), x[1]), i)  # increment the indexes that walk along  O(m)
         c = min(map(lambda x: string[x[0]], i))    # get min character from current indexes         O(m)
         i = filter(lambda x: string[x[0]] == c, i) # keep only the indexes that have that character O(m)
         # if there's only one index left after filtering, we're done
         if len(i) == 1:
            break
      # either there are multiple identical runs, or
      # we found the unique best run, in either case, we start the string from that
      # index
      i = i[0][0]
      return string[i:] + string[:i]

def main(filename):
   cyclic_words = set()
   with open(filename) as words:
      for word in words.readlines():
         cyclic_words.add(normalize(word[:-1])) # normalize without the trailing newline
   print len(cyclic_words)

if __name__ == '__main__':
   if len(sys.argv) > 1:
      main(sys.argv[1])
   else:
      main("/dev/stdin")

3

Tekrar Python (3)

Kullandığım yöntem, dizgideki her karakterden başlayarak her kelimenin yuvarlanma karmasını hesaplamaktı; yuvarlanan bir karma olduğundan, tüm n karmaları hesaplamak için O (n) (burada n kelime uzunluğu) zaman alır. Dize, karmaların benzersiz olmasını sağlayan bir base-1114112 numarası olarak kabul edilir. (Bu Haskell çözümüne benzer, ancak yalnızca iki kez geçtiği için daha etkilidir.)

Daha sonra, her bir giriş sözcüğü için, algoritma zaten görülen karma kümesinde olup olmadığını görmek için en düşük karmasını kontrol eder (bir Python kümesi, bu nedenle arama kümenin boyutunda O (1)); öyleyse, o zaman kelime veya rotasyonlarından biri zaten görülmüştür. Aksi takdirde, kümeye bu karmayı ekler.

Komut satırı bağımsız değişkeni, satır başına bir kelime içeren (örneğin /usr/share/dict/words) bir dosyanın adı olmalıdır .

import sys

def rollinghashes(string):
    base = 1114112
    curhash = 0
    for c in string:
        curhash = curhash * base + ord(c)
    yield curhash
    top = base ** len(string)
    for i in range(len(string) - 1):
        curhash = curhash * base % top + ord(string[i])
        yield curhash

def cycles(words, keepuniques=False):
    hashes = set()
    uniques = set()
    n = 0
    for word in words:
        h = min(rollinghashes(word))
        if h in hashes:
            continue
        else:
            n += 1
            if keepuniques:
                uniques.add(word)
            hashes.add(h)
    return n, uniques

if __name__ == "__main__":
    with open(sys.argv[1]) as words_file:
        print(cycles(line.strip() for line in words_file)[0])

1

Haskell

Bunun verimliliğinden emin değilim, büyük olasılıkla oldukça kötü. Fikir ilk önce tüm kelimelerin tüm olası rotasyonlarını oluşturmak, dizeleri benzersiz olarak temsil eden değerleri saymak ve minimum değeri seçmek. Bu şekilde, döngüsel bir gruba özgü bir sayı elde ederiz.
Bu numaraya göre gruplayabilir ve bu grupların sayısını kontrol edebiliriz.

N, listedeki sözcük sayısı ve m bir sözcüğün uzunluğu ise O(n*m), tüm sözcükler için 'döngüsel grup numarasını' hesaplamak , sıralama O(n log n)ve gruplamadır O(n).

import Data.List
import Data.Char
import Data.Ord
import Data.Function

groupUnsortedOn f = groupBy ((==) `on` f) . sortBy(compare `on` f)
allCycles w = init $ zipWith (++) (tails w)(inits w)
wordval = foldl (\a b -> a*256 + (fromIntegral $ ord b)) 0
uniqcycle = minimumBy (comparing wordval) . allCycles
cyclicGroupCount = length . groupUnsortedOn uniqcycle

1

Mathematica

Tekrar başlamaya karar verdim, şimdi oyunun kurallarını anlıyorum (sanırım).

Uzunluk 3'ün benzersiz rasgele oluşturulmuş "kelimeleri" (sadece küçük harf) olan 10000 kelimelik bir sözlük. Benzer şekilde, 4, 5, 6, 7 ve 8 uzunluklu dizelerden oluşan diğer sözlükler de oluşturulmuştur.

ClearAll[dictionary]      
dictionary[chars_,nWords_]:=DeleteDuplicates[Table[FromCharacterCode@RandomInteger[{97,122},
chars],{nWords}]];
n=16000;
d3=Take[dictionary[3,n],10^4];
d4=Take[dictionary[4,n],10^4];
d5=Take[dictionary[5,n],10^4];
d6=Take[dictionary[6,n],10^4];
d7=Take[dictionary[7,n],10^4];
d8=Take[dictionary[8,n],10^4];

gkontrol etmek için sözlüğün geçerli sürümünü alır. En üstteki kelime döngüsel varyantlarla (varsa) birleştirilir. Sözcük ve eşleşmeleri, outişlenen sözcüklerin çıktı listesine eklenir . Çıktı sözcükleri sözlükten kaldırılır.

g[{wds_,out_}] := 
   If[wds=={},{wds,out},
   Module[{s=wds[[1]],t,c},
   t=Table[StringRotateLeft[s, k], {k, StringLength[s]}];
   c=Intersection[wds,t];
   {Complement[wds,t],Append[out,c]}]]

f tüm kelimeler sözlüğü üzerinden çalışır.

f[dict_]:=FixedPoint[g,{dict,{}}][[2]]

Örnek 1 : Gerçek kelimeler

r = f[{"teaks", "words", "spot", "pots", "sword", "steak", "hand"}]
Length[r]

{{"steak", "teaks"}, {"el"}, {"tencere", "spot"}, {"kılıç", "kelimeler"}}
4


Örnek 2 : Yapay kelimeler. Uzunluk dizeleri sözlüğü 3. İlk olarak, zamanlama. Sonra döngü kelimelerinin sayısı.

f[d3]//AbsoluteTiming
Length[%[[2]]]

d3

5402


Kelime uzunluğunun bir fonksiyonu olarak zamanlamalar . Her sözlükte 10000 kelime.

zamanlamaları

Bulguları O açısından nasıl yorumlayacağımı özellikle bilmiyorum. Basit bir ifadeyle, zamanlama kabaca üç karakter sözlüğünden dört karakter sözlüğüne iki katına çıkar. Zamanlama 4 ile 8 karakter arasında neredeyse göz ardı edilebilir.


Sizinkilerle karşılaştırabilmem için kullandığınız sözlüğe bir bağlantı gönderebilir misiniz?
eggonlegs

Dictionary.txt dosyasına aşağıdaki link çalışmalıdır: bitshare.com/files/oy62qgro/dictionary.txt.html (İndirme işleminin başlaması için beklemek zorunda olduğunuz dakika için üzgünüz.) BTW, dosyada 3char, 4char var ... 8char sözlükler hep birlikte, her birinde 10000 kelime. Onları ayırmak isteyeceksiniz.
DavidC

Muhteşem. Çok teşekkürler :)
eggonlegs

1

Bu, kuadratik zamandan kaçınarak O (n) 'de yapılabilir. Fikir, temel dizeyi iki kez geçerek tam daireyi oluşturmaktır. Bu yüzden, "amazing" a karşılık gelen tüm döngüsel dizeleri kontrol etmek için tam daire dizesi olarak "amazingamazin" inşa ediyoruz.

Java çözümü aşağıdadır:

public static void main(String[] args){
    //args[0] is the base string and following strings are assumed to be
    //cyclic strings to check 
    int arrLen = args.length;
    int cyclicWordCount = 0;
    if(arrLen<1){
        System.out.println("Invalid usage. Supply argument strings...");
        return;
    }else if(arrLen==1){
        System.out.println("Cyclic word count=0");
        return;         
    }//if

    String baseString = args[0];
    StringBuilder sb = new StringBuilder();
    // Traverse base string twice appending characters
    // Eg: construct 'amazingamazin' from 'amazing'
    for(int i=0;i<2*baseString.length()-1;i++)
        sb.append(args[0].charAt(i%baseString.length()));

    // All cyclic strings are now in the 'full circle' string
    String fullCircle = sb.toString();
    System.out.println("Constructed string= "+fullCircle);

    for(int i=1;i<arrLen;i++)
    //Do a length check in addition to contains
     if(baseString.length()==args[i].length()&&fullCircle.contains(args[i])){
        System.out.println("Found cyclic word: "+args[i]);
        cyclicWordCount++;
    }

    System.out.println("Cyclic word count= "+cyclicWordCount);
}//main

0

Bunun çok verimli olup olmadığını bilmiyorum, ama bu benim ilk çatlak.

private static int countCyclicWords(String[] input) {
    HashSet<String> hashSet = new HashSet<String>();
    String permutation;
    int count = 0;

    for (String s : input) {
        if (hashSet.contains(s)) {
            continue;
        } else {
            count++;
            for (int i = 0; i < s.length(); i++) {
                permutation = s.substring(1) + s.substring(0, 1);
                s = permutation;
                hashSet.add(s);
            }
        }
    }

    return count;
}

0

Perl

emin değilim sorunu anlamak, ama bu en azından yorumlarda yayınlanan örnek @dude eşleşir. lütfen kesinlikle yanlış analizimi düzeltin.

dize listesinin verilen N kelimesindeki her W kelimesi için, en kötü durumda W'nun tüm karakterlerine göz atmanız gerekir. Karma işlemlerinin sabit zamanda yapıldığını varsaymak zorundayım.

use strict;
use warnings;

my @words = ( "teaks", "words", "spot", "pots", "sword", "steak", "hand" );

sub count
{
  my %h = ();

  foreach my $w (@_)
  {
    my $n = length($w);

    # concatenate the word with itself. then all substrings the
    # same length as word are rotations of word.
    my $s = $w . $w;

    # examine each rotation of word. add word to the hash if
    # no rotation already exists in the hash
    $h{$w} = undef unless
      grep { exists $h{substr $s, $_, $n} } 0 .. $n - 1;
  }

  return keys %h;
}

print scalar count(@words), $/;
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.