Hash

Hash

Hash는 각 고유한 키를 특정 값에 매핑하는 객체예요. 배열과 비슷하지만 배열의 인덱스는 항상 정수인 반면, 해시의 키는 (거의) 모든 객체가 될 수 있어요.

출처: Ruby 4.0 API

본문

해시는 Object 클래스를 상속받고 Enumerable 모듈을 포함해요. 여기서는 해시 데이터 문법, 키의 동치, 기본값 처리, 그리고 모든 인스턴스 메서드를 카테고리별로 정리할게요.

해시 데이터 문법

가장 오래된 문법은 "hash rocket" =>를 쓰는 방식이에요:

h = {:foo => 0, :bar => 1, :baz => 2}
h # => {foo: 0, bar: 1, baz: 2}

키가 심볼이라면 JSON 스타일 문법도 쓸 수 있어요. 이때 bareword는 심볼이 돼요:

h = {foo: 0, bar: 1, baz: 2}
# 문자열도 키로 쓸 수 있어요.
h = {'foo': 0, 'bar': 1, 'baz': 2}
# 두 문법을 섞어도 되고요.
h = {foo: 0, :bar => 1, 'baz': 2}

JSON 스타일은 bareword나 문자열이 아닌 키에는 쓸 수 없어요(예: {0: 'zero'}SyntaxError).

값을 생략하면 키 이름으로 컨텍스트에서 값을 가져와요:

x = 0; y = 100
h = {x:, y:}
h # => {x: 0, y: 100}

해시는 객체에 이름을 붙이거나, 메서드 인자에 이름을 붙이는 데 써요. 메서드 호출의 마지막 인자가 해시면 중괄호를 생략할 수 있어요:

def some_method(hash)
  p hash
end
some_method(foo: 0, bar: 1) # => {foo: 0, bar: 1}

해시 만들기

  • 해시 리터럴(중괄호)로 만들 수 있어요: {} 또는 {foo: 0}.
  • Kernel#Hash로 객체를 변환할 수 있어요.
  • Hash.new로 새 해시를 만들 수 있어요.
  • Hash[...]로 초기 항목을 넣어 만들 수 있어요.

항목 순서 (Entry Order)

해시는 항목을 생성된 순서대로 보여줘요. each, each_key, each_pair, each_value, shift, keys, values, inspect가 모두 이 순서를 따라요.

  • 새 항목은 끝에 추가돼요.
  • 값 수정은 순서를 바꾸지 않아요.
  • 삭제된 항목을 다시 만들면 순서가 바뀔 수 있어요 (맨 끝으로 가요).

해시 키

키 동치: 두 객체가 같은 해시 키로 취급되려면 hash 값이 같고 두 객체가 서로 eql?이어야 해요.

활성 키 수정 주의: 사용 중인 키를 수정하면 해시의 인덱스가 손상돼요. 배열 키의 요소를 바꾸면 hash 값이 변해 h[a0]nil을 돌려줘요. 이때 rehash로 복구할 수 있어요:

h.rehash # => {[:bam, :bar]=>0, [:baz, :bat]=>1}

문자열 키는 항상 안전해요. 언프로즌 문자열은 키로 넘기면 복제·프로즌된 문자열로 대체되거든요. compare_by_identity를 쓰면 hash/eql? 대신 객체 id로 비교해서, 사용자 키를 커스텀할 때도 hasheql? 구현이 필요 없어요.

사용자 정의 키: 해시 키로 쓰려면 객체가 hasheql? 메서드를 구현해야 해요. 보통 hash는 데이터 기반으로, eql?은 재정의된 ==에 alias로 만드는 걸 권장해요.

키를 못 찾았을 때 (Key Not Found?)

메서드에 따라 키를 못 찾으면 기본값을 돌려주거나 KeyError를 던져요.

  • nil 반환: [], assoc, dig, values_at.
  • KeyError 발생: fetch, fetch_values.
  • 해시 기본값: [], dig, values_atdefault(기본값)와 default_proc(기본 프로시저)의 영향을 받아요.
    • 임의 키 기본값: default_procnil이고 default가 설정돼 있으면, 아무 키나 없을 때 그 값을 돌려줘요. Hash.new(default_value)default=로 설정.
    • 키별 기본값: Hash.new {|hash, key| ... } 블록이나 default_proc=로 프로시저를 설정하면 키에 따라 값을 다르게 줄 수 있어요. 단 프로시저가 self를 수정하는 건 thread-safe 하지 않아요.

클래스 메서드

Hash → new_empty_hash
Hash[other_hash] → new_hash
Hash[ [*2_element_arrays] ] → new_hash
Hash[*objects] → new_hash

인자 없이 호출하면 빈 해시를, 해시 하나를 주면 그 항목들을 복사한 해시를, 2-요소 배열의 배열을 주면 각 배열을 키-값 쌍으로, 짝수 개의 인자를 주면 연속된 쌍을 키-값으로 만들어요. 규칙에 맞지 않으면 ArgumentError.

new(default_value = nil, capacity: 0) → new_hash
new(capacity: 0) {|self, key| ... } → new_hash

새 빈 해시를 돌려줘요. default_value 인자로 기본값을, 블록으로 기본 프로시저를 설정할 수 있어요. 둘 다 주면 ArgumentError. capacity: 키워드에 양의 정수를 주면 그 개수만큼 재사이즈 없이 담을 용량으로 초기화해요.

ruby2_keywords_hash(hash) → hash
ruby2_keywords_hash?(hash) → true or false

주어진 해시를 복제하고 ruby2_keywords 플래그를 붙이거나 확인해요. 일상용이 아니라 인자 역직렬화/직렬화 같은 특수 상황에서 써요.

try_convert(object) → object, new_hash, or nil

object가 해시면 그대로, :to_hash에 반응하면 변환 결과(해시가 아니면 TypeError), 반응하지 않으면 nil을 돌려줘요.

비교 메서드 (Comparing)

self < other → true or false   # 진부분집합(proper subset)
self <= other → true or false  # 부분집합
self == object → true or false # 동등
self > other_hash → true or false  # 진상위집합
self >= other_hash → true or false # 상위집합

</<=/>/>=는 항목의 부분집합/상위집합 관계를 판별해요 (순서는 무관). ==는 키와 값이 모두 같은지 판별해요 (순서 무관). other가 해시로 변환 불가능하면 TypeError.

조회 메서드 (Querying)

empty? → true or false
size → integer        # (length와 alias)
any? → true or false
any?(entry) → true or false
any? {|key, value| ... } → true or false
include?(key) → true or false   # (has_key?, member?, key?와 alias)
has_value?(value) → true or false  # (value?와 alias)
eql?(object) → true or false
hash → an_integer    # 해시 코드(정수) 반환
compare_by_identity? → true or false
default → object    # 기본값
default(key) → object
default_proc → proc or nil  # 기본 프로시저

가져오기 메서드 (Fetching)

[] (key) → object                  # (#[key]로 씀)
assoc(key) → entry or nil          # [key, 값] 2-요소 배열이나 nil
dig(key, *identifiers) → object    # 중첩 객체 탐색
fetch(key) → object
fetch(key, default_value) → object
fetch(key) {|key| ... } → object
fetch_values(*keys) → new_array
fetch_values(*keys) {|key| ... } → new_array
key(value) → key or nil            # 값과 일치하는 첫 키
keys → new_array
rassoc(value) → new_array or nil   # 값과 일치하는 첫 [키, 값]
values → new_array
values_at(*keys) → new_array

fetch는 키가 없으면 KeyError(또는 기본값/블록), []는 기본값/nil을 돌려줘요.

할당 메서드 (Assigning)

[]= (key, value) → value        # (store와 alias)
merge(*other_hashes) → new_hash
merge(*other_hashes) {|key, old_value, new_value| ... } → new_hash
update(*other_hashes) → self    # (merge!와 alias)
replace(other_hash) → self      # 내용 전체 교체 (initialize_copy와 alias)

삭제 메서드 (Deleting)

self에서 항목을 제거하는 메서드:

clear → self
compact! → self or nil        # nil 값 항목 제거
delete(key) → value or nil
delete(key) {|key| ... } → object
delete_if {|key, value| ... } → self
select! {|key, value| ... } → self or nil   # (filter!와 alias)
keep_if {|key, value| ... } → self
reject! {|key, value| ... } → self or nil
shift → [key, value]          # 첫 항목 제거·반환

self의 복사본을 돌려주는 메서드:

compact → new_hash
except(*keys) → new_hash
select {|key, value| ... } → new_hash   # (filter와 alias)
reject {|key, value| ... } → new_hash
slice(*keys) → new_hash

반복 메서드 (Iterating)

each_pair {|key, value| ... } → self   # (each와 alias)
each_key {|key| ... } → self
each_value {|value| ... } → self

변환 메서드 (Converting)

flatten(depth = 1) → new_array
inspect → new_string   # (to_s와 alias)
to_a → new_array       # [키, 값] 쌍들의 배열
to_h → self or new_hash
to_h {|key, value| ... } → new_hash
to_hash → self
to_proc → proc         # 키를 값으로 매핑하는 프로시저

키·값 변환 메서드 (Transforming)

invert → new_hash             # 키-값 뒤집기
transform_keys {|old_key| ... } → new_hash
transform_keys(other_hash) → new_hash
transform_keys(other_hash) {|old_key| ... } → new_hash
transform_keys! {|old_key| ... } → self     # (위 variant 동일)
transform_values {|value| ... } → new_hash
transform_values! {|old_value| ... } → self

상태 설정 메서드 (Setting State)

compare_by_identity → self
default = value → object
default_proc = proc → proc
rehash → self   # 각 키의 해시 인덱스를 다시 계산해 테이블 재구축