오늘은 slice_before, slice_when, slice_after 세 가지 메서드에 대해 알아보겠습니다. 이 메서드들은 배열이나 기타 열거형(enumerable)의 요소들을 임의의 기준에 따라 그룹으로 묶어야 할 때 매우 유용합니다.
Array#slice는 이미 익숙하실 겁니다. 인덱스 범위를 지정해 배열의 일부분을 추출할 수 있는 메서드죠:
a = ["a", "b", "c"]
a.slice(1, 2)
# => ["b", "c"]
유용한 메서드이지만, 열거형에는 사용할 수 없습니다. 열거형에는 인덱스가 없기 때문입니다.
반면 slice_before, slice_when, slice_after 메서드는 인덱스에 의존하지 않기 때문에 어떤 열거형에도 적용할 수 있습니다. 하나씩 살펴보겠습니다!
Enumerable#slice_before 사용법
Enumerable#slice_before는 조건과 일치하는 지점의 '직전'에서 열거형을 분할해 그룹을 만듭니다.
조건 일치 여부는 === 연산자로 판단하기 때문에 다양한 종류의 값으로 매칭할 수 있다는 것이 특징입니다.
값(Value) 매칭
가장 기본적으로 단일 값으로 매칭할 수 있습니다. 당연하죠? :)
a = ["a", "b", "c"]
a.slice_before("b").to_a
# => [["a"], ["b", "c"]]
정규표현식 매칭
더 복잡한 텍스트 매칭이 필요하다면 정규표현식을 활용할 수 있습니다.
a = ["000", "b", "999"]
a.slice_before(/[a-z]/).to_a
# => [["000"], ["b", "999"]]
범위(Range) 매칭
숫자 데이터를 다룰 때는 범위(range)를 기준으로 배열을 나눌 수 있습니다.
a = [100, 200, 300]
a.slice_before(150..250).to_a
# => [[100], [200, 300]]
클래스(Class) 매칭
다소 생소하게 느껴질 수 있지만, 이 역시 === 연산자의 동작 방식에 충실한 활용법입니다.
a = [1, "200", 1.3]
a.slice_before(String).to_a
# => [[1], ["200", 1.3]]
블록 사용하기
앞서 소개한 방법들만으로는 원하는 조건을 표현하기 어렵다면, 블록을 사용해 프로그래밍 방식으로 매칭 로직을 직접 정의할 수 있습니다.
a = [1, 2, 3, 4, 5]
a.slice_before { |item|
item % 2 == 0
}.to_a
# => [[1], [2, 3], [4, 5]]
Enumerable#slice_after 사용법
Enumerable#slice_after는 Enumerable#slice_before와 동작 방식이 거의 동일합니다. 단 한 가지 차이점이 있다면, 조건과 일치하는 지점의 '직후'에서 분할한다는 것뿐이죠.
a = ["a", "b", "c"]
a.slice_after("b").to_a
# => [["a", "b"], ["c"]]
물론 정규표현식, 범위, 블록을 사용한 매칭도 모두 가능합니다. 예제가 지루해질 수 있으니 여기서는 생략하겠습니다.
Enumerable#slice_when 사용법
Enumerable#slice_when은 앞선 두 메서드와는 조금 다른 방식으로 동작합니다. 배열의 개별 요소가 아닌, 인접한 두 요소의 '쌍'을 기준으로 매칭합니다.
즉, 요소들 사이의 '경계'를 기준으로 그룹을 나눌 수 있다는 의미입니다.
예를 들어, 아래 코드는 인접한 요소와의 '근접성'을 기준으로 항목들을 묶습니다.
a = [1, 2, 3, 100, 101, 102]
# 인접한 두 요소의 차이가 10보다 크면 새로운 그룹 생성
a.slice_when { |x, y|
(y - x) > 10
}.to_a
# => [[1, 2, 3], [100, 101, 102]]
더 자세히 알고 싶으시다면 Ruby 공식 문서의 slice_when 항목을 확인해 보세요. 훌륭한 코드 예제가 여러 개 담겨 있습니다.
배열 vs 열거형
지금까지의 예제에서는 이해하기 쉽도록 주로 배열을 사용했습니다. 하지만 slice_before, slice_when, slice_after는 어떤 열거형에도 사용할 수 있다는 점을 꼭 기억하세요.
예를 들어, 여러 개의 이메일이 담긴 파일이 있다면 slice_before를 사용해 개별 이메일로 손쉽게 분리할 수 있습니다. 아래 코드는 공식 문서에서 가져온 예제입니다.
open("mbox") { |f|
f.slice_before { |line|
line.start_with?("From ")
}.each { |mail|
puts mail
}
}
마지막으로, 슬라이스 메서드들은 배열이 아닌 열거형(enumerator)을 반환한다는 점도 눈여겨보세요. 덕분에 반환된 결과에 map, each 등 익숙한 열거형 메서드를 자유롭게 체이닝할 수 있습니다. 심지어 결과를 한 번 더 분할하는 것도 가능하죠! :)