Линукс, Vim, LaTeX, полезные скрипты, визуализация данных, численные расчёты, немного ФП

20090526

Twtrize — сократитель речи

Как известно, письменность избыточна: мы можем угадывать написанные слова, даже если некоторые буквы неразборчивы, перепутаны местами или вообще отсутствуют. К счастью, в компьютерной письменности все буквы разборчивы, почерк у всех одинаково хорош. Именно поэтому появилась возможность очень сильно сокращать слова, убирая из них «лишние» буквы.

Люди иногда сознательно сокращают слова, набирая SMS или твиты — чтобы потратить меньше денег или укоротить сообщение.

Идея возникла, когда на одном из многочисленных «сократителей URL» я увидел надпись «Shrink text». И мне пришло в голову, что вот он возьмёт, и сократит сам текст: выдаст что-нибудь вроде «shrnk txt». Конечно, сервис всего лишь заменял в тексте URL, но я подумал, что можно было бы сокращать и сам текст.

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

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

Зависимости


Программа написана на Literate Haskell (это значит, что то, что, вы сейчас читаете, и есть программа!). Используются следующие модули:
> import System.IO.UTF8 as U
> import Data.Char (toLower)
> import Text.Regex.Posix ((=~))
> import Data.Char (isPunctuation)

TODO: Я использую старый способ работать с UTF-8 (utf8-string), надо переделать под новую библиотеку text.

Алгоритм


Данная программа «сжимает» русский текст так:
I. Из слов убираются (почти) все гласные и мягкие знаки,
> filterVowels = filter (`notElem` (aVowels ++ jVowels))

Неприкосновенны гласные, которые:
I.a. являютя частью приставки «не-»
> rmVowels = map wordFilter
> where
> wordFilter ('н':'е':cs) = "не" ++ wordFilter cs

I.b. стоят в трёх- и менее -буквенных словах
>    wordFilter w = if length w <= 3
> then w

I.c. стоят в начале или конце слова
>                    else
> let (prefix,inner,ending) = splitWord w
> in prefix ++ (ajaFilter inner) ++ ending

>    splitWord s  = let p = takeWhile dontRemove s
> r = drop (length p) s
> e = reverse $ takeWhile dontRemove $ reverse r
> m = take ((length r) - (length e)) r
> dontRemove c = c `elem` vowels || isPunctuation c
> in (p,m,e)

I.d. являются комбинациями со звуком «й»: «-ою-», «-ая—» и проч.
>    ajaFilter [] = []
> ajaFilter s = let (b,m,a) = s =~ diftPat :: (String,String,String)
> diftPat = "[" ++ vowels ++ "][" ++ jVowels ++ "]"
> in (sameConsFilter b) ++ m ++ (ajaFilter a)

I.e. стоят меж двух одинаковых согласных
>    sameConsFilter [] = []
> sameConsFilter s =
> let (b,m,a) = s =~ sameConsPat :: (String,String,String)
> sameConsPat = "(["++consonants++"])[" ++ vowels ++ "]\\1"
> in (filterVowels b) ++ m ++ (sameConsFilter a)

Программа использует такой список гласных:
> vowels = aVowels ++ jVowels

где есть и простые гласные (к ним же причислен и мягкий знак)
> aVowels = "аиоуыэь"

и дифтонгообразующие (не знаю правильного термина — в общем, дающие звук «й»),
к ним же причислена и буква «й»:
> jVowels = "яйёюе"

Для некоторых правил требуется также список русских согласных:
> consonants = "бвгджзклмнпрстфхцчшщ"

II. из предложений убираются знаки препинания, кроме точек, вопросительных и восклицательных знаков
> rmSomePunctuation = filter (not . null) . map rmTrailing
> where rmTrailing = reverse . rmHead . reverse
> rmHead [] = []
> rmHead s@(c:cs) = case c `elem` rmlist of
> True -> rmHead cs
> False -> s

Список подлежащих удалению знаков препинания:
>         rmlist = ",;-—:–"

III. из текста удаляются некоторые предлоги (в телеграфном стиле)
> rmPrepositions = filter (`notElem` preps) . words
> where preps = [ "в", "во", "на", "над", "к", "от", "из"
> , "по", "под", "через" ]

IV. для пущей стилизации текст пишется в нижнем регистре
> tolower = map toLower


Использование программы


Программу можно использовать как простой unix-фильтр: он читает текст из потока stdin и печает «сжатый» текст в стандартный вывод (stdout).
> main = U.interact $ (++ "\n") . twtrize

> twtrize = unwords . filter ( not . null ) .
> rmVowels . rmSomePunctuation . rmPrepositions . tolower

Пример:
    $ printf "Гласные, а также некоторые предлоги — как, например, «на», — из \
текста удаляются, но какие-то остаются.\n" | runhaskell twtrize.lhs

глсные а ткже нектрые прдлги как нпрмр «на» ткста удляются но какие-то
остаются.


Последняя версия: исходник здесь. Лицензия: BSD-3.

13 коммент.:

  1. Вот это да! я по такому алгоритму лекции в институте сокращал )) даже достал тетрадку 7 летней давности, ой помню ругались люди тогда ...

    ОтветитьУдалить
  2. Вандализм это, согласен :)

    ОтветитьУдалить
  3. Было бы занятно к этому скрипту веб-интерфейс прикрутить или в какой-нибудь твиттер-клиент его встроить.

    ОтветитьУдалить
  4. а в немецком это почти официальный способ сокращения слов - выкидывание гласных.

    ОтветитьУдалить
  5. Серёжа, срочно патентуй :-) Потом ваяешь вебморду и раскручиваешь как средство отправки SMS-ок. Предвижу грандиозный успех предприятия :-)

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

    А программа хороша.

    ОтветитьУдалить
  6. Вот очень люблю я тебя за такие посты.

    ОтветитьУдалить
  7. Вот бы обратное действие этот алгоритм мог делать =)

    ОтветитьУдалить
  8. Вы филологией занимались? Или каким-другим образом придумали такой оригинальный способ?

    ОтветитьУдалить
  9. 2 axet:

    Да нет, филологического образования у меня нет, я математик (прикладной). Просто по опыту знаю, что можно без ущерба выкидывать некоторые буквы в смс. Ещё читал об исследовании Мэтта Дэвиса, что внутренние буквы в слове можно переставлять, потому что люди читают слова целиком, как единый образ (как иероглифы, в общем, а иероглифы со временем упрощаются). И знаю, что в некоторых языках (например, в церковно-славянском) гласные при записи тоже могут сокращаться.

    Вначале попробовал удалять все гласные, но получалось нечитаемо. Думал оставлять только ударные, но тогда нужен словарь ударений, такую программу сложнее сделать. Поэтому стал добавлять простые исключения-эвристики. Конечные хорошо оставлять, потому что они несут информацию о склонении, и с ними фраза остаётся связной. Начальные редки, поэтому с ними слова лучше узнаются. «Не-» — слишком сильно важная для понимания смысла приставка, чтобы её можно было портить. Исключение для гласных между одинаковыми согласными — мне кажется, так лучше (не «склнние», а «склнение»). Необходимо было и что-то сделать с комбинациями дающими звук «й», они часты, а если их убирать, то и указания на звук «й» исчезает, читается хуже. Решил такие пары оставлять.

    Вот так и придумал.

    ОтветитьУдалить
  10. А мы-таки пользуемся таким алгоритмом уже более 2000 лет.

    ОтветитьУдалить
  11. 2 korey4ik: арабский или иврит?

    ОтветитьУдалить
  12. Этот комментарий был удален автором.

    ОтветитьУдалить
  13. Этот комментарий был удален администратором блога.

    ОтветитьУдалить