Regex для всіх 10 літерних слів, з унікальними літерами


23

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

Поки що у мене є

grep --colour -Eow '(\w{10})'

Яка сама перша частина питання. Як би я пішов на перевірку «унікальності»? У мене справді немає поняття, крім того, що мені потрібно використовувати зворотні посилання.


1
Це треба зробити за допомогою регулярного вираження?
Hauke ​​Laging

Я практикую регекс, тому бажано так :)
Ділан Мейус

3
Я не вірю, що ви можете це зробити за допомогою регулярного вираження в стилі інформатики: те, що ви хочете, вимагає «пам’яті» того, що є попередніми відповідними символами, а регулярні вирази просто цього не мають. Це означає, що ви можете зробити це за допомогою зворотних посилань і нерегулярних виразів, які можуть відповідати стилі PCRE.
Брюс Едігер

3
@BruceEdiger, поки в мові є кінцева кількість символів (26) та літер у рядку (10), це цілком можливо зробити. Її просто багато штатів, але нічого, що зробило б це не звичайною мовою.

1
Ви маєте на увазі "Усі англійські слова ..."? Ви маєте на увазі включити те, що написано дефісами та апострофами чи ні (свекруха, ні)? Ви маєте на увазі такі слова, як кафе, наївність, фасад?
hippietrail

Відповіді:


41
grep -Eow '\w{10}' | grep -v '\(.\).*\1'

виключає слова, які мають два однакових символи.

grep -Eow '\w{10}' | grep -v '\(.\)\1'

виключає ті, що мають повторювані символи.

POSIXly:

tr -cs '[:alnum:]_' '[\n*]' |
   grep -xE '.{10}' |
   grep -v '\(.\).*\1'

trрозміщує слова у власному рядку, перетворюючи будь-яке sрівняння несловесних символів ( cзміщення буквено-цифрових та підкреслювальних знаків) у новий символ.

Або з одним grep:

tr -cs '[:alnum:]_' '[\n*]' |
   grep -ve '^.\{0,9\}$' -e '.\{11\}' -e '\(.\).*\1'

(виключайте рядки менше 10 та більше 10 символів та рядки із символом, що з’являються щонайменше двічі).

Тільки з одним grep(GNU grep з підтримкою PCRE або pcregrep):

grep -Po '\b(?:(\w)(?!\w*\1)){10}\b'

Тобто, границя слів ( \b), за якою слідує послідовність із 10 символів слова (за умови, що за кожним не йде послідовність символів слова та їх самих, використовуючи негативний оператор PCRE з випередженням (?!...)).

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

Зауважте, що (як мінімум, у моїй версії GNU grep)

grep -Pow '(?:(\w)(?!\w*\1)){10}'

Не працює, але

grep -Pow '(?:(\w)(?!\w*\2)){10}'

робить (як echo aa | grep -Pw '(.)\2'), що звучить як помилка.

Ви можете:

grep -Po '(*UCP)\b(?:(\w)(?!\w*\1)){10}\b'

якщо ви хочете \wабо \bрозглядаєте будь-яку букву як компонент слова, а не лише ASCII у не-ASCII-локалі.

Ще одна альтернатива:

grep -Po '\b(?!\w*(\w)\w*\1)\w{10}\b'

Це межа слова (така, за якою не слідує послідовність символів слова, один з яких повторюється) з 10 символами слова.

Речі, які, можливо, знаходяться в глибині розуму:

  • Порівняння залежно від регістру, тому, Babylonishнаприклад, було б зібрано, оскільки всі символи різні, навіть якщо є два Bs, один нижній і один верхній регістр (використовуйте, -iщоб змінити це).
  • для -w, \wі \b, слово це буква (ASCII ті тільки для GNU grep зараз , то [:alpha:]клас символів у вашій місцевості при використанні -Pі (*UCP)), десяткових цифр або підкреслення .
  • це означає, що c'est(два слова відповідно до французького визначення слова) або it's(одне слово згідно з деякими англійськими визначеннями слова) або rendez-vous(одне слово згідно з французьким визначенням слова) не вважаються одним словом.
  • Навіть при використанні (*UCP)символів, що поєднують Unicode, не вважаються компонентами слова, тому téléphone( $'t\u00e9le\u0301phone') вважається 10 символами, один з яких не альфа. défavorisé( $'d\u00e9favorise\u0301') буде збігатися, навіть якщо у нього є два, éтому що це 10 усіх різних символів альфа, за якими слід поєднувати гострий наголос (не-альфа, тому між словом eта його наголосом є межа слова ).

1
Дивовижно. \wне відповідає, -хоча.
Graeme

@Stephane Чи можете ви опублікувати коротке пояснення останніх двох виразів.
mkc

Іноді здається, що lookarounds - це рішення всіх речей, які раніше були неможливими з RE.
Бармар

1
@Barmar вони все ще неможливі за допомогою регулярних виразів. "Регулярне вираження" - це математична конструкція, яка явно дозволяє лише певні конструкції, а саме буквальні символи, класи символів та оператори '|', '(...)', '?', '+' Та '*'. Будь-який так званий "регулярний вираз", який використовує оператор, який не є одним із перерахованих, насправді не є регулярним виразом.
Жуль

1
@Jules Це unix.stackexchange.com, а не math.stackexchange.com. Математичні РЕ не мають значення в цьому контексті, ми говоримо про типи РЕ, які ви використовуєте з grep, PCRE тощо.
Barmar

12

Гаразд ... ось незграбний спосіб для п'яти символьної струни:

grep -P '^(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3)(.)(?!\1|\2|\3|\4).$'

Оскільки ви не можете поставити зворотну посилання в клас символів (наприклад [^\1|\2]), ви повинні використовувати негативний погляд вперед - (?!foo). Це функція PCRE, тому вам потрібен -Pкомутатор.

Візерунок для 10-символьного рядка буде набагато довшим, звичайно, але існує коротший метод, який використовує змінну довжину будь-якого відповідності ('. *') В lookahead:

grep -P '^(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!.*\4)(.)(?!.*\5).$'

Прочитавши просвічуючу відповідь Стефана Шазеласа, я зрозумів, що існує подібний простий зразок для цього, який можна використовувати за допомогою -vперемикача grep :

    (.).*\1

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

grep -v '\(.\).*\1'

буде працювати для визначення рядка будь-якої довжини з унікальними символами, тоді як:

grep -P '(.)(?!.*\1)'

не буде, оскільки він буде відповідати будь-якому суфіксу з унікальними символами (наприклад, abcabcзбіги через abcкінець, а aaaaчерез aкінець - отже, будь-який рядок). Це ускладнення, спричинене тим, що lookarounds має нульову ширину (вони нічого не споживають).


Молодці! Це буде працювати лише в поєднанні з тим, що в Q.
Graeme

1
Я вважаю, що ви можете спростити перший, якщо ваш регекс-движок дозволяє негативну (.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
підказку

@ChristopherCreutzig: Абсолютно приємний дзвінок. Я додаю, що це.
goldilocks

6

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

perl -nle 'MATCH:while(/\W(\w{10})\W/g){
             undef %seen;
             for(split//,$1){next MATCH if ++$seen{$_} > 1}
             print
           }' your_file

Зверніть увагу на додаткові \Wприв’язки, щоб переконатися, що збігаються лише слова довжиною рівно 10 символів.


Дякую, але мені хотілося б це як регулярний вираз oneliner :)
Ділан

4

Інші вважають, що це неможливо без різних розширень на певні системи регулярних виразів, які насправді не є регулярними. Однак, оскільки мова, яку ви хочете відповідати, є кінцевою, вона, очевидно, регулярна. Для 3 букв з 4-літерного алфавіту було б легко:

(abc|abd|acb|acd|bac|bad|bcd|bdc|cab|cad|cbd|cdb|dab|dac|dbc|dcb)

Очевидно, що це виходить з рук у поспіху з більшою кількістю літер та великих букв. :-)


Мені довелося підтримати це, бо це насправді відповідь, яка спрацювала б. Хоча це насправді може бути найменш ефективним способом, коли хтось написав регулярний вираз: P
Ділан Мейус

4

Варіант --perl-regexp(короткий -P) GNU grepвикористовує більш потужні регулярні вирази, які включають шаблони випередження. Наступна картина шукає для кожної літери, що ця літера не відображається в решті слова:

grep -Pow '((\w)(?!\w*\g{-1})){10}'

Однак поведінка під час виконання є досить поганою, оскільки \w*може мати майже нескінченну довжину. Це може бути обмежено \w{,8}, але це також перевіряє межі слова, що перевищує 10 літер. Тому наступний зразок спочатку перевіряє правильну довжину слова:

grep -Pow '(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}'

Як тестовий файл я використав великий ≈ 500 Мб файл:

  • Перша картина: ≈ 43 с
  • Пізніша картина: ≈ 15 с

Оновлення:

Я не зміг знайти суттєвих змін у поведінці під час виконання не жадібного оператора ( \w*?) або привласного оператора ( (...){10}+). Трохи швидше здається заміна варіанту -w:

grep -Po '\b(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}\b'

Оновлення grep від версії 2.13 до 2.18 було набагато ефективнішим. Тестовий файл зайняв лише ≈ 6 с.


Продуктивність буде багато залежати від характеру даних. Роблячи тести на моєму, я виявив, що використання не жадібних операторів ( \w{,8}?) допомагає для певного типу введення (хоча і не дуже суттєво). Приємно використовувати \g{-1}для роботи навколо греп-помилки GNU.
Stéphane Chazelas

@StephaneChazelas: Дякую за відгук. Я також спробував не жадібних і прихильних операторів і не знайшов суттєвих змін у поведінці під час виконання (версія 2.13). Версія 2.18 набагато швидша, і я міг побачити хоча б крихітний вдосконалення. Помилка GNU grep присутня в обох версіях. У будь-якому випадку я віддаю перевагу відносному посиланню \g{-1}, оскільки це робить шаблон більш незалежним від місця розташування. У такому вигляді його можна використовувати як частину більшого малюнка.
Хайко Обердік

0

Рішення Perl:

perl -lne 'print if (!/(.)(?=$1)/g && /^\w{10}$/)' file

але це не працює з

perl -lne 'print if (!/(.)(?=\1)/g && /^\w{10}$/)' file

або

perl -lne 'print if ( /(.)(?!$1)/g && /^\w{10}$/)' file

перевірено perl v5.14.2 та v5.18.2


1-й і 3-й не роблять нічого, 2-й виводить будь-який рядок з 10 і більше символів, не маючи більше 2 послідовних пробілів. pastebin.com/eEDcy02D
манатура

це, мабуть, версія perl. перевірено на v5.14.2 та v5.18.2

Я спробував їх з v5.14.1 на Linux та v5.14.2 на Cygwin. Обидва поводилися так, як у зразку пастбіну, який я пов’язував раніше.
манатура

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

Перевірено з останніми оновленнями. Лише 2-й виводить правильно. (Однак слово повинне бути одне в рядку, тоді як питання про відповідність слів, а не цілих рядків.)
manatwork
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.