0915 - Python

오늘·2022년 9월 15일
0

A

목록 보기
14/46

복습문제

1. 정규표현식에서 a.b와 a[.]b 의 차이점을 서술하시오
[답변]
. : a와 b 사이에 문자가 있음
[.] : 문자 그대로의 dot



2. 정규식 a[.]b는 "a0b" 문자열과는 매ㅣ되고
"a.b"와는 매치되지 않는다 (ox)
[답변]o



3. 다음의 문자열 중에서
정규식 cb*s 와 매칭되는 문자열의 갯수를 모두 고르시오
[문자열]
"cs", "cbbbs", "bbscc", "cbbbbs", "bbbs", "cbs"
[답변] 4*0~2억개 까지의 반복



4. 문자와 숫자 아닌 문자와 매치되는
[^a-zA-z0-9]와 동일한 표현식은?
[답변] \W



5.숫자와 매치되는 [0-9]와 동일한 표현식은?
[답변] \d



6. random 모듈을 사용하여 [1, 2, 3, 4, 5]를
무작위로 섞어서 출력하시오
[답변]
import random

a = [1, 2, 3, 4, 5]
random.shuffle(a)



7. maplambda를 사용하여 [5, 6, 7, 8] 리스트의
각 요소값에 5가 곱해진 리스트 b를 만들어보자
[답변]
a = [5, 6, 7, 8]
b = list(map(lambda x:x*5, a))


8. while 문으로 1-9까지 반복하는 반복문
[답변]
i = 1
while i < 10 :
    print(i)
    i += 1


9. a=[1, 2, 3, 4,] 리스트를 for문으로 
li 리스트 안에 [5, 10, 15, 20] 넣기
[답변]
a = [1, 2, 3, 4, 5]
li = []
for i in a :
    li.append(i * 5)



10. 정규식을 사용하여 "I like apble And abple" 문장을 수정하세요
[답변]
import re
string = "I like apble And abple"
re.sub("apble|abple", "apple", string)



11. 메타문자에서 $는 무슨 뜻인가?
[답변] 마지막,12. 정규식에 쓰이는 반복표현을 나열하시오
[답변] +, *, {m, n}, ?



13. 아래 전화번호 리스트를 010-1**-**** 형태로 출력하시오
[답변]
phone_number= ['010-123-1234', '010-111-1111',
               '010-222-2222', '010-333-3333',
               '010-444-4444', '010-555-5555',
               '010-666-6666', '010-777-7777',
               '010-888-8888', '010-999-9999'] 
for Number in phone_number :
    # print(re.sub("[0-9][0-9]-[0-9]{4}", "**-****", Number))
    # print(re.sub("\d{2}-\d{4}", "**-****", Number))
    print(re.sub(Number[5:], "**-****", Number))



14. 외자인 이름을 'A'로 바꿔주세요
li = ['손흥민', '황의조', '이 호', '황희찬', '홍 철'] 
[답변]
li = ['손흥민', '황의조', '이 호', '황희찬', '홍 철'] 
for name in li :
    # print(re.sub("\w[ ]\w", "A", name))
    # print(re.sub(".[ ].", "A", name))
    # print(re.sub("\w\s\w", "A", name))
    # print(re.sub("\D \D", "A", name))
    print(re.sub("\w \w", "A", name))



15. 20개의 문자열을 가진 리스트를 만드세요
조건 1. 각 문자열의 길이는 최대 10, 최소 1
조건 2. 각각 33.3333 퍼센트의 확률로 문자열(string) 안의 문자(char)
는 대문자 이거나 소문자 이거나 숫자
[답변]
words = []
for count in range(20) :
    word = ""
    while len(word) < random.randint(1, 10) :
        word += ''.join(random.choice(str(random.randint(0,9)) + chr(random.randint(65, 90))+chr(random.randint(97, 122))))
    words.append(word)
print(words)
[추가답변]
words = []
for count in range(20):
    word = ''
    while len(word) < random.randint(1,10):
        a = random.randint(1,3)    
        if a == 1:
            English = chr(random.randint(65,90))
            word += English
        elif a == 2:
            english = chr(random.randint(97,122))
            word += english
        else:
            number = str(random.randint(0,9))
            word += number
    words.append(word)
print(words)




15-1. 리스트 words 안의 문자열 중
시작은 소문자, 끝은 대문자인 문자열을 #으로 대체하세요.
[답변]
words_2 = []
for string in words :
    b = re.sub("^[a-z].+[A-Z]$", "#", string)
    words_2.append(b)
words_2

match 객체의 메서드

match()객체를 사용하다보면 궁금할 수 있다. 어떤 문자열이 매치되었는가. 이때 사용할 수 있는 메서드가 있다

메서드 목적
group() 매치된 문자열을 돌려준다
start() 매치된 문자열의 시작 위치를 돌려준다
end() 매치된 문자열의 끝 위치를 돌려준다
span() 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다.
import re
# '모든 영어 문자들'을 컴파일 해주기
p = re.compile('[a-z]+')
m = p.match("python")

# 매치하기 위해 넣어준 문자열 확인
m.group()
> 'python'

# 어느 인덱스부터 시작했는가
# match는 가장 처음부터 매칭한다
m.start()
> 0
# 만약 search로 하면
s = p.search("3 python")
s.start()
> 2

# 매칭문자열의 끝 인덱스
m.end()
> 6

# 매치된 문자열의 시작끝 인덱스가 있는 튜플
m.span()
> (0, 6)

문제 풀어보기

1) 전화번호 골라내기

# 전화번호 골라내기
data = ["010-1234-5678", "010-1234-5679",
		"010-1234-5670", "010-1234-670",
        "02-334-2243"] 
# data 리스트에서 올바른 전화번호만 골라내세요
data = ["010-1234-5678", "010-1234-5679",
        "010-1234-5670", "010-1234-670",
        "02-334-2243"]

def is_phone_number_ok(data) :
    phone_number=[]
    for i in data :
        if re.search("010-\d{4}-\d{4}", i) :
            phone_number.append(i)
    return phone_number
is_phone_number_ok(data)


# 굳이 group을 사용한다면...
is_phone_number_ok = []
p = re.compile("010-\d{4}-\d{4}")
for i in data :
    a = p.search(i)
    if a :
        is_phone_number_ok.append(a.group())
is_phone_number_ok


# finditer를 사용한다면
data = '''"010-1234-5678", "010-1234-5679",
        "010-1234-5670", "010-1234-670", "02-334-2243"'''

def is_phone_number(data):
    phone_number = []
    for i in re.finditer('010-\d{4}-\d{4}', data):
        phone_number.append(i.group())
    return phone_number
is_phone_number(data)


# findall 사용한다면
re.findall('010-\d{4}-\d{4}', data)

2) 문자열에서 span()

# 하세요, 하지요, 싶어요 의 span()을 출력하세요
msg = """안녕하세요. 제 소개부터 드리도록 하지요.
        저는 의미있는 일을 하고 싶어요. 건강하세요."""
for i in re.finditer('하세요|하지요|싶어요',msg):
    print(i.span())

컴파일 옵션

DOTALL or S

메타문자 dot(.)은 \n을 제외한 모든 문자와 매치된다는 규칙이 있다. 그런데 만약 \n도 포함하여 매치하고 싶다면 DOTALL 혹은 S 옵션을 사용하여 컴파일 하면 된다.

import re
# a와 b사이에 모든 문자
p = re.compile('a.b')
m = p.match('a\nb')
m
> 아무것도 나오지 않는다

# compile 시킬때 넣는 옵션
p = re.compile('a.b', re.DOTALL)
m = p.match('a\nb')
m
> <re.Match object; span=(0, 3), match='a\nb'>

# re.S도 동일하게 작동
p = re.compile('a.b', re.S)

IGNORECASE or I

원래 [a-z] 는 소문자, [A-Z] 는 대문자만을 의미하지만 이 옵션으로는 대소문자 구별없이 매치된다.

import re
p = re.compile('[a-z]', re.I)
m = p.match('python')
n = p.match('PYTHON')
print(m, n)
> <re.Match object; span=(0, 1), match='p'> <re.Match object; span=(0, 1), match='P'>

p = re.compile('[A-Z]', re.IGNORECASE)
m = p.match('python')
n = p.match('PYTHON')
print(m, n)
> <re.Match object; span=(0, 1), match='p'> <re.Match object; span=(0, 1), match='P'>

백슬래스 문제

정규 표현식을 파이선에서 사용할 때 혼란을 주는 요소가 있다면 \n 일 것이다.
\find[\t\n\s]end 와 같이 \s가 공백으로 인식되는 문제가 발생될 수 있다. 이럴때는 사용한 \가 문자 자체임을 알려주기 위해 백슬래시 2개를 사용하여 이스케이프 처리를 해야한다.

\find 가 문자 자체라면
re.compile('\find') --- x
re.compile('\\find') -- o

그런데 만약.. \가 한번이 아니라 \\\\\와 같이 여러번 쓰인다면 이를 표현하기위해 \\\\\\\\\\ 를 써줘야 하는 일이 발생한다. 이런 비효율적인..

이러한 문제로 인해 파이썬 정규식에는 Raw String 규칙이 생겨났다.

p = re.compile(r'\\\\\find')

위와 같이 정규식 문자열 앞에 r 을 삽입하면 이 식은 백슬래시를 여러번 사용할 필요없이 그 자체로 문자인식이 가능하다.

메타문자

1) \b
단어 구분자. 공백이라고 생각하면 된다.

p = re.compile(r"\bclcass\b")
p.search('no class at all')
> 매치

p.search('no bclass at all')
> 매치 x
> 문자열 안에 class라는 단어가 포함되어 있기는 하지만
공백으로 구분된 단어가 아니므로

2) \B
공백이 아닌 것.

p.re.compile(r'\Bclass\B')
p.search('no class at all')
> 매치 x
p.search('no bclassa at all')
> 매치 o

그루핑

보통 반복되는 문자열을 찾을 때 그룹을 사용하는데, 그룹을 사용하는 보다 큰 이유는 매치된 문자열 중에서 특정 부분의 문자열만 뽑아내기 위해서인 경우가 더 많다.

group

패턴 안에서 정규표현식을 괄호()로 묶으면 그룹이된다

sentence = "park 010-1234-5678"
p = re.compile(r'(\w+)\s(\d+)[-]\d+[-]\d+')
p.search(sentence).group(0)
> 'park 010-1234-5678'

group 메서드의 인덱스는 다음과 같은 의미를 가진다.

group(인덱스) 설명
group(0) 매치된 전체 문자열
group(1) 첫 번째 그룹에 해당하는 문자열
group(2) 두 번째 그룹에 해당하는 문자열
group(3) n번째 그룹에 해당하는 문자열

그루핑한 문자열에 이름 붙이기

(?P<그룹 이름>) 의 형태
그룹의 개수가 많아지면 구분하기가 힘들어 진다. 이때 그룹에 이름을 지어 관리하면 편하다는데, 반복적인 정규식을 사용할 때 좋은 것 같다.

`(?P<그룹 이름>\w) = (\w+)`

# 이름을 지정하고 이름으로 참조하기
p = re.compile(r'(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)')
m = p.search("park 010-1234-5678")
print(m.group("name"))

# 이름으로 정규식 안에서 재참조
# 같은 두 단어가 반복되는 거 찾기
p = re.compile(r'(?P<word>\w+)\s+(?P=word)')
p.search('Paris in the spring spring')

전방탐색

p = re.compile(".+:")
m = p.search("http://google.com")
m.group()
> http:

정규식 .+ 와 일치하는 문자열로 http: 가 결과로 나왔다. 이 결과에서 :을 제외하고 그루핑은 추가로 할 수 없다는 조건까지 더해진다면 어떡할까?

정규식 종류 설명
(?= ) 긍정형 전방 탐색 해당하는 정규식과 매치되어야 하며
조건이 통과되어도 문자열이 소비되지 않는다
(?!= ) 부정형 전방 탐색 해당하는 정규식과 매치되지 않아야 하며
조건이 통과되어도 문자열이 소비되지 않는다

긍정형

검색에서는 기존 정규식과 동일한 효과를 발휘하지만 :에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아 (검색에는 포함되지만 검색결과에서는 제외) 검색 결과에서는 제거된 것 처럼 출력되는 효과가 있다.

→ 그 앞까지만 찾아주는 것처럼 보이는 듯

p = re.compile(".+(?=:)")
m = p.search("http://google.com")
m.group()
> 'http'


p = re.compile(".+(?=0)")
m = p.search("hello2022")
m.group()
> 'hello2'

부정형

-가 아닌 경우에만 통과된다는 의미로, 해당 문자열이 있는지 조사하는 과정에서 문자열이 소비되지 않으므로 (걸러지기때문에) 아니라고 판단 되어야 그 이후 정규식 매치가 진행된다.

# 예를 들어 확장자가 bat이 아닌것만 걸러내고 싶다
.*[.](?!bat$).*$
# exe인것도 걸러내고 싶다
.*[.](?!bat$|exd$).*$

+) 후방탐색

(?<= ) 뒤부터 해당 문자열까지만 탐색

p = re.compile("(?<=o).+")
m = p.search("hello2022")
m.group()
> '2022'

문제

data = '''
http://www.forta.com a
https://mail.forta.com b
ftp://ftp.forta.com c
''' 

p = re.compile("(?<=f).+")
p.findall(data)
# > ['orta.com a', 'orta.com b', 'tp://ftp.forta.com c']

p = re.compile(r"f\w+[.]\w{3}")
p.findall(data)
# > ['forta.com', 'forta.com', 'ftp.for']

p = re.compile(r"[.](?=f).+")
p.findall(data)
# > ['.forta.com a', '.forta.com b', '.forta.com c']

p = re.compile("(?<=[.]).+")
p.findall(data)
# > ['forta.com a', 'forta.com b', 'forta.com c']

Greedy vs Lazy

정규표현식은 기본적으로 탐욕적(Greedy) 방식으로 동작한다. 의도치않게 너무 많이 일치하는 경우가 발생하는 이유 중 하나이다.

s = 'lgreedylazyUseregularexpressionsa'
# 위 문자열에서 l로 시작하고 a로 끝나는 문자를 찾는다면
p = re.compile("l.+a")
p.search(s).group()
# > 'lgreedylazyUseregularexpressionsa'

결과로 문자열 전체가 일치하게 되었습니다. 문자열에서 l로 시작하고 a로 끝나는 모든 문자를 찾고싶다면 Lazy 방식으로 검색해야하는데

? 문자를 해당 메타문자 뒤에 붙이면 됩니다. *? 처럼 말이죠

# 문자열
lgreedylazyUseregularexpressionsa


# 정규표현식
l.+?a

결과

0개의 댓글