Meta regex golf


29

No espírito deste xkcd

insira a descrição do link aqui

Escreva um programa que reproduza regex golf com pares arbitrários de listas. O programa deve pelo menos tentar encurtar a regex, um programa que apenas produza /^(item1|item2|item3|item4)$/ou similar não é permitido.

A pontuação é baseada na capacidade de gerar o menor regex. As listas de testes são de candidatos presidenciais dos EUA bem-sucedidos e sem êxito, encontrados aqui (obrigado @ Peter). Obviamente, o programa deve funcionar para todas as listas disjuntas, portanto, basta retornar uma resposta para o presidente que não conta.


3
/^item1|atem2|item3|item4$/provavelmente tem precedência não intencional (a string deve começar item1, conter atem2, conter item3ou terminar com item4).
Konrad Borowski

7
Esse seria um desafio mais interessante se tivesse um sistema de pontuação baseado principalmente no tamanho das expressões regulares geradas.
Peter Taylor

11
No espírito do texto do título do XKCD, candidatos presidenciais dos EUA com e sem êxito . (NB: eu fiz essa lista manualmente seguindo a Wikipedia , então pode haver pequenos erros; removi da lista de perdedores todos os sobrenomes que correspondem a um vencedor, porque diferenciar as listas é impossível, mas, de outra forma, não deduplicei deliberadamente) .
Peter Taylor

4
Pergunto-me se Randall Munroe é um melhor escritor de desafios code-golfe do que nós ...
Johannes Kuhn

6
Gostaria de saber se Randall Munroe vai rejeitar esta questão.
usar o seguinte comando

Respostas:


8

Perl (111 110 122 caracteres)

use Regexp::Assemble;@ARGV=shift;my$r=new Regexp::Assemble;chomp,add$r "^\Q$_\E\$"while<>;$_=as_string$r;s/\(\?:/(/g;print

Isso usa o módulo CPAN chamado Regexp::Assemblepara otimizar as expressões regulares. Porque qual é a melhor linguagem para expressões regulares do que Perl.

Além disso, versão legível, apenas por diversão (feita com a ajuda de -MO=Deparse).

use Regexp::Assemble;
my $r = Regexp::Assemble->new;
while (<>) {
    chomp($_);
    $r->add("^\Q$_\E\$");
}
$_ = $r->as_string;
# Replace wasteful (?:, even if it's technically correct.
s/\(\?:/(/g;
print $_;

Saída de amostra (fiz CTRL-D depois item4).

$ perl assemble.pl
item1
atem2
item3
item4
^(item[134]|atem2)$

Além disso, como bônus, estou escrevendo o regex para cada palavra da pergunta.

^(a((ttemp)?t|llowed\.|rbitrary)?|\/\^item1\|atem2\|item3\|item4\$\/|s(ho(rt,|uld)|imilar)|p((air|lay)s|rogram)|(Writ|mak|Th)e|l(ists\.|east)|o([fr]|utputs)|t(h(at|e)|o)|(jus|no)t|regex|golf|with|is)$

Além disso, lista de presidentes (262 bytes).

^(((J(effer|ack|ohn)s|W(ashingt|ils)|Nix)o|Van Bure|Lincol)n|C(l(eveland|inton)|oolidge|arter)|H(a(r(rison|ding)|yes)|oover)|M(cKinley|adison|onroe)|T(a(ylor|ft)|ruman)|R(oosevelt|eagan)|G(arfield|rant)|Bu(chanan|sh)|P(ierce|olk)|Eisenhower|Kennedy|Adams|Obama)$

Parece ler stdin para uma lista e forçar a outra a ficar vazia. Certamente não é isso que a pergunta está pedindo?
Peter Taylor

11
@ PeterTaylor: Bem, não é que a segunda lista seja de alguma importância. A menos que a segunda lista tenha duplicatas da primeira lista, o regexp é válido. Seria bom ter uma regexp mais curta, mas sou meio preguiçosa.
precisa saber é o seguinte

Na IMO, você deve pelo menos ter uma maneira de tomá-lo como entrada, mesmo que você o descarte.
Peter Taylor

@ PeterTaylor: Se você diz. Meu programa agora usa dois argumentos, um deles sendo a primeira lista.
21978 Konrad Borowski

4
Isso é legal; mas produz expressões desnecessariamente longas, pois cria exclusão (para qualquer outra lista) ao corresponder a todas as palavras completas possíveis . O que não é exatamente o mesmo espírito que o golfe original.
Nicole

4

Não é a minha solução (obviamente não sou Peter Norvig!), Mas aqui está uma solução da pergunta (ligeiramente modificada), cortesia dele: http://nbviewer.ipython.org/url/norvig.com/ipython/xkcd1313.ipynb

o programa que ele dá é o seguinte (seu trabalho, não o meu):

def findregex(winners, losers):
    "Find a regex that matches all winners but no losers (sets of strings)."
    # Make a pool of candidate components, then pick from them to cover winners.
    # On each iteration, add the best component to 'cover'; finally disjoin them together.
    pool = candidate_components(winners, losers)
    cover = []
    while winners:
        best = max(pool, key=lambda c: 3*len(matches(c, winners)) - len(c))
        cover.append(best)
        pool.remove(best)
        winners = winners - matches(best, winners)
    return '|'.join(cover)

def candidate_components(winners, losers):
    "Return components, c, that match at least one winner, w, but no loser."
    parts = set(mappend(dotify, mappend(subparts, winners)))
    wholes = {'^'+winner+'$' for winner in winners}
    return wholes | {p for p in parts if not matches(p, losers)}

def mappend(function, *sequences):
    """Map the function over the arguments.  Each result should be a sequence. 
    Append all the results together into one big list."""
    results = map(function, *sequences)
    return [item for result in results for item in result]

def subparts(word):
    "Return a set of subparts of word, consecutive characters up to length 4, plus the whole word."
    return set(word[i:i+n] for i in range(len(word)) for n in (1, 2, 3, 4)) 

def dotify(part):
    "Return all ways to replace a subset of chars in part with '.'."
    if part == '':
        return {''}  
    else:
        return {c+rest for rest in dotify(part[1:]) for c in ('.', part[0]) }

def matches(regex, strings):
    "Return a set of all the strings that are matched by regex."
    return {s for s in strings if re.search(regex, s)}

answer = findregex(winners, losers)
answer
# 'a.a|i..n|j|li|a.t|a..i|bu|oo|n.e|ay.|tr|rc|po|ls|oe|e.a'

onde vencedores e perdedores são as listas de vencedores e perdedores, respectivamente (ou duas listas, é claro), consulte o artigo para obter explicações detalhadas.


8
Embora o artigo vinculado seja interessante e eu goste de lê-lo, isso teria sido melhor publicado como um comentário sobre a pergunta e não como uma resposta, uma vez que não responde à pergunta colocada.
Gareth

11
Você está certo, pode ter sido melhor como comentário, eu o publiquei como resposta simplesmente porque responde perfeitamente à pergunta. Não copiei a solução, pois achei que seria falso e tentava tirar crédito pelo trabalho de outra pessoa, além de fornecer um programa que reproduz regex golf com 2 pares de listas, também fornece uma função de condicionamento físico e um código detalhado explicação, juntamente com o problema paralelo ao conjunto, que eu não havia considerado. Se você ainda achar que isso não é relevante, informe-me e vou excluir e postar como comentário.
Mike HR

11
Se você está preocupado em receber crédito pelo trabalho de outra pessoa, sinalize e peça um mod para fazer sua resposta "Community wiki".
Peter Taylor

11
@ Peter Taylor legal, eu não sabia que era o protocolo, feito.
Mike HR

2

Minha solução escrita em Fator :

USING:
    formatting fry
    grouping
    kernel
    math math.combinatorics math.ranges
    pcre
    sequences sets ;
IN: xkcd1313

: name-set ( str -- set )
    "\\s" split members ;

: winners ( -- set )
    "washington adams jefferson jefferson madison madison monroe
monroe adams jackson jackson vanburen harrison polk taylor pierce buchanan
lincoln lincoln grant grant hayes garfield cleveland harrison cleveland     mckinley
 mckinley roosevelt taft wilson wilson harding coolidge hoover roosevelt
roosevelt roosevelt roosevelt truman eisenhower eisenhower kennedy johnson     nixon
nixon carter reagan reagan bush clinton clinton bush bush obama obama" name-set ;

: losers ( -- set )
    "clinton jefferson adams pinckney pinckney clinton king adams
jackson adams clay vanburen vanburen clay cass scott fremont breckinridge
mcclellan seymour greeley tilden hancock blaine cleveland harrison bryan bryan
parker bryan roosevelt hughes cox davis smith hoover landon wilkie dewey dewey
stevenson stevenson nixon goldwater humphrey mcgovern ford carter mondale
dukakis bush dole gore kerry mccain romney" name-set winners diff
    { "fremont" } diff "fillmore" suffix ;

: matches ( seq regex -- seq' )
    '[ _ findall empty? not ] filter ;

: mconcat ( seq quot -- set )
    map concat members ; inline

: dotify ( str -- seq )
    { t f } over length selections [ [ CHAR: . rot ? ] "" 2map-as ] with map ;

: subparts ( str -- seq )
    1 4 [a,b] [ clump ] with mconcat ;

: candidate-components ( winners losers -- seq )
    [
        [ [ "^%s$" sprintf ] map ]
        [ [ subparts ] mconcat [ dotify ] mconcat ] bi append
    ] dip swap [ matches empty? ] with filter ;

: find-cover ( winners candidates -- cover )
    swap [ drop { } ] [
        2dup '[ _ over matches length 3 * swap length - ] supremum-by [
            [ dupd matches diff ] [ rot remove ] bi find-cover
        ] keep prefix
    ] if-empty ;

: find-regex ( winners losers -- regex )
    dupd candidate-components find-cover "|" join ;

: verify ( winners losers regex -- ? )
    swap over [
        dupd matches diff "Error: should match but did not: %s\n"
    ] [
        matches "Error: should not match but did: %s\n"
    ] 2bi* [
        dupd '[ ", " join _ printf ] unless-empty empty?
    ] 2bi@ and ;

: print-stats ( legend winners regex -- )
    dup length rot "|" join length over /
    "separating %s: '%s' (%d chars %.1f ratio)\n" printf ;

: (find-both) ( winners losers legend -- )
    -rot 2dup find-regex [ verify t assert= ] 3keep nip print-stats ;

: find-both ( winners losers -- )
    [ "1 from 2" (find-both) ] [ swap "2 from 1" (find-both) ] 2bi ;



IN: scratchpad winners losers find-both 
separating 1 from 2: 'a.a|a..i|j|li|a.t|i..n|bu|oo|ay.|n.e|ma|oe|po|rc|ls|l.v' (55 chars 4.8 ratio)
separating 2 from 1: 'go|e..y|br|cc|hu|do|k.e|.mo|o.d|s..t|ss|ti|oc|bl|pa|ox|av|st|du|om|cla|k..g' (75 chars 3.3 ratio)

É o mesmo algoritmo que o de Norvig. Se prejudicar a legibilidade é o objetivo, provavelmente você poderá jogar fora muitos outros personagens.


11
Para sua informação, está faltando um monte de perdedores na lista oficial (Burr, Jay, Badnarik, provavelmente outros que não estou vendo). Portanto, seus resultados estão incorretos; por exemplo, o primeiro regex não funciona, porque corresponde a Burr e Jay.
Elixenide

1

Meu código não é muito sofisticado e condensado, mas você pode verificá-lo em https://github.com/amitayd/regexp-golf-coffeescript/ (ou especificamente o algoritmo em src / regexpGolf.coffee).

É baseado no algoritmo de Peter Norvig, com duas melhorias:

  1. Crie peças para usar com conjuntos de caracteres (por exemplo, use [ab] z, [ac] z e [bc] z se partes válidas forem az, bz e cz).
  2. Permita a construção de "caminhos ótimos principais" de capas, e não apenas uma capa feita do melhor candidato a cada iteração.

(E também jogou uma aleatoriedade opcional)

Para os conjuntos de vencedores / perdedores neste questionário, encontrei um regex de 76 caracteres usando-o:

[Jisn]e..|[dcih]..o|[AaG].a|[sro].i|T[ar]|[PHx]o|V|[oy]e|lev|sh$|u.e|rte|nle

Mais alguns detalhes no meu post sobre como portar o solucionador para coffeescript .


2
Você poderia conter seu código na sua resposta? Caso contrário, não podemos ver o código sem clicar no link!
Wizzwizz4
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.