입력에서 순환 단어 수를 센다


9

주기적 단어

문제 설명

순환 단어는 원 안에 쓰여진 단어라고 생각할 수 있습니다. 주기적 단어를 나타 내기 위해 임의의 시작 위치를 선택하고 문자를 시계 방향으로 읽습니다. 따라서 "picture"와 "turepic"은 동일한 순환 단어를 나타냅니다.

String [] 단어가 주어지며, 각 요소는 순환 단어를 나타냅니다. 표시되는 다른 순환 단어 수를 리턴하십시오.

가장 빠른 승리 (Big O, 여기서 n = 문자열의 문자 수)


3
코드에 대한 비판을 찾고 있다면 갈 곳은 codereview.stackexchange.com입니다.
피터 테일러

멋있는. 도전에 중점을두고 편집하고 비판 부분을 코드 검토로 옮길 것입니다. 고마워 피터
eggonlegs

1
우승 기준은 무엇입니까? 가장 짧은 코드 (코드 골프) 또는 다른 것? 입력 및 출력 형식에 제한이 있습니까? 함수 나 완전한 프로그램을 작성해야합니까? Java로되어 있어야합니까?
우고 렌

1
@eggonlegs big-O를 지정했지만 어떤 매개 변수와 관련하여? 배열의 문자열 수? 문자열 비교가 O (1)입니까? 아니면 문자열의 문자 수 또는 총 문자 수? 아니면 다른 것?
Howard

1
@ 친구, 확실히 4입니까?
피터 테일러

답변:


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 램)에서이 기능을 실행 /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

파이썬 (3) 다시

내가 사용한 방법은 문자열의 각 문자에서 시작하는 각 단어의 롤링 해시를 계산하는 것이 었습니다. 롤링 해시이므로 모든 n 해시를 계산하는 데 O (n) (여기서 n은 단어 길이) 시간이 걸립니다. 문자열은 기본 1114112 숫자로 처리되므로 해시가 고유합니다. (이것은 Haskell 솔루션과 유사하지만 문자열을 두 번만 통과하기 때문에 더 효율적입니다.)

그런 다음 각 입력 단어에 대해 알고리즘은 가장 낮은 해시를 확인하여 이미 해시 세트에 있는지 확인합니다 (파이썬 세트, 따라서 조회는 세트 크기에서 O (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

매스 매 티카

게임의 규칙을 이해 했으므로 다시 시작하기로 결정했습니다.

길이가 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 개의 문자 사전으로 두 배가된다. 타이밍은 4 ~ 8 자로 거의 무시할 정도로 증가합니다.


내가 사용했던 사전에 대한 링크를 게시하여 귀하의 사전과 비교할 수 있습니까?
eggonlegs

dictionary.txt에 대한 다음 링크가 작동해야합니다. bitshare.com/files/oy62qgro/dictionary.txt.html (다운로드가 시작되기를 기다려야하는 시간에 대해 죄송합니다.) BTW, 파일은 3char, 4char입니다. ... 8char 사전은 모두 10000 단어로 구성됩니다. 그것들을 분리하고 싶을 것입니다.
DavidC

대박. 대단히 감사합니다 :)
eggonlegs

1

이것은 2 차 시간을 피하면서 O (n)에서 수행 할 수 있습니다. 아이디어는 기본 문자열을 두 번 통과하는 완전한 원을 구성하는 것입니다. 따라서 "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

확실하지 않은 문제를 이해하고 있지만 이것은 댓글에 게시 된 @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.