Home » Julia 정규식 다루기

Julia 정규식 다루기

Julia에서 정규식은 별도의 모듈을 반입하지 않고 사용할 수 있으며, 패턴 자체는 Base.RegExp 타입으로 표현된다. 정규식 패턴을 만들 때에는 r"..." 형태의 리터럴로 바로 정의할 수 있다. (대신, raw string문자열은 raw"..."이다.) 한번 생성한 Regexp 값은 .pattern 필드로 그 패턴을 다시 확인할 수 있다. 참고로 r" .... " 내의 표현에서 이스케이프 문자는 두 번이 아니라 한 번만 쓰면 된다. (r"\d+")

정규식 객체 및 매치 결과

생성된 정규식 객체에 대해서 match() 함수를 통해서 문자열에서 매치하는 부분을 찾을 수 있다. (match(pat, str) )매치한 결과는 단순히 문자열이 아니라, 범위라든지, 그룹별 매치 결과 등의 정보를 포함해야 하므로 이 정보들로 구성된 RegexMatch 타입의 객체가 되며, 다음과 같은 속성이 있다.

  • m.match : 패턴에 의해 매치되는 영역 전체의 문자열. 만약 반복적으로 매치되는 패턴이라면 마지막 매치 결과의 정보를 보여준다.
  • m.offset : m.match의 값이 원본 문자열에서 시작하는 위치
  • m.captures : 패턴에 캡쳐 그룹이 포함되어 있을 때, 각 캡쳐 그룹의 내용
  • m.offsets : 각 캡쳐 그룹의 오프셋값

캡쳐 그룹이 있는 패턴의 경우, 각각의 매치 그룹은 m[1], m[2] 와 같은 식으로도 액세스할 수 있다. match() 함수에서 패턴에 일치하는 영역을 찾지 못한 경우에는 비어있는 RegexMatch 객체 대신 nothing이 리턴된다.

s = "Use match(pat, str) to get the matched substring from a string."
pat = r"\b(\w[A-Za-z0-9_]+)\(([^\)]*)\)"
m = match(p, s)
# => RegexMatch("match(pat, str)", 1="match", 2="pat, str")
m.match #=> "match(pat, str)"
m[1]    #=> "match
m[2]    #=> "pat, str"

패턴에 매치되는 영역이 반복적으로 나타나는 경우에는 항상 첫번째 매치가 리턴된다. 일치되는 영역을 더 찾으려면 match() 함수를 다시 호출하면서 세번째 인자로 시작할 인덱스를 넘겨주면, 그 위치부터 다시 매치를 하게 된다. 따라서 반복해서 매치되는 패턴은 다음과 같은 방식으로 체크할 수 있다.

pat = r"(?:a|b)ce"
s = "acebcedcabcacebacedace"
o = 1
while true
  m = match(pat, s, o)
  (m != nothing) || break
  println("$(m.match)")
  o = lastindex(m.match) + m.offset
end

이와 동일한 환경에서 사용할 수 있는 것이 eachmatch(pat, str) 이다. 이 함수는 매치되는 구간을 찾고 다시 매치 뒤쪽부터 탐색을 계속하여 문자열의 끝까지 탐색하여, RegexMatch의 이터레이터를 리턴한다.

for m in eachmatch(pat, s)
  println(m.match)
end

# 왠지 이런 코드로 바꿔쓰고 싶은 강박이 든다.
# println.(map(x->x.match, eachmatch(pat, s)))

정규식을 사용하여 문자열 치환하기

정규식을 사용하는 주된 용도 중 하나는 고정된 인덱스가 아닌 영역을 찾거나, 찾은 영역을 다른 값으로 치환하는 것이다. 줄리아에서도 이는 마찬가지이며, 문자열의 일부를 치환하여 새로운 문자열을 만드는 함수로 replace()가 있다. replace()함수는 꼭 문자열이 아니어도 배열 등에서도 사용 가능한 함수이다.

  • replace(A, old_new::Pair...; count=)
  • replace(f, A; count=)
  • replace!(A, old_new::Pair...; count=)
  • replace!(f, A; count=)

제자리에서 교체하는 replace!()버전도 있다. 원래 연속열과 교체대상=>교체값으로 구성된 Pair를 넘겨주는 식으로 작동하여, 여러 변경을 한 번에 수행할 수 있다. 혹은 (함수, 연속열)의 형태로 map()과 동일하게 작동하는 버전도 있다.

replace() 함수에서 교체할 부분을 지정할 때에는 Pair{String, String} 타입을 쓸 수도 있지만, 정규식 패턴과 “교체문자열”을 사용할 수 있다.(Pair{RegExp, SubstitutionString{String}}) 교체 문자열은 정규식에서 캡쳐한 그룹을 사용할 수 있는 템플릿 문자열로, 정규식 리터럴처럼 별도의 s" .. " 리터럴을 사용하여 작성할 수 있다.

s = "Use match to get the matched substring from a string."
pat = r"(match)"
replace(s, pat => s"\1()") |> println
# Use match() to get the matched substring from a string.

그외 문자열 관련 함수에서 정규식 사용

문자열에서 특정 부분열을 찾는 find*()류 함수 중에서도 몇 가지 함수는 부분 문자열 대신에 정규식을 인자로 받을 수 있다.

  • findfirst(p, s) : 패턴이 최초로 나타나는 영역을 찾고 그 구간의 Range를 리턴한다.
  • findnext(p, s, start=) : 특정 위치 이후로 패턴이 나타나는 영역을 찾는다.
  • findall(p, s) : 패턴이 매치되는 모든 영역의 Range를 찾는다.

참고로 이런 함수들에서 어떤 것이 Regex 객체를 인자로 받을 수 있는지 알아내는 방법을 소개한다. Juila에서는 같은 이름의 함수도 인자의 타입이나 개수에 따라서 여러 정의를 가질 수 있다. 이 때 각각의 구현을 ‘메소드’라 한다. methods(f) 함수는 주어진 함수의 모든 메소드를 찾아서 리턴한다. 이 결과를 사용해서 특정 타입을 지원하는 메소드가 있는지 검사할 수 있다.

usingRegex(f) = begin
  ms = repr.(methods(f) |> collect)
  filter(ms) do item
    occursin("regex", item)
  end
end
usingRegex(findall)
# 1-element Array{String,1}:
#  "findall(t::Union{Regex, AbstractString}, s::AbstractString; overlap) in Base at # regex.jl:361"
usingRegex(findprev)
# String[]

댓글 남기기