루비 해시(Ruby Hash)란 무엇일까요?
해시는 데이터를 고유한 키(key)-값(value) 쌍의 형태로 저장하는 자료구조입니다. 배열과 달리 숫자 인덱스가 없으며, 대신 키를 사용하여 해시의 값에 접근합니다.
해시가 활용되는 대표적인 예
- 국가 이름과 해당 국가 코드 목록 (예: ES ⇾ 스페인)
- 사전 — 각 단어마다 여러 개의 정의가 연결되어 있는 형태
- 도메인 이름과 그에 대응하는 IP 주소
적절한 상황에서 해시를 활용하면 루비 프로그램의 실행 속도를 크게 높일 수 있습니다.
다시 말해:
데이터가 사전과 같은 형태이거나, 키와 연관된 값으로 그룹화할 수 있는 구조라면 해시가 최적의 선택입니다.
해시 생성 방법
해시가 무엇인지는 이제 감을 잡았으니, 실제로 어떻게 만드는지 알아보겠습니다.
다음과 같이 생성합니다:
{}
위 코드는 빈 해시입니다.
키-값 쌍 세 개를 가진 해시는 다음과 같습니다:
{ a: 1, b: 2, c: 3 }
여기서 a는 키이고, 1은 그 키에 대응하는 값입니다. 각 키-값 쌍은 쉼표로 구분됩니다.
자, 이제 루비 프로젝트에서 해시를 실제로 활용하는 방법과 자주 사용되는 해시 메서드들을 살펴보겠습니다.
루비 해시에 값 저장하기
앞서 본 것처럼 초기값을 설정한 상태로 해시를 만들 수 있습니다.
또 다른 예제입니다:
fruits = { coconut: 1, apple: 2, banana: 3 }
기존 해시에 새로운 값을 추가할 수도 있습니다.
다음과 같이 말이죠:
fruits[:orange] = 4
여기서 :orange는 해시 키이고, 4는 그에 대응하는 값입니다.
값을 조회할 때는 콜론이 단어 앞에 붙고(
:orange), 해시를 생성할 때는 단어 뒤에 붙는(orange:) 이유가 궁금하신가요? 이는 해시 로켓(=>) 기호 없이 해시를 더 깔끔하게 작성할 수 있게 해주는 문법입니다. 물론=>방식도 유효하지만, 비교적 오래된 방식입니다.
해시의 값으로는 모든 루비 객체를 사용할 수 있습니다.
예를 들면:
- 문자열(String)
- 정수(Integer) 및 부동소수점(Float)
- 배열(Array)
키 역시 어떤 객체든 가능하지만, 실무에서는 심볼(:banana 같은 형태)과 문자열이 가장 흔하게 사용됩니다.
기억하세요…
키는 고유합니다. 즉, :orange 키나 :apple 키는 각각 하나만 존재할 수 있습니다.
같은 키를 두 번 추가하면 기존 값이 새로운 값으로 덮어써집니다.
해시에서 값 가져오기
해시의 값은 반드시 키를 통해 접근합니다.
만약 값을 직접 순회하며 접근해야 하는 상황이라면, 해시가 데이터 구조로 적합하지 않을 수 있습니다.
예제:
fruits[:orange] # 4
이것이 바로 해시의 핵심입니다. 키를 통해 빠르게 항목을 조회하는 것이죠.
존재하지 않는 키에 접근하면 nil이 반환됩니다.
fruits[:peach] # nil
대안으로 fetch 메서드를 사용하면 기본값(default value)을 지정할 수 있습니다.
예제:
fruits.fetch(:peach, 0)
만약 두 번째 인자인 기본값 없이 fetch를 호출하면, 루비는 KeyError 예외를 발생시킵니다.
이는 오히려 유용한데, 어떤 키가 누락되었는지 명확히 알 수 있기 때문입니다.
두 개의 루비 해시 병합하기
두 개의 해시를 하나의 새로운 해시로 합칠 수 있습니다.
어떤 메서드를 사용할까요?
이름에서 바로 알 수 있듯이, merge 메서드를 사용합니다.
사용 방법은 다음과 같습니다:
defaults = { a: 1, b: 2, c: 3 }
preferences = { c: 4 }
defaults.merge!(preferences)
# {:a=>1, :b=>2, :c=>4}
키는 고유하기 때문에, 나중에 들어온 값이 기존 값을 덮어쓴다는 점에 주목하세요.
이 특성을 활용하면 재미있는 해결책을 만들 수 있습니다. 예를 들어 "기본 설정(defaults)" 해시를 만들어 두고, 사용자가 자신만의 해시를 전달하여 기본값을 덮어쓰도록 하는 방식이 있습니다.
키 병합 과정을 더 세밀하게 제어하고 싶다면 블록(block)을 전달할 수도 있습니다.
다음과 같이 말이죠:
defaults.merge!(preferences) { |key, old, new| [old, new].max }
여기서 old는 defaults에서 넘어온 값이고, new는 preferences에서 넘어온 값입니다.
하나의 키에 여러 값 담기
사전을 생각해 보세요.
단어는 고유하지만, 하나의 단어에 여러 개의 값(정의)이 연결될 수 있습니다.
루비에서도 이것이 가능합니다!
예제:
dictionary = {
opportunity: [
"a set of circumstances that makes it possible to do something",
"a situation or condition favorable for attainment of a goal"
],
creativity: [
"the use of imagination or original ideas to create something new",
"the ability to create",
"the process where new ideas emerge from combining existing ideas in new ways"
]
}
dictionary[:creativity][1]
dictionary[:creativity]는 배열을 반환하고, 여기에 [1]을 붙이면 그 배열의 두 번째 요소에 접근하게 됩니다.
즉:
키는 심볼이고, 값은 배열입니다. 해시에 접근하면 배열이 반환되며, 이후에는 일반적인 배열처럼 다루면 됩니다.
해시 정렬하기
배열을 정렬할 수 있다는 것은 잘 알고 있겠지만, 해시도 정렬할 수 있다는 사실을 알고 계셨나요?
해시에 sort를 호출하면 키를 기준으로 정렬됩니다.
예제:
{ b: 1, a: 2 }.sort
# [[:a, 2], [:b, 1]]
물론 값 기준으로도 정렬할 수 있습니다:
{ c: 3, b: 1, a: 2 }.sort_by(&:last)
눈치채셨겠지만, 해시를 정렬한 결과는 또 다른 해시가 아닙니다.
배열입니다!
하지만 to_h 메서드를 사용하면 이 배열을 다시 해시로 변환할 수 있습니다.
해시의 모든 키와 값 가져오기
해시의 모든 키 목록이 필요하신가요? 좋은 소식이 있습니다. 바로 그런 용도의 메서드가 존재합니다!
바로 이것입니다:
{ apple: 1, banana: 2 }.keys
# [:apple, :banana]
값들만 담은 배열을 반환하는 메서드도 있습니다:
{ apple: 1, banana: 2 }.values
# [1, 2]
키 목록 전체가 아니라 특정 키의 존재 여부만 확인하고 싶다면 key? 메서드를 사용하세요.
이 메서드는 true 또는 false를 반환합니다.
마무리
지금까지 키-값 쌍으로 구성된 유용한 자료구조인 루비 해시에 대해 배웠습니다. 키를 통해 해시에 접근하는 방법과, 해시에 새로운 데이터를 저장하는 방법도 익혔습니다.
이제 irb(또는 pry)를 열고 직접 해시를 가지고 놀아보세요!
읽어주셔서 감사합니다 🙂