코드 2020의 출현: 정규 표현식 명칭 그룹을 사용한 02 (b) 일

25092 단어 pythonadventofcode
네, 이것은 당신이 원하는 정규 표현식입니다. Day 2 of Advent of Code 2020
본고에서 언급한 것: 정규 표현식, 조작부호 링크, 명칭 그룹, XOR

레식스 일어나래요.


앞부분에서 나는 상태기 기반의 해결 방안을 연구했다.국가 기계는 매우 재미있다.나는 로봇 기술과 게임 개발에서 자주 그것들을 사용한다.그러나 이 문제를 해결하는 더 좋은 방법은 정규 표현식이다.이 부분에서 나는 매우 치밀한 정규 표현식 해결 방안을 추구할 것이다.
형식 주의:
  • 번호(저급)
  • 대시
  • 디지털(고량정)
  • 공간
  • 편지 한 통(문제가 있는 그 편지)
  • 콜론
  • 공간
  • 검증할 문자열
  • Regex가 하나 있는데...그것을 연구하는 사람들에게 이것은 명실상부한 것일 수도 있다. 왜냐하면 그것은 뚫을 수 없기 때문이다. 그러나 나는 이것이 그것의 기호가 얼마나 치밀한지와 매우 큰 관계가 있다고 생각한다.나는 정규 표현식은 읽기보다 쓰기가 훨씬 쉽다고 생각한다. 그래서 만약 내가 이 과정을 설명할 수 있다면, 나는 그것이 좀 간단하길 바란다.
    이 모드를 한 걸음 한 걸음 분해해 보겠습니다.
    세다
    그래서 우리는 한 줄의 한 개 이상의 숫자와 일치해야 한다.대부분의 정규 표현식에서, 우리는 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]az 사이의 모든 소문자와 일치할 것이다.범위를 조합할 수도 있으므로 [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가 있는데 lowhigh 사이에 있다.Python은 count() 라는 문자열 방법을 가지고 있으며, 문자열에 몇 개의 하위 문자열 실례를 되돌려줍니다.그래서 password.count(letter)lowhigh 사이에 있어야 효과적인 숫자이다.

    비교 체인


    파이톤에 비교 링크가 있는데, 이것은 일부 사람들을 놀라게 할 수도 있다.즉, 다음 작업을 수행하여 계수가 두 값 사이에 있는지 확인할 수 있습니다.
    if low <= password.count(letter) <= high:
    
    많은 언어에서 이것은 작용하지 않거나 이상한 값이 발생한다.예를 들어 표현식이 4 > 3 > 2라면 일부 언어는 먼저 계산4 > 3하고'true'를 되돌린 다음에 계속 계산true > 3하고 보통false를 되돌린다.
    그러나 파이톤은 사용자의 요구에 따라 실행됩니다. 위의 코드는 password.count(letter) lowhigh 사이에 있는지 확인합니다.
    그러니까...만약 우리가 정말로 우리의 해결 방안을 위해 코드를 작성하고 싶다면, 우리는 지금까지 본 모든 내용을 세 줄로 압축할 수 있다.
    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에서는 암호 확인 규칙이 변경됩니다.그 전에 우리는 자모가 암호에 나타나는 횟수lowhigh 사이에 있기를 희망한다.현재 숫자는 문자 위치(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)
    
    여기서, 우리는 기본적으로 두 위치 중 하나가 일치하는지 확인하기 위해 블로이드나 조작을 사용하지만, 둘 다 일치하는 것은 아니다.
    앞으로!

    좋은 웹페이지 즐겨찾기