코드 2020의 출현: 정규 표현식 명칭 그룹을 사용한 02 (b) 일
본고에서 언급한 것: 정규 표현식, 조작부호 링크, 명칭 그룹, XOR
레식스 일어나래요.
앞부분에서 나는 상태기 기반의 해결 방안을 연구했다.국가 기계는 매우 재미있다.나는 로봇 기술과 게임 개발에서 자주 그것들을 사용한다.그러나 이 문제를 해결하는 더 좋은 방법은 정규 표현식이다.이 부분에서 나는 매우 치밀한 정규 표현식 해결 방안을 추구할 것이다.
형식 주의:
이 모드를 한 걸음 한 걸음 분해해 보겠습니다.
세다
그래서 우리는 한 줄의 한 개 이상의 숫자와 일치해야 한다.대부분의 정규 표현식에서, 우리는 0에서 9 사이의 모든 숫자와 일치하는 문자 클래스
\d
를 가지고 있다.우리는 이것\d
의 수량을 수정할 수 있다.하나 이상의 속기량사 문자+
가 있다.간단하게 말하면\d+
은'한 줄의 한 개 또는 여러 개의 숫자'를 나타낸다.만약 우리가 이 속기 문자를 사용하지 않는다면, 우리는 우리가 기대하는 정확한 문자 수를 지정할 수 있기 때문에 \d{1}
는 '딱 한 자리' 를 나타내고, \d{1,2}
는 '한 자리 또는 두 자리 사이' 를 나타낸다.현재 우리는 계속 사용할 것이다\d+
.단거리 경주
Regex는 기본적으로 그룹과 텍스트입니다.만약 우리가 대시 번호와 일치하고 싶다면, 우리는 대시 번호
-
를 쓰기만 하면 된다.다른 어떤 내용에도 없으면 문자 문자-
로 간주됩니다.다른 번호
예전과 마찬가지로 우리는 이곳에서 사용할 수 있다
\d+
.지금까지 우리의 정규 표현식은 다음과 같다. \d+-\d+
이것은 23-99
또는 1-15
같은 것과 일치할 것이다.스페이스
이전과 마찬가지로 공백을 일치시킬 수 있습니다.이것은 텍스트 공백 문자
.
a letter
We need to match exactly one letter. In regex, we can make character groups. something like [abc]
로 문자 a
, b
또는 c
와 일치합니다.너도 과녁장을 만들 수 있어.그래서 [a-z]
는 a
와 z
사이의 모든 소문자와 일치할 것이다.범위를 조합할 수도 있으므로 [a-zA-Z]
소문자나 대문자를 나타냅니다.그러나 속기도 있다. 위\d
가 [0-9]
의 속기인 것처럼 어떤 자모나 밑줄과 일치할 수 있는 속기\w
가 있다. (기억하기 쉬운 것은 프로그래밍 언어에서 변수 이름이 허용하는 문자이다)우리는 그중의 하나만 필요하기 때문에, 양사는 필요 없다.
콜론 및 공백
마찬가지로 우리는 이곳에서literal:
을 사용할 수 있다.
많은 메시지 *
우리는 +
류에서 낡은 \w
양사를 사용하여 여러 자모와 일치시킬 수 있다.우리의 정규 표현식은 지금 이렇게 보인다.
\d+-\d+ \w: \w+
그것이 효과가 있다는 것을 증명하기 위해서 우리는 regex101로 검사를 진행할 수 있다.
그러나 일치하는 것은 우리가 유일하게 해야 할 일이 아니다.우리는 우리가 실제로 그것을 사용해서 검증할 수 있도록 일치하는 그룹을 찾아야 한다.따라서 정규 표현식은 그룹으로 추출해야 할 값을 나타내는 괄호가 필요합니다.
(\d+)-(\d+) (\w): (\w+)
정규 표현식에 익숙하지 않은 사람들에게는 무섭지 않기를 바란다.만약 네가 한숨을 돌렸다면, 내가 너에게 왜 레렉스의 명성이 이렇게 나쁘고 너를 불편하게 하는지 알려줄게.
내가 \d
는 [0-9]류의 줄임말이라고 말한 것을 기억한다.+
는 속기량사입니까?만약 내가 어떤 속기를 사용하지 않고 위의 속기를 다시 썼다면:
([0-9]{1,})-([0-9]{1,}) ([a-zA-Z_]): ([a-zA-Z_]{1,})
갑자기 읽기가 쉽지 않게 되었다.그래도 괜찮아, 이보다 더 나빠.그것이 여전히 유효하다는 것을 증명하다.
구현
파이톤의 정규 표현식 라이브러리 이름은 re
입니다. 만약에 우리가 가지고 있는 이전의 entry
값이 있다면 이를 사용하는 방식은 다음과 같습니다.
match = re.match('(\d+)-(\d+) (\w): (\w+)', entry)
print(match.groups())
출력
('9', '12', 't', 'ttfjvvtgxtctrntnhtt')
보시다시피, 우리는 텍스트를 해석하기 위해 많은 줄 코드를 사용했고,regex는 몇 줄의 코드만으로 실현되었다.
이때python의 패키지 해제 값을 이용하여 group()
되돌아오는 값을 변수에 붙일 수 있습니다.
match = re.match('(\d+)-(\d+) (\w): (\w+)', entry)
low_str, high_str, letter, password = match.groups()
이제 우리는 마침내 비밀번호를 검증할 수 있게 되었다.규칙에 따르면 letter
내부에 일부password
가 있는데 low
와 high
사이에 있다.Python은 count()
라는 문자열 방법을 가지고 있으며, 문자열에 몇 개의 하위 문자열 실례를 되돌려줍니다.그래서 password.count(letter)
는 low
와 high
사이에 있어야 효과적인 숫자이다.
비교 체인
파이톤에 비교 링크가 있는데, 이것은 일부 사람들을 놀라게 할 수도 있다.즉, 다음 작업을 수행하여 계수가 두 값 사이에 있는지 확인할 수 있습니다.
if low <= password.count(letter) <= high:
많은 언어에서 이것은 작용하지 않거나 이상한 값이 발생한다.예를 들어 표현식이 4 > 3 > 2
라면 일부 언어는 먼저 계산4 > 3
하고'true'를 되돌린 다음에 계속 계산true > 3
하고 보통false를 되돌린다.
그러나 파이톤은 사용자의 요구에 따라 실행됩니다. 위의 코드는 password.count(letter)
low
와 high
사이에 있는지 확인합니다.
그러니까...만약 우리가 정말로 우리의 해결 방안을 위해 코드를 작성하고 싶다면, 우리는 지금까지 본 모든 내용을 세 줄로 압축할 수 있다.
import re
data = [re.match('(\d+)-(\d+) (\w): (\w+)', line).groups() for line in open("input.txt").readlines()]
print("Valid passwords", sum(int(low) <= password.count(letter) <= int(high) for low, high, letter, password in data))
거의 읽을 수 없지만, 그것은 매우 치밀하다.
이런 간단한 연습에는 좋지만 현실 세계에서는 받아들일 수 없다.
아니오, 정말 그것을 실행하는 것과 같습니다
우리는 코드의 가독성을 높이기 위해 몇 가지 일을 해야 한다. 중요한 것은 코드의 유지보수성을 높이는 것이다. 아마도 약간의 최적화가 있을 것이다.
내가 해야 할 첫 번째 일은 이 정규 표현식을 대량으로 반복해서 사용해야 하기 때문에 그것을 컴파일해야 한다는 것이다. (파이톤이 할 수 있는 작은 최적화)
pattern = re.compile("(\d+)-(\d+) (\w): (\w+)")
이것은 앞으로 내가 할 수 있다는 것을 의미한다.
match = pattern.match(entry)
내가 해야 할 두 번째 일은 '이름 그룹' 을 사용하는 것이다. 이것은 파이톤의 정규 표현식의 독특한 특성이다. 여기서 우리는 그룹에 이름을 붙일 수 있다. 파이톤은 원조가 아닌 사전을 출력할 것이다.이론적으로 말하자면, 이것은 너로 하여금 더욱 실수를 범하기 어렵게 할 것이다. 왜냐하면 너는 원조의 순서를 잘못했기 때문이다.
pattern = re.compile('(?P<low>\d+)-(?P<high>\d+) (?P<letter>\w): (?P<password>\w+)', entry)
print(pattern.match(entry).groupdict())
출력
{'low': '9', 'high': '12', 'letter': 't', 'password': 'ttfjvvtgxtctrntnhtt'}
이것은regex를 더 길고, 읽을 수 있는 것은 더 낮지만, 반환값 처리를 더욱 튼튼하게 한다.
세 번째는 이 암호와 관련된 모든 것을 자신의 클래스에 두는 것이다. 그러면 모든 가장자리 상황을 처리하고 예상한 오류 처리를 수행할 수 있도록 단원 테스트를 작성할 수 있다.
class Password:
pattern = re.compile("(?P<low>\d+)-(?P<high>\d+) (?P<letter>\w): (?P<password>\w+)", entry)
def __init__(self, string: str):
extracted = Password.pattern.match(string)
groups = extracted.groupsdict()
self.low = int(groups["low"])
self.high = int(groups["high"])
self.letter = groups["letter"]
self.password = groups["password"]
def is_valid(self) -> bool:
return self.low <= self.password.count(self.letter) <= self.high
이 모드에서 사용하는 클래스 변수를 주의하십시오.그중에 몇 가지 장점과 단점이 있으니, 나는 지금 말하지 않겠다.
python 형식 암시도 주의하십시오.이것은 쓸모가 있다
예외 처리
파이톤에 아주 좋은 이상 처리가 있습니다.일부 프로그래밍 철학에서는 이상을 사용해서는 안 된다고 생각하지만 (이상 처리를 추가하려면 처리해야 하기 때문에), 현실은python이 이상 처리를 강력하게 추진하고 대부분의 내부 라이브러리에서 이상을 일으키기 때문에 사용하지 않습니다.
우리의 예에서, 정규 표현식이 일치하지 않는 매우 큰 예외가 있다.우리는 코드를 약간 업데이트해야 한다.
class PasswordGeneralError(exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
class PasswordFormatError(PasswordGeneralError):
pass
class Password:
pattern = re.compile("(?P<low>\d+)-(?P<high>\d+) (?P<letter>\w): (?P<password>\w+)", entry)
def __init__(self, string: str):
extracted = Password.pattern.match(string)
if not extracted:
raise PasswordFormatError("Password line does not match pattern")
groups = extracted.groupsdict()
self.low = int(groups["low"])
self.high = int(groups["high"])
self.letter = groups["letter"]
self.password = groups["password"]
def is_valid(self) -> bool:
return self.low <= self.password.count(self.letter) <= self.high
현재 일치 (extract()
가 nothing으로 되돌아오지 않으면 코드가 PasswordFormatError
이상을 일으켜 다른 곳에서 포착할 수 있습니다.
이제 한 항목에서 암호 검사기를 실행할 수 있습니다.
Password(entry).is_valid()
entry
가 유효한 암호이면 True로 돌아가고 그렇지 않으면 False로 돌아갑니다.
파일 처리
Password
클래스는 하나의 암호를 처리할 수 있지만, 우리는 입력 파일 전체를 사용하기 위해 더 많은 코드가 필요합니다.이를 위해, 우리는 다른 종류를 작성할 수 있습니다. 이것은 이상 처리를 보여 줍니다. validate
이상을 일으킬 때 그것을 포착하고, 예를 들어 입력한 파일의 어느 줄에 문제가 있는지 사용자에게 알려주기를 원하기 때문입니다.
class PasswordFileParseError(PasswordGeneralError):
def __init__(self, message, line):
self.message = f"{message} on line {line}"
super().__init__(self.message)
class PasswordChecker:
def __init__(self, file):
self.file = file
def count_valid(self) -> int:
total = 0
with open(self.file) as file:
for idx, line in enumerate(file):
try:
valid = self.validate(line)
except PasswordFormatError as err:
raise PaswordFileParseError("Could not validate", idx) from err
total += int(valid)
return total
@staticmethod
def validate(line) -> bool:
return Password(line).is_valid()
이제 형식이 잘못된 줄에 입력 파일 줄 번호를 포함하는 오류가 발생합니다.
주의해 주십시오. 저는 이미 이 두 개의 이상을 상속시켰습니다. PasswordgeneralError
이상.이로써 이 두 가지 이상을 동시에 포획하는 것이 더욱 쉬워졌다. 비록 이것은 확실히 이상 팽창 현상을 초래하지만, 결국에는 너무 많은 사용자 정의 이상이 발생할 것이다.
현재의 최종 사용법은 매우 간단하다.
checker = PasswordChecker("input.txt")
print("Number valid", checker.count_valid()
도전의 두 번째
섹션 2에서는 암호 확인 규칙이 변경됩니다.그 전에 우리는 자모가 암호에 나타나는 횟수low
와 high
사이에 있기를 희망한다.현재 숫자는 문자 위치(1인덱스)로 문자는 두 문자 위치 중 하나만 나타날 것으로 예상된다.
클래스를 작성했기 때문에 is_valid()
함수만 변경하면 됩니다.이것은 다음과 같은 몇 가지 방식 중 하나를 통해 실현할 수 있으며, 구체적인 상황에 따라 코드를 편집할 수 있다.Password
의 하위 클래스를 만들고 다시 쓸 수 있는 is_valid()
방법입니다.너는 긴급한 상황에서 패치를 수리할 수 있다.
어떤 상황이 발생하든지 간에 이전 코드는 다음과 같다.
def is_valid(self) -> bool:
return self.low <= self.password.count(self.letter) <= self.high
현재:
def is_valid(self) -> bool:
return (self.password[self.low-1] == self.letter) != (self.password[self.high-1] == self.letter)
여기서, 우리는 기본적으로 두 위치 중 하나가 일치하는지 확인하기 위해 블로이드나 조작을 사용하지만, 둘 다 일치하는 것은 아니다.
앞으로!
Reference
이 문제에 관하여(코드 2020의 출현: 정규 표현식 명칭 그룹을 사용한 02 (b) 일), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/meseta/advent-of-code-day-02-b-290a텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)