트랙
/
Elixir
Elixir
/
연습 문제
/
날짜 파서
날짜 파서

날짜 파서

학습 연습 문제

소개

정규 표현식

정규 표현식(regex)은 Elixir에서 문자열을 다룰 때 강력한 도구예요. Elixir의 정규 표현식은 PCRE 명세(Perl Compatible Regular Expressions)를 따라요. 정규 표현식의 의미를 나타내는 문자열 패턴은 먼저 컴파일된 다음, 문자열 전체 또는 일부를 일치시키는 데 사용돼요.

Elixir에서 정규 표현식을 만드는 가장 흔한 방법은 ~r 시길을 사용하는 거예요. 시길은 Elixir에서 자주 하는 작업을 위한 문법적 설탕 지름길을 제공해요. _문자열 리터럴_을 일치시키려면, 시길 뒤에 문자열 자체를 패턴으로 사용하면 돼요.

~r/test/

=~/2 연산자는 문자열에 정규 표현식 일치를 수행해서 boolean 결과를 반환할 때 유용해요.

"this is a test" =~ ~r/test/
# => true

시길을 사용할 때 알아 둘 점 두 가지가 있어요:

  • 필요에 따라 / 대신 여러 가지 구분 기호를 사용할 수 있어요
  • 문자열 패턴은 이미 _이스케이프_되어 있어요. 정규 표현식을 사용하지 않고 패턴을 문자열로 작성할 때는 백슬래시(\)를 _이스케이프_해야 해요

문자 클래스

대괄호 []로 문자 범위를 일치시키면 _문자 클래스_가 정의돼요. 이것은 클래스 안의 문자 중 어느 한 문자와 일치해요. a-z처럼 문자 범위를 지정할 수도 있는데, 시작과 끝이 연속된 코드 포인트 범위를 나타내야 해요.

regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false

_축약 문자 클래스_는 패턴을 더 간결하게 만들어요. 예를 들어:

  • \d는 [0-9]의 축약형이에요 (모든 숫자)
  • \w는 [A-Za-z0-9_]의 축약형이에요 (모든 '단어' 문자)
  • \s는 [ \t\r\n\f]의 축약형이에요 (모든 공백 문자)

_축약 문자 클래스_를 시길 밖에서 사용할 때는 이스케이프해야 해요: "\\d"

대안

_대안_은 하나 또는 다른 하나를 일치시킨다는 것을 나타내는 특수 문자로 |를 사용해요

regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true

수량자

_수량자_는 정규 표현식에서 패턴을 반복할 수 있게 해줘요. 수량자는 바로 앞의 그룹에 영향을 줘요.

  • {N, M}에서 N은 최소 반복 횟수이고, M은 최대예요
  • {N,}은 N번 이상 반복을 일치시켜요
    • {0,}은 *로도 쓸 수 있어요: 0번 이상 반복을 일치시켜요
    • {1,}은 +로도 쓸 수 있어요: 1번 이상 반복을 일치시켜요
  • {,N}은 최대 N번 반복을 일치시켜요

그룹

둥근 괄호 ()는 _그룹_과 _캡처_를 나타내는 데 사용해요. 경우에 따라 그룹이 사용할 수 있도록 반환되기 위해 _캡처_될 수도 있어요. Elixir에서는 이름을 붙일 수도 있고 붙이지 않을 수도 있어요. 캡처는 여는 괄호 뒤에 ?<name>을 붙여서 이름을 지정해요. 그룹은 _수량자_가 뒤따를 때처럼 하나의 단위로 동작해요.

regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"

regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]

앵커

_앵커_는 정규 표현식을 일치시킬 문자열의 시작이나 끝에 고정하는 데 사용해요:

  • ^는 문자열의 시작에 고정해요
  • $는 문자열의 끝에 고정해요

보간

~r은 "pattern" |> Regex.escape() |> Regex.compile!()의 지름길이기 때문에, 문자열 보간을 사용해서 정규 표현식 패턴을 동적으로 만들 수도 있어요:

anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true

지침

이벤트를 수집하는 서비스를 작성하는 일을 맡았어요. 각 이벤트에는 날짜가 하나씩 연결되어 있는데, 서비스의 엔드포인트로 세 가지 서로 다른 형식이 들어오고 있다는 걸 알아챘어요:

  • "01/01/1970"
  • "January 1, 1970"
  • "Thursday, January 1, 1970"

이들 사이에 몇 가지 공통점이 보이자, 조합 가능한 정규 표현식 패턴을 몇 개 작성하기로 했어요.

1. 날짜에서 일, 월, 연도를 일치시켜요

day/0, month/0, year/0를 구현해, 컴파일했을 때 "01/01/1970"(dd/mm/yyyy)의 숫자 구성 요소와 일치하는 문자열 패턴을 반환하도록 해요. 일과 월은 1 또는 01처럼 (앞을 0으로 채워) 나타날 수 있어요.

"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true

2. 요일 이름과 월 이름을 일치시켜요

day_names/0와 month_names/0를 구현해, 컴파일했을 때 각각 이름으로 표기된 요일과 월에 일치하는 문자열 패턴을 반환하도록 해요.

"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true

3. 일, 월, 연도를 캡처해요

capture_day/0, capture_month/0, capture_year/0, capture_day_name/0, capture_month_name/0를 구현해, 각각의 구성 요소를 "day", "month", "year", "day_name", "month_name"이라는 이름으로 캡처하는 문자열 패턴을 반환하도록 해요.

DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")

# => %{"month_name" => "December"}

4. 캡처를 조합해 전체 날짜를 캡처해요

capture_numeric_date/0, capture_month_name_date(), capture_day_month_name_date/0를 구현해, 3번에서 다룬 구성 요소를 각각의 날짜 형식으로 캡처하는 문자열 패턴을 반환하도록 해요:

  • 숫자 날짜 - "01/01/1970"
  • 월 이름 날짜 - "January 1, 1970"
  • 요일 + 월 이름 날짜 - "Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")

# => %{"day" => "01", "month" => "01", "year" => "1970"}

5. 캡처 범위를 좁혀 날짜에만 일치시켜요

match_numeric_date/0, match_month_name_date/0, match_day_month_name_date/0를 구현해, 날짜에만 일치하면서 구성 요소도 캡처할 수 있는, 컴파일된 정규 표현식을 반환하도록 해요.

"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true

DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")

# => %{
#     "day" => "1",
#     "day_name" => "Thursday",
#     "month_name" => "January",
#     "year" => "1970"
#   }
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Elixir Exercism

날짜 파서 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Elixir 트랙을 개념 58개연습 문제 168개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.