Hitung jumlah kata siklik dalam suatu input


9

Kata Siklik

Pernyataan masalah

Kita dapat menganggap kata siklik sebagai kata yang ditulis dalam lingkaran. Untuk mewakili kata siklik, kami memilih posisi awal yang sewenang-wenang dan membaca karakter dalam urutan searah jarum jam. Jadi, "gambar" dan "turepic" adalah representasi untuk kata siklik yang sama.

Anda diberi kata String [], yang setiap elemennya merupakan representasi kata siklik. Kembalikan jumlah kata siklik yang berbeda yang diwakili.

Kemenangan tercepat (Big O, di mana n = jumlah karakter dalam sebuah string)


3
Jika Anda mencari kritik terhadap kode Anda maka tempat untuk pergi adalah codereview.stackexchange.com.
Peter Taylor

Keren. Saya akan mengedit untuk penekanan pada tantangan dan memindahkan bagian kritik ke tinjauan kode. Terima kasih Peter.
Eggonlegs

1
Apa kriteria yang menang? Kode terpendek (Golf Code) atau yang lainnya? Apakah ada batasan pada bentuk input dan output? Apakah kita perlu menulis fungsi atau program yang lengkap? Apakah harus di Jawa?
ugoren

1
@eggonlegs Anda menentukan big-O - tetapi sehubungan dengan parameter yang mana? Jumlah string dalam array? Apakah perbandingan string kemudian O (1)? Atau jumlah karakter dalam string atau jumlah karakter? Atau sesuatu yang lain?
Howard

1
@ Bung, pasti itu 4?
Peter Taylor

Jawaban:


4

Python

Inilah solusi saya. Saya pikir itu mungkin masih O (n 2 ), tapi saya pikir kasus rata-rata jauh lebih baik dari itu.

Pada dasarnya ini bekerja dengan menormalkan setiap string sehingga setiap rotasi akan memiliki bentuk yang sama. Sebagai contoh:

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

Normalisasi dilakukan dengan mencari karakter minimum (dengan kode char), dan memutar string sehingga karakter berada di posisi terakhir. Jika karakter itu muncul lebih dari satu kali, maka karakter setelah setiap kejadian digunakan. Ini memberikan setiap kata siklik representasi kanonik, yang dapat digunakan sebagai kunci dalam peta.

Normalisasi adalah n 2 dalam kasus terburuk (di mana setiap karakter dalam string adalah sama, misalnya aaaaaa), tetapi sebagian besar waktu hanya akan ada beberapa kejadian, dan waktu berjalan akan lebih dekat n.

Di laptop saya (dual core Intel Atom @ 1.66GHz dan ram 1GB), menjalankan ini /usr/share/dict/words(234.937 kata dengan panjang rata-rata 9,5 karakter) membutuhkan waktu sekitar 7,6 detik.

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

Metode yang saya gunakan adalah untuk menghitung hash bergulir dari setiap kata dimulai pada setiap karakter dalam string; karena ini adalah hash yang bergulir, dibutuhkan O (n) (di mana n adalah panjang kata) waktu untuk menghitung semua hash n. String diperlakukan sebagai nomor basis-1114112, yang memastikan hash unik. (Ini mirip dengan solusi Haskell, tetapi lebih efisien karena hanya melewati string dua kali.)

Kemudian, untuk setiap kata input, algoritme memeriksa hash terendah untuk melihat apakah hash sudah ada dalam kumpulan hash yang terlihat (himpunan Python, sehingga pencariannya adalah O (1) dalam ukuran himpunan); jika ya, maka kata atau salah satu rotasinya sudah terlihat. Kalau tidak, ia menambahkan hash ke set.

Argumen baris perintah haruslah nama file yang berisi satu kata per baris (seperti /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

Tidak yakin dengan efisiensi ini, kemungkinan besar agak buruk. Idenya adalah untuk pertama-tama membuat semua kemungkinan rotasi dari semua kata, menghitung nilai-nilai yang secara unik mewakili string dan memilih minimum. Dengan begitu kita mendapatkan nomor yang unik untuk grup siklik.
Kami dapat mengelompokkan berdasarkan nomor ini dan memeriksa jumlah grup ini.

Jika n adalah jumlah kata dalam daftar dan m adalah panjang kata maka hitung 'nomor kelompok siklik' untuk semua kata tersebut O(n*m), pengurutan O(n log n)dan pengelompokan 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

Memutuskan untuk memulai lagi, sekarang saya mengerti aturan permainan (saya pikir).

Kamus 10.000 kata dengan "kata-kata" unik yang disusun secara acak (hanya huruf kecil) dengan panjang 3. Dengan cara yang sama, kamus lain dibuat yang terdiri dari string dengan panjang 4, 5, 6, 7, dan 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];

gmengambil versi kamus saat ini untuk memeriksa. Kata teratas digabungkan dengan varian siklik (jika ada). Kata dan kecocokannya ditambahkan ke daftar output out,, dari kata-kata yang diproses. Kata-kata keluaran dihapus dari kamus.

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 berjalan melalui semua kata kamus.

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

Contoh 1 : kata-kata aktual

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

{{"steak", "teak"}, {"hand"}, {"pots," spot "}, {" sword "," words "}}
4


Contoh 2 : Kata-kata tiruan. Kamus string panjang 3. Pertama, waktu. Kemudian jumlah kata siklus.

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

d3

5402


Pengaturan waktu sebagai fungsi dari panjang kata . 10000 kata dalam setiap kamus.

pengaturan waktu

Saya tidak terlalu tahu bagaimana menafsirkan temuan dalam hal O. Secara sederhana, waktunya kira-kira dua kali lipat dari kamus tiga karakter ke kamus empat karakter. Penentuan waktunya meningkat hampir secara diabaikan dari 4 hingga 8 karakter.


Bisakah Anda memposting tautan ke kamus yang Anda gunakan sehingga saya bisa membandingkannya dengan kamus Anda?
Eggonlegs

Tautan berikut ke dictionary.txt akan berfungsi: bitshare.com/files/oy62qgro/dictionary.txt.html (Maaf tentang saat Anda harus menunggu pengunduhan dimulai.) BTW, file memiliki 3char, 4char ... 8char kamus bersama-sama, 10.000 kata di masing-masing. Anda ingin memisahkan mereka.
DavidC

Luar biasa. Terima kasih banyak :)
eggonlegs

1

Ini dapat dilakukan dalam O (n) menghindari waktu kuadratik. Idenya adalah untuk membangun lingkaran penuh melintasi string dasar dua kali. Jadi kami membangun "amazingamazin" sebagai string lingkaran penuh untuk memeriksa semua string siklik yang sesuai dengan "menakjubkan".

Di bawah ini adalah solusi 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

Saya tidak tahu apakah ini sangat efisien, tetapi ini adalah celah pertama saya.

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

tidak yakin saya mengerti masalahnya, tapi ini cocok dengan contoh @dude diposting di komentar setidaknya. tolong perbaiki analisis saya yang pasti salah.

untuk setiap kata W dalam kata-kata N yang diberikan dari daftar string, Anda harus menelusuri semua karakter W dalam kasus terburuk. saya harus menganggap operasi hash dilakukan dalam waktu yang konstan.

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), $/;
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.