Meta regex golf


29

Nello spirito di questo xkcd

inserisci qui la descrizione del link

Scrivi un programma che gioca a regex golf con coppie arbitrarie di liste. Il programma dovrebbe almeno tentare di accorciare il regex, un programma che produce solo output /^(item1|item2|item3|item4)$/o simili non è permesso.

Il punteggio si basa sulla capacità di generare la regex più breve. Le liste dei test sono quelle dei candidati presidenziali statunitensi di successo e senza successo, disponibili qui (grazie a @Peter). Ovviamente, il programma deve funzionare per tutte le liste disgiunte, quindi semplicemente non è possibile restituire una risposta per il presidente.


3
/^item1|atem2|item3|item4$/probabilmente ha una precedenza non intenzionale (la stringa deve iniziare item1, contenere atem2, contenere item3o terminare con item4).
Konrad Borowski il

7
Questa sarebbe una sfida più interessante se avesse un sistema di punteggio basato principalmente sulla dimensione delle regex generate.
Peter Taylor,

1
Nello spirito del testo del titolo XKCD, candidati presidenziali statunitensi di successo e senza successo . (NB Ho fatto questo elenco a mano seguendo Wikipedia , quindi potrebbero esserci piccoli errori; ho rimosso dall'elenco dei perdenti tutti i cognomi che corrispondono a un vincitore, perché altrimenti è impossibile distinguere gli elenchi, ma deliberatamente non ho deduplicato altrimenti) .
Peter Taylor,

4
Mi chiedo se Randall Munroe sia uno scrittore migliore delle sfide del code-golf di quanto siamo ...
Johannes Kuhn,

6
Mi chiedo se Randall Munroe lancerà questa domanda.
stand dal

Risposte:


8

Perl (111 110 122 caratteri)

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

Questo utilizza il modulo CPAN chiamato Regexp::Assembleper ottimizzare le espressioni regolari. Perché qual è la lingua migliore per le espressioni regolari allora Perl.

Inoltre, versione leggibile, solo per divertimento (realizzata con l'aiuto di -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 $_;

Esempio di output (dopo ho fatto CTRL-D item4).

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

Inoltre, come bonus, sto scrivendo la regex per ogni parola nella domanda.

^(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)$

Inoltre, elenco dei presidenti (262 byte).

^(((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)$

Questo sembra leggere lo stdin per un elenco e forzare l'altro a essere vuoto. Sicuramente non è questa la domanda?
Peter Taylor,

1
@PeterTaylor: Beh, non è che la seconda lista abbia importanza. A meno che il secondo elenco non presenti duplicati del primo elenco, regexp è valido. Sarebbe bello avere una regexp più breve, ma sono un po 'pigro.
Konrad Borowski il

IMO dovresti almeno avere un modo di prenderlo come input, anche se poi lo scarti.
Peter Taylor,

@PeterTaylor: se lo dici tu. Il mio programma ora accetta due argomenti, uno dei quali è il primo elenco.
Konrad Borowski il

4
Questo è bello; ma produce espressioni inutilmente lunghe poiché crea esclusione (per qualsiasi altro elenco) abbinando ogni possibile parola completa . Che non è esattamente lo stesso spirito del golf originale.
Nicole,

4

Non è la mia soluzione (ovviamente non sono Peter Norvig!), Ma ecco una soluzione della domanda (leggermente modificata) per gentile concessione di lui: http://nbviewer.ipython.org/url/norvig.com/ipython/xkcd1313.ipynb

il programma che dà è il seguente (il suo lavoro, non il mio):

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'

dove vincitori e vinti sono rispettivamente gli elenchi dei vincitori e dei perdenti (o qualsiasi 2 elenchi ovviamente) vedere l'articolo per spiegazioni dettagliate.


8
Mentre l'articolo collegato è interessante e mi è piaciuto leggerlo, questo sarebbe stato meglio pubblicato come commento sulla domanda anziché come risposta poiché non risponde alla domanda posta.
Gareth,

1
Hai ragione, avrebbe potuto essere migliore come commento, l'ho pubblicato come risposta semplicemente perché risponde perfettamente alla domanda. Non ho copiato la soluzione perché pensavo che sarebbe stato disonesto e cercare di prendersi il merito per il lavoro di qualcun altro, oltre a fornire un programma che gioca a regex golf con 2 coppie di liste, offre anche una funzione di fitness e un codice dettagliato spiegazione insieme al parallelo al problema di copertura del set che non avevo considerato. Se pensi ancora che non sia pertinente, fammi sapere, lo eliminerò e pubblicherò come commento.
Mike HR

1
Se sei preoccupato di prendersi il merito per il lavoro di qualcun altro, segnala e chiedi una mod per rendere la tua risposta "Community wiki".
Peter Taylor,

1
@PeterTaylor figo, non sapevo che fosse il protocollo, fatto.
Mike HR,

2

La mia soluzione scritta in Factor :

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)

È lo stesso algoritmo di Norvig. Se l'obiettivo è danneggiare la leggibilità, allora puoi probabilmente giocare a golf molti più personaggi.


1
Cordiali saluti, ti stai perdendo un sacco di perdenti dalla lista ufficiale (Burr, Jay, Badnarik, probabilmente altri che non vedo). Quindi, i tuoi risultati sono errati; per esempio, il primo regex non funziona, perché corrisponde a Burr e Jay.
elixenide,

1

Il mio codice non è molto simile a quello del golf e condensato, ma puoi controllarlo su https://github.com/amitayd/regexp-golf-coffeescript/ (o in particolare l'algoritmo su src / regexpGolf.coffee).

Si basa sull'algoritmo di Peter Norvig, con due miglioramenti:

  1. Creare parti da utilizzare con i set di caratteri (ovvero utilizzare [ab] z, [ac] z e [bc] z se le parti valide sono az, bz e cz).
  2. Consentire di costruire "percorsi ottimali" delle copertine e non solo una copertina del miglior candidato per ogni iterazione.

(E ha anche lanciato una casualità opzionale)

Per i set di vincitori / vinti in questo quiz ho trovato un regex di 76 caratteri che lo utilizzava:

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

Alcuni ulteriori dettagli nel mio post sul blog sul porting del solutore su coffeescript .


2
Potresti per favore contenere il tuo codice nella tua risposta? Altrimenti, non possiamo vedere il codice senza fare clic sul link!
wizzwizz4,
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.