이번 글에서는 루비(Ruby)의 grep 메서드에 대해 자세히 알아보겠습니다.
이 메서드는 어떤 일을 할 수 있을까요?
grep을 사용하면 배열(Array), 범위(Range)처럼 열거 가능한(Enumerable) 객체를 손쉽게 필터링할 수 있습니다.
"그런데 select도 이미 같은 역할을 하지 않나요?"
맞습니다. 하지만 grep은 select와 다른 방식으로 동작하며, 결과물도 다르게 나타납니다.
바로 예제를 통해 살펴보겠습니다.
Ruby grep 메서드 실전 예제
다음과 같은 배열이 있다고 가정해 봅시다.
objects = ["a", "b", "c", 1, 2, 3, nil]
grep을 사용하면 문자열만 깔끔하게 추출할 수 있습니다:
objects.grep(String) # ["a", "b", "c"]
정수(Integer)만 추출하는 것도 간단합니다:
objects.grep(Integer) # [1, 2, 3]
nil이 아닌 요소만 남기고 싶다면 grep_v를 사용하세요:
objects.grep_v(NilClass) # ["a", "b", "c", 1, 2, 3]
정규표현식과 함께 사용하기
단어로 이루어진 배열이 있다면:
fruit = ["apple", "orange", "banana"]
'a'로 시작하는 단어를 찾을 수 있습니다:
fruit.grep(/^a/) # ["apple"]
'e'로 끝나는 단어도 마찬가지 방식으로 검색할 수 있습니다:
fruit.grep(/e$/) # ["apple", "orange"]
범위(Range)로 숫자 필터링하기
숫자 목록이 있을 때:
numbers = [9, 10, 11, 20]
특정 범위에 포함된 숫자만 손쉽게 골라낼 수 있습니다:
numbers.grep(5..10) # [9, 10]
여기에 블록을 함께 사용하면 map과 select를 하나로 합친 효과를 얻을 수 있습니다:
numbers.grep(5..10) { |n| n * 2 }
# [18, 20]
조금 더 세련되게 활용하고 싶다면 람다(Lambda)를 넘겨볼 수도 있습니다:
times_two = ->(x) { x * 2 }
numbers.grep(5..10, ×_two)
# [18, 20]
꽤 강력하지 않나요?
grep vs select: 두 메서드의 차이점 이해하기
그렇다면 grep은 내부적으로 어떻게 작동할까요?
핵심은 루비의 === 메서드, 즉 '삼중 등호(triple equals)'에 있습니다.
grep은 인자로 전달받은 객체에 대해 이 === 메서드를 호출합니다.
흥미롭게도 클래스, 정규표현식, 범위(Range)는 모두 === 메서드를 구현하고 있습니다.
그리고 각자 자신에게 맞는 방식으로 이 메서드를 구현해 놓았습니다.
예를 들어:
- 클래스(
Integer,Array등): 주어진 객체의 클래스와 일치하는지 비교합니다. - 범위(Range): 해당 값이 범위 안에 포함되는지 확인합니다.
- 정규표현식: 패턴과 일치(match)하는지 검사합니다.
따라서 다음 코드는:
objects.grep(Integer)
사실상 아래 코드와 동일한 의미입니다:
objects.select { |obj| Integer === obj }
솔직히 말씀드리면, 저도 한동안 grep이 다소 헷갈렸습니다...
바로 이 === 원리를 이해하기 전까지요.
select 메서드는 블록의 평가 결과를 기준으로 리스트를 필터링합니다. 블록이 true를 반환하면 해당 요소가 선택되는 방식입니다.
반면 grep은 단순히 === 연산자로 두 대상을 비교할 뿐입니다.
그래서 이렇게 말할 수 있습니다. grep은 select의 더욱 특수화된 버전이라고요!
Ruby grep 마인드맵

영상 튜토리얼
마무리 정리
지금까지 루비의 grep 메서드에 대해 알아보았습니다! 작동 원리만 제대로 이해하면 정말 유용하게 활용할 수 있는 메서드입니다.
배운 내용이 몸에 익도록 반드시 직접 코드를 작성해 보며 연습해 보세요.
연습이야말로 완벽한 숙달을 위한 최고의 지름길입니다!