Порахуйте кількість циклічних слів у введенні


9

Циклічні слова

Постановка проблеми

Ми можемо думати про циклічне слово як про слово, написане по колу. Щоб представити циклічне слово, ми вибираємо довільну вихідну позицію і читаємо символи в порядку годинникової стрілки. Отже, "малюнок" і "турепік" - це зображення одного і того ж циклічного слова.

Вам даються слова String [], кожен елемент яких є поданням циклічного слова. Поверніть кількість різних циклічних слів, які представлені.

Найшвидший виграш (Big O, де n = кількість символів у рядку)


3
Якщо ви шукаєте критики свого коду, тоді вам слід зайти codereview.stackexchange.com.
Пітер Тейлор

Класно. Я відредагую акцент на виклик і перенесу критичну частину на перегляд коду. Дякую Петру.
eggonlegs

1
Які критерії виграшу? Найкоротший код (Code Golf) чи що-небудь ще? Чи є обмеження у формі введення та виведення? Чи потрібно нам написати функцію чи повну програму? Чи має бути на Яві?
ugoren

1
@eggonlegs Ви вказали big-O - але стосовно якого параметра? Кількість рядків у масиві? Чи порівняння рядків тоді O (1)? Або кількість символів у рядку або загальна кількість символів? Або що-небудь ще?
Говард

1
@dude, напевно це 4?
Пітер Тейлор

Відповіді:


4

Пітон

Ось моє рішення. Я думаю, що це все-таки може бути O (n 2 ), але я думаю, що середній випадок набагато кращий за це.

В основному це працює, нормалізуючи кожен рядок, щоб будь-яке обертання матиме однакову форму. Наприклад:

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

Нормалізація проводиться шляхом пошуку мінімального символу (за char-кодом) та обертання рядка таким чином, щоб символ знаходився в останньому положенні. Якщо цей символ трапляється не один раз, то символи після кожного виникнення використовуються. Це дає кожному циклічному слову канонічне зображення, яке можна використовувати як ключ у карті.

Нормалізація - це n 2, в гіршому випадку (де кожен символ в рядку однаковий, наприклад aaaaaa), але більшу частину часу буде лише кілька подій, і час роботи буде ближче до n.

На моєму ноутбуці (двоядерний Intel Atom при 1,66 ГГц і 1 ГБ оперативної пам’яті) запуск цього /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

Пітон (3) знову

Я використовував метод, щоб обчислити прокручування хешу кожного слова, починаючи з кожного символу в рядку; Оскільки це хеш-ролл, що обертається, для обчислення всіх п хешів потрібно O (n) (де n - довжина слова). Рядок трактується як базовий номер 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), сортування 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

Математика

Вирішив почати заново, тепер, коли я розумію правила гри (я думаю).

Словник на 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]]]

d3

5402


Тимінг як функція довжини слова . 10000 слів у кожному словнику.

таймінги

Я не знаю особливо, як інтерпретувати висновки в термінах О. Простими словами, термін приблизно подвоюється від словника трьох символів до словника чотирьох символів. Час від часу збільшується майже незначно від 4 до 8 символів.


Чи можете ви опублікувати посилання на використаний вами словник, щоб я міг порівняти ваш?
eggonlegs

Має працювати наступне посилання на dictionary.txt: bitshare.com/files/oy62qgro/dictionary.txt.html (Вибачте за хвилину, яку вам доведеться почекати, щоб почати завантаження.) BTW, файл має 3char, 4char ... 8 словників словників разом, по 10000 слів у кожному. Ви хочете їх відокремити.
DavidC

Дивовижно. Велике спасибі :)
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

Perl

не впевнений, що я розумію проблему, але це відповідає прикладу @dude, розміщеному в коментарях принаймні. будь ласка, виправте мій напевно неправильний аналіз.

для кожного слова W у заданих N словах рядкового списку ви повинні перейти через усі символи 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), $/;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.