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?

lloowen
la source
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".

Dewi Morgan
la source
@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
Lynn
la source
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

Julie Pelletier
la source
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.

choroba
la source
é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

Stéphane Chazelas
la source
@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
Jaleks
la source
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
deamentiaemundi
la source
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.

Donagh McCarthy
la source
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).

user189465
la source
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
la source