2010-05-25 5 views
2

J'ai une chaîne où différents mots-clés prédéfinis introduisent des données différentes. Y at-il un moyen de le faire en utilisant une utilisation intelligente de regexp, ou quelque chose? Voici un exemple: Les mots-clés peuvent être "first name: " et "last name: ". Maintenant, je veux analyser:Comment puis-je analyser une chaîne dans un hachage en utilisant des mots-clés dans Perl?

"character first name: Han last name: Solo" 

dans

{ "first name: " => "Han ", "last name: " => "Solo" } 

Bien sûr, l'ordre des mots-clés dans la chaîne d'entrée n'est pas fixée. Cela devrait également fonctionner sur:

"character last name: Solo first name: Han" 

Je comprends qu'il y a des problèmes à soulever avec les espaces et ainsi de suite. Je vais les ignorer ici.

Je sais comment résoudre ce problème en bouclant sur les différents mots-clés, mais je ne trouve pas cela très joli.

Split fait presque l'affaire. Son seul problème est qu'il retourne un tableau et non un hachage, donc je ne sais pas quel est le prénom ou le nom de famille.

Mon exemple est quelque peu trompeur. Voici un autre:

my @keywords = ("marker 1", "marker 2", "marker 3"); 
my $rawString = "beginning marker 1 one un marker 2 two deux marker 3 three trois and the rest"; 
my %result; 
# <grind result> 
print Dumper(\%result); 

imprimera:

$VAR1 = { 
     'marker 2' => ' two deux ', 
     'marker 3' => ' three trois and the rest', 
     'marker 1' => ' one un ' 
    }; 
+1

Peut-il y avoir plusieurs prénoms et noms comme 'Jean Marc' ou 'Syu Kyi'? – Zaid

+0

Oui. L'exemple utilise des noms, mais il s'agit en réalité d'un problème de chaîne: il correspond aux chaînes de marqueur et renvoie tous les caractères entre les valeurs du marqueur précédent. –

Répondre

7

Voici une solution en utilisant split (avec le mode de rétention de séparation) qui est extensible avec d'autres touches:

use warnings; 
use strict; 

my $str = "character first name: Han last name: Solo"; 

my @keys = ('first name:', 'last name:'); 

my $regex = join '|' => @keys; 

my ($prefix, %hash) = split /($regex)\s*/ => $str; 

print "$_ $hash{$_}\n" for keys %hash; 

qui imprime:

last name: Solo 
first name: Han 

Pour gérer les clés contenant des métacaractères regex, remplacer la ligne my $regex = ... par:

my $regex = join '|' => map {quotemeta} @keys; 
+0

Merci. C'est parfait. Je ne savais pas split pourrait retourner un hachage que vous montrez ici. Aussi surprenant pour moi est votre utilisation de => comme un séparateur d'arguments. Est-ce un idiome commun? –

+1

'split' renvoie toujours une liste. Vous pouvez affecter une liste à un hachage. '=>' est la "grosse virgule": Elle a pour effet de citer automatiquement un mot qui la précède. –

+0

OK, je l'ai eu, et maintenant j'apprécie aussi l'élégance de la solution. Aujourd'hui est une bonne journée: j'ai appris deux choses. –

2
use strict; 
use warnings; 
use Data::Dump 'dump'; # dump allows you to see what %character 'looks' like 

my %character; 
my $nameTag = qr{(?:first|last) name:\s*}; 

# Use an array slice to populate the hash in one go 
@character{ ($1, $3) } = ($2, $4) if $string =~ /($nameTag)(.+)($nameTag)(.+)/; 

dump %character; # returns ("last name: ", "Solo", "first name: ", "Han ") 
+0

Je ne pouvais pas faire fonctionner votre exemple. S'il vous plaît noter que les mots-clés ont commun sous-chaînes que par accident, par exemple un troisième mot-clé pourrait être «couleur des cheveux» ' –

+0

@ Jean-Denis Muys: Oui, j'avais oublié de faire le groupement imbriqué non-capture. Cela devrait fonctionner maintenant. Cela résout le problème original. Maintenant, pour le cas plus générique ... – Zaid

+0

Ceci est assez lisse (une fois que je l'ai eu au travail :) –

-1

Utiliser le texte :: ParseWords. Il ne fait probablement pas tout ce que vous voulez, mais vous feriez beaucoup mieux de le faire que d'essayer de résoudre entièrement le problème.

0

Cela est possible si:

1) Vous pouvez identifier un petit ensemble de regexes qui peuvent choisir les balises 2) Le regex pour extraire la valeur peut être écrite afin qu'il choisit seulement la valeur et ignore les données superflues suivantes, le cas échéant, entre la fin de la valeur et le début de l'étiquette suivante.

Voici un exemple de la façon de le faire avec une chaîne d'entrée très simple. Ceci est une session de débogage:

DB<14> $a = "a 13 b 55 c 45"; 
    DB<15> %$b = $a =~ /([abc])\s+(\d+)/g; 
    DB<16> x $b 
0 HASH(0x1080b5f0) 
    'a' => 13 
    'b' => 55 
    'c' => 45 
+0

condition 1 est oui: l'ensemble des mots-clés est déterminé à l'avance. la condition 2 est non: les données s'arrêtent à chaque démarrage d'un nouveau mot-clé, ou à la fin de la chaîne, selon la première éventualité. J'avais espéré que le bon ensemble de gourmandise pourrait aider. –

+0

Pourquoi le downvote? C'est une approche parfaitement bonne et tout à fait utilisable si vous pouvez écrire des regex génériques pour le tag et la valeur. –

2

Cela fonctionne.

use 5.010; 
use Regexp::Grammars; 
my $parser = qr{ 
     (?: 
      <[Name]>{2} 
     ) 
     <rule: Name> 
      ((?:fir|la)st name: \w+) 
}x; 

while (<DATA>) { 
    /$parser/; 
    use Data::Dumper; say Dumper $/{Name}; 
} 

__DATA__ 
character first name: Han last name: Solo 
character last name: Solo first name: Han 

Sortie:

$VAR1 = [ 
      ' first name: Han', 
      ' last name: Solo' 
     ]; 

$VAR1 = [ 
      ' last name: Solo', 
      ' first name: Han' 
     ]; 
+0

Regex :: Grammars est le nouveau noir. –

+0

Pouce. Articles Damian effrayants. Il est généralement brillant à regarder mais la brillance s'use avec le temps. À la fin, l'utilisation d'un autre générateur de parseur (Parse :: Yapp/Eyapp est mon préféré) est probablement votre meilleur pari si vous en avez besoin. – tsee

+0

Un Yapp va bien aussi. (Pendant que nous sommes au snowcloning~ ...) – daxim

3

Les boucles suivantes sur la chaîne une fois pour trouver des correspondances (après la normalisation de la chaîne). La seule façon d'éviter la boucle est que chaque mot-clé ne puisse apparaître qu'une seule fois dans le texte. Si tel était le cas, vous pouvez écrire

my %matches = $string =~ /($re):\s+(\S+)/g; 

et d'en finir.

Le script ci-dessous traite d'occurrences multiples possibles.

#!/usr/bin/perl 

use strict; use warnings; 

use File::Slurp; 
use Regex::PreSuf; 

my $re = presuf('first name', 'last name'); 

my $string = read_file \*DATA; 
$string =~ s/\n+/ /g; 

my %matches; 

while ($string =~ /($re):\s+(\S+)/g) { 
    push @{ $matches{ $1 } }, $2; 
} 

use Data::Dumper; 
print Dumper \%matches; 

__DATA__ 
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
eiusmod tempor incididunt ut labore character first name: Han last 
name: Solo et dolore magna aliqua. Ut enim ad minim veniam, quis 
nostrud character last name: Solo first name: Han exercitation 
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute 
irure dolor in reprehenderit in voluptate velit esse cillum 
character last name: Solo first name: Han dolore eu fugiat nulla 
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 
culpa qui officia deserunt mollit anim id est laborum 
Questions connexes