서론
유튜브 댓글 필터링 서비스를 하는 중 아래와 같은 댓글 데이터가 존재하는 것을 발견했습니다.
아니 시키들아 하! 위! 호! 환! 된! 다! 고! 몇 번을 말해
준빵 맛있@겠다!@
♡짜!♡릿!!♡해!!!!!!!!!!!!!!!!!!!!♡
대 상 혁
두 번째 문장의 패턴은 고치려다가 더 망하는 경우가 많아 넘기려고 하는데, 첫 번째와 세 번째 문장의 패턴은 너무 자주 보여서 이걸 해결해야 정확도가 올라가겠구나... 싶었습니다.
이 패턴을 검출하고자 ChatGPT와 Claude를 활용하여 정규식 테스트를 하려고 하였으나... 하였으나..... 계속 같은 답만 주네요?
chatgpt - r"(\b\w\b)([\s!?@.,❤]+)(\b\w\b)" <- \b는 blank입니다. 대문자로 가면 Not이라고 생각하세요!
claude - r'([가-힣])[!@#$%^&*()_\-+=[\]{}|\\;:\'",.<>/?❤]+([가-힣])' 또는 r'(.)!(.)'
chatgpt가 준 답이 정답에 더 가까웠으나 finditer에서 원하는 결과를 얻지 못하였습니다.
제가 원하는 결과는 아래와 같습니다.
하! 위
위! 호
호! 환
환! 된
된! 다
다! 고
그런데 finditer, findall을 하면 아래와 같이 나오더라고요
하! 위
호! 환
된! 다
이대로는 sub을 못써먹겠다 싶어 직접 만들기로 하였습니다.
finditer, findall의 대략적인 동작 방식
내부 동작은 정확하게 볼 수는 없지만, 체득한 내용을 바탕으로 확인해보자면...
위와 같이 동작하는 것으로 추정됩니다.
이를 간단하게 psudo code로 작성해보면 아래와 같다고 볼 수 있습니다.(finditer는 yeild를 통해 찾는 것으로 판단)
1. pattern = re.compile(plain_pattern_str)
2. result = []
3. while pos < len(text):
4. matched = pattern.match(pattern, text)
5. if matched is None:
6. pos++
7. else:
8. result.push(matched.group())
9. pos = matched.end()
제가 원하는 로직에서는 psudo code의 9번 라인 때문에 원하는 결과가 나오지 않는 것이었습니다.
본론
서론에서도 말했다시피 특정 패턴의 문자열을 일반 텍스트로 변경하는 일은 조금 어려웠습니다. 정규식 만으로는 처리할 수 없어서 꽤 애를 먹었죠. 변환하고 맞춤법은 또 다른 이야기
원하는 조건은 아래와 같았고, sub로 처리할 수 없어서 반복문을 작성하였습니다.
(word)(부호)(word), 이때 len(word) == 1을 검출## gpt에 물어봐도 이게 최선일 가능성이 높다고 하네요.
## 여기서 최적화할 부분은 pattern 정도밖에 없을듯 합니다.
1. result = []
2. sub = []
3. pos = 0
4. while pos < len(text):
5. space_matched = space_pattern.match(text, pos)
6. search_matched = search_pattern.match(text, pos)
7. if search_matched:
8. sub.extend([search_matched.group(1), search_matched.group(3)])
## 여기서 len(search_matched.group(3))을 빼도 문제 없다. 오히려 그렇게 하는 것이 더 안정적
9. pos = search_matched.end() - 1
10. elif space_matched:
11. s_end = space_matched.end()
12. result.append(''.join(sub[::2]) + text[pos:s_end].strip())
13. pos = s_end
14. sub.clear() # 재할당보다 clear가 더 싸게 먹힌다
15. else: # 혹시 모를 버그가 생길 경우를 대비
16. print('error ocurred...')
17. return ' '.join(result)
18. return ' '.join(result)
아래의 모든 항목에서 예제는 이 데이터를 사용합니다.
하! 위! 호!??!!!! 환!@@!!?!!?@@! 된!@?!@!@?!? 다!?!@!@ 고!@@@@@
sub와 result 배열
sub는 search_matched에서 검출된 데이터를 임시 저장합니다. 이후 원하는 패턴이 검출되지 않고, space_pattern에 부합하는 문장이 있는 경우에 sub를 합쳐 result 배열에 저장합니다.
9번 줄의 pos 위치 로직 수정
저의 경우에는 (1글자)(부호)(1글자)로 구성되어있어 찾는 단어의 길이가 고정이었습니다. 따라서 pos - 1을 적용하여 겹치는 범위도 검색할 수 있도록 하였습니다.
pos - 1을 하지 않으면 평범한 finditer가 되고, pos - N을 하면 겹치는 범위도 검사하는 로직이 됩니다.
12번 줄의 로직
배열 slice를 통하여 join합니다
이 예제의 경우에서 12번째 줄을 처리하는 데이터는 아래와 같습니다.
sub: [하, 위, 위, 호, 호, 환, 환, 된, 된, 다, 다, 고]
sub[::2]: [하, 위, 호, 환, 된, 다] <- step 2 적용. 파이썬 배열 slicing은 [start:end:step=1]이다
''.join(sub[::2]): 하위호환된다
text[pos:s_end]: 고!@@@@@
=> 최종 결과: 하위호환된다고!@@@@@
8번 줄의 캡처 그룹
현재 (1글자)(부호)(1글자) 패턴을 사용중이라 캡처 그룹이 3개입니다.
0은 전체 출력, 1부터는 캡처 그룹의 index근본 없죠?라고 생각하면 됩니다. 저는 캡처 그룹이 3개라 1,3을 사용하였지만, 필요없는 문장부호를 캡처하지 않는다면 1,2를 사용해도 무방합니다. 이때는 그냥 group()만 호출해도 되겠네요!
결론
위 과정을 통해 제가 원하는 것을 얻을 수 있었습니다.
sub를 적용할 수 없어서 finditer로 검사하고, 이를 다른 방식으로 적용해보는 것이 너무 좋았네요.
더 좋은 방안이 있다면 그걸 쓰는게 좋겠지만, 한번씩 이런 경우가 생겨 원초적인 방식노가다를 쓰는 것이 더 좋다는 것을 깨달았네요...
그냥 저런 댓글을 못쓰게 하면 안되나...싶기도 하고...
띄어쓰기까지 포함하면 문장 구조가 틀어지는 경우가 생겨 답이 없는 경우가 많습니다. 이 경우에는 맞춤법 검사 API를 사용하는 것도 좋은 방법이겠네요.