정규 표현식은 문자열을 _패턴 매칭_하는 매우 다재다능한 방법으로, 그 목적을 위해 설계된 도메인 특화 언어(DSL)를 사용해요.
Julia는 다른 여러 프로그래밍 언어처럼 자체 정규식 라이브러리를 구현하려고 하지 않아요. 대신 널리 쓰이는 PCRE2 라이브러리를 감싸서, (예를 들어) Gleam과 동일하고 JavaScript와 매우 유사한 정규식 문법을 제공해요.
이 Julia 실라버스는 이미 기본적인 정규식 문법에 익숙하다고 가정해요. Julia에 특화된 기능에만 집중할게요.
정규 표현식 지식을 되새길 수 있는 자료 몇 가지를 아래에 정리했어요.
Julia의 정규 표현식 인터페이스는 매뉴얼에 설명되어 있어요.
Julia에서 정규 표현식은 여는 " 앞에 r을 붙인 문자열에 불과해요.
기본 기능은 모두 표준 라이브러리에 포함되어 있어요.
사실 Strings 개념에서 이미 다룬 많은 함수가 occursin()처럼 기본적으로 정규식 검색을 위해 설계되었어요.
julia> re = r"test$"
r"test$"
julia> typeof(re)
Regex
# Does a string end with "test"?
julia> occursin(re, "this is a test")
true
julia> occursin(re, "these are tests")
false
수정자 문자는 닫는 따옴표 뒤에 올 수 있는데, 예를 들어 i는 대소문자를 구분하지 않는 매칭을 의미해요.
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
흔히 우리는 무엇이 매칭되는지 알고 싶어요. 이는 정규식 안의 괄호에 캡처 그룹을 넣고 match() 함수를 사용하면 돼요.
julia> m = match(r"(\d+g) .* (\d+ml)", "dissolve 25g sugar in 200ml water")
RegexMatch("25g sugar in 200ml", 1="25g", 2="200ml")
julia> m.captures
2-element Vector{Union{Nothing, SubString{String}}}:
"25g"
"200ml"
# how many matches?
julia> length(m.captures)
2
# what matched?
julia> m[1], m[2]
("25g", "200ml")
# Starting positions of the matches (character index)
julia> m.offsets
2-element Vector{Int64}:
10
23
물론 매칭이 실패할 수도 있어요.
그러면 결과는 RegexMatch 대신 특수 값 Nothing이 되니, 이에 대비해야 해요.
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
match는 기본적으로 문자열의 시작에서 매칭을 시작하지만, 앞의 n개 문자를 무시하도록 오프셋 n을 지정할 수도 있어요.
# capture first match
julia> m = match(r"(\wat)", "cat, sat, mat")
RegexMatch("cat", 1="cat")
# ignore first 5 characters, then match
julia> m = match(r"(\wat)", "cat, sat, mat", 5)
RegexMatch("sat", 1="sat")
Julia에서 match()는 대상 문자열 내에서 첫 번째 매칭만 찾아요. 다른 언어들처럼 전역 수정자는 없어요.
대신 매칭의 반복자를 반환하는 eachmatch()가 있어요.
이는 지연 평가되므로, 원하는 형식으로 변환해야 할 수도 있어요.
julia> matches = eachmatch(r"(\wat)", "cat, sat, mat")
Base.RegexMatchIterator{String}(r"(\wat)", "cat, sat, mat", false)
# convert to vector
julia> collect(matches)
3-element Vector{RegexMatch}:
RegexMatch("cat", 1="cat")
RegexMatch("sat", 1="sat")
RegexMatch("mat", 1="mat")
# convert with comprehension
julia> [m.match for m in matches]
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
# broadcast an anonymous function
julia> (m -> m.match).(matches)
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
정규식을 사용하는 흔한 이유 중 하나는 매칭된 부분을 다른 문자열로 바꾸는 것이에요.
replace() 함수는 Strings 개념에서 문자열 리터럴로 검색하는 방식으로 다뤘어요.
같은 함수로 정규식 매칭의 모든 기능을 활용할 수 있어요.
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
위의 두 번째 예시는 s" " 문자열 안에서 번호가 붙은 캡처 그룹과 이름이 붙은 캡처 그룹을 모두 치환에 사용하는 방법을 보여줘요.
더 자세한 내용은 매뉴얼을 참고해요. 이 주제는 대부분의 프로그래머를 끊임없이 문서로 되돌아가게 만드는 주제예요!
여러분은 챗봇을 개발하는 회사에 정규 표현식 전문가로 채용되었어요.
아직 개발의 아주 기초 단계이기 때문에, 정규 표현식을 사용해서 챗봇이 자연어를 이해하고 생성하는 능력을 향상시키는 것이 여러분의 임무예요.
학습 목적으로, 이 과제들은 정규 표현식을 사용해서 풀어보세요.
다른 방법도 가능하고 (어쩌면 더 성능이 좋을 수도 있다는 것도!) 알고 있어요.
챗봇은 똑똑할 뿐만 아니라 충실한 비서이기도 해요.
챗봇에게 무언가를 물어보려면, 사용자는 명령의 첫 번째 위치에 "Chatbot"이라는 단어를 말해야 해요.
키워드가 대문자든 소문자든 상관없어요. 여기서 중요한 것은 단어의 위치예요.
사용자가 명령을 내리고 있는지 챗봇이 인식하도록 돕는 is_valid_command() 함수를 구현해 보세요.
julia> is_valid_command("Chatbot, play a song from the 80's.")
True
julia> is_valid_command("Hey Chatbot, where is the closest pharmacy?")
False
julia> is_valid_command("CHATBOT, do you have a solution for this challenge?")
True
챗봇은 사람들이 감정을 표현하기 위해 이모지를 어떻게 사용하는지 이해하는 것을 어려워해요.
챗봇이 사용자 메시지를 받으면, 각 이모지는 "emoji" 뒤에 id 번호가 붙은 형태로 표현돼요.
문자열을 받아 메시지 전체에서 모든 이모지의 암호화를 제거하는 remove_emoji() 메서드를 구현해 보세요.
이모지 텍스트가 없는 줄은 수정되지 않은 채로 반환되어야 해요.
그냥 이모지 문자열만 제거해요. 공백을 조정하려고 하지는 마세요.
julia> remove_emoji("I love playing videogames emoji3465 it's one of my hobbies")
"I love playing videogames it's one of my hobbies"
모바일 앱에서 챗봇 기능을 다운로드하는 것을 고려하면, 사용자는 대화 중에 전화번호를 입력할 것으로 예상돼요.
문제는 챗봇이 특정 형식의 번호만 읽고 검증할 수 있다는 거예요.
번호가 유효하면 (정규 표현식으로 지정된 문자 시퀀스와 일치하면), 챗봇은 사용자에게 감사하고 번호를 확인하는 메시지로 답해요. 번호가 유효하지 않으면, 함수는 전화번호가 유효하지 않다고 사용자에게 알려줘요.
예상 형식은 다음과 같아요: (+##) ###-###-###
julia> check_phone_number("(+34) 659-771-594")
"Thanks! You can now download me to your phone."
julia> check_phone_number("659-771-594")
"Oops, it seems like I can't reach out to 659-771-594"
챗봇은 정말 호기심 많은 소프트웨어예요. 특정 주제에 대해 인터넷에서 검색할 수 있지만, 관련 정보를 찾아볼 수 있는 멋진 웹사이트나 URL을 사용자에게 묻는 것을 좋아해요.
대화 예시:
각 웹사이트의 링크만 담은 배열을 반환할 수 있는 getURL() 함수를 구현해 보세요.
julia> getURL("I learned a lot from exercism.org")
["exercism.org"]
챗봇과 대화한 모든 사람의 데이터를 저장하기 위해, 챗봇은 사용자 프로필에서 "smith, john" 형식으로 전체 이름을 가져올 수 있어요.
이렇게 해서 우리 챗봇이 정말 예의 바르고 좋은 인상을 주기를 바라요.
사용자의 전체 이름이 담긴 문자열을 받아서 "Nice to meet you, John Smith" 문자열을 반환하는 nice_to_meet_you() 함수를 작성해 보세요.
julia> str = "Smith, John"
julia> nice_to_meet_you(str)
"Nice to meet you, John Smith"
Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.