3
votes

Comment capturer plusieurs mots à l'aide de regex sur ce texte particulier?

J'essaie d'extraire les intitulés de poste les mieux rémunérés de cet exemple de texte:

my @arr_found = ($content =~ m/"$regex"/g);

en utilisant l'expression régulière et le code Perl suivants.

use File::Glob;
local $/ = undef;
my $file = @ARGV[0];

open INPUT, "<", $file
    or die "Couldn't open file $!\n";

my $content = <INPUT>;

my $regex = "^\w+(\w+)*$\n\n#(\d+)";

my @arr_found = ($content =~ m/^\w+(\w+)*$\n\n#(\d+)/g);

close (INPUT);


2 commentaires

Q2: utilisez l'opérateur qr // (voir perlop ) pour compiler des expressions rationnelles. C'est à dire. mon $ regex = qr / ^ \ w + (\ w +) * $ \ n \ n # (\ d +) /; . Vous pouvez également combiner des sous-expressions régulières de cette manière, c'est-à-dire. mon $ regex = qr / $ {subre1} $ {subre2} $ {subre3} /;


Q1 est un peu flou. Par ex. le premier exemple: voulez-vous faire correspondre Data Scientist et # 1 ?


3 Réponses :


5
votes

Pourquoi ne pas traiter ligne par ligne, simple et facile

say "#$_ $jobs{$_}" for sort { $a <=> $b } keys %jobs;

Cela repose sur l'exigence que la ligne #N soit la première ligne non vide après le titre du poste.

Il imprime

my %jobs;
while ($content =~ /$regex/g) {
    $jobs{$2} = $1;
}

La question ne dit pas si les classements sont également recherchés, mais il y a un indice dans l'expression régulière ils peuvent être. Ensuite, en supposant que l'ordre dans le fichier est "correct", vous pouvez parcourir les indices du tableau et imprimer les éléments (titres) avec leurs indices (rang).

Ou, pour être sûr, capturez-les dans l'expression régulière, / ^ \ s * # ([0-9] +) / . Ensuite, vous pouvez imprimer directement à la fois le titre et son rang, ou peut-être les stocker dans un hachage avec des paires clé-valeur rank => title .


Comme pour l'expression régulière , il y a quelques corrections nécessaires. Pour composer une regex avant la mise en correspondance, ce qui est une bonne idée, vous voulez le opérateur qr . Pour travailler avec des chaînes multilignes, vous avez besoin du modificateur / m . (Voir perlretut .) L'expression régulière elle-même doit être corrigée. Par exemple,

my $regex = qr/^(.+)?(?:\n\s*)+\n\s*#\s*([0-9]+)/m;
my %jobs  = reverse  $content =~ /$regex/g;

ce qui capture une ligne suivie d'au moins une ligne vide, puis #N sur une autre ligne.

Si le classement des titres est également nécessaire, capturez-le également et stockez-le dans un hachage

my $regex  = qr/^(.+)?(?:\n\s*)+\n\s*#\s*[0-9]/m;
my @titles = $content =~ /$regex/g

ou mieux vaut ne pas le pousser avec reverse -ing la liste des correspondances mais itérer à travers des paires à la place

Data Scientist
Programmer
SAP Module Consultant

car avec cela nous pouvons vérifier notre "catch" à chaque itération, faire d'autres traitements, etc. Ensuite, vous pouvez trier les clés pour imprimer dans l'ordre

use warnings;
use strict;
use feature 'say';

my $file = shift || die "Usage: $0 file\n";

open my $fh, '<', $file  or die "Can't open $file: $!";

my (@jobs, $prev_line);

while (my $line = <$fh>) { 
    chomp $line;
    next if not $line =~ /\S/;

    if ($line =~ /^\s*#[0-9]/) {
        push @jobs, $prev_line;
    }   

    $prev_line = $line;
}

say for @jobs;

et juste en général choisir les emplois en fonction de leur rang si nécessaire.

Je pense qu'il est juste de dire que l'expression régulière ici est beaucoup plus complexe que le premier programme.


2 commentaires

Merci pour la réponse. eh bien, je voulais traiter le texte entier comme une seule chaîne parce que ce n'était pas un texte très long et je voulais voir si je pouvais l'aborder avec une seule expression régulière.


@Romario Bien sûr, c'est comme ça que nous apprenons :) Je voulais seulement proposer une approche plus simple (en principe cruciale en production et avec des problèmes difficiles: la décomposer en utilisant des approches informatiques et des algorithmes adaptés, pour simplifier), pas pour décourager. J'ai ajouté une section sur regex, par souci d'exhaustivité.



2
votes

Vous ne preniez pas en compte les espaces (comme dans Data Scientist ):

^\w+.*$\R+#(\d+)

Voir une démo sur regex101.com .


\R

est égal à (?> \ r \ n | \ n | \ r | \ f | \ x0b | \ x85) code> (correspond aux séquences de retours à la ligne Unicode).


2 commentaires

Merci pour la réponse. C'est une bonne regex. Mais il ne contenait pas comment transporter le jeu de résultats de l'expression régulière dans le tableau Perl, donc je ne pouvais pas le sélectionner avec une coche.


@Romario: Pas de soucis, heureux d'aider.



2
votes

Réponses à vos questions:

  1. vous capturez uniquement le deuxième mot et vous ne laissez pas d'espace entre eux. C'est pourquoi cela ne correspondra pas, par exemple Data Scientist

  2. utilisez l'opérateur qr // pour compiler des expressions rationnelles avec un contenu dynamique. L'erreur provient du $ au milieu de l'expression régulière que le compilateur de regex Perl suppose que vous vous êtes trompé, car $ ne devrait venir qu'à la fin d'une expression régulière. P >

Le code suivant devrait réaliser ce que vous voulez. Notez l'approche en deux étapes:

  1. Rechercher le texte correspondant

    • début d'une ligne ( ^ )
    • un ou plusieurs mots séparés par des espaces ( \ w + (?: \ s + \ w +) * , pas besoin de capturer la correspondance)
    • Fin de 2 lignes ( \ n \ n )
    • # suivi d'un nombre ( \ d + )
    • appliquer une expression régulière plusieurs fois ( / g ) et traiter les chaînes comme plusieurs lignes ( / m , c'est-à-dire que ^ correspondra à tout début d'une ligne dans le texte d'entrée)
  2. Diviser la correspondance aux fins de ligne ( \ n ) et extraire le 1er et le 3ème champ

    • comme nous savons que $ match contiendra trois lignes, cette approche est beaucoup plus simple que d'écrire une autre expression régulière.
my $regex = qr/^(\w+(?:\s+\w+)*\R\R#\d+)/m;
...
    my($title, undef, $rank) = split(/\R/, $match);

Test exécuté sur l'exemple de texte que vous avez fourni dans votre question.

$ perl dummy.pl dummy.txt
MATCH 'Data Scientist' '1'
MATCH 'Programmer' '2'
MATCH 'SAP Module Consultant' '3'

MISE À JOUR UNICODE : comme suggéré par la réponse de @ Jan, le code peut être amélioré comme ceci:

#!/usr/bin/perl
use strict;
use warnings;

use feature qw(say);
use File::Slurper qw(read_text);

my $input = read_text($ARGV[0])
    or die "slurp: $!\n";

my $regex = qr/^(\w+(?:\s+\w+)*\n\n#\d+)/m;

foreach my $match ($input =~ /$regex/g) {
    #say $match;
    my($title, undef, $rank) = split("\n", $match);
    $rank =~ s/^#//;
    say "MATCH '${title}' '${rank}'";
}

exit 0;

C'est probablement l'approche la plus générique, comme UTF-8 est la valeur par défaut pour File :: Slurper :: read_text () de toute façon ...


4 commentaires

Merci pour la réponse. J'avais entendu parler du qr // mais je l'ai totalement oublié. La partie boucle est sympa. Jamais vu ou utilisé la fonction use qw (disons); et utilisent File :: Slurper qw (read_text); avant mais ils sont très pratiques et très bien.


à l'intérieur de l'expression (?: \ s + \ w +) * il y a : J'ai essayé de regexing sans elle et cela n'a pas changé la sortie. Serait-ce une faute de frappe? Qu'est ce que ça fait?


Voir perlre : " (?: Pattern) Ceci est pour le clustering, pas pour la capture; il regroupe des sous-expressions comme () , mais ne fait pas de références arrière comme le fait () "AKA non-capturing group (NCG). Lorsque vous devez regrouper des éléments, mais pas capturer le contenu, cela est préférable pour une légère amélioration des performances.


Je pensais à : séparément, mais il s'est avéré être la combinaison ?: et cela a du sens maintenant. Je vous remercie.