计算输入中循环词的数量


9

循环词

问题陈述

我们可以将循环词视为一个圆圈中写的词。为了表示一个循环词,我们选择一个任意的起始位置并按顺时针顺序读取字符。因此,“图片”和“图形”是同一循环词的表示。

给您一个String []单词,每个元素代表一个循环单词。返回表示的不同循环字的数量。

最快获胜(大O,其中n =字符串中的字符数)


3
如果您正在寻找对代码的批评,那么可以去的地方是codereview.stackexchange.com。
彼得·泰勒

凉。我将编辑以强调挑战,并将批评部分移至代码审查。谢谢彼得。
eggonlegs 2013年

1
获奖标准是什么?最短的代码(Code Golf)还是其他?输入和输出形式是否有限制?我们需要编写一个函数还是一个完整的程序?是否必须使用Java?
ugoren

1
@eggonlegs您指定了big-O-但是关于哪个参数?数组中的字符串数?然后是字符串比较O(1)吗?还是字符串中的字符数或字符总数?还是其他?
霍华德

1
@dude,肯定是4吗?
彼得·泰勒

Answers:


4

蟒蛇

这是我的解决方案。我认为可能仍然是O(n 2),但我认为平均情况要好得多。

基本上,它通过规范化每个字符串来起作用,以便任何旋转都具有相同的形式。例如:

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

通过查找最小字符(按字符代码)并旋转字符串以使字符位于最后位置来完成规范化。如果该字符多次出现,则使用每次出现后的字符。这给每个循环词一个规范的表示形式,可以用作映射中的键。

在最坏的情况下(例如,字符串中的每个字符都相同),归一化为n 2aaaaaa,但是大多数情况下只会出现几次,并且运行时间将接近n

在我的笔记本电脑(双核Intel Atom @ 1.66GHz和1GB内存)上,运行此程序/usr/share/dict/words(234,937个单词,平均长度为9.5个字符)大约需要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)

我使用的方法是计算从字符串中每个字符开始的每个单词的滚动哈希值;由于它是滚动哈希,因此需要O(n)(其中n是字长)时间来计算所有n个哈希。该字符串被视为base-1114112数字,以确保哈希值是唯一的。(这类似于Haskell解决方案,但效率更高,因为它只两次穿过字符串。)

然后,对于每个输入单词,算法检查其最低哈希值,以查看它是否已存在于所看到的哈希集合中(Python集合,因此在该集合的大小中查找为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)sorting O(n log n)and grouping 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

现在我了解了游戏规则(我认为),因此决定重新开始。

10000个单词字典,由长度为3的唯一随机组成的“单词”(仅小写)组成。以类似的方式创建了其他字典,这些字典由长度为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]

{{“牛排”,“柚木”},{“手”},{“锅”,“点”},{“剑”,“字”}}}
4


例子2:人造词。长度为3的字符串字典。首先是时间。然后是循环字数。

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

3天

5402


时间是单词长度的函数。每本字典10000个单词。

时机

我不太了解如何用O来解释发现。简单来说,从三字符字典到四字符字典的时间大约翻倍。时间从4个字符增加到8个字符几乎可以忽略不计。


您可以张贴指向您使用的词典的链接,以便我与您的词典进行比较吗?
eggonlegs 2013年

以下指向dictionary.txt的链接应该可以正常工作:bitshare.com/files/oy62qgro/dictionary.txt.html (很抱歉,您必须等待下载开始的时间。)顺便说一句,该文件包含3char,4char ...总共8个字符的字典,每个字典10000个字。您需要将它们分开。
DavidC 2013年

太棒了 非常感谢:)
eggonlegs

1

可以用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), $/;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.