Un script que elimina espacios adicionales entre letras en el texto.


12

¡Tengo un documento de texto que tiene una carga de texto que tiene un espacio adicional agregado después de cada letra!

Ejemplo:

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 ...

Tenga en cuenta que hay un espacio adicional después de cada letra, por lo que hay dos espacios entre palabras consecutivas.

¿Hay alguna forma de obtener awko sedeliminar los espacios adicionales? (Desafortunadamente, este documento de texto es masivo y tomaría mucho tiempo para pasarlo manualmente).  Aprecio que este es probablemente un problema mucho más complejo de resolver con solo un simple script bash, ya que también debe haber algún tipo de reconocimiento de texto.

¿Cómo puedo abordar este problema?


2
es trivial reemplazar todos los espacios con nada ... pero creo que querrías separar las palabras?
Sundeep

por ejemplo:echo 't h i s i s a n e x a m p l e' | sed 's/ //g'
Sundeep

1
Eso no limita el cambio a espacios entre letras . (Los dígitos y la puntuación no son letras , por ejemplo). Puede hacer esto en sed con un bucle. Esto también es probablemente un duplicado.
Thomas Dickey

1
restringir solo 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

44
@JuliePelletier: La fuente de la revisión original muestra que los espacios entre palabras se duplicaron. ¿Por qué los desdoblaste en tu edición?
El'endia Starman

Respuestas:


16

La siguiente expresión regular eliminará el primer espacio en cualquier cadena de espacios. Eso debería hacer el trabajo.

s/ ( *)/\1/g

Entonces algo como:

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

... reemplazará infile.txt con una versión "fija".


@terdon He notado en los últimos tiempos que la gente ha dejado de escribir scripts de perl pie como se perl -piemuestra en su edición. ¿Cuál es la razón de esto? La pieza siempre ha funcionado bien para mí, y es una gran mnemotécnica. ¿Ha cambiado el comportamiento de -i para tratar todo lo que sigue como una extensión, en lugar de solo aquellas cosas que comienzan con un punto? Les parecería extraño romper algo tan idiomático.
Dewi Morgan

1
Huh, bueno, no es un idioma con el que estoy familiarizado. Perl ha estado así durante todo el tiempo que he estado usando -i. Por otro lado, solo lo he usado en máquinas Linux y no lo he sabido por más de unos años, por lo que no puedo hablar de su comportamiento anterior. En mi máquina, sin embargo, esto: perl -pie 's/a/b/' f, produce un error: Can't open perl script "s/o/A/": No such file or directory. Mientras perl -i -pe 's/o/A/' ffunciona como se esperaba. Entonces sí, ese toma como la extensión de respaldo.
terdon

Cara triste. Ah, bueno, el tiempo pasa, y solo significa que necesito volver a aprender un orden de parámetros. Mantiene mi cerebro blando, supongo. ¡Gracias por avisarme y por corregir mi código!
Dewi Morgan

17

Use wordsegmentun paquete NLP de segmentación de palabras de 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

1
Usar PNL es probablemente la solución más efectiva si no hay nada más para distinguir las palabras. PNL funciona mejor que un diccionario de anticipación en la mayoría de los casos.
grochmal

13

Basado en el hecho de que la entrada incluye espacios dobles entre palabras, hay una solución mucho más simple. Simplemente cambie los espacios dobles a un carácter no utilizado, elimine los espacios y vuelva a cambiar el carácter no utilizado a un espacio:

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'

... salidas:

El libro también tiene un propósito analítico que es más importante.


55
Un comando sed con un significado "reemplazar cada aparición de un carácter no espacial, seguido de un espacio con solo el carácter no espacial correspondiente" hace lo mismo:sed -e "s/\([^ ]\) /\1/g"
woodengod

3
Esa es de hecho una buena alternativa. Debe publicarlo como respuesta para obtener crédito por ello.
Julie Pelletier

10

¡Perl al rescate!

Necesita un diccionario, es decir, un archivo con una palabra por línea. En mi sistema, existe como /var/lib/dict/words, también he visto archivos similares como /usr/share/dict/britishetc.

Primero, recuerdas todas las palabras del diccionario. Luego, lee la entrada línea por línea e intenta agregar caracteres a una palabra. Si es posible, recuerda la palabra e intenta analizar el resto de la línea. Si llega al final de la línea, genera la línea.

#!/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 su entrada, genera 4092 lecturas posibles en mi sistema.


falla la prueba con la versión espaciada de a cat a logiea c a t a l o g
ctrl-alt-delor

@richard: OBOE, arreglado. Pero ahora genera demasiadas posibilidades, intente eliminar palabras de una letra.
choroba

@richard Puede combatir este problema con la ayuda de un algoritmo no determinista (por ejemplo, todas las lecturas posibles se almacenan) y aplicar un analizador en él. Luego puede filtrar las 4000 lecturas posibles a una sola con el menor recuento de errores.
bash0r

6

Nota: esta respuesta (como algunas otras aquí) se basa en una versión anterior de la pregunta donde las palabras no estaban delimitadas. La versión más nueva se puede responder trivialmente .

En una 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

Tu podrías intentar:

 $ 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

Procesa de izquierda a derecha y encuentra una palabra más larga después de la siguiente.

Obviamente, aquí, no es la mejor selección de palabras, ya que esa oración no tiene ningún sentido, pero para encontrar la correcta, necesitaría herramientas capaces de comprender la gramática o el significado del texto o al menos algunas estadísticas información sobre qué palabras es probable que se encuentren juntas para llegar al conjunto de palabras más probable. Parece que la solución es una biblioteca especializada encontrada por Lynn


@terdon, ver edición. El problema es que esa pregunta cambió de una compleja e interesante a una trivial. ¿Hay alguna manera de dividirlo en las dos preguntas que tenía antes y después de la edición?
Stéphane Chazelas

Me temo que no, no. Sin embargo, sigue siendo un truco inteligente, incluso si no es perfecto.
terdon

1
Hablando estrictamente, la pregunta fue trivial desde el principio: vea la primera versión y su fuente . Desafortunadamente, el OP no entendió cómo Stack Exchange representa el texto, por lo que el texto de entrada correcto no fue visible hasta que trichoplax arregló el formato , y, aún más desafortunadamente, no fue visible entonces , porque la persona que aprobó esa edición de inmediato Fui y lo rompí.
Scott

2

Similar a la versión de Dewi Morgan, pero con sed:

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

Eso es sedsolo GNU y no es equivalente a Dewi. El sedequivalente estándar de Dewi seríased 's/ \( *\)/\1/g'
Stéphane Chazelas el

tenga en cuenta el "similar" ;-)
Jaleks

1

Aunque podría (y debería) hacerse con una línea Perl, un analizador de C pequeño también sería muy rápido, y también es muy pequeño (y con suerte muy correcto):

#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 con

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

(el programa es un poco menos de 9kb)

Usar en una tubería como por ejemplo:

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

Intenté esto y parece funcionar:

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

El sedcomando captura dos grupos y devuelve solo el primero.


0

En c ++, haría esto:

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

Cambiará el contenido del archivo de texto de prueba, en la misma cadena, pero con espacios entre letras eliminados. (Se requiere un espacio entre cada letra para ser exacto).


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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.