Regex per tutte le parole di 10 lettere, con lettere uniche


23

Sto provando a scrivere una regex che mostrerà tutte le parole che sono lunghe 10 caratteri e nessuna delle lettere si ripete.

Finora ho

grep --colour -Eow '(\w{10})'

Qual è la prima parte della domanda. Come farei per verificare la "unicità"? Davvero non ne ho idea, a parte quello ho bisogno di usare riferimenti a ritroso.


1
Questo deve essere fatto con una regex?
Hauke ​​Laging,

Sto praticando regex, quindi preferibilmente sì :)
Dylan Meeus,

3
Non credo che tu possa farlo con un'espressione regolare di stile informatico: ciò che vuoi richiede "memoria" di ciò che sono i precedenti caratteri abbinati, e le espressioni regolari semplicemente non ce l'hanno. Detto questo, potresti essere in grado di farlo con i riferimenti posteriori e le cose di espressione non regolare che la corrispondenza in stile PCRE può fare.
Bruce Ediger,

3
@BruceEdiger fintanto che ci sono un numero finito di caratteri nella lingua (26) e lettere nella stringa (10), è abbastanza possibile farlo. È solo un sacco di stati, ma niente che non lo renderebbe un linguaggio normale.

1
Intendi "Tutte le parole inglesi ..."? Intendi includere quelli scritti con trattini e apostrofi o no (suoceri, no)? Intendi includere parole come caffè, ingenuo, facciata?
hippietrail,

Risposte:


41
grep -Eow '\w{10}' | grep -v '\(.\).*\1'

esclude le parole che hanno due caratteri identici.

grep -Eow '\w{10}' | grep -v '\(.\)\1'

esclude quelli che hanno caratteri ripetuti.

POSIXly:

tr -cs '[:alnum:]_' '[\n*]' |
   grep -xE '.{10}' |
   grep -v '\(.\).*\1'

tr mette le parole sulla propria linea convertendo qualsiasi s equazione di caratteri non di parole ( ccomplemento di caratteri alfanumerici e di sottolineatura) in un carattere di nuova riga.

O con uno grep :

tr -cs '[:alnum:]_' '[\n*]' |
   grep -ve '^.\{0,9\}$' -e '.\{11\}' -e '\(.\).*\1'

(esclude le righe di meno di 10 e più di 10 caratteri e quelle con un carattere che appare almeno due volte).

Con uno grepsolo (grep GNU con supporto PCRE o pcregrep):

grep -Po '\b(?:(\w)(?!\w*\1)){10}\b'

Cioè, una parola limite (\b ) seguito da una sequenza di caratteri di 10 parole (a condizione che ciascuno non sia seguito da una sequenza di caratteri di parole e se stessi, usando l'operatore PCRE di look-look negativo (?!...)).

Siamo fortunati che funzioni qui, poiché non molti motori regexp funzionano con riferimenti secondari all'interno di parti ripetute.

Nota che (almeno con la mia versione di GNU grep)

grep -Pow '(?:(\w)(?!\w*\1)){10}'

Non funziona, ma

grep -Pow '(?:(\w)(?!\w*\2)){10}'

fa (come echo aa | grep -Pw '(.)\2') che suona come un bug.

Potresti volere:

grep -Po '(*UCP)\b(?:(\w)(?!\w*\1)){10}\b'

se si desidera \wo \bconsiderare qualsiasi lettera come un componente word e non solo quelli ASCII in locali non ASCII.

Un'altra alternativa:

grep -Po '\b(?!\w*(\w)\w*\1)\w{10}\b'

Questo è un limite di parole (uno che non è seguito da una sequenza di caratteri di parole uno dei quali si ripete) seguito da 10 caratteri di parole.

Cose da possibilmente avere in fondo alla mente:

  • Il confronto fa distinzione tra maiuscole e minuscole, quindi Babylonishad esempio verrebbe abbinato, poiché tutti i caratteri sono diversi anche se ci sono due Bs, una maiuscola e una maiuscola (usare -iper cambiarla).
  • per -w, \we \b, una parola è una lettera (quelle ASCII solo per GNU grep per ora , la [:alpha:]classe di caratteri nel tuo locale se usi -Pe (*UCP)), cifre decimali o trattino basso .
  • ciò significa che c'est(due parole secondo la definizione francese di una parola) o it's(una parola secondo alcune definizioni inglesi di una parola) o rendez-vous(una parola secondo la definizione francese di una parola) non sono considerate una parola.
  • Anche con (*UCP), Unicode che combina i caratteri non è considerato come un componente di parola, quindi téléphone( $'t\u00e9le\u0301phone') è considerato come 10 caratteri, uno dei quali non alfa. défavorisé( $'d\u00e9favorise\u0301') sarebbe abbinato anche se ne ha due éperché sono 10 tutti i diversi caratteri alfa seguiti da un accento acuto combinato (non alfa, quindi c'è un confine di parole tra il esuo accento e).

1
Eccezionale. \wnon corrisponde -però.
Graeme,

@Stephane Puoi pubblicare una breve spiegazione delle ultime due espressioni.
mkc,

A volte sembra che le soluzioni alternative siano la soluzione a tutte le cose che prima erano impossibili con RE.
Barmar,

1
@Barmar sono ancora impossibili con le espressioni regolari. Una "espressione regolare" è un costrutto matematico che consente esplicitamente solo determinati costrutti, vale a dire caratteri letterali, classi di caratteri e gli operatori '|', '(...)', '?', '+' E '*'. Qualsiasi cosiddetta "espressione regolare" che utilizza un operatore che non è uno dei precedenti non è in realtà un'espressione regolare.
Jules,

1
@Jules Questo è unix.stackexchange.com, non math.stackexchange.com. Le RE matematiche sono irrilevanti in questo contesto, stiamo parlando dei tipi di RE che usi con grep, PCRE, ecc.
Barmar

12

Va bene ... ecco il modo goffo per una stringa di cinque caratteri:

grep -P '^(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3)(.)(?!\1|\2|\3|\4).$'

Poiché non è possibile inserire un riferimento a ritroso in una classe di caratteri (ad esempio [^\1|\2]), è necessario utilizzare un aspetto negativo - (?!foo). Questa è una funzione PCRE, quindi è necessario l' -Pinterruttore.

Il modello per una stringa di 10 caratteri sarà molto più lungo, ovviamente, ma c'è un metodo più breve che utilizza una lunghezza variabile qualsiasi cosa corrisponda ('. *') Nel lookahead:

grep -P '^(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!.*\4)(.)(?!.*\5).$'

Dopo aver letto la risposta illuminante di Stephane Chazelas, mi sono reso conto che esiste un modello semplice simile per questo utilizzabile tramite grep -v interruttore :

    (.).*\1

Poiché il controllo procede un carattere alla volta, questo vedrà se ogni dato carattere è seguito da zero o più caratteri ( .*) e quindi una corrispondenza per il riferimento posteriore. -vinverte, stampando solo cose che non lo fanno corrispondono a questo modello. Questo rende i riferimenti posteriori più utili poiché non possono essere negati con una classe di caratteri e significativamente:

grep -v '\(.\).*\1'

lavorerà per identificare una stringa di qualsiasi lunghezza con caratteri univoci mentre:

grep -P '(.)(?!.*\1)'

no, dal momento che l'importo da suffisso con personaggi unici (ad esempio, abcabcpartite a causa di abcalla fine, e la aaaacausa della afine - quindi qualsiasi stringa). Questa è una complicazione causata dal fatto che i lookaround sono a larghezza zero (non consumano nulla).


Molto bene! Questo funzionerà solo in combinazione con quello nella Q però.
Graeme,

1
Credo che tu possa semplificare il primo se il tuo motore regex consente lookahead negativi di lunghezza variabile:(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
Christopher Creutzig

@ChristopherCreutzig: Assolutamente, bella chiamata. L'ho aggiunto.
Riccioli d'oro

6

Se non hai bisogno di fare tutto in regex, lo farei in due passaggi: prima confronta tutte le parole di 10 lettere, quindi le filtra per unicità. Il modo più breve che conosco è in Perl:

perl -nle 'MATCH:while(/\W(\w{10})\W/g){
             undef %seen;
             for(split//,$1){next MATCH if ++$seen{$_} > 1}
             print
           }' your_file

Nota le \Wancore aggiuntive per assicurarti che vengano abbinate solo le parole lunghe esattamente 10 caratteri.


Grazie, ma mi piacerebbe come reelice oneliner :)
Dylan Meeus,

4

Altri hanno suggerito che ciò non è possibile senza varie estensioni a determinati sistemi di espressione regolare che non sono in realtà regolari. Tuttavia, poiché la lingua che si desidera abbinare è limitata, è chiaramente regolare. Per 3 lettere di un alfabeto di 4 lettere, sarebbe facile:

(abc|abd|acb|acd|bac|bad|bcd|bdc|cab|cad|cbd|cdb|dab|dac|dbc|dcb)

Ovviamente questo sfugge di mano in fretta con più lettere e alfabeti più grandi. :-)


Ho dovuto votare questo perché in realtà è una risposta che avrebbe funzionato. Anche se in realtà potrebbe essere il modo meno efficace che qualcuno abbia mai scritto regex: P
Dylan Meeus,

4

L'opzione --perl-regexp(breve -P) di GNU greputilizza espressioni regolari più potenti che includono modelli di previsione. Il seguente modello cerca ogni lettera che questa lettera non appare nel resto della parola:

grep -Pow '((\w)(?!\w*\g{-1})){10}'

Tuttavia, il comportamento di runtime è piuttosto negativo, perché \w*può avere una lunghezza quasi infinita. Può essere limitato a \w{,8}, ma controlla anche oltre il limite di parole di 10 lettere. Pertanto, il modello seguente controlla prima la lunghezza della parola corretta:

grep -Pow '(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}'

Come file di prova ho usato un file ≈ 500 MB di grandi dimensioni:

  • Primo motivo: ≈ 43 s
  • Schema degli ultimi: ≈ 15 s

Aggiornare:

Non sono riuscito a trovare un cambiamento significativo nel comportamento di runtime per un operatore non avido ( \w*?) o possessivo ( (...){10}+). Un po 'più veloce sembra la sostituzione dell'opzione -w:

grep -Po '\b(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}\b'

Un aggiornamento di grep dalla versione 2.13 alla 2.18 è stato molto più efficace. Il file di test ha richiesto solo ≈ 6 s.


Le prestazioni dipenderanno molto dalla natura dei dati. Durante i miei test ho scoperto che l'uso di operatori non avidi ( \w{,8}?) mi ha aiutato per alcuni tipi di input (anche se non in modo molto significativo). Un buon uso di \g{-1}aggirare il bug grep GNU.
Stéphane Chazelas,

@StephaneChazelas: Grazie per il feedback. Avevo anche provato operatori non avidi e possessivi e non ho trovato un cambiamento significativo nel comportamento di runtime (versione 2.13). La versione 2.18 è molto più veloce e ho potuto vedere almeno un piccolo miglioramento. Il bug GNU grep è presente in entrambe le versioni. Comunque preferisco il riferimento relativo \g{-1}, perché rende il modello più indipendente dalla posizione. In questa forma può essere usato come parte di un modello più grande.
Heiko Oberdiek,

0

Una soluzione Perl:

perl -lne 'print if (!/(.)(?=$1)/g && /^\w{10}$/)' file

ma non funziona con

perl -lne 'print if (!/(.)(?=\1)/g && /^\w{10}$/)' file

o

perl -lne 'print if ( /(.)(?!$1)/g && /^\w{10}$/)' file

testato con perl v5.14.2 e v5.18.2


Il 1o e il 3o non fanno nulla, il 2o emette qualsiasi riga di 10 o più caratteri, con non più di 2 spazi consecutivi. pastebin.com/eEDcy02D
arte

è probabilmente la versione perl. testato con v5.14.2 e v5.18.2

Li ho provati con v5.14.1 su Linux e v5.14.2 su Cygwin. Entrambi si sono comportati come nell'esempio di pastebin che ho collegato in precedenza.
arte

la prima riga funziona per me con le versioni note di perl. i due ultimi dovrebbero funzionare, perché sono gli stessi ri, ma non lo hanno fatto. perlre nota spesso che alcune espressioni golose sono altamente sperimentali.

Testato nuovamente con gli ultimi aggiornamenti. Solo il secondo emette correttamente. (Tuttavia la parola deve essere sola in una riga, mentre la domanda riguarda le parole corrispondenti, non intere righe.)
manatwork
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.