Skrypt, który usuwa dodatkowe spacje między literami w tekście

12

Mam dokument tekstowy, który zawiera mnóstwo tekstu, a po każdej literze jest dodawane dodatkowe miejsce!

Przykład:

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

Naocznie:

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…

Należy pamiętać, że istnieje dodatkowa przestrzeń po każdym liście, więc są dwie przestrzenie między kolejnymi słowami.

Czy istnieje sposób, aby uzyskać awklub sedusunąć dodatkowe spacje? (Niestety ten dokument tekstowy jest bardzo obszerny i przejście go ręcznie zajęłoby bardzo dużo czasu).  Rozumiem, że jest to prawdopodobnie o wiele bardziej skomplikowany problem do rozwiązania za pomocą prostego skryptu bash, ponieważ musi istnieć także pewien rodzaj rozpoznawania tekstu.

Jak podejść do tego problemu?

Lloowen
źródło
2
Zastępowanie wszystkich spacji nic nie jest trywialne ... ale myślę, że chcesz rozdzielić słowa?
Niedziela
na przykład:echo 't h i s i s a n e x a m p l e' | sed 's/ //g'
Sundeep,
1
To nie ogranicza zmiany do spacji między literami . (Na przykład cyfry i znaki interpunkcyjne nie są literami ). Możesz to zrobić za pomocą pętli. To też prawdopodobnie jest duplikat.
Thomas Dickey,
1
ograniczać tylko między literami: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'
Niedziela
4
@JuliePelletier: Źródło oryginalnej wersji pokazuje, że odstępy między słowami zostały podwojone. Dlaczego cofnąłeś je dwukrotnie w swojej edycji?
El'endia Starman

Odpowiedzi:

16

Poniższy regex usunie pierwszą spację z dowolnego ciągu spacji. To powinno wystarczyć.

s/ ( *)/\1/g

Więc coś takiego:

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

... zastąpi plik infile.txt wersją „naprawioną”.

Dewi Morgan
źródło
@terdon Zauważyłem ostatnio, że ludzie przestali pisać skrypty perla jako perl -pie- jak pokazuje twoja edycja. Jakie jest tego uzasadnienie? Ten kawałek zawsze działał dla mnie dobrze i jest świetnym mnemonikiem. Czy zachowanie -i zmieniło się, aby traktować wszystko jako rozszerzenie, a nie tylko te, które zaczynają się od kropki? Dziwne byłoby dla nich złamanie czegoś tak idiomatycznego.
Dewi Morgan
1
Huh, cóż, nie znam tego idiomu. Perl był w ten sposób od tak dawna -i. Z drugiej strony używałem go tylko na maszynach z Linuksem i nie wiedziałem o tym od ponad kilku lat, więc nie mogę mówić o jego starszym zachowaniu. Na moim komputerze jednak tak: perl -pie 's/a/b/' fprodukuje błąd: Can't open perl script "s/o/A/": No such file or directory. Chociaż perl -i -pe 's/o/A/' fdziała zgodnie z oczekiwaniami. Tak, ejest traktowane jako rozszerzenie kopii zapasowej.
terdon
Smutna mina. Ach, cóż, czas płynie dalej, a to po prostu oznacza, że ​​muszę ponownie nauczyć się kolejności parametrów. Zgaduję, że mózg mi się kręci. Dziękujemy za informację i za naprawienie mojego kodu!
Dewi Morgan
17

Użyj wordsegmentpakietu NLP o czystej segmentacji słów:

$ 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
źródło
1
Korzystanie z NLP jest prawdopodobnie najskuteczniejszym rozwiązaniem, jeśli nie ma nic innego do rozróżnienia słów. NLP w większości przypadków działa lepiej niż słownik wybiegający w przyszłość.
grochmal
13

Oparte na fakcie, że dane wejściowe zawierają podwójne spacje między słowami, istnieje znacznie prostsze rozwiązanie. Wystarczy zmienić podwójne spacje na nieużywaną postać, usunąć spacje i zmienić nieużywaną postać z powrotem na spację:

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'

... wyjścia:

Książka ma również cel analityczny, który jest ważniejszy

Julie Pelletier
źródło
5
Komenda sed w znaczeniu „zamień każde wystąpienie znaku spacji, a następnie spacja z odpowiednim znakiem spacji” robi to samo:sed -e "s/\([^ ]\) /\1/g"
woodengod
3
To rzeczywiście dobra alternatywa. Powinieneś opublikować go jako odpowiedź, aby uzyskać kredyt.
Julie Pelletier,
10

Perl na ratunek!

Potrzebujesz słownika, tzn. Pliku zawierającego jedno słowo w wierszu. W moim systemie istnieje, ponieważ /var/lib/dict/wordswidziałem również podobne pliki jak /usr/share/dict/britishitp.

Najpierw pamiętasz wszystkie słowa ze słownika. Następnie czytasz wprowadzany wiersz po wierszu i próbujesz dodawać znaki do słowa. Jeśli to możliwe, pamiętasz słowo i próbujesz przeanalizować resztę linii. Po osiągnięciu końca linii wyprowadzasz linię.

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

Na podstawie twoich danych generuje 4092 możliwe odczyty w moim systemie.

choroba
źródło
nie powiedzie się test z rozłożoną wersją a cat a logiea c a t a l o g
ctrl-alt-delor,
@richard: OBOE, naprawiony. Ale teraz generuje zbyt wiele możliwości, spróbuj usunąć jedną literę słów.
choroba
@richard Możesz rozwiązać ten problem za pomocą niedeterministycznego algorytmu (np. wszystkie możliwe odczyty są przechowywane) i zastosować na nim parser. Następnie możesz przefiltrować wszystkie 4000 możliwych odczytów do jednego z najmniejszą liczbą błędów.
bash0r 10.09.16
6

Uwaga: ta odpowiedź (podobnie jak kilka innych tutaj) oparta jest na wcześniejszej wersji pytania, w którym słowa nie były rozdzielane. Na nowszą wersję można w prosty sposób odpowiedzieć .

Na wejściu takim jak:

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

Możesz spróbować:

 $ 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

Przetwarza od lewej do prawej i znajduje jedno najdłuższe słowo po drugim.

Oczywiście nie jest to najlepszy wybór słów, ponieważ zdanie to nie ma sensu, ale aby znaleźć właściwe, potrzebujesz narzędzi, które będą w stanie zrozumieć gramatykę lub znaczenie tekstu lub przynajmniej niektóre statystyki informacje o tym, jakie słowa można znaleźć razem, aby uzyskać najbardziej prawdopodobny zestaw słów. Wygląda na to, że rozwiązaniem jest specjalistyczna biblioteka znaleziona przez Lynn

Stéphane Chazelas
źródło
@terdon, patrz edycja. Problem polega na tym, że pytanie to zostało zmienione ze złożonego i interesującego na trywialne. Czy istnieje sposób na podzielenie go na dwa pytania, które były przed edycją i po niej?
Stéphane Chazelas,
Obawiam się, że nie. Mimo to sprytna sztuczka, nawet jeśli nie idealna.
terdon
1
Ściśle mówiąc, od początku pytanie było trywialne - patrz pierwsza wersja i jej źródło . Niestety, OP nie zrozumiał, w jaki sposób Stack Exchange renderuje tekst, więc poprawny tekst wejściowy nie był widoczny, dopóki trichoplax nie naprawił formatowania - i, co jeszcze bardziej niestety, nie był wtedy widoczny , ponieważ osoba, która natychmiast zatwierdziła tę edycję poszedł i złamał to.
Scott
2

Podobne do wersji Dewi Morgan, ale z sed:

$ echo "f o o  t h e  b a r" | sed -r "s/[ ]{1}([^ ]{1})/\1/g"
foo the bar
Jaleks
źródło
To sedtylko GNU i to nie jest równoważne z Dewi. Standardowy sedodpowiednik Dewi byłbysed 's/ \( *\)/\1/g'
Stéphane Chazelas
zwróć uwagę na „podobne” ;-)
Jaleks
1

Chociaż można to (i należy) zrobić z liniową wersją Perla, mały parser C również byłby bardzo szybki, a także bardzo mały (i mam nadzieję, że bardzo poprawny):

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

Kompilowany z

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

(program jest nieco mniejszy niż 9 KB)

Użyj w rurze, takiej jak np .:

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
źródło
1

Próbowałem tego i wydaje się, że działa:

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

sedKomenda rejestruje dwie grupy i powraca dopiero pierwszy.

Donagh McCarthy
źródło
0

W c ++ zrobiłbym to:

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

Zmieni zawartość testowego pliku tekstowego na ten sam ciąg, ale ze spacjami między literami. (Wymagana jest spacja między każdą literą).

użytkownik189465
źródło
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
źródło