컴퓨터 공부/🐍 Python

Remove Non-alphanumeric Characters in Python

letzgorats 2023. 12. 13. 06:06

알고리즘 문제를 풀다가 "Non-alphanumeric Characters" 와 관련한 문제에 직면했다.

비단, 알고리즘 문제 뿐만 아니라 사용자 입력을 정리하거나 데이터를 추출하는 데에도 이러한 "Non-alphanumeric Characters"를 삭제하는 방법을 알면 유용할 것 같은 생각이 들었다.

 

https://leetcode.com/problems/valid-palindrome/description/?envType=study-plan-v2&envId=top-interview-150

 

Valid Palindrome - LeetCode

Can you solve this real interview question? Valid Palindrome - A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric cha

leetcode.com

이와 관련해서 엄청 쉬운 문제를 가져와봤다. 한 번 풀어보는 것을 추천한다.

 

Non-alphanumeric Characters은 영숫자, 즉 비문자-숫자 문자를 뜻한다.

예를 들어, 문자열에 구두점, 기호, 공백 또는 특수문자 등이 있으면 이를 제거하고 싶은 경우가 있을 것이다.

그래서 이번 포스팅에서는 non-alphanumeric characters 를 삭제하는 방법에 대해 포스팅 해보려고 한다.

 

예를 들어 아래와 같은 문자열이 있다고 가정해보자.

text = "456-abc78!@#$%9AB,C"

 

 

해당 문자열에 있는 공백, 반점, 콜론 등의 non-alphanumeric Character를 없애고 오로지 영문자와 숫자만 남게 하고 싶다면, 어떻게 하면 좋을까?

우리가 원하는 결과는 아래와 같다.

456abc789ABC

 

 

해결방법은 크게 3가지가 있다.

1. 정규 표현식 사용(regular expression)

  • "re" 모듈의 "re.sub()" 메소드를 사용한다.
import re
text = "456abc789ABC-{}[]:*(!@#$%-=\"<>?/~,^&*().`"
extracted_string = re.sub(r'\W+', '', text)
print(extracted_string)

 

're.sub()' 메서드는 다음과 같은 파라미터를 받을 수 있으며, 각각에 대한 설명은 아래와 같다.

re.sub(pattern, repl, string)의 형태로 사용
  • 'pattern' - 일치시고자 하는 정규 표현식이다. "Pattern" 객체일 수 있다.
  • 'repl' - 이 파라미터는 대체 문자열을 포함한다.
  • 'string' - 실제 입력 문자열을 담고 있다.
  • 'count' - 이 파라미터는 're.sub()' 메서드를 사용하여 대체할 최대 교체 횟수 또는 최대 매치 횟수를 지정하는 데 사용된다.
  • 'flag' - 하나 이상의 정규 표현식 플래그를 나타내며, 패턴의 표준 동작을 조작하는 데 사용된다.

위에서 're.sub()' 메소드와 함께 첫 세 개의 파라미터를 사용하여 비문자-숫자 문자의 모든 발생을 대체했다.

 

pattern 인자에 정규 표현식으로 '\W'를 사용했으며, 이는 '$','&' 등과 같은 비단어 문자에 일치한다.

repl 인자는 빈 문자열로 대체했고, 이는 비단어 문자가 빈 문자열로 대체된다.

string 인자에는 실제 입력받은 text 문자열을 넣어줬다.

 

'\w'(소문자 w)는 숫자, 문자, 밑줄('[a-zA-Z0-9_]')과 같은 단어 문자에 일치하는 데 사용되며,

'\W'(대문자 W)는 ['a-zA-z0-9_]' 를 제외한 모든 것을 포함하는 비단어 문자에 일치하는 데 사용된다.

 

더 자세한 정규식 표현 방법은 아래링크를 참고하면 좋다.

https://chaelist.github.io/docs/data_handling/regular_expressions/

 

Regular Expressions

chaelist’s blog

chaelist.github.io

 

2. isalnum() 메소드 사용

 

두 번째 방법은 isalnum() 을 사용하는 것이다.

  • 'filter()' 메서드를 'str.isalnum'과 함께 사용하면 문자열이 문자-숫자(alphanumeric) 문자를 포함하고 있는지 확인하고, 이들을 필터링한다.
  • 이전 단계에서 필터링된 문자열의 모든 문자를 연결하기 위해 join() 메서드를 사용한다.
text = "456abc789ABC-{}[]:*(!@#$%-=\"<>?/~,^&*().`"
extracted_string = ''.join(filter(str.isalnum, text))
print(extracted_string)

 

간단히 말해, 이 방법은 문자열에서 문자-숫자가 아닌 모든 문자를 제거하는 데 사용된다.

'filter()' 함수는 각 문자가 문자-숫자인지 확인하고, 'join()' 함수는 이러한 문자들을 다시 하나의 문자열로 결합한다.

 

'str.isalnum()' 메서드는 python 에서 문자열에 사용되는 메서드로, 문자열이 알파벳 문자(문자)와 숫자(숫자)로만 구성되어 있는지를 확인하는 데 사용된다. 이 메서드는 문자열의 모든 문자가 알파벳 또는 숫자일 경우 'True'를 반환하고, 그렇지 않으면 'False'를 반환하다.

 

예를 들어,

"Hello123".isalnum()은 True를 반환한다. (모든 문자가 알파벳 또는 숫자이다)
"Hello 123".isalnum()은 False를 반환한다. (공백이 포함되어 있다)
"Hello!123".isalnum()은 False를 반환한다. (특수 문자 !가 포함되어 있다)

 

'filter()' 메서드는 두 개의 인자를 받는데, 위에서는 'str.isalnum' 함수와 문자열 이터러블을 받았다. 'str.isalnum' 함수의 출력에 기반하여 문자열 이터러블에서 항목을 선택한다. 만약, 'str.isalnum'이 'True'를 반환하면, 현재 항목이 'filter()' 메서드에 의해 선택된다. 그렇지 않으면 선택되지 않는다.

 

3. str.translate() 메소드 사용

마지막으로, str.translate() 메소드를 사용하는 것이다.

  • 'str.maketrans()' 메소드를 사용하여 매핑 테이블을 얻는다.
  • 이전 단계에서 생성한 매핑 테이블에서 한 문자를 다른 문자로 대체하기 위해 'string.translate()' 메서드를 사용한다.
import string
my_string = "456abc789ABC-{}[]:*(!@#$%-=\"<>?/~,^&*().`"
mapping_table = str.maketrans('', '', string.punctuation)
extracted_string = my_string.translate(mapping_table)
print(extracted_string)

 

 

먼저, 표준 Python 문자열을 처리하기 위한 함수들을 사용하기 위해 'string' 모듈을 가져와야 한다.

그 다음, 매핑 테이블(번역 테이블)을 만들기 위해  'str.maketrans()'를 사용했으며, 이것은 'my_string.translate()' 메서드의 매개변수로 전달되었다.

 

'str.maketrans()' 함수는 세 개의 인자를 받는다. 매핑할 문자열, 매핑될 문자열, 그리고 삭제할 문자열 순이다.

첫 번째와 두 번째 인자에 빈 문자열을 사용하고, 세 번째 인자에는 모든 문자-숫자(alphanumeric) 문자를 포함하는 문자열을 사용했다.

'string.punctuation' 은 모든 표준 ASCII 구두점 문자를 포함하는 문자열이다. 이렇게 설정함으로써, 모든 문자-숫자 문자를 None으로 매핑하는 번역 테이블이 생성되며, 이는 구두점 문자가 입력 문자열에서 삭제되는 것을 의미한다.

그런 다음, 'str.translate()' 메서드를 사용하여 입력 문자열에 번환 테이블을 적용한다. 이 메소드는 'mapping_table' 에 따라 문자열에서 지정된 문자를 삭제하거나 다른 문자로 대체한다. 

 

※ str.maketrans()와 translate() 메서드를 사용하여 문자열 변환을 수행하는 예시를 또 살펴보자.

inputs = "ALLU.com"

x = "LLU"

y = "llu"

z = "Acom."

ch = inputs.maketrans(x, y, z)

print(ch)
print(inputs.translate(ch))

 

여기서 maketrans(x,y,z)를 보면, LLU 는 llu 로 대체되고, 'A','c','o','m','.'문자가 inputs에서 삭제된다.

 maketrans() 메서드 자체는 대체 설명하는 Dictionary를 반환한다.

이 딕셔너리는 문자를 다른 문자로 매핑하거나 특정 문자를 삭제하는 데 사용된다.

ch를 print 해보면,

{76: 108, 85: 117, 65: None, 99: None, 111: None, 109: None, 46: None} 가 나온다.

  • 76: 108: ASCII 코드 76 ('L')을 ASCII 코드 108 ('l')로 대체한다.
  • 85: 117: ASCII 코드 85 ('U')을 ASCII 코드 117 ('u')로 대체한다.
  • 65: None: ASCII 코드 65 ('A')를 삭제한다.
  • 99: None: ASCII 코드 99 ('c')를 삭제한다.
  • 111: None: ASCII 코드 111 ('o')를 삭제한다.
  • 109: None: ASCII 코드 109 ('m')를 삭제한다.
  • 46: None: ASCII 코드 46 ('.')를 삭제한다.

이 딕셔너리는 문자열의 'translate()' 메서드와 함께 사용되어 문자열에서 특정 문자를 다른 문자로 대체하거나 삭제하는 데 사용된다.

결국 결과적으로 llu 만 남게된다.

※ 성능

import re
import string
import timeit

def remove_non_alnum_re(s):
    return re.sub(r'\W+', '', s)

def remove_non_alnum_isalnum(s):
    return ''.join(c for c in s if c.isalnum())

def remove_non_alnum_translate(s):
    table = str.maketrans('', '', string.punctuation)
    return s.translate(table).replace(' ', '')

big_string = 'This is a big string with some non-alphanumeric characters!@#$%^&*()_+{}[]|\/<>?`~'

# Run each function 1000 times and print the average time
print('re.sub:', timeit.timeit(lambda: remove_non_alnum_re(big_string), number=1000))
print('isalnum:', timeit.timeit(lambda: remove_non_alnum_isalnum(big_string), number=1000))
print('translate:', timeit.timeit(lambda: remove_non_alnum_translate(big_string), number=1000))

결과

re.sub: 0.002696799999999999
isalnum: 0.001636900000000001
translate: 0.0010567000000000007

 

즉, translate() 메서드가 가장빠르고, 그 다음에는 isalnum() 메서드, 가장 느린게 re.sub() 메서드이다. 하지만, 이 차이는 매우 크지 않고, 메서드 선택은 가독성, 호환성 또는 특별한 경우와 같은 다른 요인에 따라 달라질 수 있다.

 

반응형