入力の循環ワードの数を数える


9

循環する言葉

問題文

循環単語は、円で書かれた単語と考えることができます。循環単語を表すには、任意の開始位置を選択し、時計回りに文字を読み取ります。したがって、「picture」と「turepic」は同じ循環する単語の表現です。

String []ワードが与えられ、その各要素は巡回ワードの表現です。表現されている異なる巡回単語の数を返します。

最速の勝利(Big O、n =文字列内の文字数)


3
コードの批評を探しているなら、行くべき場所はcodereview.stackexchange.comです。
Peter Taylor

涼しい。課題を強調するために編集し、批評の部分をコードレビューに移します。ピーターに感謝します。
eggonlegs 2013年

1
受賞基準は何ですか?最短コード(Code Golf)または他に何かありますか?入力と出力の形式に制限はありますか?関数や完全なプログラムを記述する必要がありますか?それはJavaである必要がありますか?
ugoren 2013年

1
@eggonlegs big-Oを指定しましたが、どのパラメータに関してですか?配列内の文字列の数?文字列比較はO(1)ですか?または文字列の文字数または文字の総数?または何か他に?
ハワード

1
@おい、確かにそれは4ですか?
Peter Taylor

回答:


4

パイソン

これが私の解決策です。まだO(n 2)かもしれませんが、平均的なケースはそれよりはるかに良いと思います。

基本的には、各文字列を正規化することで機能し、どの回転も同じ形式になります。例えば:

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

正規化は、最小文字を(文字コードで)探し、文字が最後の位置になるように文字列を回転させることによって行われます。その文字が複数回出現する場合は、各出現後の文字が使用されます。これにより、各巡回単語に正規表現が与えられ、マップのキーとして使用できます。

正規化は最悪の場合(文字列内のすべての文字が同じである場合など)はn 2ですが、aaaaaaほとんどの場合、数回しか発生せず、実行時間はに近づきnます。

私のラップトップ(デュアルコアIntel Atom @ 1.66GHzおよび1GBのRAM)でこれを実行すると/usr/share/dict/words(平均文字数9.5文字の234,937ワード)、約7.6秒かかります。

#!/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

再びPython(3)

私が使用した方法は、文字列の各文字で始まる各単語のローリングハッシュを計算することでした。これはローリングハッシュであるため、すべてのn個のハッシュを計算するのにO(n)(nは語長)の時間がかかります。文字列はbase-1114112番号として扱われ、ハッシュが一意であることを保証します。(これはHaskellソリューションに似ていますが、文字列を2回通過するだけなので、より効率的です。)

次に、各入力ワードについて、アルゴリズムはその最小ハッシュをチェックして、ハッシュのセット(Pythonセットなので、ルックアップはセットのサイズでO(1)です)にすでにあるかどうかを確認します。そうである場合、その単語またはそのローテーションの1つはすでに確認されています。それ以外の場合は、そのハッシュをセットに追加します。

コマンドライン引数は、1行に1ワードを含むファイルの名前である必要があります(など/usr/share/dict/words)。

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

ハスケル

これの効率についてはよくわからない、おそらくかなり悪い。アイデアは、最初にすべての単語の可能なローテーションをすべて作成し、文字列を一意に表す値をカウントして、最小値を選択することです。これにより、循環グループに固有の数値が得られます。
この数でグループ化し、これらのグループの数を確認できます。

nがリスト内の単語数であり、mが単語の長さである場合、すべての単語の「循環グループ番号」を計算するとO(n*m)、並べ替えO(n log n)とグループ化が行われます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

ゲームのルールがわかったので、もう一度始めることにしました(そう思います)。

長さ3のランダムに構成された一意の「単語」(小文字のみ)の10000語辞書。同様に、長さ4、5、6、7、および8の文字列で構成される他の辞書が作成されました。

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];

gチェックするディクショナリの現在のバージョンを取得します。最上位の単語は、循環変種(存在する場合)と結合されます。単語とその一致はout、処理された単語の出力リストに追加されます。出力単語は辞書から削除されます。

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 すべての単語辞書を実行します。

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

例1:実際の単語

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

{{"steak"、 "teaks"}、{"hand"}、{"pots"、 "spot"}、{"sword"、 "words"}}
4


例2:人工的な言葉。長さ3の文字列の辞書。最初に、タイミング。次に、サイクルワードの数。

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

d3

5402


語長の関数としてのタイミング。各辞書に10000語。

タイミング

調査結果をOでどのように解釈するかは特にわかりません。簡単に言えば、3文字の辞書から4文字の辞書へとタイミングがおよそ2倍になります。タイミングは4文字から8文字までほとんど無視できます。


あなたが使用している辞書へのリンクを投稿して、私があなたの辞書と比較できるようにすることはできますか?
eggonlegs 2013年

dictionary.txtへの次のリンクは動作するはずです:bitshare.com/files/oy62qgro/dictionary.txt.htmlは (申し訳分についてあなたが開始するダウンロードを待つ必要があります。)ところで、ファイルが3char、4charを持っています... 8charの辞書をまとめて、それぞれに10000語。それらを分離する必要があります。
DavidC 2013年

驚くばかり。:)
eggonlegs 2013年

1

これは、二次時間を回避するO(n)で実行できます。アイデアは、ベース文字列を2回横断する完全な円を作成することです。したがって、「amazingamazin」を完全な円ストリングとして構成し、「amazing」に対応するすべての循環ストリングをチェックします。

以下はJavaソリューションです。

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

これが非常に効率的かどうかはわかりませんが、これが私の最初の亀裂です。

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

私が問題を理解しているのかわかりませんが、これは少なくともコメントに投稿された@dudeの例と一致します。私の確かに間違った分析を訂正してください。

文字列リストの指定されたNワードの各ワードWについて、最悪の場合、Wのすべての文字をステップスルーする必要があります。ハッシュ操作は一定の時間で行われると想定する必要があります。

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), $/;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.