Реджекс: відповідь егалітарному ряду


18

Вступ

Я не бачу тут багатьох проблем з регулярними виразками, тому я хотів би запропонувати цей оманливо простий, який можна зробити різними способами, використовуючи ряд ароматів регулярних виразів. Я сподіваюся, що це забезпечує любителів регулярного генексу весело провести час у гольф.

Виклик

Завдання полягає в тому, щоб відповідати тому, що я дуже вільно назвав "егалітарною" серією: серією рівної кількості різних персонажів. Це найкраще описано на прикладах.

Матч:

aaabbbccc
xyz 
iillppddff
ggggggoooooollllllffffff
abc
banana

Не відповідають:

aabc
xxxyyzzz
iilllpppddff
ggggggoooooollllllfff
aaaaaabbbccc
aaabbbc
abbaa
aabbbc

Узагальнювати, ми хочемо , щоб відповідати темі форми ( для будь-якого списку символів в , де для всіхc1)n(c2)n(c3)n...(ck)nc1ckci != ci+1i, k > 1, and n > 0.

Роз'яснення:

  • Введення не буде порожнім.

  • Символ може повторитися пізніше в рядку (наприклад, "банан")

  • k > 1, тож у рядку завжди буде щонайменше 2 різних символи.

  • Ви можете припустити, що лише символи ASCII будуть передані як вхідні дані, а жоден символ не буде лінійним термінатором.

Правила

(Дякую Мартіну Ендеру за цей чудово викладений блок правил)

Ваша відповідь повинна складатися з одного регулярного вираження без будь-якого додаткового коду (крім, необов'язково, списку модифікаторів регулярних виразів, необхідних для роботи вашого рішення). Ви не повинні використовувати функції смаку регулярних виразів вашої мови, які дозволяють викликати код на мові хостингу (наприклад, eмодифікатор Perl ).

Ви можете використовувати будь-який аромат регексу, який існував до цього виклику, але, будь ласка, вкажіть аромат.

Не вважайте, що регулярний вирівнювання закріплено неявно, наприклад, якщо ви використовуєте Python, припустіть, що ваш регулярний вираз використовується з re.search, а не з re.match. Ваш регулярний вираз повинен відповідати всій рядку для дійсних егалітарних рядків і не відповідати недійсним рядкам. Ви можете використовувати стільки груп захоплення, скільки бажаєте.

Ви можете припустити, що вхід завжди буде рядком з двох або більше символів ASCII, що не містять жодних рядкових термінаторів.

Це гольф-реджекс, тому виграє найкоротший регулярний вираз у байтах. Якщо ваша мова вимагає роздільників (як правило /.../) для позначення регулярних виразів, не рахуйте самих роздільників. Якщо для вашого рішення потрібні модифікатори, додайте один байт на модифікатор.

Критерії

Це хороший гольф, так що забудьте про ефективність і просто намагайтеся зробити свій регекс якомога меншим.

Будь ласка, зазначте, який аромат регексу ви використовували, і, якщо можливо, включіть посилання, що показує онлайн-демонстрацію вашого вираження в дії.


Це конкретно регекс-гольф? Вам, мабуть, слід уточнити це, поряд із правилами для нього. Більшість проблем на цьому сайті - це гольфи різних мов програмування.
LyricLy

@LyricLy Дякую за пораду! Так, я хотів би, щоб це був чисто виразний, тобто. єдиний регулярний вираз у відтінку регулярного вибору на вибір подаючого. Чи є якісь правила, про які я повинен пам’ятати?
jaytea

Я не розумію вашого визначення поняття "егалітарій", такого, яке bananaє егалітарним.
msh210

@ msh210 Коли я придумав термін "егалітарій" для опису серіалу, я не вважав, що дозволять повторювати персонажів пізніше в серіалі (наприклад, "банан" або "aaabbbcccaaa" тощо) . Я просто хотів, щоб термін представляв ідею, що кожен шматок повторюваних персонажів має однаковий розмір. Оскільки "банан" не має повторних символів, це визначення справедливо для нього.
jaytea

Відповіді:


11

.NET аромат, 48 байт

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

Спробуйте в Інтернеті! (використовуючи сітківку )

Ну, виходить, що НЕ заперечуючи логіку простіше в кінці кінців. Я роблю це окремою відповіддю, тому що два підходи абсолютно різні.

Пояснення

^            # 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.

Мені подобається, що ти бачив між двома методами! Я також подумав, що негативний підхід повинен був бути коротшим, поки я насправді не спробував його, і вважав його набагато незграбнішим (навіть не дивлячись на те, що він повинен бути простішим). У мене 48b в PCRE, і 49b в Perl зовсім іншим методом, і при вашому 3-му методі в .NET приблизно однакового розміру, я б сказав, що це досить круте завдання для регулярних
виразів

@jaytea Я хотів би їх побачити. Якщо за тиждень ніхто нічого не придумає, я сподіваюся, що ви опублікуєте їх самі. :) І так погодилися, приємно, що підходи настільки близькі за кількістю байтів.
Мартін Ендер

Я просто можу! Крім того, Perl один був зіграний у гольф до 46b;)
jaytea

Тому я подумав, що ви можете побачити їх зараз! Ось 48b у PCRE: ((^.|\2(?=.*\4\3)|\4(?!\3))(?=\2*+((.)\3?)))+\3$я експериментував \3*замість того, (?!\3)щоб зробити це 45b, але це не вдається на "aabbcc" :( Версію Perl зрозуміти простіше, і зараз до 45b: ^((?=(.)\2*(.))(?=(\2(?4)?\3)(?!\3))\2+)+\3+$- тому я називаю це Perl, навіть якщо це Очевидно, що PCRE є дійсним у тому, що PCRE думає, що (\2(?4)?\3)може повторюватися на невизначений час, тоді як Perl трохи розумніший / прощає!
jaytea

@jaytea Ах, це справді акуратні рішення. Справді слід опублікувати їх окремою відповіддю. :)
Мартін Ендер

9

.NET аромат, 54 байти

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

Спробуйте в Інтернеті! (використовуючи сітківку )

Я майже впевнений, що це недостатньо оптимально, але зараз я найкраще підходжу для балансування груп. У мене одна і та ж кількість байтів, яка в основному однакова:

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

Пояснення

Основна ідея - перевернути проблему, узгодити неегалітарні рядки і поставити всю річ у негативну позицію, щоб зняти результат. Перевага полягає в тому, що нам не доводиться відслідковувати n протягом усього рядка (тому що в силу характеру балансуючих груп ви зазвичай споживаєте n під час перевірки), щоб перевірити, чи всі запуски мають однакову довжину. Натомість ми просто шукаємо одну пару сусідніх прогонів, які не мають однакової довжини. Таким чином, мені потрібно тільки використовувати п раз.

Ось розбиття регулярного вираження.

^(?!.*         # 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.

Я намагався зробити це не в змозі на: banana, aba, bbbaaannnaaannnaaa, bbbaaannnaaannnaaaaaa, The Nineteenth Byte, 11, 110, ^(?!.*(?<=(\2)*(.))(?!\2)(?>(.)(?<-1>\3)*)(?(1)|\3)).+, bababa. Це я не зміг. :( +1
Erik the Outgolfer

1
У той момент, коли ви закінчите своє пояснення, а потім зрозумієте, що ви можете зберегти 1 байт, використовуючи прямо протилежний підхід ... Я думаю, я ще трохи відповім.
Мартін Ендер

@MartinEnder ... І тоді зрозумій, що ти можеш пограти у цей байт на 2 байти ха-ха: P
Містер Xcoder

@ Mr.Xcoder Зараз повинно бути 7 байт, тож сподіваюся, що я в безпеці. ;)
Мартін Ендер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.