파이썬에서 하위 문자열 존재 여부-in, index 등

2022. 11. 15. 18:33프로그래밍

728x90

 

 

How to Check if a String Contains a Substring in Python: In, Index, and More | The Renegade Coder

One concept that threw me for a loop when I first picked up Python was checking if a string contains a substring. After all, in…

therenegadecoder.com

내가 Python을 처음 접했을 때 나를 사로잡았던 한 가지 개념은 문자열에 하위 문자열이 포함되어 있는지 확인하는 것이었습니다.

결국, 제 첫 언어인 Java에서는 indexOf() 또는 contains()와 같은 메서드를 호출하는 것이었습니다.

기쁘게도 파이썬은 자바보다 훨씬 더 깔끔한 구문을 가지고 있으며 오늘 그것을 다룰 것입니다.

간단히 말하자면, in 키워드를 사용하여 문자열에 하위 문자열이 포함되어 있는지 확인할 수 있습니다.

 

예를 들어 "Hi, John"의 "Hi"는 true를 반환 할 것입니다.

 

즉, index() 및 find()와 같은 메서드를 사용하는 것을 포함하여 이 문제를 해결하는 몇 가지 다른 방법이 존재합니다.

 

자세한 내용을 시작해 봅시다.

 

문제 설명

프로그래밍의 일반적인 문제는 문자열이 나머지 문자열의 하위 문자열인지 찾아 내는 것입니다.

 

예를 들어 문자열로 저장된 주소 목록을 가지고 있고, 특정 거리(예: Elm Street)내의 전체 주소를 찾고 싶을 수 있을 것입니다.

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
 

이 경우 우리는 주소에 거리 이름이 포함되어 있는지 확인할 수 있을 것입니다.(예: 123 Elm Street). Python에서 이런 작업을 수행하려면 어떻게 해야 할까요?

 

대부분의 프로그래밍 언어에는 일반적으로 일부 하위 문자열 메서드가 있습니다.

예를 들어 Java는 문자열에는 하위 문자열이 발견되면 양수를 반환하는 indexOf 메서드를 가지고 있습니다. 이런 특수한 메서드가 없어도 대부분의 언어에서는 배열과 같은 것으로 문자열을 색인화할 수 있습니다.

 

결과적으로 일치 항목을 직접 찾아 문자열에 하위 문자열이 포함되어 있는지 수동으로 확인하는 것도 가능합니다. 다음 섹션에서는 Python에서 가능한 몇 가지 해법들을 살펴 볼 것입니다.

 

해법들

항상 그렇듯이 저는 이 문제에 대한 몇 가지 가능한 솔루션을 공유하도록 하겠습니다.

즉, 최상의 해법을 원한다면 마지막 해법만 보는 것도 한 방법입니다.

 

Brute Force로 문자열에 하위 문자열이 포함되어 있는지 확인하기

저는 이런 문제를 풀려고 할 때마다 먼저 문제의 근본적인 구조에 대해 생각하는 것을 좋아합니다.

이 문제의 경우 실제로 문자 목록인 문자열이 있습니다.

결론적으로 우리가 하위 문자열을 찾기 위해 해당 문자를 순환하지 못하는 이유는 무엇일까요?

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
 
for address in addresses:
    address_length = len(address)
    street_length = len(street)
    for index in range(address_length - street_length + 1):
        substring = address[index:street_length + index]
        if substring == street:
            print(address)
 

여기에서 모든 주소에 대해 순환하여 어떤 문자열의 길이를 계산하고, 적절한 크기의 모든 하위 문자열에 대해 순환하고, 해당하는 하위 문자열이 발견되면 결과를 인쇄하는 일종의 불쾌한 순환문들을 작성했보았습니다.

다행히도 이에 대한 자체 해법으로 이를 구현 할 필요는 없습니다.

 

사실, 전체 내부 루프는 이미 문자열의 일부로 구현되어 있습니다. 다음 섹션에서 이러한 방법 중 하나를 살펴 볼 것입니다.

 

index()를 사용하여 문자열에 하위 문자열이 포함되어 있는지 확인하기

Python에서 문자열에 하위 문자열이 포함되어 있는지 확인하려면 Java와 같은 언어에서 일부 코드를 차용해 볼 수 있습니다. 앞서 언급했듯이 일반적으로 우리는 부분 문자열의 인덱스를 반환하는 indexOf() 메서드를 사용합니다.

Python에는 index()라는 유사한 메서드가 있습니다:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
 
for address in addresses:
    try:
        address.index(street)
        print(address)
    except ValueError:
        pass
 

여기서는 결과를 저장하지 않는 index 함수를 호출합니다.결국, 우리는 실제 인덱스가 무엇인지 신경 쓰지 않습니다. 메서드가 일치하는 하위 문자열을 찾지 못하면 예외가 발생합니다.

당연히 우리는 그 예외를 catch로 묶어 계속 진행할 수 있습니다. 그렇지 않으면 주소를 인쇄합니다.

 

이 해법으로 작업을 완료할 수 있지만 약간 더 깔끔한 해법이 있는 데, 다음 섹션에서 살펴보겠습니다.

 

find()를 사용하여 문자열에 하위 문자열이 포함되어 있는지 확인하기

흥미롭게도 Python에는 Java의 indexOf()와 거의 동일하게 작동하는 index()와 유사한 또 다른 메서드가 존재합니다. 이 메서드는 find()라고 하며 우리의 코드를 약간 단순화할 수 있습니다:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
 
for address in addresses:
    if address.find(street) > 0:
        print(address)
 

이제, 그것이 내가 여기서 얻을 수 있는 해결책이 이것입니다. 결국 이 코드는 유사한 Java 솔루션을 연상시킵니다.

여전히, index()처럼 작동합니다. 하지만 하위 문자열이 없으면 예외를 throw하는 대신 -1을 반환합니다. 결과적으로 try/except 블록을 단일 if 문으로 줄일 수 있는 것입니다.

 

이 말은, 다음 섹션에서 Python에는 훨씬 더 나은 해법이 존재 한다는 것입니다.

 

키워드에서 사용하여 문자열에 하위 문자열이 포함되어 있는지 확인하기

Python의 멋진 점 중 하나는 코드가 깨끗하고 가독성이 좋아 질 수 있다는 것입니다.

당연히 이것은 문자열에 하위 문자열이 포함되어 있는지 확인할 때 적용됩니다.

멋진 메서드 대신 Python에는 in 키워드가 포함된 구문이 내장되어 있습니다:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
 
for address in addresses:
    if street in address:
        print(address)
 

여기에서는 in 키워드를 두 번 사용하는 데: 한 번은 주소 목록의 모든 주소를 순환하고, 그 다음 한번은 주소에 거리 이름이 포함되어 있는지 확인하기 위한 것입니다. 보시다시피 in 키워드에는 두 가지 목적이 있습니다:

  • 값이 목록 및 문자열과 같은 시퀀스에 존재하는지 확인 하려고
  • 시퀀스를 순환 반복하려고.

물론 Java와 같은 언어를 사용하는 사람에게 이것은 꽤 성가신 결론이 될 수 있습니다.

 

결국 직관적으로 우리는 파이썬 메서드을 사용하는 것이므로 익숙해지는 데 시간이 걸립니다. 그리고 파이썬의 방식을 저는 아주 좋아 합니다. 나중에 살펴보겠지만 이것이 가장 빠른 해법이기도 합니다.

 

성능

이 모든 솔루션을 사용할 준비가 되었으므로 어떻게 비교하는지 살펴봅시다. 시작하려면 솔루션을 문자열로 설정해야 합니다:

setup = """
addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
"""
 
brute_force = """
for address in addresses:
    address_length = len(address)
    street_length = len(street)
    for index in range(address_length - street_length + 1):
        substring = address[index:street_length + index]
        if substring == street:
            pass # I don't want to print during testing
"""
 
index_of = """
for address in addresses:
    try:
        address.index(street)
        # Again, I don't actually want to print during testing
    except ValueError:
        pass
"""
 
find = """
for address in addresses:
    if address.find(street) > 0:
        pass # Likewise, nothing to see here
"""
 
in_keyword = """
for address in addresses:
    if street in address:
        pass # Same issue as above
"""
 

이 문자열을 사용할 준비가 되면 테스트를 시작할 수 있습니다.

>>>> import timeit
>>> min(timeit.repeat(setup=setup, stmt=brute_force))
4.427290499999998
>>> min(timeit.repeat(setup=setup, stmt=index_of))
1.293616
>>> min(timeit.repeat(setup=setup, stmt=find))
0.693925500000006
>>> min(timeit.repeat(setup=setup, stmt=in_keyword))
0.2180926999999997
 

이제, 이것들은 몇 가지 설득력 있는 결과입니다! 앞서말했듯이 brute force 방법은 상당히 느립니다. 

 

또한 index() 솔루션의 오류 처리가 그다지 좋아 보이지 않습니다. 기쁘게도 find()는 이러한 오버헤드 중 일부를 제거해 줍니다. 결국, in은 지금까지 가장 빠른 해법이 되겠네요.

 

파이썬에서 흔히 그렇듯이 일반적인 관용구에서 최고의 성능을 얻을 수 있습니다. 이 경우 자신만의 하위 문자열 메서드를 작성하지 마십시오.

대신 내장 키워드를 사용 하면 됩니다.

 

도전과제

문자열에 하위 문자열이 포함되어 있는지 확인하는 방법을 알았으므로 이제 도전 과제에 대해 이야기해 봅시다.  우리는 거리와 숫자라는 키워드가 아닌 두 개의 키워드를 필터링하는 간단한 주소 검색 엔진을 구현해야 합니다. 

하지만, 검색 시 두 가지 정보를 모두 얻지 못할 수도 있을 것입니다.

결과적으로 사용 가능한 키워드와 정확하게 일치하는 주소를 찾아야 합니다.

이 도전과제의 경우 검색어와 정확히 일치하는 주소 목록을 인쇄하기만 하면 된다면, 원하는 해법을 구현 할 수 있습니다.

예를 들어 다음 주소 목록을 사용한다면:

addresses = [
    "123 Elm Street",
    "123 Oak Street",
    "678 Elm Street"
]
 

사용자가 "Elm Street"만 검색하면 솔루션이 "123 Elm Street" 및 "678 Elm Street"를 반환할 것으로 예상 됩니다.

마찬가지로 사용자가 "123"을 검색하면 솔루션이 "123 Elm Street" 및 "123 Oak Street"를 반환할 것으로 예상 될 것입니다.

하지만 사용자가 "123"과 "Elm Street"를 모두 넣어 준다면 솔루션에서는 세개의 전체 주소가 아닌 "123 Elm Street"만 반환할 것으로 예상 됩니다.

즐기는 맘을 가지고 해보시면 됩니다. 

 

예를 들어, 거리 및 숫자 키워드를 수집하기 위해 전체 프론트 엔드를 구현해도 되고 이러한 변수들이 모두 이미 존재한다고 가정할 수도 있습니다.

입력 데이터와 관련하여 자유롭게 자신의 주소 목록을 작성하거나 내 간단한 예제를 사용해 보십시오. 

대안으로 임의의 주소를 생성하는 웹사이트를 사용해 볼 수도 있을 것입니다.

궁극적으로 프로그램은 두 개의 키워드에 대한 필터링을 보여줘야 합니다. 

다시말해, 실행 시점에 사용 가능한 항목에 따라 이 기사의 솔루션 중 하나를 수정하여 거리, 주소 또는 둘 다를 일치시키는 방법을 찾아보세요.

아래 코멘트에서 솔루션을 공유하겠습니다. 똑같이 해보세요!

 

약간의 요약

이것으로 끝났습니다. 오늘 해본 모든 솔루션은 다음과 같습니다:

addresses = [
    "123 Elm Street",
    "531 Oak Street",
    "678 Maple Street"
]
street = "Elm Street"
 
# Brute force (don't do this)
for address in addresses:
    address_length = len(address)
    street_length = len(street)
    for index in range(address_length - street_length + 1):
        substring = address[index:street_length + index]
        if substring == street:
            print(address)
 
# The index method
for address in addresses:
    try:
        address.index(street)
        print(address)
    except ValueError:
        pass
 
# The find method
for address in addresses:
    if address.find(street) > 0:
        print(address)
 
# The in keyword (fastest/preferred)
for address in addresses:
    if street in address:
        print(address)
 
 

이상.