Esiste un modo efficace per eseguire centinaia di sostituzioni di testo in Ruby?

Sto cercando di utilizzare un elenco di centinaia di errori ortografici comuni per pulire alcuni input prima di cercare i duplicati.

È un processo critico dal punto di vista del tempo, quindi spero che ci sia un modo più veloce di avere centinaia di regex (o una con cento rami).

Esiste un modo efficace per eseguire centinaia di sostituzioni di testo in Ruby?

Un approccio alternativo, se i dati di input sono parole separate, sarebbe semplicemente creare una tabella hash di {error => correction} .

La ricerca della tabella hash è veloce , quindi se riesci a piegare i dati di input in questo formato, sarà quasi certamente abbastanza veloce.

Sono felice di dire che ho appena trovato ” RegexpTrie ” che è una sostituzione utilizzabile per il codice e che ho bisogno di, Perl’s Regexp :: Assemble.

Installalo e provalo:

 require 'regexp_trie' foo = %w(miss misses missouri mississippi) RegexpTrie.union(foo) # => /miss(?:(?:es|ouri|issippi))?/ RegexpTrie.union(foo, option: Regexp::IGNORECASE) # => /miss(?:(?:es|ouri|issippi))?/i 

Ecco un confronto delle uscite. I primi output commentati nell’array provengono da Regexp :: Assemble e l’output finale proviene da RegexpTrie:

 require 'regexp_trie' [ 'how now brown cow', # /(?:[chn]ow|brown)/ 'the rain in spain stays mainly on the plain', # /(?:(?:(?:(?:pl|r)a)?i|o)n|s(?:pain|tays)|mainly|the)/ 'jackdaws love my giant sphinx of quartz', # /(?:jackdaws|quartz|sphinx|giant|love|my|of)/ 'fu foo bar foobar', # /(?:f(?:oo(?:bar)?|u)|bar)/ 'ms miss misses missouri mississippi' # /m(?:iss(?:(?:issipp|our)i|es)?|s)/ ].each do |s| puts "%-43s # /%s/" % [s, RegexpTrie.union(s.split).source] end # >> how now brown cow # /(?:how|now|brown|cow)/ # >> the rain in spain stays mainly on the plain # /(?:the|rain|in|s(?:pain|tays)|mainly|on|plain)/ # >> jackdaws love my giant sphinx of quartz # /(?:jackdaws|love|my|giant|sphinx|of|quartz)/ # >> fu foo bar foobar # /(?:f(?:oo(?:bar)?|u)|bar)/ # >> ms miss misses missouri mississippi # /m(?:iss(?:(?:es|ouri|issippi))?|s)/ 

Riguardo a come utilizzare il link di Wikipedia e le parole errate:

 require 'nokogiri' require 'open-uri' require 'regexp_trie' URL = 'https://en.wikipedia.org/wiki/Wikipedia:Lists_of_common_misspellings/For_machines' doc = Nokogiri::HTML(open(URL)) corrections = doc.at('div#mw-content-text pre').text.lines[1..-1].map { |s| a, b = s.chomp.split('->', 2) [a, b.split(/,\s+/) ] }.to_h # {"abandonned"=>["abandoned"], # "aberation"=>["aberration"], # "abilityes"=>["abilities"], # "abilties"=>["abilities"], # "abilty"=>["ability"], # "abondon"=>["abandon"], # "abbout"=>["about"], # "abotu"=>["about"], # "abouta"=>["about a"], # ... # } misspelled_words_regex = /\b(?:#{RegexpTrie.union(corrections.keys, option: Regexp::IGNORECASE).source})\b/i # => /\b(?:(?:a(?:b(?:andonned|eration|il(?:ityes|t(?:ies|y))|o(?:ndon(?:(?:ed|ing|s))?|tu|ut(?:it|the|a)... 

A questo punto è ansible utilizzare gsub(misspelled_words_regex, corrections) , tuttavia, i valori nelle corrections contengono alcuni array perché potrebbero essere state utilizzate più parole o frasi per sostituire la parola errata. Dovrai fare qualcosa per determinare quale delle scelte utilizzare.


Ruby manca un modulo molto utile trovato in Perl, chiamato Regexp :: Assemble . Python ha haikir-regex che sembra fare lo stesso genere di cose.

Regexp :: Assemble crea un’espressione regolare molto efficiente, basata su elenchi di parole e espressioni semplici. È davvero notevole … o … diabolico?

Guarda l’esempio per il modulo; È estremamente semplice da utilizzare nella sua forma base:

 use Regexp::Assemble; my $ra = Regexp::Assemble->new; $ra->add( 'ab+c' ); $ra->add( 'ab+-' ); $ra->add( 'a\w\d+' ); $ra->add( 'a\d+' ); print $ra->re; # prints a(?:\w?\d+|b+[-c]) 

Nota come combina i modelli. Farebbe lo stesso con le parole normali, solo sarebbe ancora più efficiente perché le stringhe comuni saranno combinate:

 use Regexp::Assemble; my $lorem = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; my $ra = Regexp::Assemble->new('flags' => 'i'); $lorem =~ s/[^a-zA-Z ]+//g; $ra->add(split(' ', lc($lorem))); print $ra->anchor_word(1)->as_string, "\n"; 

Quali uscite:

 \b(?:a(?:dipisicing|liqua|met)|(?:consectetu|tempo)r|do(?:lor(?:emagna)?)?|e(?:(?:li)?t|iusmod)|i(?:ncididunt|psum)|l(?:abore|orem)|s(?:ed|it)|ut)\b 

Questo codice ignora il caso e onora i confini delle parole.

Consiglierei di scrivere una piccola app Perl che possa prendere una lista di parole e usare quel modulo per produrre la versione con stringa del modello regex. Dovresti essere in grado di importare quel modello in Ruby. Questo ti permetterebbe di trovare rapidamente le parole con errori di ortografia. Si potrebbe anche fare in modo che emetta il pattern in un file YAML, quindi carica quel file nel codice Ruby. Analizza periodicamente le pagine di parole errate, esegui l’output tramite il codice Perl e il tuo codice Ruby avrà uno schema di aggiornamento.

Puoi usare questo modello contro un pezzo di testo solo per vedere se ci sono parole con errori di ortografia. In tal caso, si interrompe il testo in frasi o parole e si verifica nuovamente la regex. Non testare immediatamente le parole perché la maggior parte delle parole sarà scritta correttamente. È quasi come una ricerca binaria contro il tuo testo: prova l’intera faccenda, se c’è un hit, rompi in blocchi più piccoli per restringere la ricerca finché non trovi i singoli errori ortografici. Come abbattere i pezzi dipende dalla quantità di testo in arrivo. Un pattern regex può testare l’intero blocco di testo e restituire un valore nullo o indice, oltre alle singole parole allo stesso modo, in modo da guadagnare molta velocità facendo grossi pezzi del testo.

Quindi, se sai di avere una parola errata, puoi eseguire una ricerca hash per l’ortografia corretta. Sarebbe un grosso hash, ma il compito di spulciare le ortografie buone o cattive è quello che impiegherà più tempo. La ricerca sarebbe estremamente veloce.


Ecco alcuni esempi di codice:

get_words.rb

 #!/usr/bin/env ruby require 'open-uri' require 'nokogiri' require 'yaml' words = {} ['0-9', *('A'..'Z').to_a].each do |l| begin print "Reading #{l}... " html = open("http://en.wikipedia.org/wiki/Wikipedia:Lists_of_common_misspellings/#{l}").read puts 'ok' rescue Exception => e puts "got \"#{e}\"" next end doc = Nokogiri::HTML(html) doc.search('div#bodyContent > ul > li').each do |n| n.content =~ /^(\w+) \s+ \(([^)]+)/x words[$1] = $2 end end File.open('wordlist.yaml', 'w') do |wordfile| wordfile.puts words.to_yaml end 

regex_assemble.pl

 #!/usr/bin/env perl use Regexp::Assemble; use YAML; use warnings; use strict; my $ra = Regexp::Assemble->new('flags' => 'i'); my %words = %{YAML::LoadFile('wordlist.yaml')}; $ra->add(map{ lc($_) } keys(%words)); print $ra->chomp(1)->anchor_word(1)->as_string, "\n"; 

Esegui il primo, quindi esegui il secondo piping il suo output in un file per catturare l’espressione regolare emessa.


E altri esempi di parole e l’output generato:

 'how now brown cow' => /\b(?:[chn]ow|brown)\b/ 'the rain in spain stays mainly on the plain' => /\b(?:(?:(?:(?:pl|r)a)?i|o)n|s(?:pain|tays)|mainly|the)\b/ 'jackdaws love my giant sphinx of quartz' => /\b(?:jackdaws|quartz|sphinx|giant|love|my|of)\b/ 'fu foo bar foobar' => /\b(?:f(?:oo(?:bar)?|u)|bar)\b/ 'ms miss misses missouri mississippi' => /\bm(?:iss(?:(?:issipp|our)i|es)?|s)\b/ 

Regexp.union ‘s Regexp.union non è Regexp.union lontanamente alla raffinatezza di Regexp::Assemble . Dopo aver catturato l’elenco delle parole errate, ci sono 4225 parole, composte da 41.817 caratteri. Dopo aver eseguito Perl’s Regexp :: Assemble contro quell’elenco, è stata generata una regex di 30.954 caratteri. Direi che è efficiente.

Provalo al contrario. Anziché correggere errori di ortografia e verificare la presenza di duplicati sul risultato, rilasciare tutto in un formato simile al suono (come Metaphone o Soundex) e verificare la presenza di duplicati in tale formato.

Ora, non so quale sia la via più veloce: da un lato, ci sono centinaia di regex, ognuna delle quali non riuscirà a eguagliare quasi istantaneamente e a tornare. Dall’altro, hai 30 sostituzioni potenziali di regex, una o due delle quali si abbineranno sicuramente per ogni parola.

Ora, il metaphone è piuttosto veloce – non c’è davvero molto dell’algoritmo – quindi posso solo suggerire di provarlo e misurare se è abbastanza veloce per il tuo uso.