Отправляет email-рассылки с помощью сервиса Sendsay

KirovLUG: пользователи Linux в Вятке

Генерация индексов для проекта lindocs.

Может кому покажется интересным.

В качестве индексов в базе данных ссылок в проект lindocs планируется
использовать простые английские слова, аббревиатуры, общепринятые
сокращения. Поэтому, скорее всего, у составителей может возникнуть
проблема с слишком творческим подходом к этому вопросу.

Одним из способов предотвратить это - заранее создать достаточно большую
коллекцию индексных слов. Например, ее можно извлечь из статей
опубликованных на сайте www.opennet.ru, т.к. там материалы уже
проиндексированы (индексы прямо указаны после ключевого слова
"Keywords:" для txt-вариантов статей).

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

$ for i in `find . -name "*.txt"`; do head -n 1 $i; done | \
awk '{ for(i=2;i<=NF;i++) { print $i } }' | \
sort | uniq -c | \
sed 's/^ \{1,\}//g' | \
awk '{ print substr("000",1,3-length($1))$1" "$2 }' | \
sort

Результаты будут примерно такими:

028 php
029 auth
029 database
029 install
029 patch
030 web
032 apache
032 win
034 mysql
044 perl
066 mail
100 freebsd
144 linux

В дальнейшем результаты конвейера можно будет легко вставить в SQLite
базу данных.

Ответить   Wed, 12 Jan 2005 17:28:25 +0300 (#295614)

 

Ответы:

Генерация индексов для проекта lindocs. Часть 2

Продолжим сбор статистики по материалам с www.opennet.ru. Напомню, что в
этих материалах упоминаются ключевые слова, указывающие к каким темам
можно отнести тот или иной материал. Данные ключевые слова можно будет
использовать как индексы для проекта lindocs.

Также в данном материале я постараюсь рассмотреть некоторые аспекты
программирования на Shell и Python.

1. Найдем все txt-файлы в текущем каталоге и выведем строчку каждого
файла.

$ find . -name "*.txt" -exec head -n 1 '{}' ';'

2. После выполнения первой команды видно, что не все первые строчки
начинаются с "Keywords:" - не будем выводить такие строки. Построим
конвейер, добавив к предыдущей команде вот такую:

awk '/Keywords\:/ {print}'

3. Также нужно избавиться от строчек, которые содержат в себе только
"Keywords: " и больше никакой полезной информации. Сразу нужно сказать,
что некоторые строчки будут содержать непечатаемый символ 0x0D,
оставшийся в фалйике со времен Windows и DOS (в Unix конец строки 0x0A,
а в Windows 0x0D;0x0A), - это нужно учесть. Изменим предыдущую команду в
конвейере на

sed 's/\x0D//' | awk '/Keywords: [ a-z]/ {print}

4. Начальное слово "Keywords: " при составлении словаря ключевых слов
вообще не нужно, поэтому уберем его. Добавим к конвейеру следующую
команду:

sed 's/Keywords: //'

5. В некоторых строках пробелы стоят в начале и конце строк - уберем и
их, добавив к конвейеру

sed 's/^ //' | sed 's/ $//'

6. В итоге у нас должен получится конвейер следующего вида:

$ find . -name "*.txt" -exec head -n 1 '{}' ';' | sed 's/\x0D//' | \
awk '/Keywords: [ a-z]/ {print}' | \
sed 's/Keywords: //' | sed 's/^ //' | sed 's/ $//'

А результат его работы, примерно, следующего вида:

keyboard scancode print charset
linux rus koi8r unicode locale
linux rus slackware
mozilla print font rus
font size gtk
rus lyx tex
linux suse rus
freebsd rus locale
rus emacs gnus

Дополнение.
Если теперь в конце конвейера добавить команду "wc -l", то можно
получить количество проиндексированных статей (у меня 689).

Ответить   Fri, 14 Jan 2005 16:12:54 +0300 (#297078)

 

Теперь пришла очередь Python.

Для начала создадим скрипт, который принимает со стандартного ввода
(stdin) строки с ключевыми словами и выводит их каждое слово в одну
строчку.

begin on_stat.py #!/usr/bin/python
# -*- coding: KOI8-R -*-

from sys import stdin

def main():
while 1:
line = stdin.readline()
if not line:
break
keywords = line.split()
for i in range(len(keywords)):
print keywords[i]

if __name__ == "__main__":
main()
end on_stat.py Пробежимся по тексту программы.

stdin.readline() - читает из стандартного ввода одну строку, если строка
пустая (не путать со строкой, заканчивающейся на "\n"), то данных на
стандартном вводе больше нет, поэтому цикл завершится в следующей
строке-условии.
line.split() - вызывается метод для объекта-строки "line". Метод считает
строку списком слов, разделенных стандартным разделителем - строка из
символов пробела (пробел, табуляция, новая строка, возврат, прогон
страницы). В результате формируется объект-список.
len(keywords) - вычисляем количество элементов в списке
range(len(keywords)) - создает список, содержащий арифметическую
прогрессию от 0 до длины списка keywords. Цикл "for" извлекает
поочередно элементы этого списка, которые затем используются для
обращения к каждому элементу списка. Поэтому если бы задача сводилась
просто к печати содержимого списка keywords, то цикл выглядел бы иначе:

for i in keywords:
print i
Чтобы посмотреть, что делает скрипт наш первоначальный конвейер нужно
удлинить:

$ find . -name "*.txt" -exec head -n 1 '{}' ';' | sed 's/\x0D//' | \
awk '/Keywords: [ a-z]/ {print}' | \
sed 's/Keywords: //' | sed 's/^ //' | sed 's/ $//' \
./on_stat.py

Ответить   Fri, 14 Jan 2005 17:17:16 +0300 (#297145)

 

Чтобы результаты были еще очевиднее, для тестирования будем использовать
следующий набор команд:

$ echo "firewall faq ipfw freebsd nat" | ./on_stat.py

Сформируем пары из передаваемых ключевых слов. Для каждой статьи будут
свои пары. Для этого изменим цикл следующим образом:

for i in range(len(keywords)-1):
map(prn,[keywords[i]]*(len(keywords)-i-1),keywords[i+1:len(keywords)])
Конструкция "[keywords[i]]*(len(keywords)-i-1)" создает список, в
котором элемент keywords[i] повторяется (len(keywords)-i-1) раз. А
конструкция "keywords[i+1:len(keywords)]" формирует срез списка
keywords. Срез - это тоже список, в котором будут находится все
последующие элементы списка keywords, начиная с i+1.
Элементы из двух получившихся списков попарно передаются как аргументы
функции prn - за это отвечает функция map.

Так как функция prn не определена, так что создадим ее:

def prn(a,b):
print a, b
Эта функция просто печатает передаваемые ей аргументы.

Результат работы скрипта будет следующим:

firewall faq
firewall ipfw
firewall freebsd
firewall nat
faq ipfw
faq freebsd
faq nat
ipfw freebsd
ipfw nat
freebsd nat

Ответить   Mon, 17 Jan 2005 16:34:27 +0300 (#298767)

 

Данное отображение навело на мысли.

Человек знакомый с теорией графов увидит, что это ничто иное как
описание графа. Каждое слово - вершина графа, одинаковые слова - одни и
те же вершины, пара слов - указание какие вершины связаны между собой.

Итак, чем же отобразить получившийся граф? Александр Лубягин подсказал
мне воспользоваться для этого программным пакетом graphviz (в ALT Linux
Master 2.4 есть также пакет с примерами описания графов graphviz-demos).

В первую очередь из выводимых данных нужно сформировать описание графа,
для этого перенаправим вывод программы, выводящей пары вершин (в нашем
случае "./on_stat.py") на вход команды awk:

$ ./on_stat.py | awk '{print $1" -- "$2";"}'
firewall -- faq;
firewall -- ipfw;
firewall -- freebsd;
firewall -- nat;
faq -- ipfw;
faq -- freebsd;
faq -- nat;
ipfw -- freebsd;
ipfw -- nat;
freebsd -- nat;

Если среди вершин встречаются такие, в названии которых есть "-", то в
таком случае вершины лучше заключить в кавычки:

$ ./on_stat.py | awk '{print "\""$1"\" -- \""$2"\";"}'
"firewall" -- "faq";
"firewall" -- "ipfw";
"firewall" -- "freebsd";
"firewall" -- "nat";
"faq" -- "ipfw";
"faq" -- "freebsd";
"faq" -- "nat";
"ipfw" -- "freebsd";
"ipfw" -- "nat";
"freebsd" -- "nat";

Теперь добавим шапку и подвал описания графа:

$ echo "graph G {"; \
./on_stat.py | awk '{print "\""$1"\" -- \""$2"\";"}'; \
echo "}"
graph G {
"firewall" -- "faq";
"firewall" -- "ipfw";
"firewall" -- "freebsd";
"firewall" -- "nat";
"faq" -- "ipfw";
"faq" -- "freebsd";
"faq" -- "nat";
"ipfw" -- "freebsd";
"ipfw" -- "nat";
"freebsd" -- "nat";
}

Описание графа сформировано. Осталось подать эти данные на вход
программе генерирующей графическое отображение заданного графа:

$ (echo "graph G {"; \
./on_stat.py | awk '{print "\""$1"\" -- \""$2"\";"}'; \
echo "}") | \
dot -Tpng -o graph.png

Результирующее изображение будет помещено в graph.png. Помимо растровых
типов (png, jpg, gif) поддерживаются PS и SVG.

На самом деле описание графа может быть более полным - вид графа, вид
вершин, метки, цвета, ссылки - очень многое может повлиять на конечный
вид сгенерированной схемы графа.

Ответить   Tue, 18 Jan 2005 12:18:21 +0300 (#299461)

 

Сейчас, пусть функция prn, не просто печатает аргументы передаваемые ей,
а создает из них строку, которая будет использоваться в качестве ключа в
объекте-словаре (ассоциативный массив). Заодним, сменим название этой
функции.
Почему будет использоваться объект-словарь? Если мы хотим в дальнейшем
сосчитать количество материалов, в которых участвуют оба ключевых слова,
то при каждой сформированной паре ключевых слов, пришлось бы искать -
упоминалась эта пара уже до этого или нет. А в случае использования
конкатенации пары слов в качестве ключа в ассоциативном массиве, можно
воспользоваться стандартными средствами поиска.

def count_pair(a, b):
c_keys = [a, b]
c_keys.sort()
s_key = string.join(c_keys,':')
Сначала в функции создается список, содержащий два ключевых слова.
c_keys.sort() - стандартный метод для объектов-списков, сортирующий
элементы списка.
string.join(c_keys,':') - функция модуля string, производящая
конкатенацию элементов списка в одну строку, в качестве разделителя
между элементами в строке будет выступать ":". Для работоспособности
данного кода в начало скрипта, в секции, где указываются импортируемые
модули, необходимо указать, что импортируется модуль string:

import string
Ключ для словаря сформирован, теперь необходимо реализовать работу с
самим словарем. В начале скрипта создадим пустой объект-словарь:

dc_graph = {}
Теперь допишем функцию count_pair. Если записи с данным ключом еще не
было в словаре, то создадим ее, а если уже была такая запись, то
увеличим значение-счетчик, которое говорит, в скольких материалах была
такая пара ключевых слов.

def count_pair(a, b):
c_keys = [a, b]
c_keys.sort()
s_key = string.join(c_keys,':')
if dc_graph.has_key(s_key):
c = dc_graph[s_key]
else:
c = 0
dc_graph[s_key] = c+1
dc_graph.has_key(s_key) - проверяем имеется ли строка s_key среди ключей
словаря
c = dc_graph[s_key] - получаем значение-счетчик соответствующее ключу
s_key
dc_graph[s_key] = c+1 - присваиваем новое значение записи с ключом
s_key, если такой записи не было, то она создается. Вообще-то, она в
любом случае создается заново, просто в случае, если запись с таким
ключом уже была, то она удаляется (это особенности реализации работы со
словарями в Python)

Если присмотреться к тому, что в итоге получится в объекте-словаре, то
можно увидеть граф: ключи словаря описывают ребра графа, а значения
- вес ребер графа.

Ответить   Tue, 18 Jan 2005 14:15:09 +0300 (#299553)

 

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

Так как вес ребер сосчитан, то перейдем к определению веса вершин. За
это будет отвечать функция count_node, она очень похожа на count_pair:

def count_node(a):
if dc_nodes.has_key(a):
c = dc_nodes[a]
else:
c = 0
dc_nodes[a] = c+1
Так же необходимо изменить тело основного цикла:

for i in range(len(keywords)-1):
count_node(keywords[i])
map(count_pair,
[keywords[i]]*(len(keywords)-i-1),
keywords[i+1:len(keywords)])
count_node(keywords[-1])
keywords[-1] - обращается к первому с конца (т.е. последнему) элементу
списка keywords.

Ответить   Wed, 19 Jan 2005 11:33:56 +0300 (#300304)

 

Вот, может будет интересно

http://www.linux.org.ru/view-message.jsp?msgid=775270

Ответить   Sun, 23 Jan 2005 12:40:29 +0300 (#302970)