22859번 - HTML 파싱

논리가 맞았는데, 어디에서 틀린건지 모르겠어서이다.
여기서 내가 틀렸음을 쉽게 인정하고 물러나면, 나는 코테에서도 비슷하게 행동할지도 모른다.
한 발자국만 더 나아가면, 정답으로 바로 갈 수 있는데도 말이다.

포기하지 말자. 원래 미래는 보이지 않고 불안하다.

1. HTML 파싱

A. 📜 문제

위 백준 사이트에 접속하여 문제를 확인해주세요.

B. 💡 내 답안

a. 😅 1차 시도 (실패)

def div(html: str) -> None:
    """
    div 태그가 닫는 태그가 아니라면 title을 출력.
    """
    if "/div" not in html.replace(' ', ''):
        # title의 형태가 title="A"라서 왼쪽에서 count하는 index와 오른쪽에서 count하는 index를 사용하여 슬라이싱함.
        text = "title : " + html[html.index('"')+1:html.rindex('"')]
        print(text)

def p(html: str) -> None:
    """
    p 태그가 존재하는 문장에 여는 괄호와 닫는 괄호를 구함.
    [i번째 닫는 괄호: i+1번째 여는 괄호]로 슬라이싱하면, 태그 사이에 text를 추출할 수 있음
    text가 공백으로만 이루어진 경우를 제외하고 texts 리스트에 추가.
    texts 리스트는 공백이 2번이상인 경우 1개로 변환.
    """
    starts, ends = find_start_end(html)
    texts = []

    for i, value in enumerate(starts[1:]):
        start = value
        end = ends[i]

        # if html[end+1:start].strip():
        texts.append(html[end+1:start])

    text = ''.join(texts).strip()

    while "  " in text:
        text = text.replace("  ", ' ')

    print(text)

def open(html: str) -> None:
    """
    html에서 문단을 나누는 div와 문장을 의미하는 p 태그를 파싱함.
    즉, 현재 태그를 파악하고 원하는 태그만 선별.
    """
    starts, ends = find_start_end(html)

    for i, value in enumerate(starts):
        start = value
        end = ends[i]

        # print(html[start:end+1])

        # '/ div'같은 태그를 제외하기 위해 replace함
        if "div" in html[start:end+1].replace(' ', ''):
            div(html[start:end+1])
        # 'p' 태그 사이에 다양한 태그가 올 수 있어서, 'p'태그의 여는 태그와 닫는 태그는 p_end, p_start에 따로 저장
        if "/p" in html[start:end+1].replace(' ', ''):
            p_end = end
            p(html[p_start:p_end+1])
        elif "p" in html[start:end+1].replace(' ', ''):
            p_start = start



def find_start_end(html: str) -> list:
    """
    문서에 존재하는 모든 여는 괄호(<)와 닫는 괄호(>)의 index를 구함.
    여는 괄호가 존재하면 반드시 닫는 괄호가 존재하기 때문에 starts와 ends의 길이는 동일함.
    """
    starts = []
    ends = []

    for i, value in enumerate(html):
        if value == "<":
            starts.append(i)
        elif value == ">":
            ends.append(i)

    return starts, ends


if __name__ == "__main__":
    html = input()

    # print(html)

    open(html)

b. 😊 2차 시도 (성공 - 문자열 - 구현)

def div(html: str) -> None:
    """
    div 태그가 닫는 태그가 아니라면 title을 출력.
    """
    if "</div>" not in html:
        # title의 형태가 title="A"라서 왼쪽에서 count하는 index와 오른쪽에서 count하는 index를 사용하여 슬라이싱함.
        text = "title : " + html[html.index('"')+1:html.rindex('"')]
        print(text)

def p(html: str) -> None:
    """
    p 태그가 존재하는 문장에 여는 괄호와 닫는 괄호를 구함.
    [i번째 닫는 괄호: i+1번째 여는 괄호]로 슬라이싱하면, 태그 사이에 text를 추출할 수 있음
    text가 공백으로만 이루어진 경우를 제외하고 texts 리스트에 추가.
    texts 리스트는 공백이 2번이상인 경우 1개로 변환.
    """
    starts, ends = find_start_end(html)
    texts = ''

    for i, value in enumerate(starts[1:]):
        start = value
        end = ends[i]

        # if html[end+1:start].strip():
        texts += html[end+1:start]

    text = ' '.join(texts.split())

    print(text)

def open(html: str) -> None:
    """
    html에서 문단을 나누는 div와 문장을 의미하는 p 태그를 파싱함.
    즉, 현재 태그를 파악하고 원하는 태그만 선별.
    """
    starts, ends = find_start_end(html)
    p_start = 0
    p_end = 0

    for i, value in enumerate(starts):
        start = value
        end = ends[i]

        # print(html[start:end+1])

        # '/ div'같은 태그를 제외하기 위해 replace함
        if "<div title=" in html[start:end+1]:
            div(html[start:end+1])
        # 'p' 태그 사이에 다양한 태그가 올 수 있어서, 'p'태그의 여는 태그와 닫는 태그는 p_end, p_start에 따로 저장
        if "</p>" in html[start:end+1]:
            p_end = end
            p(html[p_start:p_end+1])
        elif "<p>" in html[start:end+1]:
            p_start = start


def find_start_end(html: str) -> list:
    """
    문서에 존재하는 모든 여는 괄호(<)와 닫는 괄호(>)의 index를 구함.
    여는 괄호가 존재하면 반드시 닫는 괄호가 존재하기 때문에 starts와 ends의 길이는 동일함.
    """
    starts = []
    ends = []

    for i, value in enumerate(html):
        if value == "<":
            starts.append(i)
        elif value == ">":
            ends.append(i)

    return [starts, ends]


if __name__ == "__main__":
    html = input()

    open(html)

c. 😊 3차 시도 (성공 - 정규식)

import re  
  
if __name__ == '__main__':  
    html_doc = input()  
  
    html_doc = html_doc[len('<main>'): -len('</main>')]  
  
    html_doc = re.sub(r'<div +title="([\w ]*)">', r'title : \1\n', html_doc)  
    html_doc = re.sub(r'</div>', '', html_doc)  
  
    html_doc = re.sub(r'<p>', '', html_doc)  
    html_doc = re.sub(r'</p>', '\n', html_doc)  
  
    html_doc = re.sub(r'</?[\w ]*>', '', html_doc)  
    html_doc = re.sub(r' ?\n ?', '\n', html_doc)  
    html_doc = re.sub(r' {2,}', ' ', html_doc)  
  
    print(html_doc)

d. 🙄 회고

내 풀이

  • 맞았는데... 왜 틀렸지?

반성

  • 논리는 맞았다.
    • 다만, 작은 구멍을 놓쳤다.

결론

  • 작은 구멍도 다 매꿀정도로 코드를 잘 짜야겠다.

나는 스터디원의 도움으로 정규식으로 이 문제를 해결했다. 그런데 왜 맨 처음 틀린 코드로 돌아가서 5%에 발생하던 value error을 해결하려고 했을까?

논리가 맞았는데, 어디에서 틀린건지 모르겠어서이다.
여기서 내가 틀렸음을 쉽게 인정하고 물러나면, 나는 코테에서도 비슷하게 행동할지도 모른다.
한 발자국만 더 나아가면, 정답으로 바로 갈 수 있는데도 말이다.

포기하지 말자. 원래 미래는 보이지 않고 불안하다.

C. 🧐 문제 해설

이해한 내용을 바탕으로 작성했습니다.

a. 공통 해설
  1. <div>, </div>로 문단을 나눈다.
    • <div>의 속성으로는 title이 존재한다.
      • <div title="(A)"> 형태이며, title : (A) 형식으로 출력해야 한다.
      • (A)는 항상 알파벳(a-z, A-Z), 언더바(_), 공백( )으로 구성되어 있다.
  2. <p>, </p>로 문장을 나눈다.
    • <p> 태그 안에는 다양한 태그들이 존재할 수 있다.
      • 해당 태그들은 모두 지운다.
      • 태그들은 아직 닫히지 않은 태그가 있을 때, 다른 닫는 태그가 올 수 없다.
        • ex) <b><i></b></i> (x), <b><i></i></b>
    • <p> 태그 안에 존재하는 문장의 시작과 끝에 존재하는 공백은 지운다.
      • 문장은 알파벳(a-z, A-Z), 공백( )으로 주어진다.
    • 공백이 2개 이상 연속된다면 하나의 공백으로 바꾼다.
    • <p>, </p> 태그도 지운다.
  • 태그를 표시하는 <> 사이에는 소문자 알파벳(a-z), 공백( ), 슬래시(/)로 이루어져 있다.

문제 자체는 그렇게 어렵지 않다.
다만, 우리의 상상력이 문제를 어렵게 만든다.

나는 알파벳(a-z, A-Z), 언더바(_), 공백( )으로 구성을 잘못 이해하여 이런 상상을 했다.
<div>가 아닌 <d i v>, </ div>가 테스트 케이스로 입력되는건가?
그러나, 수십번 시도해보고 내린 결론은 그렇지 않다였다.

문제에는 대장 태그 2개(<div>, <p>)가 나온다.
이번 문제에서는 이 두 대장 태그에는 장난을 치지 않았다.
그냥 <div>, <p> 문자 그대로이다.

만약, 5%에서 알 수 없는 이유로 자꾸 틀린다면, 아래 테스트 케이스를 실행해보라.

<main><div title="title_name_1"><p>paragraph 1</p><p>paragraph 2 <diving>Italic Tag</diving> <br > </p><p>paragraph 3 <b>Bold Tag</b> end.</p></div><div title="title_name_2"><p>paragraph 4</p><p>paragraph 5 <i>Italic Tag 2</i> <br > end.</p></div></main>
b. 문자열 - 구현

문제를 간단히 생각하고 풀어보자.

나는 <>를 모두 구해놓고 풀었다.

태그를 판단할 때는 <a>이런 구성으로 판단하였고, <p>태그에서 Text를 추출할때는 >text<이런 방법으로 추출했다.

c. 정규식

정규식은 스터디원이 아이디어를 제공해줘서 풀어봤다.

코드가 상당히 간결한게, 정규식을 잘 이해한다면 대단한 무기가 될 수 있겠다는 생각도 들었다.

나는 아래 3개의 사이트를 참고해서 정규식을 적용했다.

정규표현식 기초 – JSYoo5B.Dev();

re — 정규식 연산 — Python 3.10.2 문서

07-3 강력한 정규 표현식의 세계로 - 점프 투 파이썬 (wikidocs.net)


여담으로, 정규식을 사용하지 않고도 정규식만큼 간결하게 코드를 작성할 수 있다. 해당 코드는 문제를 해결해보고 백준 사이트를 잘 참고해보면 될 듯 하다.

참고문헌

baekjoon. 22859번: HTML 파싱 (acmicpc.net). Baekjoon. (accessed Feb 12, 2022)

이 글이 도움이 되었나요?
0 minutes ago
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.