Una sceneggiatura che elimina spazi aggiuntivi tra le lettere nel testo


12

Ho un documento di testo che ha un carico di testo che ha uno spazio aggiuntivo aggiunto dopo ogni lettera!

Esempio:

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

visivamente:

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

Nota che c'è uno spazio extra dopo ogni lettera, quindi ci sono due spazi tra le parole consecutive.

C'è un modo per ottenere awko sedeliminare gli spazi extra? (Sfortunatamente questo documento di testo è enorme e richiederebbe molto tempo per essere esaminato manualmente.)  Mi rendo conto che questo è probabilmente un problema molto più complesso da risolvere con un semplice script bash in quanto deve esserci anche una sorta di riconoscimento del testo.

Come posso affrontare questo problema?


2
è banale sostituire tutti gli spazi con niente .. ma penso che vorresti separare le parole?
Sundeep

per esempio:echo 't h i s i s a n e x a m p l e' | sed 's/ //g'
Sundeep,

1
Ciò non limita la modifica degli spazi tra le lettere . (Cifre e punteggiatura non sono lettere , per esempio). Puoi farlo in sed con un ciclo. Anche questo è probabilmente un duplicato.
Thomas Dickey,

1
per limitare solo tra le lettere: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: la fonte della revisione originale mostra che gli spazi tra le parole sono stati raddoppiati. Perché le hai raddoppiate nella modifica?
El'endia Starman,

Risposte:


16

La seguente regex rimuoverà il primo spazio in qualsiasi stringa di spazi. Questo dovrebbe fare il lavoro.

s/ ( *)/\1/g

Quindi qualcosa del tipo:

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

... sostituirà infile.txt con una versione "fissa".


@terdon Ho notato negli ultimi tempi che le persone hanno smesso di scrivere script perl come perl -pie- come mostra la tua modifica. Qual è la logica di questo? La torta ha sempre funzionato bene per me ed è un grande mnemonico. Il comportamento di -i è cambiato per considerare qualcosa che segue come un'estensione, piuttosto che solo quelle cose che iniziano con un punto? Sembrerebbe strano per loro rompere qualcosa di così idiomatico.
Dewi Morgan,

1
Eh, beh non è un idioma con cui ho familiarità. Perl è stato così per tutto il tempo che ho usato -i. D'altra parte, l'ho mai usato solo su macchine Linux e non lo conosco da più di qualche anno, quindi non posso parlare del suo comportamento precedente. Sulla mia macchina, però, questo: perl -pie 's/a/b/' f, genera un errore: Can't open perl script "s/o/A/": No such file or directory. Mentre perl -i -pe 's/o/A/' ffunziona come previsto. Quindi sì, eviene presa come estensione di backup.
Terdon

Faccia triste. Ah, bene, il tempo passa e significa solo che devo imparare di nuovo un ordine di parametri. Mantiene il mio cervello squishy, ​​immagino. Grazie per avermelo fatto notare e per aver corretto il mio codice!
Dewi Morgan,

17

Utilizzare wordsegmentun pacchetto NLP di segmentazione delle parole pure-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
L'uso della PNL è probabilmente la soluzione più efficace se non c'è nient'altro da distinguere tra le parole. Nella maggior parte dei casi, la PNL funziona meglio di un dizionario di previsione.
grochmal,

13

Sulla base del fatto che l'input include doppi spazi tra le parole, esiste una soluzione molto più semplice. Devi semplicemente cambiare i doppi spazi in un personaggio inutilizzato, rimuovere gli spazi e cambiare il personaggio inutilizzato in uno spazio:

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'

... uscite:

Il libro ha anche uno scopo analitico che è più importante


5
Un comando sed con un significato "sostituisce ogni ricorrenza di un carattere non spaziale, seguito da uno spazio con solo il corrispondente carattere non spaziale" fa lo stesso:sed -e "s/\([^ ]\) /\1/g"
woodengod

3
Questa è davvero una buona alternativa. Dovresti pubblicarlo come risposta per ottenere credito.
Julie Pelletier,

10

Perl in soccorso!

È necessario un dizionario, ovvero un file che elenchi una parola per riga. Sul mio sistema, esiste come /var/lib/dict/words, ho visto anche file simili come /usr/share/dict/britishecc.

Innanzitutto, ricordi tutte le parole del dizionario. Quindi, leggi l'input riga per riga e provi ad aggiungere caratteri a una parola. Se è possibile, ricordi la parola e provi ad analizzare il resto della riga. Se si raggiunge la fine della linea, si genera la linea.

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

Per il tuo input, genera 4092 possibili letture sul mio sistema.


fallisce il test con la versione distanziata di a cat a logiea c a t a l o g
ctrl-alt-delor,

@richard: OBOE, risolto. Ma ora genera troppe possibilità, prova a rimuovere le parole di una lettera.
Choroba,

@richard Potresti combattere questo problema con l'aiuto di un algoritmo non deterministico (ad es. tutte le letture possibili sono memorizzate) e applicare un parser su di esso. Quindi è possibile filtrare tutte le 4000 letture possibili su quella singola con il minor numero di errori.
bash0r

6

Nota: questa risposta (come poche altre qui) si basa su una versione precedente della domanda in cui le parole non erano delimitate. Alla versione più recente può essere data una risposta banale .

Su un input come:

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

Puoi provare:

 $ 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

Elabora da sinistra a destra e trova una parola più lunga dopo la successiva.

Ovviamente, qui, non è la migliore selezione di parole poiché quella frase non ha alcun senso, ma per trovare quella giusta, avresti bisogno di strumenti in grado di comprendere la grammatica o il significato del testo o almeno alcune statistiche informazioni su quali parole è probabile che si trovino insieme per trovare l'insieme di parole più probabile. Sembra che la soluzione sia una libreria specializzata trovata da Lynn


@terdon, vedi modifica. Il problema è che quella domanda è stata cambiata da una complessa e interessante in una banale. C'è un modo per dividerlo in due domande prima e dopo la modifica?
Stéphane Chazelas,

Temo di no, no. Comunque un trucco intelligente, anche se non perfetto.
terdon

1
A rigor di termini, la domanda era banale sin dall'inizio: vedi la prima versione e la sua fonte . Sfortunatamente, l'OP non ha capito come Stack Exchange esegue il rendering del testo, quindi il testo di input corretto non è stato visibile fino a quando il tricoplax non ha riparato la formattazione e, ancor più sfortunatamente, non era visibile allora , perché la persona che ha approvato la modifica immediatamente è andato e l'ha rotto.
Scott,

2

Simile alla versione di Dewi Morgan, ma con sed:

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

Questo è sedsolo GNU e non è equivalente a quello di Dewi. L' sedequivalente standard di Dewi sarebbesed 's/ \( *\)/\1/g'
Stéphane Chazelas,

notare il "simile" ;-)
Jaleks,

1

Sebbene potrebbe (e dovrebbe) essere fatto con un one-liner Perl, anche un piccolo parser C sarebbe molto veloce, ed è anche molto piccolo (e si spera molto corretto):

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

Compilato con

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

(programm è un po 'meno di 9kb)

Utilizzare in un tubo come ad esempio:

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

Ho provato questo e sembra funzionare:

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

Il sedcomando acquisisce due gruppi e restituisce solo il primo.


0

In c ++, farei questo:

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

Cambierà il contenuto del file di testo di prova, nella stessa stringa, ma rimuovendo gli spazi tra le lettere. (Richiede uno spazio tra ogni lettera per essere precisi).


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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.