web-dev-qa-db-fra.com

Le moyen le plus rapide de vérifier si une chaîne correspond à une expression régulière ou non en ruby?

Quel est le moyen le plus rapide de vérifier si une chaîne correspond à une expression régulière en Ruby?

Mon problème est que je dois "egrep" à travers une longue liste de chaînes pour trouver celles qui correspondent à une expression rationnelle donnée au moment de l'exécution. Je me soucie seulement de savoir si la chaîne correspond à l'expression rationnelle, pas où elle correspond, ni quel est le contenu des groupes correspondants. J'espère que cette hypothèse pourra être utilisée pour réduire le temps que mon code utilise pour les expressions rationnelles correspondantes.

Je charge l'expression régulière avec

pattern = Regexp.new(ptx).freeze

J'ai trouvé que string =~ pattern est légèrement plus rapide que string.match(pattern).

Existe-t-il d'autres astuces ou raccourcis pouvant rendre ce test encore plus rapide?

64
gioele

À partir de Ruby 2.4.0, vous pouvez utiliser RegExp#match?:

pattern.match?(string)

Regexp#match? est explicitement répertorié comme une amélioration des performances dans les notes de publication de pour 2.4.0 , car il évite les attributions d'objets effectuées par d'autres méthodes telles que Regexp#match et =~:

Regexp # correspond?
Ajout de Regexp#match?, qui exécute une correspondance d'expression rationnelle sans créer un objet de référence arrière et en modifiant $~ pour réduire l'allocation d'objet.

62
Wiktor Stribiżew

Ceci est un repère simple:

require 'benchmark'

"test123" =~ /1/
=> 4
Benchmark.measure{ 1000000.times { "test123" =~ /1/ } }
=>   0.610000   0.000000   0.610000 (  0.578133)

"test123"[/1/]
=> "1"
Benchmark.measure{ 1000000.times { "test123"[/1/] } }
=>   0.718000   0.000000   0.718000 (  0.750010)

irb(main):019:0> "test123".match(/1/)
=> #<MatchData "1">
Benchmark.measure{ 1000000.times { "test123".match(/1/) } }
=>   1.703000   0.000000   1.703000 (  1.578146)

Donc =~ est plus rapide mais cela dépend de ce que vous voulez avoir comme valeur retournée. Si vous voulez juste vérifier si le texte contient une regex ou non, utilisez =~

66
Dougui

C’est le point de repère que j’ai utilisé après avoir trouvé des articles sur Internet. 

Avec 2.4.0, le gagnant est re.match?(str) (comme suggéré par @ wiktor-stribiżew), dans les versions précédentes, re =~ str semble être le plus rapide, bien que str =~ re soit presque aussi rapide.

#!/usr/bin/env Ruby
require 'benchmark'

str = "aacaabc"
re = Regexp.new('a+b').freeze

N = 4_000_000

Benchmark.bm do |b|
    b.report("str.match re\t") { N.times { str.match re } }
    b.report("str =~ re\t")    { N.times { str =~ re } }
    b.report("str[re]  \t")    { N.times { str[re] } }
    b.report("re =~ str\t")    { N.times { re =~ str } }
    b.report("re.match str\t") { N.times { re.match str } }
    if re.respond_to?(:match?)
        b.report("re.match? str\t") { N.times { re.match? str } }
    end
end

Résultats IRM 1.9.3-O551:

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re =~ str         2.390000   0.000000   2.390000 (  2.397331)
str =~ re         2.450000   0.000000   2.450000 (  2.446893)
str[re]           2.940000   0.010000   2.950000 (  2.941666)
re.match str      3.620000   0.000000   3.620000 (  3.619922)
str.match re      4.180000   0.000000   4.180000 (  4.180083)

Résultats IRM 2.1.5:

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re =~ str         1.150000   0.000000   1.150000 (  1.144880)
str =~ re         1.160000   0.000000   1.160000 (  1.150691)
str[re]           1.330000   0.000000   1.330000 (  1.337064)
re.match str      2.250000   0.000000   2.250000 (  2.255142)
str.match re      2.270000   0.000000   2.270000 (  2.270948)

Résultats IRM 2.3.3 (il semble y avoir une régression dans l'appariement regex):

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re =~ str         3.540000   0.000000   3.540000 (  3.535881)
str =~ re         3.560000   0.000000   3.560000 (  3.560657)
str[re]           4.300000   0.000000   4.300000 (  4.299403)
re.match str      5.210000   0.010000   5.220000 (  5.213041)
str.match re      6.000000   0.000000   6.000000 (  6.000465)

Résultats IRM 2.4.0:

$ ./bench-re.rb  | sort -t $'\t' -k 2
       user     system      total        real
re.match? str     0.690000   0.010000   0.700000 (  0.682934)
re =~ str         1.040000   0.000000   1.040000 (  1.035863)
str =~ re         1.040000   0.000000   1.040000 (  1.042963)
str[re]           1.340000   0.000000   1.340000 (  1.339704)
re.match str      2.040000   0.000000   2.040000 (  2.046464)
str.match re      2.180000   0.000000   2.180000 (  2.174691)
34
gioele

Qu'en est-il de re === str (comparaison de casse)?

Puisqu'il est évalué comme vrai ou faux et qu'il n'est pas nécessaire de stocker les correspondances ni de renvoyer l'index des correspondances, je me demande si ce serait un moyen de correspondance encore plus rapide que =~.


Ok, j'ai testé ça. =~ est toujours plus rapide, même si vous avez plusieurs groupes de capture, mais il est plus rapide que les autres options.

BTW, à quoi bon freeze? Je ne pouvais pas en mesurer le gain de performance.

6
Heiko

En fonction de la complexité de votre expression régulière, vous pouvez éventuellement utiliser un simple découpage en chaîne. Je ne suis pas sûr de l'aspect pratique de ceci pour votre application ou s'il offrirait réellement des améliorations de vitesse.

'testsentence'['stsen']
=> 'stsen' # evaluates to true
'testsentence'['koala']
=> nil # evaluates to false
4
jimmydief

Ce que je me demande, c'est s'il existe un moyen étrange d'accélérer cette vérification, en exploitant peut-être une méthode étrange dans Regexp ou une construction étrange. 

Les moteurs d’expression régulière varient dans la manière dont ils implémentent les recherches, mais, en général, ancrez vos modèles de vitesse et évitez les correspondances gloutonnes, en particulier lors de la recherche de longues chaînes.

La meilleure chose à faire, jusqu'à ce que vous soyez familiarisé avec le fonctionnement d'un moteur particulier, consiste à effectuer des tests de performances et à ajouter/supprimer des ancres, à limiter le nombre de recherches, à utiliser des caractères génériques ou des correspondances explicites, etc.

La gemme Fruity est très utile pour analyser rapidement les résultats, car elle est intelligente. Le code Benchmark intégré de Ruby est également utile, bien que vous puissiez écrire des tests qui vous trompent en ne faisant pas attention.

J'ai utilisé les deux dans de nombreuses réponses ici sur Stack Overflow, vous pouvez donc parcourir mes réponses et voir beaucoup de petites astuces et de résultats pour vous donner des idées sur la manière d'écrire du code plus rapidement.

La chose la plus importante à retenir est qu’il est mauvais d’optimiser prématurément votre code avant de savoir où les ralentissements se produisent. 

2
the Tin Man

Pour compléter les réponses de Wiktor Stribiżew et Dougui, je dirais que /regex/.match?("string") est légèrement plus rapide que "string".match?(/regex/).

2.4.0 > require 'benchmark'
 => true 
2.4.0 > Benchmark.measure{ 10000000.times { /^CVE-[0-9]{4}-[0-9]{4,}$/.match?("CVE-2018-1589") } }
 => #<Benchmark::Tms:0x005563da1b1c80 @label="", @real=2.2060338060000504, @cstime=0.0, @cutime=0.0, @stime=0.04000000000000001, @utime=2.17, @total=2.21> 
2.4.0 > Benchmark.measure{ 10000000.times { "CVE-2018-1589".match?(/^CVE-[0-9]{4}-[0-9]{4,}$/) } }
 => #<Benchmark::Tms:0x005563da139eb0 @label="", @real=2.260814556000696, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=2.2500000000000004, @total=2.2600000000000007> 
0
noraj