Um script que exclui espaços extras entre letras no texto

12

Eu tenho um documento de texto que tem uma carga de texto que tem um espaço extra adicionado após cada letra!

Exemplo:

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

Visualmente:

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…

Observe que há um espaço extra após cada letra, portanto, há dois espaços entre palavras consecutivas.

Existe uma maneira de obter awkou sedexcluir os espaços extras? (Infelizmente, este documento de texto é enorme e levaria muito tempo para ser processado manualmente.)  Compreendo que esse seja provavelmente um problema muito mais complexo a ser resolvido com apenas um script bash simples, pois também precisa haver algum tipo de reconhecimento de texto.

Como posso abordar esse problema?

lloowen
fonte
2
é trivial substituir todos os espaços por nada .. mas acho que você gostaria de separar as palavras?
Sundeep 10/09/16
por exemplo:echo 't h i s i s a n e x a m p l e' | sed 's/ //g'
Sundeep 10/09
1
Isso não limita a mudança de espaços entre as letras . (Dígitos e pontuação não são letras , por exemplo). Você pode fazer isso no sed com um loop. Isso também é provavelmente uma duplicata.
21416 Thomas Dickey #
1
restringir apenas entre letras: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 10/09
4
@JuliePelletier: A fonte da revisão original mostra que os espaços entre as palavras foram duplicados. Por que você os dobrou em sua edição?
El'endia Starman 10/09/16

Respostas:

16

O regex a seguir removerá o primeiro espaço em qualquer sequência de espaços. Isso deve fazer o trabalho.

s/ ( *)/\1/g

Então, algo como:

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

... substituirá infile.txt por uma versão "fixa".

Dewi Morgan
fonte
@terdon Eu notei nos últimos tempos que as pessoas pararam de escrever scripts perl pie como perl -pie- como mostra sua edição. Qual é a justificativa para isso? A torta sempre funcionou bem para mim e é um ótimo mnemônico. O comportamento de -i mudou para tratar qualquer coisa que se segue como uma extensão, em vez de apenas as coisas que começam com um ponto? Pareceria estranho para eles quebrar algo tão idiomático.
Dewi Morgan #
1
Bem, não é um idioma que eu conheça. Perl tem sido assim desde que eu tenho usado -i. Por outro lado, eu só o usei em máquinas Linux e não o conheço há mais de alguns anos, por isso não posso falar sobre seu comportamento mais antigo. No meu embora máquina, isto: perl -pie 's/a/b/' f, produz um erro: Can't open perl script "s/o/A/": No such file or directory. Enquanto perl -i -pe 's/o/A/' ftrabalha como esperado. Então, sim, eé considerado como a extensão de backup.
terdon
Rosto triste. Ah, bem, o tempo avança, e isso significa que preciso reaprender uma ordem de parâmetros. Mantém meu cérebro mole, eu acho. Obrigado por me informar e por corrigir meu código!
Dewi Morgan
17

Use wordsegment, um pacote NLP de segmentação de palavras em Python puro:

$ 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
Lynn
fonte
1
Usar a PNL é provavelmente a solução mais eficaz se não houver mais nada para diferenciar as palavras. A PNL tem melhor desempenho do que um dicionário antecipado na maioria dos casos.
grochmal 11/09/16
13

Com base no fato de que a entrada inclui espaços duplos entre as palavras, existe uma solução muito mais simples. Você simplesmente altera os espaços duplos para um caracter não utilizado, remove os espaços e altera o caracter não utilizado de volta para um espaço:

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'

... saídas:

O livro também tem um objetivo analítico mais importante

Julie Pelletier
fonte
5
Um comando sed com um significado "substituir cada ocorrência de um caractere não-espaço, seguido por um espaço com apenas o caráter não-espaço correspondente" faz o mesmo:sed -e "s/\([^ ]\) /\1/g"
woodengod
3
Essa é realmente uma boa alternativa. Você deve publicá-lo como uma resposta para obter crédito por isso.
Julie Pelletier
10

Perl para o resgate!

Você precisa de um dicionário, ou seja, um arquivo listando uma palavra por linha. No meu sistema, ele existe como /var/lib/dict/words, também vi arquivos semelhantes como /usr/share/dict/britishetc.

Primeiro, você se lembra de todas as palavras do dicionário. Em seguida, você lê a entrada linha por linha e tenta adicionar caracteres a uma palavra. Se possível, lembre-se da palavra e tente analisar o resto da linha. Se você chegar ao final da linha, você produzirá a linha.

#!/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;
}

Para sua entrada, ele gera 4092 possíveis leituras no meu sistema.

choroba
fonte
falha no teste com a versão espaçada do a cat a logiea c a t a l o g
ctrl-alt-delor 10/09/16
@richard: OBOE, fixo. Mas agora gera muitas possibilidades, tente remover as palavras de uma letra.
choroba 10/09/16
@richard Você pode combater esse problema com a ajuda de um algoritmo não determinístico (por exemplo, todas as leituras possíveis são armazenadas) e aplicar um analisador. Em seguida, você pode filtrar todas as 4000 leituras possíveis para a única com a menor contagem de erros.
precisa saber é o seguinte
6

Nota: esta resposta (como algumas outras aqui) é baseada em uma versão anterior da pergunta em que as palavras não foram delimitadas. A versão mais recente pode ser respondida trivialmente .

Em uma entrada como:

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

Você poderia tentar:

 $ 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

Ele processa da esquerda para a direita e encontra uma palavra mais longa após a seguinte.

Obviamente, aqui, não é a melhor seleção de palavras, pois essa frase não faz sentido, mas, para chegar à correta, você precisará de ferramentas capazes de entender a gramática ou o significado do texto ou, pelo menos, algumas estatísticas. informações sobre quais palavras provavelmente serão encontradas para criar o conjunto de palavras mais provável. Parece que a solução é uma biblioteca especializada, encontrada por Lynn

Stéphane Chazelas
fonte
@terdon, veja editar. O problema é que essa pergunta foi alterada de complexa e interessante para trivial. Existe uma maneira de dividi-lo nas duas perguntas anteriores e posteriores à edição?
Stéphane Chazelas
Receio que não, não. Ainda é um truque inteligente, mesmo que não seja perfeito.
terdon
1
A rigor, a pergunta foi trivial desde o início - veja a primeira versão e sua fonte . Infelizmente, o OP não entendeu como o Stack Exchange renderiza texto; portanto, o texto de entrada correto não estava visível até a trichoplax corrigir a formatação - e, ainda mais infelizmente, não estava visível na época , porque a pessoa que aprovou a edição imediatamente foi e quebrou.
Scott
2

Semelhante à versão de Dewi Morgan, mas com sed:

$ echo "f o o  t h e  b a r" | sed -r "s/[ ]{1}([^ ]{1})/\1/g"
foo the bar
Jaleks
fonte
Isso é sedapenas o GNU e não é equivalente ao de Dewi. O sedequivalente padrão de Dewi's seriased 's/ \( *\)/\1/g'
Stéphane Chazelas
observe o "similar" ;-)
Jaleks 18/09/16
1

Embora possa (e deva) ser feito com uma linha única do Perl, um pequeno analisador C também seria muito rápido e também é muito pequeno (e espero que muito correto):

#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);
}

Compilado com

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

(o programa é um pouco menor que 9kb)

Use em um tubo como por exemplo:

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
deamentiaemundi
fonte
1

Eu tentei isso e parece funcionar:

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

O sedcomando captura dois grupos e retorna apenas o primeiro.

Donagh McCarthy
fonte
0

Em c ++, eu faria isso:

#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;
    }

Alterará o conteúdo do arquivo de texto de teste, na mesma sequência, mas com os espaços entre as letras removidos. (Requer um espaço entre cada letra para ser preciso).

user189465
fonte
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
Kaz
fonte