Regex: Dopasuj egalitarną serię

18

Wprowadzenie

Nie widzę tu wielu wyzwań wyrażeń regularnych, więc chciałbym zaoferować to zwodniczo proste, które można wykonać na wiele sposobów, używając wielu smaków wyrażeń regularnych. Mam nadzieję, że zapewni entuzjastom regex trochę radości z gry w golfa.

Wyzwanie

Wyzwanie polega na dopasowaniu tego, co bardzo luźno nazwałam serią „egalitarną”: serią jednakowej liczby różnych postaci. Najlepiej to opisuje przykłady.

Mecz:

aaabbbccc
xyz 
iillppddff
ggggggoooooollllllffffff
abc
banana

Nie pasuj:

aabc
xxxyyzzz
iilllpppddff
ggggggoooooollllllfff
aaaaaabbbccc
aaabbbc
abbaa
aabbbc

Uogólniając, chcemy dopasować temat formularza ( dla dowolnej listy znaków do , gdzie dla wszystkichc1)n(c2)n(c3)n...(ck)nc1ckci != ci+1i, k > 1, and n > 0.

Wyjaśnienia:

  • Dane wejściowe nie będą puste.

  • Znak może się powtórzyć później w ciągu (np. „Banan”)

  • k > 1, więc w łańcuchu zawsze będą znajdować się co najmniej 2 różne znaki.

  • Możesz założyć, że tylko znaki ASCII będą przekazywane jako dane wejściowe, a żaden znak nie będzie zakończeniem linii.

Zasady

(Dziękuję Martinowi Enderowi za ten doskonale określony blok zasad)

Twoja odpowiedź powinna składać się z jednego wyrażenia regularnego, bez dodatkowego kodu (z wyjątkiem, opcjonalnie, listy modyfikatorów wyrażeń regularnych wymaganych do działania rozwiązania). Nie wolno używać funkcji smaku regularnego swojego języka, które pozwalają na wywołanie kodu w języku hostingowym (np. eModyfikator Perla ).

Możesz użyć dowolnego smaku wyrażenia regularnego, który istniał przed tym wyzwaniem, ale określ go.

Nie zakładaj, że wyrażenie regularne jest zakotwiczone niejawnie, np. Jeśli używasz języka Python, załóż, że wyrażenie regularne jest używane z re.search, a nie z re.match. Twoje wyrażenie regularne musi pasować do całego łańcucha dla prawidłowych łańcuchów egalitarnych i nie może dawać żadnych dopasowań dla łańcuchów nieprawidłowych. Możesz użyć dowolnej liczby grup przechwytywania.

Możesz założyć, że wejście będzie zawsze ciągiem dwóch lub więcej znaków ASCII niezawierających żadnych terminatorów linii.

To jest wyrażenie regularne, więc wygrywa najkrótszy wyrażenie regularne w bajtach. Jeśli twój język wymaga (zwykle /.../) ograniczników do wyrażenia regularnego, nie licz samych ograniczników. Jeśli Twoje rozwiązanie wymaga modyfikatorów, dodaj jeden bajt na modyfikator.

Kryteria

To dobry golf, więc zapomnij o wydajności i po prostu staraj się, aby regex był jak najmniejszy.

Podaj użyty smak wyrażenia regularnego i, jeśli to możliwe, dołącz link pokazujący demonstrację online wyrażenia w akcji.

jaytea
źródło
Czy to konkretnie golf regex? Prawdopodobnie powinieneś to wyjaśnić, wraz z regułami. Większość wyzwań na tej stronie to gry w różne języki programowania.
LyricLy,
@LyricLy Dzięki za radę! Tak, chciałbym, aby było to wyłącznie wyrażenie regularne, tj. pojedyncze wyrażenie regularne o smaku wyrażenia regularnego wybranego przez zgłaszającego. Czy są jakieś inne zasady, o których powinienem pamiętać?
jaytea
Nie rozumiem twojej definicji „egalitarnej”, takiej jak bananaegalitarna.
msh210,
@ msh210 Kiedy wymyśliłem termin „egalitarian”, aby opisać serię, nie pomyślałem, że pozwolę na powtórzenie znaków w późniejszej serii (np. w „banana” lub „aaabbbcccaaa” itp.) . Chciałem tylko, aby termin reprezentował ideę, że każda część powtarzających się znaków ma ten sam rozmiar. Ponieważ „banan” nie ma powtarzających się znaków, ta definicja jest prawdziwa.
jaytea

Odpowiedzi:

11

Smak .NET, 48 bajtów

^(.)\1*((?<=(\5())*(.))(.)(?<-4>\6)*(?!\4|\6))+$

Wypróbuj online! (za pomocą Retina )

Okazuje się, że nie negowanie logiki jest jednak prostsze. Robię z tego osobną odpowiedź, ponieważ te dwa podejścia są zupełnie inne.

Wyjaśnienie

^            # Anchor the match to the beginning of the string.
(.)\1*       # Match the first run of identical characters. In principle, 
             # it's possible that this matches only half, a quarter, an 
             # eighth etc of of the first run, but that won't affect the 
             # result of the match (in other words, if the match fails with 
             # matching this as the entire first run, then backtracking into
             # only matching half of it won't cause the rest of the regex to
             # match either).
(            # Match this part one or more times. Each instance matches one
             # run of identical letters.
  (?<=       #   We start with a lookbehind to record the length
             #   of the preceding run. Remember that the lookbehind
             #   should be read from the bottom up (and so should
             #   my comments).
    (\5())*  #     And then we match all of its adjacent copies, pushing an
             #     empty capture onto stack 4 each time. That means at the
             #     end of the lookbehind, we will have n-1 captures stack 4, 
             #     where n is the length of the preceding run. Due to the 
             #     atomic nature of lookbehinds, we don't have to worry 
             #     about backtracking matching less than n-1 copies here.
    (.)      #     We capture the character that makes up the preceding
             #     run in group 5.
  )
  (.)        #   Capture the character that makes up the next run in group 6.
  (?<-4>\6)* #   Match copies of that character while depleting stack 4.
             #   If the runs are the same length that means we need to be
             #   able to get to the end of the run at the same time we
             #   empty stack 4 completely.
  (?!\4|\6)  #   This lookahead ensures that. If stack 4 is not empty yet,
             #   \4 will match, because the captures are all empty, so the
             #   the backreference can't fail. If the stack is empty though,
             #   then the backreference will always fail. Similarly, if we
             #   are not at the end of the run yet, then \6 will match 
             #   another copy of the run. So we ensure that neither \4 nor
             #   \6 are possible at this position to assert that this run
             #   has the same length das the previous one.
)+
$            # Finally, we make sure that we can cover the entire string
             # by going through runs of identical lengths like this.
Martin Ender
źródło
Uwielbiam to, że przeszedłeś między dwiema metodami! Pomyślałem również, że negatywne podejście powinno być krótsze, dopóki go nie spróbowałem i nie poczułem, że jest o wiele bardziej niezręczne (chociaż wydaje się, że powinno być prostsze). Mam 48b w PCRE i 49b w Perlu z zupełnie inną metodą, a przy twojej trzeciej metodzie w .NET mniej więcej tego samego rozmiaru, powiedziałbym, że to całkiem fajne wyzwanie wyrażenia regularnego: D
jaytea
@jaytea Chciałbym je zobaczyć. Jeśli nikt nie wymyśli niczego przez tydzień, mam nadzieję, że sam je opublikujesz. :) I tak, zgadza się, fajnie, że podejścia są tak bliskie pod względem liczby bajtów.
Martin Ender,
Po prostu może! Ponadto Perl One został zagrany w golfa do 46b;)
jaytea
Pomyślałem więc, że możesz chcieć je teraz zobaczyć! Oto 48b w PCRE: ((^.|\2(?=.*\4\3)|\4(?!\3))(?=\2*+((.)\3?)))+\3$eksperymentowałem \3*zamiast (?!\3)zrobić 45b, ale to nie działa na „aabbbc” :( Wersja Perla jest łatwiejsza do zrozumienia, a teraz do 45b: ^((?=(.)\2*(.))(?=(\2(?4)?\3)(?!\3))\2+)+\3+$- powód, który nazywam Perl, mimo że to wydaje się być poprawnym PCRE polega na tym, że PCRE uważa, że (\2(?4)?\3)może się powtarzać w nieskończoność, podczas gdy Perl jest trochę mądrzejszy / wybaczający!
jaytea
@jaytea Ah, to są naprawdę fajne rozwiązania. Powinieneś naprawdę opublikować je w osobnej odpowiedzi. :)
Martin Ender
9

Smak .NET, 54 bajty

^(?!.*(?<=(\2)*(.))(?!\2)(?>(.)(?<-1>\3)*)(?(1)|\3)).+

Wypróbuj online!(za pomocą Retina )

Jestem prawie pewien, że jest to nieoptymalne, ale jest to najlepsze, co mogę teraz wymyślić dla równoważenia grup. Mam jedną alternatywę przy tej samej liczbie bajtów, która jest w większości taka sama:

^(?!.*(?<=(\3())*(.))(?!\3)(?>(.)(?<-2>\4)*)(\2|\4)).+

Wyjaśnienie

Główną ideą jest odwrócenie problemu, dopasowanie nie-egalitarnych łańcuchów i postawienie całej sprawy w negatywnym spojrzeniu, aby zanegować wynik. Korzyścią jest to, że nie musimy śledzić liczby n w całym łańcuchu (ponieważ ze względu na naturę grup równoważących zwykle zużywasz n podczas sprawdzania), aby sprawdzić, czy wszystkie przebiegi są równej długości. Zamiast tego szukamy tylko jednej pary sąsiednich przebiegów, które nie mają tej samej długości. W ten sposób muszę użyć n tylko raz.

Oto podział wyrażenia regularnego.

^(?!.*         # This negative lookahead means that we will match
               # all strings where the pattern inside the lookahead
               # would fail if it were used as a regex on its own.
               # Due to the .* that inner regex can match from any
               # position inside the string. The particular position
               # we're looking for is between two runs (and this
               # will be ensured later).

  (?<=         #   We start with a lookbehind to record the length
               #   of the preceding run. Remember that the lookbehind
               #   should be read from the bottom up (and so should
               #   my comments).
    (\2)*      #     And then we match all of its adjacent copies, capturing
               #     them separately in group 1. That means at the
               #     end of the lookbehind, we will have n-1 captures
               #     on stack 1, where n is the length of the preceding
               #     run. Due to the atomic nature of lookbehinds, we
               #     don't have to worry about backtracking matching
               #     less than n-1 copies here.
    (.)        #     We capture the character that makes up the preceding
               #     run in group 2.
  )
  (?!\2)       #   Make sure the next character isn't the same as the one
               #   we used for the preceding run. This ensures we're at a
               #   boundary between runs.
  (?>          #   Match the next stuff with an atomic group to avoid
               #   backtracking.
    (.)        #     Capture the character that makes up the next run
               #     in group 3.
    (?<-1>\3)* #     Match as many of these characters as possible while
               #     depleting the captures on stack 1.
  )
               #   Due to the atomic group, there are three two possible
               #   situations that cause the previous quantifier to stopp
               #   matching. 
               #   Either the run has ended, or stack 1 has been depleted.
               #   If both of those are true, the runs are the same length,
               #   and we don't actually want a match here. But if the runs
               #   are of different lengths than either the run ended but
               #   the stack isn't empty yet, or the stack was depleted but
               #   the run hasn't ended yet.
  (?(1)|\3)    #   This conditional matches these last two cases. If there's
               #   still a capture on stack 1, we don't match anything,
               #   because we know this run was shorter than the previous
               #   one. But if stack 1, we want to match another copy of 
               #   the character in this run to ensure that this run is 
               #   longer than the previous one.
)
.+             # Finally we just match the entire string to comply with the
               # challenge spec.
Martin Ender
źródło
Próbowałem zrobić to nie on: banana, aba, bbbaaannnaaannnaaa, bbbaaannnaaannnaaaaaa, The Nineteenth Byte, 11, 110, ^(?!.*(?<=(\2)*(.))(?!\2)(?>(.)(?<-1>\3)*)(?(1)|\3)).+, bababa. To ja zawiodłem. :( +1
Erik the Outgolfer
1
Ten moment, kiedy skończysz wyjaśnienie, a potem zorientujesz się, że możesz zaoszczędzić 1 bajt, stosując dokładnie odwrotne podejście ... Myślę, że za chwilę udzielę innej odpowiedzi ...: |
Martin Ender,
@MartinEnder ... A potem zdaj sobie sprawę, że możesz zagrać w golfa o 2 bajty haha: P
Mr. Xcoder,
@ Mr.Xcoder Musiałby mieć teraz 7 bajtów, więc mam nadzieję, że jestem bezpieczny. ;)
Martin Ender,