Un script qui supprime les espaces supplémentaires entre les lettres du texte


12

J'ai un document texte qui a une charge de texte qui a un espace supplémentaire ajouté après chaque lettre!

Exemple:

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

Visuellement:

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…

Notez qu'il y a un espace supplémentaire après chaque lettre, donc il y a deux espaces entre les mots consécutifs.

Existe-t-il un moyen d'obtenir awkou sedde supprimer les espaces supplémentaires? (Malheureusement, ce document texte est volumineux et prendrait beaucoup de temps à parcourir manuellement.)  J'apprécie que c'est probablement un problème beaucoup plus complexe à résoudre avec un simple script bash car il doit également y avoir une sorte de reconnaissance de texte.

Comment puis-je aborder ce problème?


2
il est trivial de remplacer tous les espaces par rien .. mais je pense que vous voudriez séparer les mots?
Sundeep

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

1
Cela ne limite pas le changement d'espaces entre les lettres . (Les chiffres et la ponctuation ne sont pas des lettres , par exemple). Vous pouvez le faire en sed avec une boucle. C'est aussi probablement un doublon.
Thomas Dickey

1
pour restreindre uniquement entre les lettres: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 source de la révision originale montre que les espaces entre les mots ont été doublés. Pourquoi les avez-vous annulés dans votre montage?
El'endia Starman

Réponses:


16

L'expression régulière suivante supprimera le premier espace de toute chaîne d'espaces. Cela devrait faire l'affaire.

s/ ( *)/\1/g

Donc quelque chose comme:

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

... remplacera infile.txt par une version "fixe".


@terdon J'ai remarqué ces derniers temps que les gens ont cessé d'écrire des scripts de tarte perl comme perl -pie- comme le montre votre édition. Quelle en est la raison? La tarte a toujours bien fonctionné pour moi et est un grand mnémonique. Le comportement de -i a-t-il changé pour traiter tout ce qui suit comme une extension, plutôt que seulement les choses qui commencent par un point? Il leur semblerait étrange de casser quelque chose d'aussi idiomatique.
Dewi Morgan,

1
Eh bien, ce n'est pas un idiome que je connais. Perl est ainsi depuis aussi longtemps que j'utilise -i. D'un autre côté, je ne l'ai utilisé que sur des machines Linux et je ne le connais pas depuis plus de quelques années, donc je ne peux pas parler de son comportement plus ancien. Sur ma machine , cependant, ceci: perl -pie 's/a/b/' f, produit une erreur: Can't open perl script "s/o/A/": No such file or directory. Tout perl -i -pe 's/o/A/' ffonctionne comme prévu. Alors oui, le eest considéré comme l'extension de sauvegarde.
terdon

Visage triste. Ah, eh bien, le temps passe, et cela signifie simplement que je dois réapprendre un ordre de paramètre. Garde mon cerveau visqueux, je suppose. Merci de m'avoir prévenu et d'avoir corrigé mon code!
Dewi Morgan

17

Utilisez wordsegmentun package NLP de segmentation de mots en Python pur:

$ 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'utilisation de la PNL est probablement la solution la plus efficace s'il n'y a rien d'autre pour distinguer les mots. La PNL fonctionne mieux qu'un dictionnaire prospectif dans la plupart des cas.
grochmal

13

Sur la base du fait que l'entrée comprend des espaces doubles entre les mots, il existe une solution beaucoup plus simple. Vous changez simplement les espaces doubles en un caractère inutilisé, supprimez les espaces et changez le caractère inutilisé en espace:

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'

...les sorties:

Le livre a également un objectif analytique qui est plus important


5
Une commande sed avec un sens "remplacer chaque occurrence d'un caractère non-espace, suivi d'un espace avec juste le caractère non-espace correspondant" fait la même chose:sed -e "s/\([^ ]\) /\1/g"
woodengod

3
C'est en effet une bonne alternative. Vous devez le poster comme réponse pour obtenir un crédit.
Julie Pelletier

10

Perl à la rescousse!

Vous avez besoin d'un dictionnaire, c'est-à-dire d'un fichier contenant un mot par ligne. Sur mon système, il existe en tant que /var/lib/dict/words, j'ai également vu des fichiers similaires comme /usr/share/dict/britishetc.

Tout d'abord, vous vous souvenez de tous les mots du dictionnaire. Ensuite, vous lisez l'entrée ligne par ligne et essayez d'ajouter des caractères à un mot. Si c'est possible, vous vous souvenez du mot et essayez d'analyser le reste de la ligne. Si vous atteignez la fin de la ligne, vous sortez la ligne.

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

Pour votre entrée, il génère 4092 lectures possibles sur mon système.


échoue au test avec une version espacée de a cat a logiea c a t a l o g
ctrl-alt-delor

@richard: OBOE, fixe. Mais il génère maintenant trop de possibilités, essayez de supprimer les mots d'une lettre.
choroba

@richard Vous pouvez lutter contre ce problème à l'aide d'un algorithme non déterministe (par exemple, toutes les lectures possibles sont stockées) et appliquer un analyseur sur celui-ci. Ensuite, vous pouvez filtrer les 4000 lectures possibles en une seule avec le moins d'erreurs.
bash0r

6

Remarque: cette réponse (comme quelques autres ici) est basée sur une version antérieure de la question où les mots n'étaient pas délimités. On peut répondre trivialement à la nouvelle version .

Sur une entrée comme:

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 pourrais essayer:

 $ 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

Il traite de gauche à droite et trouve un mot le plus long après le suivant.

Évidemment, ici, ce n'est pas la meilleure sélection de mots car cette phrase n'a aucun sens, mais pour trouver la bonne, vous auriez besoin d'outils capables de comprendre la grammaire ou le sens du texte ou au moins quelques statistiques des informations sur les mots susceptibles d'être trouvés ensemble pour trouver l'ensemble de mots le plus probable. On dirait que la solution est une bibliothèque spécialisée trouvée par Lynn


@terdon, voir modifier. Le problème est que cette question est passée d'une question complexe et intéressante à une question banale. Existe-t-il un moyen de le diviser en deux questions avant et après la modification?
Stéphane Chazelas

Je crains que non, non. Pourtant, c'est un truc intelligent, même s'il n'est pas parfait.
terdon

1
À strictement parler, la question était triviale depuis le début - voir la première version et sa source . Malheureusement, l'OP n'a pas compris comment Stack Exchange rend le texte, donc le texte d'entrée correct n'était pas visible jusqu'à ce que trichoplax corrige la mise en forme - et, plus malheureusement encore, il n'était pas visible à ce moment- , car la personne qui a approuvé cette modification immédiatement est allé et l'a cassé.
Scott

2

Similaire à la version de Dewi Morgan, mais avec sed:

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

C'est GNU seduniquement et ce n'est pas équivalent à Dewi. L' sedéquivalent standard de Dewi seraitsed 's/ \( *\)/\1/g'
Stéphane Chazelas

notez le "similaire" ;-)
Jaleks

1

Bien que cela puisse (et devrait) être fait avec une doublure Perl, un petit analyseur C serait également très rapide, et est également très petit (et, espérons-le, très correct):

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

Compilé avec

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

(le programme est un peu moins de 9kb)

Utiliser dans un tuyau comme par exemple:

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

J'ai essayé cela et cela semble fonctionner:

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

La sedcommande capture deux groupes et renvoie uniquement le premier.


0

En c ++, je ferais ceci:

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

Modifie le contenu du fichier texte de test, dans la même chaîne, mais en supprimant les espaces entre les lettres. (Il faut un espace entre chaque lettre pour être précis).


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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.