Java Remplacement de plusieurs sous-chaînes différentes dans une chaîne à la fois (ou de la manière la plus efficace)

J’ai besoin de remplacer plusieurs sous-chaînes différentes dans une chaîne de la manière la plus efficace. Y a-t-il une autre manière autre que le moyen brut de remplacer chaque champ en utilisant ssortingng.replace?

Si la chaîne sur laquelle vous travaillez est très longue, ou si vous travaillez sur plusieurs chaînes, cela peut être intéressant d’utiliser un java.util.regex.Matcher (cela nécessite du temps pour la compilation, il ne sera donc pas efficace) si votre entrée est très petite ou si votre modèle de recherche change fréquemment).

Vous trouverez ci-dessous un exemple complet, basé sur une liste de jetons tirés d’une carte. (Utilise SsortingngUtils d’Apache Commons Lang).

Map tokens = new HashMap(); tokens.put("cat", "Garfield"); tokens.put("beverage", "coffee"); Ssortingng template = "%cat% really needs some %beverage%."; // Create pattern of the format "%(cat|beverage)%" Ssortingng patternSsortingng = "%(" + SsortingngUtils.join(tokens.keySet(), "|") + ")%"; Pattern pattern = Pattern.comstack(patternSsortingng); Matcher matcher = pattern.matcher(template); SsortingngBuffer sb = new SsortingngBuffer(); while(matcher.find()) { matcher.appendReplacement(sb, tokens.get(matcher.group(1))); } matcher.appendTail(sb); System.out.println(sb.toSsortingng()); 

Une fois que l’expression régulière est compilée, l’parsing de la chaîne d’entrée est généralement très rapide (bien que si votre expression régulière est complexe ou implique un retour en arrière, vous devrez toujours effectuer un test afin de le confirmer!)

Algorithme

L’un des moyens les plus efficaces de remplacer les chaînes correspondantes (sans expressions régulières) consiste à utiliser l’ algorithme Aho-Corasick avec un Trie performant (prononcé “try”), un algorithme de hachage rapide et une implémentation efficace des collections .

Code simple

Peut-être le code le plus simple à écrire exploite- SsortingngUtils.replaceEach -il SsortingngUtils.replaceEach d’Apache comme suit:

  private Ssortingng testSsortingngUtils( final Ssortingng text, final Map definitions ) { final Ssortingng[] keys = keys( definitions ); final Ssortingng[] values = values( definitions ); return SsortingngUtils.replaceEach( text, keys, values ); } 

Cela ralentit les gros textes.

Code rapide

L’implémentation par Bor de l’algorithme Aho-Corasick introduit un peu plus de complexité qui devient un détail d’implémentation en utilisant une façade avec la même signature de méthode:

  private Ssortingng testBorAhoCorasick( final Ssortingng text, final Map definitions ) { // Create a buffer sufficiently large that re-allocations are minimized. final SsortingngBuilder sb = new SsortingngBuilder( text.length() << 1 ); final TrieBuilder builder = Trie.builder(); builder.onlyWholeWords(); builder.removeOverlaps(); final String[] keys = keys( definitions ); for( final String key : keys ) { builder.addKeyword( key ); } final Trie trie = builder.build(); final Collection emits = sortinge.parseText( text ); int prevIndex = 0; for( final Emit emit : emits ) { final int matchIndex = emit.getStart(); sb.append( text.subssortingng( prevIndex, matchIndex ) ); sb.append( definitions.get( emit.getKeyword() ) ); prevIndex = emit.getEnd() + 1; } // Add the remainder of the ssortingng (contains no more matches). sb.append( text.subssortingng( prevIndex ) ); return sb.toSsortingng(); } 

Repères

Pour les benchmarks, le buffer a été créé en utilisant randomNumeric comme suit:

  private final static int TEXT_SIZE = 1000; private final static int MATCHES_DIVISOR = 10; private final static SsortingngBuilder SOURCE = new SsortingngBuilder( randomNumeric( TEXT_SIZE ) ); 

MATCHES_DIVISOR dicte le nombre de variables à injecter:

  private void injectVariables( final Map definitions ) { for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) { final int r = current().nextInt( 1, SOURCE.length() ); SOURCE.insert( r, randomKey( definitions ) ); } } 

Le code de référence lui-même ( JMH semblait exagéré):

 long duration = System.nanoTime(); final Ssortingng result = testBorAhoCorasick( text, definitions ); duration = System.nanoTime() - duration; System.out.println( elapsed( duration ) ); 

1 000 000: 1 000

Un simple micro-benchmark avec 1 000 000 de caractères et 1 000 chaînes placées au hasard à remplacer.

  • TestSsortingngUtils: 25 secondes, 25533 millis
  • testBorAhoCorasick: 0 seconde, 68 millis

Pas de compétition.

10 000: 1 000

En utilisant 10 000 caractères et 1 000 chaînes correspondantes pour remplacer:

  • testSsortingngUtils: 1 seconde, 1402 millis
  • testBorAhoCorasick: 0 seconde, 37 millis

La fracture se ferme.

1000: 10

En utilisant 1 000 caractères et 10 chaînes correspondantes pour remplacer:

  • testSsortingngUtils: 0 seconde, 7 millis
  • testBorAhoCorasick: 0 secondes, 19 millis

Pour les chaînes courtes, la configuration de Aho-Corasick éclipse l’approche de force brute de SsortingngUtils.replaceEach .

Une approche hybride basée sur la longueur du texte est possible pour obtenir le meilleur des deux implémentations.

Les implémentations

Envisagez de comparer d’autres implémentations pour un texte de plus de 1 Mo, notamment:

Papiers

Documents et informations relatifs à l’algorithme: