Đếm số lượng từ theo chu kỳ trong một đầu vào


9

Từ tuần hoàn

Báo cáo vấn đề

Chúng ta có thể nghĩ về một từ tuần hoàn như một từ được viết trong một vòng tròn. Để biểu thị một từ tuần hoàn, chúng tôi chọn một vị trí bắt đầu tùy ý và đọc các ký tự theo thứ tự theo chiều kim đồng hồ. Vì vậy, "hình ảnh" và "turepic" là các đại diện cho cùng một từ tuần hoàn.

Bạn được cung cấp một chuỗi String [], mỗi phần tử là một đại diện của một từ tuần hoàn. Trả về số lượng từ theo chu kỳ khác nhau được biểu diễn.

Chiến thắng nhanh nhất (Big O, trong đó n = số ký tự trong chuỗi)


3
Nếu bạn đang tìm kiếm những lời chỉ trích về mã của mình thì nơi cần đến là codereview.stackexchange.com.
Peter Taylor

Mát mẻ. Tôi sẽ chỉnh sửa để nhấn mạnh vào thách thức và chuyển phần phê bình sang đánh giá mã. Cảm ơn Peter.
eggonlegs

1
Các tiêu chí chiến thắng là gì? Mã ngắn nhất (Code Golf) hay bất cứ thứ gì khác? Có bất kỳ giới hạn về hình thức đầu vào và đầu ra? Chúng ta có cần phải viết một chức năng hoặc một chương trình hoàn chỉnh không? Nó có phải ở trong Java không?
ugoren

1
@eggonlegs Bạn đã chỉ định big-O - nhưng liên quan đến tham số nào? Số chuỗi trong mảng? Là so sánh chuỗi sau đó O (1)? Hoặc số lượng ký tự trong chuỗi hoặc tổng số ký tự? Hay bất cứ điều gì khác?
Howard

1
@dude, chắc chắn là 4?
Peter Taylor

Câu trả lời:


4

Con trăn

Đây là giải pháp của tôi. Tôi nghĩ nó vẫn có thể là O (n 2 ), nhưng tôi nghĩ trường hợp trung bình tốt hơn thế nhiều.

Về cơ bản, nó hoạt động bằng cách chuẩn hóa từng chuỗi để bất kỳ phép quay nào cũng có cùng dạng. Ví dụ:

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

Việc chuẩn hóa được thực hiện bằng cách tìm kiếm ký tự tối thiểu (theo mã char) và xoay chuỗi sao cho ký tự đó ở vị trí cuối cùng. Nếu ký tự đó xuất hiện nhiều lần, thì các ký tự sau mỗi lần xuất hiện sẽ được sử dụng. Điều này cung cấp cho mỗi từ theo chu kỳ một biểu diễn chính tắc, có thể được sử dụng làm khóa trong bản đồ.

Chuẩn hóa là n 2 trong trường hợp xấu nhất (trong đó mọi ký tự trong chuỗi đều giống nhau, ví dụ aaaaaa), nhưng hầu hết thời gian sẽ chỉ có một vài lần xuất hiện và thời gian chạy sẽ gần hơn n.

Trên máy tính xách tay của tôi (Intel Atom lõi kép @ 1,66GHz và 1GB ram), chạy nó trên /usr/share/dict/words(234.937 từ với độ dài trung bình 9,5 ký tự) mất khoảng 7,6 giây.

#!/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) một lần nữa

Phương pháp tôi đã sử dụng là tính toán hàm băm của mỗi từ bắt đầu từ mỗi ký tự trong chuỗi; vì nó là một hàm băm lăn, phải mất thời gian O (n) (trong đó n là độ dài từ) để tính tất cả các giá trị băm. Chuỗi được coi là số cơ sở 1114112, đảm bảo giá trị băm là duy nhất. (Điều này tương tự như giải pháp Haskell, nhưng hiệu quả hơn vì nó chỉ đi qua chuỗi hai lần.)

Sau đó, với mỗi từ đầu vào, thuật toán sẽ kiểm tra hàm băm thấp nhất của nó để xem liệu nó đã có trong tập băm được nhìn thấy chưa (một bộ Python, do đó tra cứu là O (1) trong kích thước của tập hợp); nếu có, thì từ hoặc một trong các phép quay của nó đã được nhìn thấy. Mặt khác, nó thêm băm đó vào tập hợp.

Đối số dòng lệnh phải là tên của tệp chứa một từ trên mỗi dòng (như /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

Haskell

Không chắc chắn về hiệu quả của việc này, rất có thể là khá tệ. Ý tưởng trước tiên là tạo ra tất cả các phép quay có thể có của tất cả các từ, đếm các giá trị đại diện duy nhất cho các chuỗi và chọn mức tối thiểu. Bằng cách đó, chúng tôi nhận được một số duy nhất cho một nhóm tuần hoàn.
Chúng tôi có thể nhóm theo số này và kiểm tra số lượng của các nhóm này.

Nếu n là số lượng từ trong danh sách và m là độ dài của một từ thì tính 'số nhóm theo chu kỳ' cho tất cả các từ là O(n*m), sắp xếp O(n log n)và nhóm 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

Toán học

Quyết định bắt đầu lại, bây giờ tôi đã hiểu luật chơi (tôi nghĩ).

Một từ điển 10000 từ gồm các "từ" được tạo ngẫu nhiên duy nhất (chỉ chữ thường) có độ dài 3. Theo cách tương tự, các từ điển khác được tạo ra bao gồm các chuỗi có độ dài 4, 5, 6, 7 và 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];

glấy phiên bản hiện tại của từ điển để kiểm tra. Từ trên cùng được nối với các biến thể tuần hoàn (nếu có). Từ và từ khớp của nó được thêm vào danh sách đầu ra out, của các từ được xử lý. Các từ đầu ra được loại bỏ khỏi từ điển.

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 chạy qua tất cả các từ điển.

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

Ví dụ 1 : từ thực tế

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

{{ "bít tết", "teaks"}, { "bàn tay"}, { "chậu", "điểm"}, { "thanh kiếm", "chữ"}}
4


Ví dụ 2 : Từ nhân tạo. Từ điển các chuỗi có độ dài 3. Đầu tiên, thời gian. Sau đó, số lượng từ chu kỳ.

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

d3

5402


Thời gian là một hàm của chiều dài từ . 10000 từ trong mỗi từ điển.

thời gian

Tôi đặc biệt không biết làm thế nào để diễn giải các phát hiện theo thuật ngữ O. Nói một cách đơn giản, thời gian tăng gấp đôi từ từ điển ba ký tự đến từ điển bốn ký tự. Thời gian tăng gần như không đáng kể từ 4 đến 8 ký tự.


Bạn có thể đăng một liên kết đến từ điển bạn đã sử dụng để tôi có thể so sánh với từ điển của bạn không?
eggonlegs

Liên kết sau đến dictionary.txt sẽ hoạt động: bitshare.com/files/oy62qgro/dipedia.txt.html (Xin lỗi về phút bạn sẽ phải đợi quá trình tải xuống bắt đầu.) BTW, tệp có 3char, 4char ... 8 từ điển tất cả cùng nhau, 10000 từ trong mỗi từ. Bạn sẽ muốn tách chúng ra.
DavidC

Tuyệt vời. Cảm ơn rất nhiều :)
eggonlegs

1

Điều này có thể được thực hiện trong O (n) tránh thời gian bậc hai. Ý tưởng là xây dựng vòng tròn đầy đủ đi qua chuỗi cơ sở hai lần. Vì vậy, chúng tôi xây dựng "awesomeamazin" là chuỗi vòng tròn đầy đủ để kiểm tra tất cả các chuỗi tuần hoàn tương ứng với "tuyệt vời".

Dưới đây là giải pháp 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

Tôi không biết nếu điều này rất hiệu quả, nhưng đây là vết nứt đầu tiên của tôi.

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

không chắc chắn tôi hiểu vấn đề, nhưng điều này phù hợp với ví dụ @dude được đăng trong các bình luận ít nhất. xin vui lòng sửa phân tích chắc chắn không chính xác của tôi.

đối với mỗi từ W trong N từ đã cho của danh sách chuỗi, bạn phải chuyển qua tất cả các ký tự của W trong trường hợp xấu nhất. tôi phải giả sử các hoạt động băm được thực hiện trong thời gian liên tục.

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), $/;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.