2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2  След.
 
 Сравнение ЯП на примере составления "словаря"
Сообщение25.07.2006, 15:46 


22/06/05
164
С интересом прочитал дискуссию о языках программирования на этом форуме,
а также пару статей о сравнении языков.
Захотелось самому попробовать на вкус Python и Lisp.
Придумал простую учебную задачу на структуры данных.

Дан большой текст input.txt.
Нужно посчитать, сколько раз в нём встречается каждое слово.
Разделителями считаются все символы, кроме a-z и A-Z.
Заглавные буквы превращать в строчные.
Пример выходного файла output.txt:
3 9
the 5
label 2
lamp 2
Сначала записаны число различных слов и число слов с повторениями.
Слова с частотами отсортированы по частоте и лексикографически.

Был бы рад увидеть хорошие решения на Лиспе и Питоне
(или на других языках, подходящих к этой задачке).
Есть ли ассоциативные массивы в стандартных библиотеках чистого Си?

Попробовал решить на Питоне и C++.
Python - 18 строк, работает 0.8 секунды,
C++ - 50 строк, работает 0.6 секунды.
Тестировал на примере двухмегабайтного художественного текста.
В текстах программ не считал пустые строки и комментарии.

 Профиль  
                  
 
 
Сообщение26.07.2006, 02:24 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Я бы написал что-либо в таком духе (хотя и не знаю, то ли это, что Вы хотите):
Код:
def process(ifn, ofn):
  text = open(ifn, "rb").read()

  xlatetab = [" "] * 256
  for och in range(ord('a'),ord('z')+1):
    ch = chr(och)
    xlatetab[och] = ch
    xlatetab[ord(ch.upper())] = ch

  words = text.translate("".join(xlatetab)).split()
  words.sort()

  f = open(ofn, "w")
  if words:
    word = words[0]; count = 0
    for w in words:
      if w == word:
        count += 1
      else:
        print >>f, word, count
        word = w; count = 1
    print >>f, word, count
  f.close()

if __name__ == "__main__":
  process("input.txt", "output.txt")

Было бы несколько легче, если бы Вы привели свои варианты...

 Профиль  
                  
 
 спасибо; вот мой вариант
Сообщение26.07.2006, 13:47 


22/06/05
164
незваный гость, спасибо за пример решения и за пропаганду Питона :).
Оформлю свой вариант на Питоне, используя элементы Вашего стиля.
У меня слова записываются в файл результатов по убыванию частот.
Код:
def cmp_pairs((word1, count1), (word2, count2)):
  if cmp(count2, count1) != 0:
    return cmp(count2, count1)
  return cmp(word1, word2)

def process(ifn, ofn):
  xlatetab = [" "] * 256
  for och in range(ord('a'), ord('z') + 1):
    ch = chr(och)
    xlatetab[och] = ch
    xlatetab[ord(ch.upper())] = ch

  text = open(ifn, "r").read()
  words = text.translate("".join(xlatetab)).split()
  d = dict()
  for word in words:
    d[word] = d.get(word, 0) + 1
  pairs = d.items()
  pairs.sort(cmp = cmp_pairs)
  m = sum([count for (word, count) in pairs])

  fdst = open(ofn, "w");
  print >>fdst, len(pairs), m
  for word, count in pairs:
    print >>fdst, word, count

if __name__ == "__main__":
  process("input.txt", "output.txt")

На C++ делал приблизительно то же самое, но получилось раза в 2-3 длиннее.
Интересно бы ещё посмотреть решение на Лиспе.

 Профиль  
                  
 
 
Сообщение27.07.2006, 00:55 


13/07/06
68
Цитата:
Дан большой текст input.txt.
Нужно посчитать, сколько раз в нём встречается каждое слово.
Разделителями считаются все символы, кроме a-z и A-Z.
Заглавные буквы превращать в строчные.

Откровенно простая задача, это всё равно что сравнивать сапёрную лопатку и бульдозер для вскапывания клумбы. Объём "служебных" операций, типа всякого рода циклов и операций ввода/вывода, которые во всех языках примерно одинаковы, будет сравним с самой логикой. Попробуй вместо этого более другую задачу, например трансляцию математических формул, записаную в человечьем стиле, в вид, пригодный для вычислений на компьютере, последующую компиляцию и выполнение полученного. В зависимости от "фичастости" такого транслятора, я думаю объём кода и скорость выполнения будет разниться на порядки.

Цитата:
Был бы рад увидеть хорошие решения на Лиспе и Питоне
(или на других языках, подходящих к этой задачке).

Советую потеребить знатоков Перла. На нём должно в пару строк решаться.
Цитата:
Есть ли ассоциативные массивы в стандартных библиотеках чистого Си?

Есть. Что-то вроде такого:
Код:
struct {
  char * key;
  int value;
} assoc;
struct assoc assocar [];


Цитата:
В текстах программ не считал пустые строки и комментарии.

Считать строки вообще - неполезное развлечение. В Лисп-программу можно при желании в одну строку записать, в С - почти всю. А ещё есть obfuscated code :lol:

Цитата:
C++ - 50 строк, работает 0.6 секунды.


Ну запости сюда тоже, для "зверинца" :)

Вот один из вариантов на Лиспе:
Код:
(defmacro how-many (obj ha) `(values (gethash ,obj ,ha 0)))

(defmacro 2list (h) `(let ((l nil)) (progn (maphash (lambda (k v) (setq l (cons (list k v) l))) ,h) l)))

(defun count-it (obj ha) (unless (null obj) (incf (how-many obj ha))))

(defun get-words (instr words &optional cur-word)
    (let ((c (read-char instr nil)))
        (cond
            ((null c) words)
            ((alpha-char-p c) (get-words instr words (concatenate 'string cur-word (string (char-downcase c)))))
            (t (progn (count-it cur-word words) (get-words instr words))))))
   
(let ((ostr (open "output.txt" :direction :output :if-exists :rename-and-delete :if-does-not-exist :create)))
    (dolist (x (sort
        (2list (get-words (open "input.txt" :direction :input) (make-hash-table :test 'equal)))
        (lambda (a b) (string< (first a) (first b)))))
    (format ostr "~A ~D~%" (first x) (second x)))
    (close ostr))
(quit)


И заодно на С:
Код:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

struct word {char * it; int cnt;};

int mystrcmp (char ** a, char ** b) {
    return  strcmp (*a, *b);
}

int main (int argc, char * argv []) {
    int bufsize = 1024;
    int wordsize = 256;
    int bufcnt = 0, wordcnt = 0;
    FILE * f = fopen ("input.txt", "r");
    char * buf = malloc (1024), ** words = (char **) malloc (256 * sizeof (char *));
    char inword = 0;
    int i, cnt;
    char * prev;

    buf [0] = 0;
    while (EOF != (i = fgetc (f))) {
        if (isalpha (i)) inword = 1, buf [bufcnt++] = (char) tolower (i);
        else if (inword) inword = 0, buf [bufcnt++] = 0;
        if (bufsize == bufcnt) bufsize += 1024, buf = realloc (buf, bufsize);
    }
    fclose (f);
    if (inword) inword = 0, buf [bufcnt++] = 0;
    words [0] = buf;
    bufcnt--;
    for (i = 1; i < bufcnt ; i++) {
        if (!buf [i]) words [wordcnt++] = &buf [i + 1];
        if (wordcnt == wordsize) wordsize += 256, words = realloc (words, wordsize * sizeof (char *));
    }
    qsort (words, wordcnt, sizeof (char *), (int (*)(const void*,const void*)) mystrcmp);
    inword = 1;
    cnt = 0;
    prev = "";
    for (i = 0; i < wordcnt; i++) {
        inword = strcmp (prev, words [i]);
        if (inword) {
            if (i) printf (" %i\n", cnt);
            cnt = 1;
            prev = words [i];
            printf ("%s", prev);
        } else cnt++;
    }
    printf (" %i\n", cnt);
    return 0;
}


ЗЫ Выяснили, что код на Лиспе ниже, но шире кода на Питоне :lol:

 Профиль  
                  
 
 
Сообщение27.07.2006, 16:20 


22/06/05
164
bugmaker, спасибо за пропаганду Лиспа и примеры.
С замечаниями согласен: упражнение простое, и считать строчки - не очень правильно.
В программах не хватает сортировки по убыванию частот, но это несложно добавить.

По поводу ассоциативных массивов - имел в виду сбалансированные бинарные деревья (например, "красно-чёрные"), для которых операции поиска, вставки и удаления занимают O(log n) времени.
Как только правильно сформулировал, так сразу и нашёл в glib. :)
Впрочем, эту задачку отлично решили и без деревьев.

Программа на C обработала двухмегабайтный текст за полсекунды.
В Лиспе ещё не научился включать оптимизацию, поэтому работало полминуты.
Запускал так (CMU Common Lisp): lisp -quiet -load bugmaker_textanalyse.lisp

 Профиль  
                  
 
 
Сообщение27.07.2006, 19:09 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Егор писал(а):
Как только правильно сформулировал, так сразу и нашёл в glib.

Я не уверен, что на glib корректно ссылаться, как на стандартную. :) Не то, чтобы она стала от этого сколь-нибудь хуже.

Егор писал(а):
Программа на C обработала двухмегабайтный текст за полсекунды.

Измерение времени работы программы -- один из самых скользких вопросов. Может быть потому, что математиков не учат экспериментированию, и, соответственно, теории оного. Некоторые вопросы, которые непременно возникают: а) какая операционная система, машина, компилятор, режимы оптимизации; 2) как измерялось время, каково разрешение таймера, как учитывались остальные процессы, происходящие в это время на компе; 3) как влияло кеширование доступа к диску, учитывалось ли оно.

Вообще говоря, можно было бы и проигнорировать ввод-вывод при замерах времени (мерять только алгоритмическую часть). С другой стороны, ввод-вывод можно организовывать по-разному, и это тоже накладывает отпечаток на время.

На мой взгляд (вкус), 0.5 секунды достоверно не меряются -- я предпочитаю иметь дело с временами порядка 2-3 секунд минимум. С другой стороны, 2 мегабайта могут запросто сесть даже не в программный кеш, а в кеш жесткого диска (сейчас обычным является 8 МБ).

 Профиль  
                  
 
 
Сообщение27.07.2006, 23:58 


13/07/06
68
Цитата:
спасибо за пропаганду Лиспа и примеры.

Да на здоровье, :D

Цитата:
В программах не хватает сортировки по убыванию частот, но это несложно добавить.

В Лисп-программу несложно, а вот в С - намного сложнее. Проявление низкоуровневости С.

Цитата:
В Лиспе ещё не научился включать оптимизацию, поэтому работало полминуты.

Полминуты - это много. Хотя известно, что в Лисп ввод-вывод жутко медленный из-за большой разницы в представлении байтовых массивов и строк и необходимости преобразовывать меж ними при чтении файла, всё равно много. Обычно программа занимается вводом-выводом весьма малую часть времени своей работы, поэтому ввод-вывод не торопятся оптимизировать, это просто никому не нужно. Есть специальные методы, ускоряющие ввод-вывод при необходимости, не всегда "чистые", например

Код:
(defun slurp-stream (i) (let ((seq (make-string (file-length i)))) (read-sequence seq i) seq))
(defmacro how-many (obj ha) `(values (gethash ,obj ,ha 0)))
(defun count-it (obj ha) (unless (null obj) (incf (how-many obj ha))))
(defun 2list (h) (let ((l nil)) (progn (maphash (lambda (k v) (setq l (cons (list k v) l))) h) l)))

(defun get-words (s words &optional (start 0))
    (let (
        (pos-a (position-if 'alpha-char-p s :start start))
        (pos-n (position-if-not 'alpha-char-p s :start start)))
        (cond
            ((null pos-a) words)
            ((null pos-n) (progn (count-it (string-downcase (subseq s start)) words) words))
            ((< pos-a pos-n) (progn (count-it (string-downcase (subseq s start pos-n)) words) (get-words s words pos-n)))
            (t (get-words s words pos-a)))))

(let (
    (ostr (open "output.txt" :direction :output :if-exists :rename-and-delete :if-does-not-exist :create))
    (istr (open "input.txt" :direction :input)))
    (dolist (x (sort (2list (get-words (slurp-stream istr) (make-hash-table :test #'equal))) (lambda (a b) (string-lessp (first a) (first b)))))
        (format ostr "~A ~D~%" (first x) (second x)))
    (close ostr)
    (close istr))
(quit)


Чтобы было быстрее, обычно достаточно скомпилировать. clisp это может делать при загрузке, если указать ключ -С, а в CMUCL это можно сделать из оболочки например так
Код:
echo '(compile-file "theprogram.lisp")(quit)' | lisp

и запускать полученый файл
Код:
lisp -quiet -noinit -load theprogram.x86f

Если нужна ещё большая оптимизация, можно воткнуть в программу такое
Код:
(declaim (optimize (speed 3) (safety 0)))

Но чтобы оно реально хорошо оптимизировало, нужны дополнительные махинации, такие как явное указание типов переменных. Места, в которых требуется вмешательство программиста для оптимизации, указываются в сообщениях при компиляции.

 Профиль  
                  
 
 
Сообщение28.07.2006, 04:15 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
С с сортировкой по частотам.
Код:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  char* word;
  int   freq;
} WORDDESCR;

int compare1(const char** w1, const char** w2) {
  return strcmp(*w1, *w2);
}

int compare2(const WORDDESCR* wd1, const WORDDESCR* wd2) {
  if (wd1->freq == wd2->freq) {
    return strcmp(wd1->word, wd2->word);
  } else {
    return wd2->freq - wd1->freq;
  }
}

void process(const char* ifn, const char* ofn) {
  FILE* f;
  long flen;
  char* text;
  char delim[256] = {0};
  int ix, iy;
  char* w;
  char **words, **pw, **wordse;
  int wordcount, wordstep, wordsize;
  WORDDESCR *descrs, *qd, *descre;

  f = fopen(ifn, "rb");
  fseek(f,0,SEEK_END); flen = ftell(f); fseek(f,0,SEEK_SET);
  text = malloc(flen+1);
  fread(text, 1, flen, f); text[flen] = 0;
  fclose(f);

  strlwr(text);

  iy = 0;
  for (ix = 1; ix < 256; ++ix) {
    if (ix < 'a' || ix > 'z') {
      delim[iy++] = ix;
    }
  }

  wordsize = wordstep = (int)(flen * 0.2);
  pw = words = malloc(wordsize * sizeof(char*));
  wordse = words + wordsize;
  for (w = strtok(text, delim); w != NULL; w = strtok(NULL, delim)) {
    if (pw >= wordse) {
      wordsize += wordstep;
      words = realloc(words, wordsize * sizeof(char*));
      wordse = words + wordsize;
      pw = wordse - wordstep;
    }
    *(pw++) = w;
  }
  wordcount = pw - words; wordse = pw;

  qsort(words, wordcount, sizeof(char*), compare1);

  descrs = malloc(wordcount * sizeof(WORDDESCR));
  w = ""; qd = descrs;
  for (pw = words; pw < wordse; ++pw) {
    if (strcmp(w, *pw) == 0) {
      ++qd[-1].freq;
    } else {
      qd->word = w = *pw; qd->freq = 1; ++qd;
    }
  }

  free(words);

  descre = qd;
  wordcount = (qd-descrs);

  qsort(descrs, wordcount, sizeof(WORDDESCR), compare2);
 
  f = fopen(ofn, "w");
  for (qd = descrs; qd < descre; ++qd) {
    fprintf(f, "%s %d\n", qd->word, qd->freq);
  }
  fclose(f);
  free(descrs);
}

int main(int argc, char* argv[])
{
  process("input.txt", "output.txt");
  return 0;
}


process() распадается на функции естественным образом. В общем, неразбиение суть от лености, хотя в чем-либо объектном вроде C++ было бы проще. Малые примеры (одноразовые программы, пишущиеся за пятнадцать минут) в чем-то странны -- жалко тратить усилия на нормальное программирование, в частности, на структурирование. Работает себе и ладно...

 Профиль  
                  
 
 
Сообщение28.07.2006, 07:03 


13/07/06
68
Цитата:
хотя в чем-либо объектном вроде C++ было бы проще.

ИМХО сомнительно. Хотя никто примера на С++ не привёл, да и я наверное неудосужусь.

 Профиль  
                  
 
 
Сообщение28.07.2006, 14:59 


22/06/05
164
Последняя lisp-программа после компиляции работает 1.8 секунды, т. е. довольно быстро.
незваный гость писал(а):
Измерение времени работы программы -- один из самых скользких вопросов. ...

С замечаниями согласен. Время работы пишу ради ознакомления, не претендуя на точность или объективность. ;)
C и C++ компилирую с опцией -O3; Питон запускаю без опций.
Процессор Pentium IV 2.80 HGz, оперативки 1 гиг.
Не знаю, насколько эффективно используется параллельность.

В обсуждаемой задачке дольше всего происходит превращение массива слов в упорядоченный словарь. Остальные операции (ввод, вывод, выделение слов, преобразования, сортировка по убыванию частот и прочее) занимают процентов 20--40 от общего времени работы.

Пусть n - общее число слов, m - число различных слов.
Если бросать слова в одну кучу, а потом сортировать, то сложность c2 * n * log(n).
Если добавлять слова в дерево, то c1 * n * log(m).
Над пробным текстом (n=400000, m=11000) эти два способа
работали приблизительно одинаковое время.

Написал ещё один вариант на C, используя гибрид быстрой и поразрядной сортировок ("3-way quicksort for strings", Sedgewick & Bentley) и выделяя память сразу по максимуму.
180 строк, работает 0.18 секунды (делал 100 итераций и делил на 100).
Наверное, к Лиспу или Питону несложно подключить такой алгоритм сортировки в качестве отдельного модуля.

P. S. Не знаю, где скачать много художественных текстов на английском. Русские тексты не очень подходят, так как там логично было бы разбираться с окончаниями, а не хочется.

 Профиль  
                  
 
 
Сообщение28.07.2006, 16:53 
Экс-модератор
Аватара пользователя


23/12/05
12047
Егор писал(а):
Не знаю, где скачать много художественных текстов на английском.

Посмотрите здесь

 Профиль  
                  
 
 
Сообщение28.07.2006, 18:36 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Егор писал(а):
Если бросать слова в одну кучу, а потом сортировать, то сложность c2 * n * log(n). ... Если добавлять слова в дерево, то c1 * n * log(m).

Верно. Только вот для большинства слов частота сильно ограничена, и имеея $m < c_3 n $ вторая оценка превращается в $c_2 n \ln n + (c_2 \ln c_4) n$. Поэтому заметного выигрыша в скорости не дает. Зато может дать заметный проигрыш, если библиотечная сортировка работает быстрее, чем написанная руками -- а это очень частый случай. Впрочем, есть и библиотечные контейнеры тоже....

 Профиль  
                  
 
 
Сообщение28.07.2006, 18:38 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
photon писал(а):
Посмотрите здесь

Только смотрите, Егор, что берете -- Проект Гутенберг содержит все, что угодно: таблицы знаков $\pi$, ДНК человека... :lol:, даже художественную литературу :D. Не все, что с Гутенберга, уместно рассматривать как текст.

 Профиль  
                  
 
 
Сообщение30.07.2006, 10:24 


22/06/05
164
photon, спасибо за ссылку.
незваный гость писал(а):
Только смотрите, Егор, что берете -- Проект Гутенберг содержит все, что угодно: таблицы знаков $\pi$, ДНК человека... :lol:, даже художественную литературу :D. Не все, что с Гутенберга, уместно рассматривать как текст.

Да, тут легко перепутать. :lol:

незваный гость, согласен, что $\log n$ обычно не очень отличается от $\log m$, так что эти два алгоритма (сортировка и деревья) должны быть примерно одинаковы по времени работы. Оказалось, что так оно и есть.

Собираюсь ещё напрячь знатока Перла, как подсказал bugmaker, и попробовать специальные деревья для работы со строками.

 Профиль  
                  
 
 тестирую
Сообщение14.08.2006, 21:09 


22/06/05
164
Добавил ещё решение на C через TST ("ternary search trie"). Работает очень резво, как и "3-way string qsort" (сортировка Сэджвика-Бентли). Видимо, узлы TST лучше всего хранить большими блоками (как в std::queue), но ради упрощения написал выделение памяти по максимуму.

В программе на Лиспе написал разбиение на слова без рекурсии (и добавил в конце сортировку пар). Теперь выполняется быстрее, но своего времени потратил немало. ;)

Сравнение пар на Питоне можно записать кратко:
Код:
def cmp_pairs((str1, cnt1), (str2, cnt2)):
  return cmp(cnt2, cnt1) or cmp(str1, str2)

На Перле сравнение и разбинение на слова ещё чуть короче. К сожалению, не смог привлечь знатока Перла.

Для измерения времени написал в программах цикл - исполнять 10 раз, и потом делил результаты на 10. При оценке объёма программ решил считать пустые строки (тут, наверное, любой подход субъективен).

Вот программы, вместе со скриптом-тестером на Питоне.

Для тестирования скачал с gutenberg.org несколько больших текстов на английском:
Bible - King James Bible,
Shakespeare - The Complete Works of William Shakespeare,
Thesaurus - Roget's Thesaurus of English Words and Phrases,
Ulysses - Ulysses, by James Joyce.
Вот эти тексты, утоптанные RARом.

Получились такие результаты:
\begin{tabular}{|l|r|r|r|r|r|r|}
\cline{1-5}
\textbf{Input files} & Shakespeare
& Bible
& Thesaurus
& Ulysses
\\ \cline{1-5}
size (bytes) &  5582655
&  4445260
&  2801695
&  1561677
\\ \cline{1-5}
n (words) &  928012
&  793877
&  197768
&  270556
\\ \cline{1-5}
m (dict's size) &  23683
&  12876
&  34977
&  29127
\\
\cline{1-5}\hline
\textbf{Programs}
&\multicolumn{4}{|c|}{time (sec)}
& size & lines \\ \hline
C, TST
& 0.25
& 0.17
& 0.13
& 0.13
& 3674 & 151
\\ \hline
C, 3-way str qsort
& 0.49
& 0.36
& 0.15
& 0.16
& 4518 & 176
\\ \hline
C, qsort
& 1.02
& 0.78
& 0.24
& 0.28
& 2599 & 98
\\ \hline
CPP, map
& 1.56
& 1.06
& 0.62
& 0.63
& 1847 & 75
\\ \hline
CPP, sort
& 5.72
& 4.75
& 1.32
& 1.56
& 2186 & 86
\\ \hline
Lisp, hash
& 4.10
& 3.25
& 1.74
& 1.58
& 1229 & 39
\\ \hline
Perl, hash
& 1.80
& 1.23
& 1.27
& 1.16
& 707 & 30
\\ \hline
Perl, sort
& 3.96
& 3.12
& 1.15
& 1.48
& 771 & 34
\\ \hline
Python, dict
& 1.70
& 1.21
& 1.11
& 1.03
& 717 & 30
\\ \hline
Python, sort
& 2.70
& 2.08
& 0.82
& 1.04
& 871 & 37
\\ \hline
\end{tabular}

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 25 ]  На страницу 1, 2  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group