Regex para todas as 10 palavras da letra, com letras exclusivas


23

Estou tentando escrever um regex que exibirá todas as palavras com 10 caracteres e nenhuma das letras esteja repetindo.

Até agora, eu tenho

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

Qual é a primeira parte da pergunta. Como eu verificaria a "singularidade"? Realmente não tenho idéia, além disso, preciso usar referências anteriores.


1
Isso deve ser feito com uma regex?
Hauke ​​Laging

Eu estou praticando regex, por isso, de preferência yes :)
Dylan Meeus

3
Não acredito que você possa fazer isso com uma expressão regular no estilo da ciência da computação: o que você deseja requer "memória" do que são os caracteres correspondentes anteriores e as expressões regulares simplesmente não têm isso. Dito isso, você poderá fazê-lo com referências anteriores e com as expressões de expressão não regular que a correspondência no estilo PCRE pode fazer.
Bruce Ediger 22/02

3
@BruceEdiger, desde que haja um número finito de caracteres no idioma (26) e letras na sequência (10), é bem possível. São apenas muitos estados, mas nada que o tornasse uma linguagem comum.

1
Você quer dizer "Todas as palavras em inglês ..."? Você quer incluir aqueles escritos com hífens e apóstrofos ou não (sogro, não)? Você quer incluir palavras como café, ingênuo, fachada?
Hippietrail 23/02

Respostas:


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

exclui palavras que possuem dois caracteres idênticos.

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

exclui aqueles que têm caracteres repetidos.

POSIXly:

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

trcoloca as palavras em sua própria linha, convertendo qualquer sequivalência de caracteres que não sejam palavras ( complemento de alfanumérico e sublinhado) em um caractere de nova linha.

Ou com um grep:

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

(exclua linhas com menos de 10 e mais de 10 caracteres e aquelas com um caractere aparecendo pelo menos duas vezes).

Com grepapenas um (GNU grep com suporte para PCRE ou pcregrep):

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

Ou seja, um limite de palavras ( \b) seguido por uma sequência de 10 caracteres de palavras (desde que cada um não seja seguido por uma sequência de caracteres de palavras e por si mesmos, usando o operador PCRE negativo antecipado (?!...)).

Temos sorte que ele funcione aqui, já que muitos mecanismos de regexp não funcionam com referências anteriores em peças repetidas.

Note que (com minha versão do GNU grep pelo menos)

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

Não funciona, mas

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

faz (as echo aa | grep -Pw '(.)\2') que soa como um bug.

Você pode querer:

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

se você quiser \wou \bconsiderar qualquer letra como um componente de palavra e não apenas as letras ASCII em códigos de idioma não ASCII.

Outra alternativa:

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

Esse é um limite de palavras (aquele que não é seguido por uma sequência de caracteres de palavras, uma das quais se repete), seguido por 10 caracteres de palavras.

Coisas que você pode ter no fundo da mente:

  • A comparação faz Babylonishdistinção entre maiúsculas e minúsculas, portanto, por exemplo, haveria correspondência, pois todos os caracteres são diferentes, mesmo que existam dois Bs, um menor e outro maiúsculo (use -ipara mudar isso).
  • para -w, \we \b, uma palavra é uma letra (ASCII apenas para GNU grep por enquanto , a [:alpha:]classe de caracteres em sua localidade, se estiver usando -Pe (*UCP)), dígitos decimais ou sublinhado .
  • isso significa que c'est(duas palavras conforme a definição francesa de uma palavra) ou it's(uma palavra de acordo com algumas definições em inglês de uma palavra) ou rendez-vous(uma palavra conforme a definição de palavra francesa) não são consideradas uma palavra.
  • Mesmo com a (*UCP)combinação de caracteres Unicode, não são considerados componentes de palavras, portanto téléphone( $'t\u00e9le\u0301phone') é considerado com 10 caracteres, um dos quais não alfa. défavorisé( $'d\u00e9favorise\u0301') seria correspondido mesmo que tenha dois, éporque são 10 caracteres alfa diferentes, seguidos por um sotaque agudo combinado (não alfa, portanto, há um limite de palavras entre o ee o sotaque).

1
Impressionante. \wnão corresponde -embora.
Graeme

@ Stephanie Você pode postar uma breve explicação das duas últimas expressões.
Mkc

Às vezes, parece que olhar para trás é a solução para todas as coisas que antes eram impossíveis no ER.
Barmar

1
@ Barmar ainda são impossíveis com expressões regulares. Uma "Expressão Regular" é uma construção matemática que permite explicitamente apenas certas construções, como caracteres literais, classes de caracteres e os operadores '|', '(...)', '?', '+' E '*'. Qualquer chamada "expressão regular" que use um operador que não seja um dos itens acima não é realmente uma Expressão Regular.
Jules

1
@Jules Este é unix.stackexchange.com, não math.stackexchange.com. Do RE matemáticas são irrelevantes neste contexto, nós estamos falando sobre os tipos de REs você usa com grep, PCRE, etc.
Barmar

12

Ok ... aqui está a maneira desajeitada para uma sequência de cinco caracteres:

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

Como você não pode colocar uma referência anterior em uma classe de personagem (por exemplo [^\1|\2]), você deve usar um olhar negativo à frente - (?!foo). Esse é um recurso PCRE, portanto você precisa da -Ptroca.

O padrão para uma sequência de 10 caracteres será muito mais longo, é claro, mas há um método mais curto usando um comprimento variável de qualquer coisa correspondente ('. *') No cabeçalho:

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

Depois de ler a resposta esclarecedora de Stephane Chazelas, percebi que existe um padrão simples semelhante para esse utilizável através do -vswitch grep :

    (.).*\1

Como a verificação prossegue com um caractere de cada vez, isso indica se algum caractere é seguido por zero ou mais caracteres ( .*) e, em seguida, corresponde à referência anterior. -vinverte, imprimindo apenas coisas que não correspondem a esse padrão. Isso torna as referências anteriores mais úteis, pois não podem ser negadas com uma classe de personagem e significativamente:

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

trabalhará para identificar uma sequência de qualquer tamanho com caracteres exclusivos, enquanto:

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

não corresponderá, pois corresponderá qualquer sufixo a caracteres únicos (por exemplo, abcabccorrespondências por causa de abcno final e aaaapor causa de ano final - portanto, qualquer string). Essa é uma complicação causada pelo fato de as lookarounds terem largura zero (elas não consomem nada).


Bem feito! Isso funcionará apenas em combinação com o Q.
Graeme

1
Acredito que você pode simplificar o primeiro caso seu mecanismo de (.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
expressão regular

@ChristopherCreutzig: Absolutamente, boa ligação. Eu adicionei isso.
goldilocks

6

Se você não precisar fazer a coisa toda no regex, eu o faria em duas etapas: primeiro corresponda a todas as palavras de 10 letras e depois filtre-as por exclusividade. A maneira mais curta de saber como fazer isso é no Perl:

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

Observe as \Wâncoras adicionais para garantir que apenas as palavras com exatamente 10 caracteres sejam correspondidas.


Obrigado, mas eu gostaria que ele como um oneliner regex :)
Dylan Meeus

4

Outros sugeriram que isso não é possível sem várias extensões para certos sistemas de expressão regular que na verdade não são regulares. No entanto, como o idioma que você deseja corresponder é finito, é claramente regular. Para 3 letras de um alfabeto de 4 letras, seria fácil:

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

Obviamente, isso sai do controle às pressas com mais letras e alfabetos maiores. :-)


Eu tive que aprovar isso porque é realmente uma resposta que funcionaria. Embora possa realmente ser a maneira menos eficiente que alguém tenha escrito regex: P
Dylan Meeus

4

A opção --perl-regexp(curta -P) do GNU grepusa expressões regulares mais poderosas que incluem padrões de antecipação. O padrão a seguir procura cada letra que esta letra não apareça no restante da palavra:

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

No entanto, o comportamento em tempo de execução é bastante ruim, porque \w*pode ter um tamanho quase infinito. Pode ser limitado a \w{,8}, mas isso também verifica além do limite de palavras de 10 letras. Portanto, o seguinte padrão verifica primeiro o tamanho correto da palavra:

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

Como arquivo de teste, usei um arquivo grande de 500 MB:

  • Primeiro padrão: s 43 s
  • Últimos padrões: s 15 s

Atualizar:

Não consegui encontrar uma alteração significativa no comportamento em tempo de execução para um operador não ganancioso ( \w*?) ou operador possessivo ( (...){10}+). Um pouquinho mais rápido parece a substituição da opção -w:

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

Uma atualização do grep da versão 2.13 para 2.18 foi muito mais eficaz. O arquivo de teste levou apenas 6 segundos.


O desempenho dependerá muito da natureza dos dados. Ao fazer testes nos meus, descobri que o uso de operadores não gananciosos ( \w{,8}?) ajudava em algum tipo de entrada (embora não muito significativamente). Bom uso \g{-1}para solucionar o bug do GNU grep.
Stéphane Chazelas

@StephaneChazelas: Obrigado pelo feedback. Eu também tentei operadores não gananciosos e possessivos e não encontrei uma alteração significativa no comportamento em tempo de execução (versão 2.13). A versão 2.18 é muito mais rápida e eu pude ver pelo menos um pouquinho de melhoria. O bug do GNU grep está presente nas duas versões. Enfim, prefiro a referência relativa \g{-1}, porque torna o padrão mais independente da localização. Nesta forma, ele pode ser usado como parte de um padrão maior.
Heiko Oberdiek

0

Uma solução Perl:

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

mas não funciona com

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

ou

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

testado com perl v5.14.2 e v5.18.2


O primeiro e o terceiro não fazem nada, o segundo gera qualquer linha de 10 ou mais caracteres, com não mais de 2 espaços consecutivos. pastebin.com/eEDcy02D
manatwork

provavelmente é a versão perl. testado com v5.14.2 e v5.18.2

Eu tentei com a v5.14.1 no Linux e a v5.14.2 no Cygwin. Ambos se comportaram como na amostra de pasta que vinculei anteriormente.
manatwork 23/02

a primeira linha funciona para mim com as versões notadas do perl. os dois últimos devem funcionar, porque são iguais, mas não funcionaram. Perlre note frequentemente que algumas expressões gananciosas são altamente experimentais.

Teste novamente com as atualizações mais recentes. Somente o segundo sai corretamente. (No entanto, a palavra deve ser sozinho em uma linha, enquanto a questão é de cerca de palavras correspondentes, não linhas inteiras.)
manatwork
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.