지난 글에 이어서 해시에서 제공하는 유용한 메서드들을 더 알아보자.
해시에 원하는 키가 있는지 key? 메서드로 확인할 수 있는데, key? 메서드는 Hash 클래스에서 직접 정의한 메서드이고 Enumerable 모듈에서 정의한 메서드인 include?나 member? 메서드를 사용해도 된다.
물론 include?와 member? 메서드 역시 Hash 클래스에서 재정의를 하긴 했지만, 해시에서뿐만 아니라 배열에서도 사용할 수 있기 때문에 코드에서 통일성을 유지하고 싶다면 괜찮은 선택이다.
키 검사처럼 해시에 특정 값이 포함되어 있는지를 확인하고 싶으면 value? 메서드를 사용하면 된다.
앞의 그림에서 Hash 클래스가 정의한 인스턴스 메서드들 중에서 메서드 이름이 ?으로 끝나는 메서드(Predicate 메서드라고 함)들을 보면 has_key? 와 has_value? 도 보이는데,
코드가 좀 더 자연스럽게 읽히길 원한다면 has_key?와 has_value? 메서드를 사용하는 것도 괜찮은 선택이다.
Hash 클래스의 Predicate 메서드(메서드 이름이 ?으로 끝나는 메서드)를 몇 개 더 살펴보자.
해시에 데이터가 몇 개인지는 size나 length 메서드를 사용하면 알 수 있는데, empty? 메서드는 해시가 비어 있으면 true를 반환해 준다.
그리고 any? 메서드는 블록 없이 호출하면 해시의 크기(size)가 0보다 크면 true를 반환해 주고, 블록을 전달하면 블록을 실행한 결괏값이 참으로 평가되는 키-값 쌍이 하나라도 있으면 true를 반환한다.
해시에서 특정 키를 삭제하고 싶다면 delete 메서드를 사용하면 되고, delete 메서드로 키를 삭제할 때 해당 키로 저장되어 있던 값을 돌려준다.
그리고 키와 함께 값도 삭제가 되므로 해당 키로 다시 값을 조회하면 nil이 반환된다.
물론 해시에 기본값이 설정되어 있다면 반환되는 값이 nil이 아닐 수 있다.
merge 메서드를 사용하면 동일한 키에 대해 현재 해시의 값을 다른 해시의 값으로 변경할 수 있고, 현재 해시에 없는 키와 값은 새롭게 추가된다.
그렇지만 호출 대상 해시 자체가 변경되지는 않고 변경된 내용을 반영한 새로운 해시 객체가 반환된다.
만약, 호출 대상 해시 자체를 변경하려면 update 또는 merge! 메서드를 사용하면 된다. merge!는 update 메서드의 별칭이다.
그리고 동일한 키에 대해 단순히 인수로 받은 해시의 값을 사용하는 게 아니라 다른 동작을 원한다면 블록을 전달하면 된다.
아래 그림의 마지막 코드를 보면 블록을 전달하여 키가 동일할 경우 두 해시의 값을 더한 것을 해당 키의 값으로 설정하도록 하였다.
두 해시에서 키 :b가 겹치는데 반환된 해시에서 키 :b의 값은 5가 된 것을 볼 수 있다.
Hash 클래스도 Array 클래스와 마찬가지로 Enumerable 모듈을 인클루드하고 있기 때문에 Enumerable 모듈이 제공하는 많은 메서드들을 사용할 수 있다.
아래 그림을 보면 Enumerable 모듈이 정의한 메서드의 개수가 60 개가 넘고 그중 일부 메서드들은 Hash 클래스에서 재정의 해 놓은 것을 볼 수 있다.
두 배열의 교집합을 돌려주는 & 연산자를 사용하면 Enumerable 모듈에서 정의한 메서드들 중에서 Hash 클래스가 재정의한 메서드들을 쉽게 찾을 수 있다.
select와 reject 메서드는 배열에서 특정 조건으로 요소를 필터링하기 위해, map 메서드는 배열의 요소를 변환하기 위해 많이 사용하는데, 해시에서도 같은 목적으로 그 메서드들을 사용한다.
그런데 반환값에서는 차이가 있는데, select와 reject 메서드는 배열에 대해 호출하면 배열을 결과로 돌려주고 해시에 대해 호출하면 해시를 결과로 돌려준다.
그리고 map 메서드는 배열과 해시 모두에서 결과를 배열로 돌려준다.
호출 후 돌려받은 값의 형태가 내가 원하는 형태가 아니더라도 걱정할 게 없다.
Enumerable 모듈에서 이미 to_a와 to_h 메서드를 정의해 놓았고, Array와 Hash 클래스 모두 Enumerable 모듈을 인클루드하고 있기 때문에 배열을 해시로 또 해시를 배열로 변화하는 것은 어렵지 않다.
해시를 생성하려면 결국 키-값 쌍이 필요한데, 키와 값의 쌍을 담은 이차원 배열에 대해 to_h 메서드를 호출하거나 아니면 to_h에 블록을 전달하고 블록 안에서 키-값 쌍을 담은 배열을 결과로 반환해야 한다.
그리고 Hash 클래스의 클래스 메서드인 [] 연산자를 사용하는 방법도 있는데, 키와 값을 순서대로 하나씩 번갈아 가며 인수로 넘겨주거나 1차원 배열을 스플랫 연산자(*)를 사용하여 각각의 개별 인수로 넘겨줘도 된다.
또 키-값 쌍을 담은 이차원 배열을 인수로 넘겨도 되고 다른 해시를 넘겨서 같은 데이터를 같는 또 다른 해시를 만들 수도 있다.
해시가 마지막 인수일 때는 '{'와 '}'를 생략해도 되므로 아래 그림의 마지막 코드처럼 작성해도 된다.
마지막으로 partition과 group_by 메서드를 살펴보자. 두 메서드 역시 Enumerable 모듈에서 정의한 메서드로서 이미 '배열과 친해지기' 글에서 다뤄본 적이 있다.
partition 메서드는 해시 안의 항목(키-값 쌍)들을 두 개의 그룹으로 나누고 싶을 때 사용한다.
두 개의 그룹은 partition 메서드 호출 시 전달하는 블록의 결괏값이 참이 되는 항목들의 그룹과 거짓이 되는 항목들의 그룹을 말한다.
각 그룹은 배열이고 두 그룹을 다시 배열에 담아 돌려 주기 때문에 배열에 대해 호출한 partition 메서드의 결괏값은 최소 2차원 배열이 되고, 해시에서는 항목 하나(키-값 쌍)가 배열에 담기기 때문에 최소 3차원 배열이 된다.
아래 그림을 보면 해시 안의 항목들을 값이 짝수인 그룹과 홀수인 그룹으로 나눠 각각을 even과 odd 변수에 할당하고 있다.
even과 odd 변수가 참조하는 배열은 키-값 쌍의 배열을 요소로 갖고 있기 때문에 to_h 메서드를 통해 쉽게 해시로 변환이 가능하다.
group_by 메서드는 해시 안의 항목들을 특정 조건에 따라 여러 개의 그룹으로 나누고 싶을 때 사용한다. partition 메서드는 블록의 결괏값이 '참'인지 '거짓'인지를 기준으로 항목들을 두 개의 그룹으로 나누지만,
group_by 메서드는 블록의 결괏값 자체를 기준으로 항목들을 나눈다. 따라서 블록의 결괏값이 boolean이라면, partition 메서드처럼 두 개의 그룹으로 나누게 된다.
group_by 메서드는 그룹 개수만큼의 항목을 포함한 해시를 결과로 돌려주는데, 결과 해시에서 각 키는 해당 그룹으로 모으는 기준이 되는 값(블록의 결괏값)이고, 값은 해당 그룹에 포함된 항목들을 담은 배열이다.
아래 그림의 첫 번째 예는 group_by 메서드를 사용하여 앞의 partition 메서드의 예제처럼 값의 짝수 여부에 따라 항목들을 나누고 있는데, 결과 해시를 보면 각각의 그룹 데이터를 담은 두 개의 항목이 보인다.
그러나 두 번째 예는 해시에 포함된 모든 값이 홀수이기 때문에 group_by 메서드의 결과 해시에는 키가 false인 항목 하나만 들어 있는 걸 볼 수 있다.
마지막 예는 프로그래밍 언어가 발표된 연도를 키로 하고, 해당 프로그래밍 언어의 이름을 값으로 하는 해시를 만들어 발표된 연도를 10년 단위 기준으로 그룹화해보았다.
결과 해시를 보면 예상대로 1970, 1980, 1990년대 새 개의 그룹으로 잘 묶인 것을 볼 수 있다.
그리고 해시의 group_by 메서드가 반환하는 결과 해시에서 값은 배열 형태인데, 이 배열을 해시로 변환하고 싶다면 transform_values 메서드를 사용하면 된다.
transform_values 메서드는 호출 대상 해시 자체를 변경하지는 않고, 값이 변경된 새로운 해시를 생성해서 돌려준다.
만약 값이 아니라 해시의 키를 원하는 값으로 변경하고 싶으면 transform_keys 메서드를 사용하면 된다.
그리고 호출 대상 해시 자체를 변경하는 transform_keys!와 transform_values! 메서드도 존재한다.
아래 그림의 마지막 예제처럼 키와 값 둘 다를 변경하고 싶으면 transform_keys와 transform_values 메서드를 연이어 호출해도 되지만, 대신 map 메서드로 처리한 후 결괏값을 해시로 변환해도 된다.
해시에서 사용할 수 있는 유용한 메서드에 대한 설명은 이것으로 마치고, 다음 글에서는 어떤 객체를 해시의 키로 사용하기 위해서는 해당 객체가 어떤 조건을 만족해야 하는지 살펴보도록 하자.
See you again~~