Скрипт, который удаляет лишние пробелы между буквами в тексте


12

У меня есть текстовый документ, в котором есть текст, в котором после каждого письма добавляется дополнительный пробел!

Пример:

T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t

Визуально:

T␣h␣e␣␣b␣o␣o␣k␣␣a␣l␣s␣o␣␣h␣a␣s␣␣a␣n␣␣a␣n␣a␣l␣y␣t␣i ␣c␣a␣l␣␣p␣u␣r␣p␣o␣s␣e␣␣w␣h␣i␣c␣h␣␣i␣s␣␣m␣o␣r␣e␣␣i␣ m␣p␣o␣r␣t␣a␣n␣t ...

Обратите внимание, что после каждой буквы есть дополнительный пробел, поэтому между последовательными словами есть два пробела.

Есть ли способ, которым я могу получить awkили sedудалить лишние пробелы? (К сожалению, этот текстовый документ очень объемный, и его ручная обработка займет очень много времени.)  Я понимаю, что это, вероятно, гораздо более сложная проблема, которую нужно решить с помощью простого bash-скрипта, поскольку также необходимо распознавание текста.

Как я могу подойти к этой проблеме?


2
тривиально заменить все пробелы ничем ... но я думаю, что вы хотите разделить слова?
Sundeep

Например:echo 't h i s i s a n e x a m p l e' | sed 's/ //g'
Sundeep

1
Это не ограничивает изменение пробелами между буквами . (Цифры и знаки препинания не являются буквами , например). Вы можете сделать это в sed с помощью цикла. Это также, вероятно, дубликат.
Томас Дики

1
ограничить только между буквами:echo 'T h i s ; i s .a n 9 8 e x a m p l e' | perl -pe 's/[a-z]\K (?=[a-z])//ig'
Sundeep

4
@JuliePelletier: источник оригинальной ревизии показывает, что пробелы между словами были удвоены. Почему вы удвоили их в своем редактировании?
El'endia Starman

Ответы:


16

Следующее регулярное выражение удалит первый пробел в любой строке пробелов. Это должно сделать работу.

s/ ( *)/\1/g

Так что-то вроде:

perl -i -pe 's/ ( *)/\1/g' infile.txt

... заменит infile.txt на "исправленную" версию.


@terdon Я недавно заметил, что люди перестали писать сценарии perl pie как ... perl -pieкак показывает ваше редактирование. В чем причина этого? Пирог всегда работал хорошо для меня и является отличной мнемоникой. Изменилось ли поведение -i для обработки всего, что следует за расширением, а не только тех, которые начинаются с точки? Для них было бы странно ломать что-то идиоматическое.
Деви Морган

1
Ну, это не та идиома, с которой я знаком. Perl был таким до тех пор, как я использовал -i. С другой стороны, я когда-либо использовал его только на компьютерах с Linux, и я не знал об этом больше нескольких лет, поэтому я не могу говорить о его старом поведении. На моей машине , хотя, это: perl -pie 's/a/b/' f, выдает ошибку: Can't open perl script "s/o/A/": No such file or directory. Пока perl -i -pe 's/o/A/' fработает как положено. Так что да, это eрасширение для резервного копирования.
Тердон

Грустное лицо. Что ж, время движется, и это просто означает, что мне нужно переучить порядок параметров. Я думаю, что мой мозг остается мягким Спасибо, что сообщили мне об этом и исправили мой код!
Деви Морган

17

Используйте wordsegmentпакет NLP для сегментации слов на чистом Python:

$ pip install wordsegment
$ python2.7 -m wordsegment <<<"T h e b o o k a l s o h a s a n a n a l y t i c a l p u r p o s e w h i c h i s m o r e i m p o r t a n t"
the book also has an analytical purpose which is more important

1
Использование НЛП, вероятно, является наиболее эффективным решением, если нечего отличать слова. НЛП работает лучше, чем упреждающий словарь в большинстве случаев.
горький

13

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

echo "T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t  " | sed 's/  /\-/g;s/ //g;s/\-/ /g'

... Выходы:

Книга также имеет аналитическую цель, которая является более важной


5
Команда sed со значением «заменить каждый вхождение непробельного символа, за которым следует пробел только с соответствующим sed -e "s/\([^ ]\) /\1/g"
непробельным

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

10

Perl на помощь!

Вам нужен словарь, то есть файл, содержащий одно слово в строке. В моей системе он существует как /var/lib/dict/words, я также видел похожие файлы и /usr/share/dict/britishт. Д.

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

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

my $words = '/var/lib/dict/words';
my %word;

sub analyze {
    my ($chars, $words, $pos) = @_;
    if ($pos == @$chars) {
        $_[3] = 1;  # Found.
        say "@$words";
        return
    }
    for my $to ($pos .. $#$chars) {
        my $try = join q(), @$chars[ $pos .. $to ];
        if (exists $word{$try}) {
            analyze($chars, [ @$words, $try ], $to + 1, $_[3]);
        }
    }
}


open my $WORDS, '<', $words or die $!;
undef @word{ map { chomp; lc $_ } <$WORDS> };

while (<>) {
    my @chars = map lc, /\S/g;
    analyze(\@chars, [], 0, my $found = 0);
    warn "Unknown: $_" unless $found;
}

Для вашего ввода он генерирует 4092 возможных показаний в моей системе.


не a cat a loga c a t a l o g
прошел

@ Richard: OBOE, исправлено. Но теперь он генерирует слишком много возможностей, попробуйте удалить слова из одной буквы.
Чороба

@richard Вы можете решить эту проблему с помощью недетерминированного алгоритма (например, все возможные показания сохраняются) и применить к нему анализатор. Затем вы можете отфильтровать все 4000 возможных показаний в одно с наименьшим количеством ошибок.
Bash0r 10.09.16

6

Примечание: этот ответ (как и некоторые другие здесь) основан на более ранней версии вопроса, где слова не были разделены. На более новую версию можно ответить тривиально .

На входе вроде:

T h e b o o k a l s o h a s a n a n a l y t i c a l p u r p o s e w h i c h i s m o r e i m p o r t a n t

Вы можете попробовать:

 $ tr -d ' ' < file | grep -oiFf /usr/share/dict/words | paste -sd ' '
 The book also has ana na l y tic al purpose which ism ore important

Он обрабатывает слева направо и находит одно самое длинное слово после следующего.

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


@terdon, см. редактировать. Проблема в том, что этот вопрос превратился из сложного и интересного в тривиальный. Есть ли способ, которым вы могли бы разделить его на два вопроса, которые были до и после редактирования?
Стефан Шазелас,

Боюсь, что нет, нет. Тем не менее, умный трюк, даже если не идеальный.
Тердон

1
Строго говоря, вопрос с самого начала был тривиальным - см. Первую версию и ее источник . К сожалению, ОП не понимает , как Stack Обмен делает текст, поэтому правильный ввод текста не было видно , пока Trichoplax не фиксируется форматирование - и, еще более , к сожалению, не было видно то , потому что человек , который одобрил это редактировать сразу пошел и сломал это.
Скотт

2

Похоже на версию Деви Моргана, но с sed:

$ echo "f o o  t h e  b a r" | sed -r "s/[ ]{1}([^ ]{1})/\1/g"
foo the bar

Это sedтолько GNU, и это не эквивалентно Dewi. Стандартным sedэквивалентом Дьюи было быsed 's/ \( *\)/\1/g'
Стефан Шазелас

обратите внимание на «похожие» ;-)
Jaleks

1

Хотя это можно (и нужно) сделать с помощью однострочного Perl, небольшой синтаксический анализатор C тоже будет очень быстрым и также очень маленьким (и, надеюсь, очень правильным):

#include <stdio.h>
#include <stdlib.h>

int main()
{
  char c1 = '\0', c2 = '\0', tmp_c;

  c1 = fgetc(stdin);
  for (;;) {
    if (c1 == EOF) {
      break;
    }
    c2 = fgetc(stdin);
    if (c2 == EOF) {
      if (c1 != ' ') {
        fputc(c1, stdout);
      }
      break;
    }
    if (c1 == c2 && c1 == ' ') {
      tmp_c = fgetc(stdin);
      if (tmp_c != EOF) {
        if (tmp_c != '\n') {
          ungetc(tmp_c, stdin);
          fputc(' ', stdout);
        } else {
          ungetc(tmp_c, stdin);
        }
      } else {
        break;
      }
    } else if (c1 != ' ') {
      fputc(c1, stdout);
    }
    c1 = c2;
  }
  exit(EXIT_SUCCESS);
}

Составлено с

gcc-4.9 -O3 -g3  -W -Wall -Wextra -std=c11 lilcparser.c -o lilcparser

(программа чуть меньше 9кб)

Используйте в трубе, например:

echo "T h e  b o o k  a l s o  h a s  a n  a n a l y t i c a l  p u r p o s e  w h i c h  i s  m o r e  i m p o r t a n t  " | ./lilcparser

1

Я попробовал это, и это похоже на работу:

echo "<text here>" | sed -r 's/(\w)(\s)/\1/g'

Команда sedзахватывает две группы и возвращает только первую.


0

В C ++ я бы сделал это:

#include <fstream>
using namespace std;

int main()
{   
    fstream is("test.txt", std::ios::in);

    char buff;
    vector<char>str;

    while (!is.eof()){is.get(buff);str.push_back(buff);} //read file to string

    for (int a=0;a<str.size();++a)if (str[a] == ' ' && str[a + 1] != ' ')str.erase(str.begin()+a);
    is.close();

    ofstream os("test.txt", std::ios::out | std::ios::trunc); //clear file for rewrite

    os.write(str.data(), str.size() * sizeof(char)); //write chars
    os.close();

    return 0;
    }

Изменит содержимое тестового текстового файла на ту же строку, но с удалением пробелов между буквами. (Требуется пробел между каждой буквой, чтобы быть точным).


0
$ echo 'F o u r  s c o r e  a n d' | \
txr -t '(mapcar* (opip (split-str @1 "  ")
                       (mapcar (op regsub #/ / ""))
                       (cat-str @1 " "))
                 (get-lines))'
Four score and


$ txr -e '(awk (:begin (set fs "  "))
               ((mf (regsub #/ / ""))))'  # mf: modify fields
F o u r  s c o r e  a n d
Four score and


$ awk -F'  ' '{for(i=1;i<=NF;i++)gsub(/ /,"",$i);print}'
F o u r  s c o r e  a n d
Four score and
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.