2010-11-19 28 views
0

Ich habe eine Zeichenfolge, die eine beliebige Anzahl von einzelnen Buchstaben enthalten kann, die durch Leerzeichen getrennt sind. Ich suche nach einer Regex (in Perl), die Leerzeichen zwischen allen (unbekannten) einzelnen Buchstaben entfernt.Leerzeichen zwischen einzelnen Buchstaben entfernen

Zum Beispiel:

ab c d sollte ab cd werden

a bcd e f gha bcd ef gh

a b c werden sollte abc

und

abc d werden sollten sollte unverändert sein (weil es keine einzelnen Buchstaben gibt, denen ein einzelnes Leerzeichen folgt oder von ihnen vorangestellt wird).

Danke für irgendwelche Ideen.

Antwort

6

Ihre Beschreibung stimmt nicht wirklich mit Ihren Beispielen überein. Es sieht für mich so aus, als ob Sie jeden Raum entfernen wollen, dem (1) ein Buchstabe vorausgeht, dem kein Buchstabe vorangestellt ist, und (2) gefolgt von einem Buchstaben, auf den kein Buchstabe folgt. Diese Voraussetzungen können genau so verschachtelt lookarounds ausgedrückt werden:

/(?<=(?<!\pL)\pL) (?=\pL(?!\pL))/ 

getestet:

use strict; 
use warnings; 

use Test::Simple tests => 4; 

sub clean { 
    (my $x = shift) =~ s/(?<=(?<!\pL)\pL) (?=\pL(?!\pL))//g; 
    $x; 
} 

ok(clean('ab c d')  eq 'ab cd'); 
ok(clean('a bcd e f gh') eq 'a bcd ef gh'); 
ok(clean('a b c')   eq 'abc'); 
ok(clean('ab c d')  eq 'ab cd'); 

Ausgang:

1..4 
ok 1 
ok 2 
ok 3 
ok 4 

Ich nehme an, Sie wirklich bedeutete ein Leerzeichen (U + 0020) ; Wenn Sie mit einem beliebigen Leerzeichen übereinstimmen möchten, sollten Sie das Leerzeichen durch \s+ ersetzen.

+0

Danke. Deine Beschreibung ist klarer als meine; Genau das habe ich gesucht. – itzy

0

Dies sollte den Trick:

my $str = ...; 

$str =~ s/ \b(\w) \s+ (\w)\b /$1$2/gx; 

dass der Raum zwischen den einzelnen Zeichen nach Leerzeichen entfernt. Fühlen Sie sich frei, \S durch eine restriktivere Zeichenklasse zu ersetzen, falls erforderlich. Es kann auch einige Randfälle geben, die sich auf Interpunktionszeichen beziehen, mit denen Sie umgehen müssen, aber das kann ich anhand der von Ihnen bereitgestellten Informationen nicht erraten.

Wie Ether hilfreich zeigt, scheitert dies in einem Fall. Hier ist eine Version, die (wenn auch nicht ganz so sauber wie die ersten) funktionieren soll:

s/ \b(\w) ((?:\s+ \w\b)+) /$1 . join '', split m|\s+|, $2/gex; 

Ich mochte Ether Test basierenden Ansatz (Nachahmung ist die aufrichtigste Form der Schmeichelei und alle):

use warnings; 
use strict; 
use Test::Magic tests => 4; 

sub clean { 
    (my $x = shift) =~ s{\b(\w) ((?: \s+ (\w)\b)+)} 
         {$1 . join '', split m|\s+|, $2}gex; 
    $x 
} 

test 'space removal', 
    is clean('ab c d')  eq 'ab cd', 
    is clean('a bcd e f gh') eq 'a bcd ef gh', 
    is clean('a b c')  eq 'abc', 
    is clean('abc d')  eq 'abc d'; 

Rückgabe: wie in perldoc perlre beschrieben

1..4 
ok 1 - space removal 1 
ok 2 - space removal 2 
ok 3 - space removal 3 
ok 4 - space removal 4 
+0

Das scheitert an den dritten Fall: es produziert "ab c". – Ether

+0

Ich bin ziemlich sicher, dass Sie den beunruhigend häufigen Fehler gemacht haben zu denken, dass ein '\ b' etwas tut, was es nicht tut. Strings wie '" = "' oder '" \ t = \ t "' werden mit ** weder ** '/ \ S \ b /' noch '/ \ b \ S /' verglichen, während Strings wie '" a "" oder "" werden von diesen beiden Mustern abgeglichen. Leute benutzen oft '\ b' deshalb falsch und werden von den Ergebnissen überrascht. – tchrist

+0

@tchrist => guter Punkt, ich habe nur versucht, den akzeptierten Problembereich größer zu machen, indem ich '\ S' in' \ w' ändere, sollte ich den Raum verkleinern und die subtilen Fehler beseitigen. Ich werde es als eine Übung für den Leser verlassen, um Unterstützung für '\ W' Zeichen –

5

du mit Look-Ahead tun können und Behauptungen Lookbehind,:

Beachten Sie den aktuellen Code nicht auf dem zweiten Testfall, wie der Ausgang ist:

ok 1 
not ok 2 
# Failed test at test.pl line 7. 
#   got: 'abcd efgh' 
#  expected: 'a bcd ef gh' 
ok 3 
ok 4 
1..4 
# Looks like you failed 1 test of 4. 

ich es so belassen, wie Ihre zweite und dritte Beispiele scheinen sich, wie führende einzelne Zeichen zu widersprechen sollte behandelt werden. Dieses Framework sollte jedoch ausreichen, um mit verschiedenen Lookaheads und Lookbehinds experimentieren zu können, um die gewünschten Ergebnisse zu erhalten. diese

+2

+1 für nette Demo der Komponententests hinzuzufügen. –

+0

@Ether, ich * wirklich * mag die Testdemo. Aber könntest du es nicht reparieren, damit es nicht nur auf ASCII der 1960er Jahre beschränkt ist? Wenn ein RFC nichts anderes sagt, möchte ich * immer * '[a-z]' als etwas mit der Buchstaben- oder alphabetischen oder Kleinbuchstaben-Eigenschaft schreiben, je nachdem, was Sie tun. Wahrscheinlich ist da nur '\ pL' genug. Außerdem bin ich nicht klar, ob das, was er wirklich braucht, ein einzelnes Leerzeichen, ein oder mehrere Leerzeichen oder ein oder mehrere Leerzeichen ist. – tchrist

+0

@Tchrist: So sehr ich Komplikation hasse, ich denke du bist hier richtig; es sei denn, man schreibt etwas * extrem * schnell und schmutzig (und möglicherweise nicht einmal dann ..) sollte man immer versuchen, die richtigen Gebietsschema-anpassbaren Zeichenklassen zu verwenden. – Ether

1

Dieses Stück Code

#!/usr/bin/perl 

use strict; 

my @strings = ('a b c', 'ab c d', 'a bcd e f gh', 'abc d'); 

foreach my $string (@strings) { 
    print "$string --> "; 
    $string =~ s/\b(\w)\s+(?=\w\b)/$1/g; # the only line that actually matters 
    print "$string\n"; 
} 

druckt:

a b c --> abc 
ab c d --> ab cd 
a bcd e f gh --> a bcd ef gh 
abc d --> abc d 

Ich denke/hoffe, das ist, was Sie suchen.

0

Es ist keine Regex, aber da ich von Natur aus faul bin, würde ich es so machen.

#!/usr/bin/env perl 
use warnings; 
use 5.012; 

my @strings = ('a b c', 'ab c d', 'a bcd e f gh', 'abc d'); 
for my $string (@strings) { 
    my @s; my $t = ''; 
    for my $el (split /\s+/, $string) { 
     if (length $el > 1) { 
     push @s, $t if $t; 
     $t = ''; 
     push @s, $el; 
     } else { $t .= $el; } 
    } 
    push @s, $t if $t; 
    say "@s"; 
} 

OK, ist meine Art der langsamste:

no_regex 130619/s   --  -60%  -61%  -63% 
Alan_Moore 323328/s  148%   --  -4%  -8% 
Eric_Storm 336748/s  158%   4%   --  -5% 
canavanin 352654/s  170%   9%   5%   -- 

ich nicht enthalten Code Äther, weil es (wie er getestet hat) unterschiedliche Ergebnisse.

0

Jetzt habe ich die langsamste und schnellste.

#!/usr/bin/perl 
use 5.012; 
use warnings; 
use Benchmark qw(cmpthese); 
my @strings = ('a b c', 'ab c d', 'a bcd e f gh', 'abc d'); 

cmpthese(0, { 
    Eric_Storm => sub{ for my $string (@strings) { $string =~ s{\b(\w) ((?: \s+ (\w)\b)+)}{$1 . join '', split m|\s+|, $2}gex; } }, 
    canavanin => sub{ for my $string (@strings) { $string =~ s/\b(\w)\s+(?=\w\b)/$1/g; } }, 
    Alan_Moore => sub{ for my $string (@strings) { $string =~ s/(?<=(?<!\pL)\pL) (?=\pL(?!\pL))//g; } }, 
    keep_uni => sub{ for my $string (@strings) { $string =~ s/\PL\pL\K (?=\pL(?!\pL))//g; } }, 
    keep_asc => sub{ for my $string (@strings) { $string =~ s/[^a-zA-Z][a-zA-Z]\K (?=[a-zA-Z](?![a-zA-Z]))//g; } }, 
    no_regex => sub{ for my $string (@strings) { my @s; my $t = ''; 
    for my $el (split /\s+/, $string) {if (length $el > 1) { push @s, $t if $t; $t = ''; push @s, $el; } else { $t .= $el; } } 
    push @s, $t if $t; 
    #say "@s"; 
    } }, 
}); 

.

  Rate no_regex Alan_Moore Eric_Storm canavanin keep_uni keep_asc                                        
no_regex 98682/s  --  -64%  -65%  -66%  -81%  -87%                                        
Alan_Moore 274019/s  178%   --  -3%  -6%  -48%  -63%                                        
Eric_Storm 282855/s  187%   3%   --  -3%  -46%  -62%                                        
canavanin 291585/s  195%   6%   3%  --  -45%  -60% 
keep_uni 528014/s  435%  93%  87%  81%  --  -28% 
keep_asc 735254/s  645%  168%  160%  152%  39%  -- 
0

Dies wird die Aufgabe erledigen.

(?<=\b\w)\s(?=\w\b) 
Verwandte Themen