Próbuję napisać wyrażenie regularne, które wyświetli wszystkie słowa o długości 10 znaków i żadna z liter nie będzie się powtarzać.
Do tej pory mam
grep --colour -Eow '(\w{10})'
Która jest pierwszą częścią pytania. Jak miałbym przejść do sprawdzania „wyjątkowości”? Naprawdę nie mam pojęcia, poza tym muszę użyć referencji.
grep
regular-expression
Dylan Meeus
źródło
źródło
Odpowiedzi:
nie obejmuje słów, które mają dwa identyczne znaki.
nie obejmuje tych, które mają powtarzające się postacie.
POSIXly:
tr
umieszcza słowa we własnej linii, konwertując dowolną srówność znaków niebędących wyrazami ( cuzupełnienie znaków alfanumerycznych i podkreślników) na znak nowej linii.Lub z jednym
grep
:(z wyłączeniem wierszy zawierających mniej niż 10 i więcej niż 10 znaków oraz wiersze o znaku pojawiającym się co najmniej dwa razy).
grep
Tylko jeden (GNU grep z obsługą PCRE lubpcregrep
):Oznacza to, że granica słowa (
\b
), po której następuje sekwencja 10 znaków słów (pod warunkiem, że po każdym nie następuje sekwencja znaków słowa i samych siebie, przy użyciu operatora PCRE o przeczącej przyszłości(?!...)
).Mamy szczęście, że tutaj działa, ponieważ niewiele silników wyrażeń regularnych działa z odwołaniami wstecznymi w powtarzających się częściach.
Zauważ, że (przynajmniej z moją wersją GNU grep)
Nie działa, ale
robi (as
echo aa | grep -Pw '(.)\2'
) co brzmi jak błąd.Może chcesz:
jeśli chcesz
\w
lub\b
rozważasz dowolną literę jako składnik słowa, a nie tylko ASCII w ustawieniach regionalnych innych niż ASCII.Inna alternatywa:
Jest to granica słów (taka, po której nie następuje ciąg znaków, z których jeden się powtarza), a następnie 10 znaków.
Rzeczy, które mogą mieć na myśli:
Babylonish
wielkość liter, więc na przykład pasują, ponieważ wszystkie znaki są różne, mimo że są dwie literyB
s, jedna mała i jedna duża (użyj,-i
aby to zmienić).-w
,\w
a\b
, słowo jest literą (ASCII tylko te, dla GNUgrep
teraz The[:alpha:]
klasa znaków w danym regionie czy korzystania-P
i(*UCP)
), cyfry dziesiętne lub podkreślenia .c'est
(dwa słowa zgodnie z francuską definicją słowa) lubit's
(jedno słowo zgodnie z niektórymi angielskimi definicjami słowa) lubrendez-vous
(jedno słowo zgodnie z francuską definicją słowa) nie są uważane za jedno słowo.(*UCP)
znaki łączące Unicode nie są uważane za składniki słowa, więctéléphone
($'t\u00e9le\u0301phone'
) jest uważane za 10 znaków, z których jeden nie jest alfa.défavorisé
($'d\u00e9favorise\u0301'
) byłby dopasowany, mimo że ma dwa,é
ponieważ to 10 różnych znaków alfanumerycznych, po których następuje łączący akcent ostry (inny niż alfa, więc granica między tyme
a jego akcentem jest ograniczona).źródło
\w
nie pasuje-
jednak.Okej ... oto niezręczny sposób na pięcioznakowy ciąg:
Ponieważ nie możesz umieścić referencji wstecz w klasie postaci (np.
[^\1|\2]
), Musisz zastosować przeczące spojrzenie -(?!foo)
. Jest to funkcja PCRE, więc potrzebujesz-P
przełącznika.Wzorzec ciągu 10 znaków będzie oczywiście o wiele dłuższy, ale istnieje krótsza metoda wykorzystująca zmienną długość cokolwiek pasuje ('. *') W lookahead:
Po przeczytaniu pouczającej odpowiedzi Stephane'a Chazelasa, zdałem sobie sprawę, że istnieje podobny prosty wzór dla tego użytecznego za pomocą
-v
przełącznika grep :Ponieważ sprawdzanie odbywa się po jednym znaku na raz, zobaczysz, czy po danym znaku następuje zero lub więcej znaków (
.*
), a następnie dopasowanie dla odwołania wstecznego.-v
odwraca, drukując tylko rzeczy, które nie pasują do tego wzoru. To sprawia, że referencje wsteczne są bardziej użyteczne, ponieważ nie można ich zanegować za pomocą klasy znaków, a znacznie:będzie działać, aby zidentyfikować ciąg dowolnej długości za pomocą unikalnych znaków, podczas gdy:
nie będzie, ponieważ będzie pasować do dowolnego sufiksu unikatowymi znakami (np.
abcabc
pasuje ze względuabc
na koniec, aaaaa
ze względua
na koniec - stąd dowolny ciąg znaków). Jest to komplikacja spowodowana tym, że spojrzenia mają zerową szerokość (nic nie zużywają).źródło
(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
Jeśli nie musisz robić wszystkiego w wyrażeniu regularnym, zrobiłbym to w dwóch krokach: najpierw dopasuj wszystkie 10-literowe słowa, a następnie odfiltruj je pod kątem wyjątkowości. Najkrótszym sposobem, w jaki wiem, jak to zrobić, jest Perl:
Zwróć uwagę na dodatkowe
\W
kotwice, aby zapewnić dopasowanie tylko słów o długości dokładnie 10 znaków.źródło
Inni sugerują, że nie jest to możliwe bez różnych rozszerzeń niektórych systemów wyrażeń regularnych, które w rzeczywistości nie są regularne. Ponieważ jednak język, który chcesz dopasować, jest skończony, jest on wyraźnie regularny. W przypadku 3 liter z 4-literowego alfabetu byłoby to łatwe:
Oczywiście wymyka się to w pośpiechu z większą ilością liter i większych alfabetów. :-)
źródło
Opcja
--perl-regexp
(krótka-P
) GNUgrep
używa bardziej wydajnych wyrażeń regularnych, które zawierają wzorce wybiegające w przyszłość. Poniższy wzór wyszukuje każdą literę, której ta litera nie pojawia się w pozostałej części słowa:Jednak zachowanie w czasie wykonywania jest dość złe, ponieważ
\w*
może mieć prawie nieskończoną długość. Można go ograniczyć do\w{,8}
, ale to także sprawdza poza limitem słów 10 liter. Dlatego następujący wzorzec najpierw sprawdza poprawną długość słowa:Jako plik testowy wykorzystałem duży plik ≈ 500 MB:
Aktualizacja:
Nie mogłem znaleźć znaczącej zmiany w zachowaniu w czasie wykonywania dla niewdzięcznego operatora (
\w*?
) lub operatora dzierżawczego ((...){10}+
). Trochę szybciej wydaje się zastąpienie opcji-w
:Aktualizacja grep z wersji 2.13 do 2.18 była znacznie bardziej skuteczna. Plik testowy zajął tylko ≈ 6 sekund.
źródło
\w{,8}?
) pomogło dla pewnego rodzaju danych wejściowych (choć niezbyt znacząco). Niezłe wykorzystanie\g{-1}
do obejścia błędu GNU grep.\g{-1}
, ponieważ sprawia, że wzorzec jest bardziej niezależny od lokalizacji. W tej formie można go wykorzystać jako część większego wzoru.Rozwiązanie Perla:
ale to nie działa
lub
testowane z perl v5.14.2 i v5.18.2
źródło