Regex: соответствует эгалитарной серии


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 ).

Вы можете использовать любой аромат regex, который существовал до этого испытания, но, пожалуйста, укажите аромат.

Не думайте, что регулярное выражение неявно привязано, например, если вы используете Python, предположите, что ваше регулярное выражение используется с re.search, а не с re.match. Ваше регулярное выражение должно соответствовать всей строке для правильных эгалитарных строк и не давать совпадений для недопустимых строк. Вы можете использовать столько групп захвата, сколько пожелаете.

Вы можете предположить, что ввод всегда будет строкой из двух или более символов ASCII, не содержащих разделителей строки.

Это рег-гольф, поэтому выигрывает самое короткое в байтах. Если ваш язык требует разделителей (обычно /.../) для обозначения регулярных выражений, не считайте сами разделители. Если вашему решению требуются модификаторы, добавьте один байт на каждый модификатор.

критерии

Это хороший старомодный гольф, поэтому забудьте об эффективности и постарайтесь сделать ваше регулярное выражение как можно меньше.

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


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

@LyricLy Спасибо за совет! Да, я хотел бы, чтобы это было просто регулярное выражение, т.е. одно регулярное выражение в виде регулярного выражения по выбору отправителя. Есть ли другие правила, которые я должен учитывать?
Jaytea

Я не понимаю вашего определения «эгалитарного», такого, bananaкоторое эгалитарно.
msh210

@ msh210 Когда я придумал термин «равноправный» для описания серии, я не думал, что позволю повторять символы позже в серии (например, «банан» или «aaabbbcccaaa» и т. д.) , Я просто хотел, чтобы термин представлял идею, что каждый кусок повторяющихся символов имеет одинаковый размер. Поскольку у «банана» нет повторяющихся символов, это определение справедливо для него.
Jaytea

Ответы:


11

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

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

Попробуйте онлайн! (используя Retina )

Что ж, получается, что не отрицать логику все- таки проще. Я делаю это отдельным ответом, потому что два подхода совершенно разные.

объяснение

^            # 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 с совершенно другим методом, и с вашим третьим методом в .NET примерно такого же размера, я бы сказал, что это довольно крутая задача регулярного выражения: D
jaytea

@jaytea Я бы хотел увидеть их. Если в течение недели или около того никто ничего не придумал, я надеюсь, что вы отправите их сами. :) И да, согласился, приятно, что подходы так близки по количеству байтов.
Мартин Эндер

Я просто мог бы! Кроме того, Perl один был в гольф до 46b;)
Jaytea

Так что я подумал, что вы можете увидеть это сейчас! Вот 48b в PCRE: ((^.|\2(?=.*\4\3)|\4(?!\3))(?=\2*+((.)\3?)))+\3$я экспериментировал \3*вместо того, (?!\3)чтобы сделать его 45b, но это не сработало на «aabbbc» :( Версия 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)).+

Попробуйте онлайн! (используя Retina )

Я почти уверен, что это неоптимально, но это лучшее, что я придумаю для балансировки групп прямо сейчас. У меня есть одна альтернатива с тем же количеством байтов, которое в основном такое же:

^(?!.*(?<=(\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
Эрик Outgolfer

1
В тот момент, когда вы закончите свое объяснение, а затем поймете, что вы можете сэкономить 1 байт, используя совершенно противоположный подход ... Я думаю, я сделаю еще один ответ немного ...: |
Мартин Эндер

@MartinEnder ... А потом поймете, что вы можете
Mr. Xcoder

@ Mr.Xcoder Теперь должно быть 7 байт, поэтому я надеюсь, что я в безопасности. ;)
Мартин Эндер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.