У меня есть массив из 1000 или около того записей с примерами ниже:
wickedweather
liquidweather
driveourtrucks
gocompact
slimprojector
Я хотел бы иметь возможность разбить их на соответствующие слова, например:
wicked weather
liquid weather
drive our trucks
go compact
slim projector
Я надеялся, что регулярное выражение поможет. Но, поскольку нет границ, на которых можно было бы остановиться, и нет какого-либо вида заглавных букв, которые я мог бы использовать, я думаю, что может потребоваться какая-то ссылка на словарь?
Я полагаю, это можно сделать вручную, но зачем - когда это можно сделать с помощью кода! =) Но это меня поставило в тупик. Есть идеи?
Привет, оптимальные решения, я видел ваш ответ на вопрос EMR, и мне было интересно, могу ли я связаться с вами с некоторыми вопросами, касающимися ИТ в области здравоохранения?
Также см. Реализации Python и Ruby на stackoverflow.com/questions/11447859/…


Я думаю, вы правы, полагая, что регулярное выражение не подходит для этого. Я бы подошел к этому, используя идею словаря - ищите самый длинный префикс, который является словом в словаре. Когда вы найдете это, отрежьте его и сделайте то же самое с оставшейся частью нити.
Вышеупомянутый метод имеет двусмысленность, например, "drivereallyfast" сначала найдет "driver", а затем возникнут проблемы с "eallyfast". Так что вам также придется вернуться, если вы столкнетесь с этой ситуацией. Или, поскольку у вас не так много строк для разделения, просто вручную обработайте те, которые не прошли автоматическое разделение.
Надо найти файл словаря, чтобы ударить по нему.
Спасибо! Я собираюсь собрать тот и тот Perl вместе, посмотреть, что получится.
Что ж, проблема не решается с помощью регулярного выражения. Решением (вероятно, не лучшим) было бы получить словарь и сопоставить регулярное выражение для каждой работы в словаре с каждым словом в списке, добавляя пробел в случае успеха. Конечно, это будет не очень быстро, но это будет легко запрограммировать и быстрее, чем вручную.
Потребуется решение на основе словаря. Это можно несколько упростить, если у вас ограниченный словарь слов, которые могут встречаться, иначе слова, образующие префикс других слов, будут проблемой.
Для этого я могу понизить мод, но пусть секретарь сделает это.
Вы потратите больше времени на словарное решение, чем на обработку вручную. Кроме того, у вас не будет 100% уверенности в решении, поэтому вам все равно придется уделить ему внимание вручную.
чувак .. теперь я действительно хочу проголосовать против тебя! :-) Мы однажды попробовали подобный подход к фильтрации непослушных поисковых запросов ... потратили больше времени на создание приятного интерфейса, который будет использовать секретарь (в моем случае специалист по связям с общественностью), чем я на классификаторе.
Может ли это сделать человек?
farsidebag far sidebag farside bag far side bag
Вы не только должны использовать словарь, но, возможно, вам придется использовать статистический подход, чтобы выяснить, что наиболее вероятно (или, не дай бог, реальный HMM для выбранного вами человеческого языка ...)
Чтобы узнать, как сделать статистику, которая может быть полезной, я обращаюсь к доктору Питеру Норвигу, который решает другую, но связанную проблему проверки орфографии в 21 строке кода: http://norvig.com/spell-correct.html
(он немного обманывает, сворачивая каждый цикл for в одну строку ... но все же).
Обновлять Это застряло у меня в голове, поэтому мне пришлось родить его сегодня. Этот код выполняет такое же разбиение, что и описанный Робертом Гэмблом, но затем он упорядочивает результаты в зависимости от частоты слов в предоставленном файле словаря (который теперь, как ожидается, будет некоторым текстовым представителем вашего домена или английского языка в целом. Я использовал большой .txt из Norvig, ссылка на который приведена выше, и добавила к нему словарь, чтобы скрыть пропущенные слова).
Комбинация двух слов в большинстве случаев превосходит комбинацию из 3 слов, если разница в частоте не очень велика.
Я разместил этот код с небольшими изменениями в своем блоге
http://squarecog.wordpress.com/2008/10/19/splitting-words-joined-into-a-single-string/ а также немного написал об ошибке недополнения в этом коде ... Я был склонен просто тихо ее исправить, но решил, что это может помочь некоторым людям, которые раньше не видели трюка с журналом: http://squarecog.wordpress.com/2009/01/10/dealing-with-underflow-in-joint-probability-calculations/
Выведите свои слова, а также несколько моих собственных - обратите внимание, что происходит с "orcore":
perl splitwords.pl big.txt words answerveal: 2 possibilities - answer veal - answer ve al wickedweather: 4 possibilities - wicked weather - wicked we at her - wick ed weather - wick ed we at her liquidweather: 6 possibilities - liquid weather - liquid we at her - li quid weather - li quid we at her - li qu id weather - li qu id we at her driveourtrucks: 1 possibilities - drive our trucks gocompact: 1 possibilities - go compact slimprojector: 2 possibilities - slim projector - slim project or orcore: 3 possibilities - or core - or co re - orc ore
Код:
#!/usr/bin/env perl
use strict;
use warnings;
sub find_matches($);
sub find_matches_rec($\@\@);
sub find_word_seq_score(@);
sub get_word_stats($);
sub print_results($@);
sub Usage();
our(%DICT,$TOTAL);
{
my( $dict_file, $word_file ) = @ARGV;
($dict_file && $word_file) or die(Usage);
{
my $DICT;
($DICT, $TOTAL) = get_word_stats($dict_file);
%DICT = %$DICT;
}
{
open( my $WORDS, '<', $word_file ) or die "unable to open $word_file\n";
foreach my $word (<$WORDS>) {
chomp $word;
my $arr = find_matches($word);
local $_;
# Schwartzian Transform
my @sorted_arr =
map { $_->[0] }
sort { $b->[1] <=> $a->[1] }
map {
[ $_, find_word_seq_score(@$_) ]
}
@$arr;
print_results( $word, @sorted_arr );
}
close $WORDS;
}
}
sub find_matches($){
my( $string ) = @_;
my @found_parses;
my @words;
find_matches_rec( $string, @words, @found_parses );
return @found_parses if wantarray;
return \@found_parses;
}
sub find_matches_rec($\@\@){
my( $string, $words_sofar, $found_parses ) = @_;
my $length = length $string;
unless( $length ){
push @$found_parses, $words_sofar;
return @$found_parses if wantarray;
return $found_parses;
}
foreach my $i ( 2..$length ){
my $prefix = substr($string, 0, $i);
my $suffix = substr($string, $i, $length-$i);
if ( exists $DICT{$prefix} ){
my @words = ( @$words_sofar, $prefix );
find_matches_rec( $suffix, @words, @$found_parses );
}
}
return @$found_parses if wantarray;
return $found_parses;
}
## Just a simple joint probability
## assumes independence between words, which is obviously untrue
## that's why this is broken out -- feel free to add better brains
sub find_word_seq_score(@){
my( @words ) = @_;
local $_;
my $score = 1;
foreach ( @words ){
$score = $score * $DICT{$_} / $TOTAL;
}
return $score;
}
sub get_word_stats($){
my ($filename) = @_;
open(my $DICT, '<', $filename) or die "unable to open $filename\n";
local $/= undef;
local $_;
my %dict;
my $total = 0;
while ( <$DICT> ){
foreach ( split(/\b/, $_) ) {
$dict{$_} += 1;
$total++;
}
}
close $DICT;
return (\%dict, $total);
}
sub print_results($@){
#( 'word', [qw'test one'], [qw'test two'], ... )
my ($word, @combos) = @_;
local $_;
my $possible = scalar @combos;
print "$word: $possible possibilities\n";
foreach (@combos) {
print ' - ', join(' ', @$_), "\n";
}
print "\n";
}
sub Usage(){
return "$0 /path/to/dictionary /path/to/your_words";
}
Можно ли это запустить в Windows XP? Как мне загрузить Perl. Очевидно, мне нужно больше узнавать (в плане других языков)! :)
Да, вы ищете что-то под названием ActivePerl, это дистрибутив Windows. Никаких модулей я не использовал, поэтому в стандартную сборку ничего добавлять не нужно. Просто найдите хороший репрезентативный словарь.
+1 - Я не знаю Perl, но я дал вам +1 за то, что вы выходите за рамки служебного долга. Хороший!
Я изменил код, чтобы попытаться сделать его более удобным в обслуживании. Хотя для начала это было довольно прилично.
Я бы не изменил его, если бы это еще не было постом сообщества вики.
да, я сам слишком сильно его изменил (не знал, что есть переключение вики). Спасибо за правки - увы, лучшая практика SE приводит к ухудшению читаемости. Мне больше нравится более ранняя версия в учебных целях, но люди все равно могут найти ее в блоге, поэтому сохраняйте свои правки для сравнения.
В этом коде, очевидно, есть недостаток, он предлагает только «экспертную смену пола» как второй наиболее вероятный результат.
У меня такая же проблема, но мне нужно решение на python :(
Лучшим инструментом для работы здесь является рекурсия, а не регулярные выражения. Основная идея состоит в том, чтобы начать поиск слова с начала строки, затем взять оставшуюся часть строки и найти другое слово и так далее, пока не будет достигнут конец строки. Рекурсивное решение является естественным, поскольку возврат с возвратом должен происходить, когда данный остаток строки не может быть разбит на набор слов. В приведенном ниже решении используется словарь для определения того, что является словом, и распечатываются решения по мере их нахождения (некоторые строки могут быть разбиты на несколько возможных наборов слов, например, wickedweather может быть проанализирован как «мы злые на нее»). Если вам нужен только один набор слов, вам нужно будет определить правила выбора наилучшего набора, возможно, выбрав решение с наименьшим количеством слов или установив минимальную длину слова.
#!/usr/bin/perl
use strict;
my $WORD_FILE = '/usr/share/dict/words'; #Change as needed
my %words; # Hash of words in dictionary
# Open dictionary, load words into hash
open(WORDS, $WORD_FILE) or die "Failed to open dictionary: $!\n";
while (<WORDS>) {
chomp;
$words{lc($_)} = 1;
}
close(WORDS);
# Read one line at a time from stdin, break into words
while (<>) {
chomp;
my @words;
find_words(lc($_));
}
sub find_words {
# Print every way $string can be parsed into whole words
my $string = shift;
my @words = @_;
my $length = length $string;
foreach my $i ( 1 .. $length ) {
my $word = substr $string, 0, $i;
my $remainder = substr $string, $i, $length - $i;
# Some dictionaries contain each letter as a word
next if ($i == 1 && ($word ne "a" && $word ne "i"));
if (defined($words{$word})) {
push @words, $word;
if ($remainder eq "") {
print join(' ', @words), "\n";
return;
} else {
find_words($remainder, @words);
}
pop @words;
}
}
return;
}
не запускал его, но он выглядит как лучшее решение, чем BKB, поскольку дает все возможности.
Это работает как по волшебству. Именно то, что я искал, большое вам спасибо. Пытаюсь перевести на PHP. Если есть версия PHP, поделитесь ею здесь.
Алгоритм Витерби намного быстрее. Он вычисляет те же оценки, что и рекурсивный поиск в ответе Дмитрия выше, но за время O (n). (Поиск Дмитрия занимает экспоненциальное время; Витерби делает это с помощью динамического программирования.)
import re
from collections import Counter
def viterbi_segment(text):
probs, lasts = [1.0], [0]
for i in range(1, len(text) + 1):
prob_k, k = max((probs[j] * word_prob(text[j:i]), j)
for j in range(max(0, i - max_word_length), i))
probs.append(prob_k)
lasts.append(k)
words = []
i = len(text)
while 0 < i:
words.append(text[lasts[i]:i])
i = lasts[i]
words.reverse()
return words, probs[-1]
def word_prob(word): return dictionary[word] / total
def words(text): return re.findall('[a-z]+', text.lower())
dictionary = Counter(words(open('big.txt').read()))
max_word_length = max(map(len, dictionary))
total = float(sum(dictionary.values()))
Тестирование:
>>> viterbi_segment('wickedweather')
(['wicked', 'weather'], 5.1518198982768158e-10)
>>> ' '.join(viterbi_segment('itseasyformetosplitlongruntogetherblocks')[0])
'its easy for me to split long run together blocks'
Чтобы быть практичным, вам, вероятно, понадобится пара уточнений:
Разве это не алгоритм, используемый для сортировки последовательностей ДНК?
Я не знаю, но общая идея Витерби (нахождение наиболее вероятной последовательности скрытых состояний с учетом последовательности наблюдений) - тоже должна иметь применение с ДНК.
У меня вопрос по этому поводу. Мне нравится это решение, однако, пытаясь воспроизвести его на C#, я столкнулся с проблемой. Поскольку первый цикл for находится в диапазоне от 1 до len + 1, а внутренний цикл находится в диапазоне от max (i-maxWordLen) до i, это означает, что первое слово, которое он просматривает, имеет значение от 0 до 1 (длина 2). Если вставить туда предложение вроде «Я думаю ясно», то «Я» не будет распознано как отдельное слово, а скорее поймает «Оно». Я прав в своей логике? Кроме того, на каком языке это написано?
@mj_, это в Python, а диапазон (low, high) означает [low, low + 1, ..., high-1] - то есть верхняя граница не включается. Цикл для j в диапазоне (0, 1) смотрит только на j = 0.
Дарий, почему тогда слово «я» не выбрано в отличие от «его». «Я» имеет большую частоту. Насколько я могу судить, при таком подходе не происходит возврата в случае невозможности сопоставления последующих букв. Я прав?
Вы правы, что не отступает. Он действительно производит сегментацию с наивысшим общим баллом (произведение вероятностей отдельных слов). Мне непонятно, с какой конкретной проблемой вы столкнулись - в вашем словаре есть слово «задуматься», а вы получаете «понятно»? Результаты с частотами, состоящими из одного слова, можно улучшить за счет значительно большего объема вычислений, перейдя к модели второго порядка, в которой вы рассматриваете условные вероятности: P (слово | предшествующее_слово). См. norvig.com/ngrams (ответ на который я должен обновить ссылкой).
Для идеального результата вам понадобится еще пара уточнений: 1) вам нужно вернуть очень маленькую вероятность для не-слов вместо нуля, иначе, если какая-то часть фразы никогда не появится в словаре, это испортит результаты для всего фраза. Я использовал (log (1 / total) -max_word_len-1) * (j-i) для логарифма вероятности отсутствия слов (это также штрафует более длинные неслова, чтобы предотвратить поглощение правильных слов). 2) Вам необходимо сохранить кортежи (-non_words_len, -non_words_count, prob) в проблемах, чтобы минимизировать нераспознанные последовательности и объединить соседние блоки, не состоящие из слов.
Этот ответ похож на «лучшее» для StackOverflow.
Что такое big.txt?
@dawg - просто любой большой текстовый файл, полный английских слов, из которого можно построить словарь. Скажем, вы можете использовать «Войну и мир» из проекта Гутенберг.
Привет, @IlyaSemenov, не могли бы вы помочь мне понять формулы сглаживания, которые вы использовали? Большое спасибо
Список английских слов здесь. raw.githubusercontent.com/dwyl/english-words/master/words.tx t
Это связано с проблемой, известной как разделение идентификатора или токенизация имени идентификатора. В случае OP входные данные кажутся конкатенациями обычных слов; при разделении идентификаторов входными данными являются имена классов, имена функций или другие идентификаторы из исходного кода, и проблема усложняется. Я понимаю, что это старый вопрос, и OP либо решил свою проблему, либо перешел, но в случае, если кто-то еще столкнется с этим вопросом при поиске разделителей идентификаторов (как я был не так давно), я хотел бы предложить Спираль ( «SPlitters для идентификаторов: библиотека»). Он написан на Python, но поставляется с утилитой командной строки, которая может читать файл идентификаторов (по одному на строку) и разбивать каждый из них.
Разделение идентификаторов обманчиво сложно. Программисты обычно используют аббревиатуры, акронимы и фрагменты слов при именовании вещей, и они не всегда используют согласованные соглашения. Даже в тех случаях, когда идентификаторы следуют некоторому соглашению, например, верблюжьему регистру, могут возникнуть неоднозначности.
Спираль реализует множество алгоритмов разделения идентификаторов, включая новый алгоритм под названием Ronin. Он использует различные эвристические правила, английские словари и таблицы частот токенов, полученные из репозиториев исходного кода майнинга. Ronin может разделять идентификаторы, в которых не используется верблюжий регистр или другие соглашения об именах, включая такие случаи, как разделение J2SEProjectTypeProfiler на [J2SE, Project, Type, Profiler], что требует, чтобы считыватель распознал J2SE как единое целое. Вот еще несколько примеров того, что может разделить Ронин:
# spiral mStartCData nonnegativedecimaltype getUtf8Octets GPSmodule savefileas nbrOfbugs
mStartCData: ['m', 'Start', 'C', 'Data']
nonnegativedecimaltype: ['nonnegative', 'decimal', 'type']
getUtf8Octets: ['get', 'Utf8', 'Octets']
GPSmodule: ['GPS', 'module']
savefileas: ['save', 'file', 'as']
nbrOfbugs: ['nbr', 'Of', 'bugs']
Используя примеры из вопроса OP:
# spiral wickedweather liquidweather driveourtrucks gocompact slimprojector
wickedweather: ['wicked', 'weather']
liquidweather: ['liquid', 'weather']
driveourtrucks: ['driveourtrucks']
gocompact: ['go', 'compact']
slimprojector: ['slim', 'projector']
Как видите, он не идеален. Стоит отметить, что Ronin имеет ряд параметров, и их настройка позволяет разделить driveourtrucks, но за счет ухудшения производительности идентификаторов программ.
Более подробную информацию можно найти в Репозиторий на GitHub для Spiral.
Выпущен пакет Python Santhosh thottingal под названием mlmorph, который можно использовать для морфологического анализа.
https://pypi.org/project/mlmorph/
Примеры:
from mlmorph import Analyser
analyser = Analyser()
analyser.analyse("കേരളത്തിന്റെ")
Дает
[('കേരളം<np><genitive>', 179)]
Также он вел блог на тему https://thottingal.in/blog/2017/11/26/towards-a-malayalam-morphology-analyser/
Простое решение с Python: установите пакет отрывок слова: pip install wordsegment.
$ echo thisisatest | python -m wordsegment
this is a test
pip install wordninja
>>> import wordninja
>>> wordninja.split('bettergood')
['better', 'good']
Лучше упомянуть ответ, который породил упомянутый пакет wordninja: stackoverflow.com/a/11642687/8291949
Это будет работать, если это camelCase. JavaScript !!!
function spinalCase(str) {
let lowercase = str.trim()
let regEx = /\W+|(?=[A-Z])|_/g
let result = lowercase.split(regEx).join("-").toLowerCase()
return result;
}
spinalCase("AllThe-small Things");
Хорошо, сначала мы создаем функцию, которая принимает значение. let lowercase = str.trim(), по сути, .trim() не проверяет пробелы в или между, строку, которая проверяет и удаляет пробелы из либо начало, либо конец, либо обе стороны, если они есть, let regEx = /\W+|(?=[A-Z])|_/g - это регулярное выражение, где \ W + не соответствует буквам и цифрам, поэтому, оставляя _ и пробелы для соответствия, | означает ИЛИ ЖЕ, (?=[A-Z]) означает положительный просмотр вперед, при котором выполняется поиск всех Capletters, затем result запускает выражение, соединяет его с дефисом и преобразует все в нижний регистр.
Одно из решений может быть с рекурсией (то же самое можно преобразовать в динамическое программирование):
static List<String> wordBreak(
String input,
Set<String> dictionary
) {
List<List<String>> result = new ArrayList<>();
List<String> r = new ArrayList<>();
helper(input, dictionary, result, "", 0, new Stack<>());
for (List<String> strings : result) {
String s = String.join(" ", strings);
r.add(s);
}
return r;
}
static void helper(
final String input,
final Set<String> dictionary,
final List<List<String>> result,
String state,
int index,
Stack<String> stack
) {
if (index == input.length()) {
// add the last word
stack.push(state);
for (String s : stack) {
if (!dictionary.contains(s)) {
return;
}
}
result.add((List<String>) stack.clone());
return;
}
if (dictionary.contains(state)) {
// bifurcate
stack.push(state);
helper(input, dictionary, result, "" + input.charAt(index),
index + 1, stack);
String pop = stack.pop();
String s = stack.pop();
helper(input, dictionary, result, s + pop.charAt(0),
index + 1, stack);
}
else {
helper(input, dictionary, result, state + input.charAt(index),
index + 1, stack);
}
return;
}
Другое возможное решение - использование структуры данных Tries.
Обратите внимание, что наивная реализация вернет "злую погоду"