Set 클래스
Set 클래스
Set은 중복 없는 정렬되지 않은 값들의 컬렉션이에요. 직관적으로 쓰기 좋은 Array의 연산 기능과, 빠른 조회가 되는 Hash를 섞어 놓은 느낌의 클래스죠.
출처: Ruby 4.0 API
본문
Set은 중복 없는 정렬되지 않은 값들의 컬렉션을 구현하는 클래스예요. Array의 직관적인 상호 연산 기능과 Hash의 빠른 조회를 결합한 형태죠.
Set은 each를 구현한 Enumerable 객체와 함께 쓰기 쉽게 설계됐어요. 초기화 메서드와 이항 연산자 대부분은 배열·셋뿐 아니라 일반 Enumerable 객체도 받아들여요. Enumerable 객체는 to_set 메서드로 Set으로 바꿀 수 있어요.
Set은 저장에 Hash와 비슷한 자료구조를 쓰는데, 값 없이 키만 있다는 점이 달라요.
- 요소의 동등성은
Object#eql?과Object#hash에 따라 결정돼요. 요소를 정체성(identity)으로 비교하게 하려면Set#compare_by_identity를 쓰세요. Set은 저장되는 동안 각 요소의 정체성이 변하지 않는다고 가정해요. 저장된 요소를 수정하면Set이 신뢰할 수 없는 상태가 돼요.- 문자열을 저장할 때는, 원본 문자열이 이미 frozen이 아니라면 복사본을 frozen 상태로 만들어 저장해요.
비교 (Comparison)
비교 연산자 <, >, <=, >=는 {proper_,}{subset?,superset?} 메서드들의 줄임 표현이에요. <=> 연산자는 이 순서를 반영해요. 두 셋이 전혀 겹치는 요소가 없다면({x, y} vs {x, z} 같은 경우) nil을 반환해요.
예시 (Example)
s1 = Set[1, 2] #=> Set[1, 2]
s2 = [1, 2].to_set #=> Set[1, 2]
s1 == s2 #=> true
s1.add("foo") #=> Set[1, 2, "foo"]
s1.merge([2, 6]) #=> Set[1, 2, "foo", 6]
s1.subset?(s2) #=> false
s2.subset?(s1) #=> true
Set 상속 (Inheriting from Set)
Ruby 4.0(2025년 12월 릴리스) 이전의 Set은 덜 효율적인 다른 구현이었어요. 4.0에서 C로 다시 구현되면서 일부 핵심 메서드의 동작도 조정됐죠.
하위 호환성을 위해, Set에서 상속받은 클래스에는 추가 모듈 Set::SubclassCompatible이 포함돼요. 이 모듈은 상속 클래스의 동작과 내부 메서드 이름을 Ruby 4.0 이전과 비슷하게 만들어 줘요.
예를 들어 inspect 동작에서 바로 확인할 수 있어요:
p Set[1, 2, 3]
# prints "Set[1, 2, 3]"
class MySet < Set
end
p MySet[1, 2, 3]
# prints "#<MySet: {1, 2, 3}>", like it was in Ruby 3.4
새 코드에서 하위 호환성이 필요 없다면, "호환 레이어"를 피하기 위해 Set::CoreSet에서 상속받는 걸 권장해요:
class MyCoreSet < Set::CoreSet
end
p MyCoreSet[1, 2, 3]
# prints "MyCoreSet[1, 2, 3]"
Set의 메서드 (Set's methods)
먼저 다른 곳에 있는 것부터 볼게요. Set 클래스는:
- 클래스
Object에서 상속받아요. - 수십 개의 추가 메서드를 제공하는
Enumerable모듈을 포함해요.
특히 Set 클래스는 가져오기(자체 fetch)나 반복 전용 메서드가 많지 않아요. 대신 Enumerable에 있는 것들에 의존하죠.
여기서 Set 클래스가 제공하는 메서드는 이렇게 나뉘어요:
- Set 생성
- Set 연산
- 비교
- 조회
- 할당
- 삭제
- 변환
- 반복
- 그 외
Set 생성 메서드
::[]: 주어진 객체들을 담은 새 셋을 반환해요.::new: 블록이 없으면 주어진 객체들을, 블록이 있으면 블록의 반환값들을 담은 새 셋을 반환해요.
Set 연산 메서드
|(union,+의 별칭):self와 주어진 enumerable의 모든 요소를 담은 새 셋을 반환해요(중복 없음).&(intersection의 별칭):self와 주어진 enumerable에 공통으로 있는 모든 요소를 담은 새 셋을 반환해요.-(difference의 별칭): 주어진 enumerable에 있는 요소를 모두 뺀self의 복사본을 반환해요.^:self와 주어진 enumerable에서 둘 다에 공통인 것을 제외한 모든 요소를 담은 새 셋을 반환해요.
비교 메서드
<=>:self가 주어진 객체보다 작으면 -1, 같으면 0, 크면 1을 반환해요.==:self와 주어진 enumerable이Object#eql?에 따라 같은지 여부를 반환해요.compare_by_identity?: 셋이 요소를 비교할 때 정체성만 고려하는지 여부를 반환해요.
조회 메서드
length(size의 별칭): 요소의 개수를 반환해요.empty?: 셋에 요소가 없는지 여부를 반환해요.include?(member?,===의 별칭): 주어진 객체가 셋의 요소인지 여부를 반환해요.subset?(<=의 별칭): 주어진 객체가 셋의 부분집합인지 여부를 반환해요.proper_subset?(<의 별칭): 주어진 enumerable이 셋의 진부분집합(proper subset)인지 여부를 반환해요.superset?(>=의 별칭): 주어진 enumerable이 셋의 상위집합인지 여부를 반환해요.proper_superset?(>의 별칭): 주어진 enumerable이 셋의 진상위집합인지 여부를 반환해요.disjoint?: 셋과 주어진 enumerable이 공통 요소가 없으면true, 있으면false를 반환해요.intersect?: 셋과 주어진 enumerable이 공통 요소가 하나라도 있으면true, 없으면false를 반환해요.
할당 메서드
add(<<의 별칭): 주어진 객체를 셋에 추가하고self를 반환해요.add?: 주어진 객체가 셋에 없으면 추가하고self를, 이미 있으면nil을 반환해요.merge: 주어진 각 enumerable 객체의 요소를 셋에 합치고self를 반환해요.replace: 셋의 내용물을 주어진 enumerable의 내용물로 바꿔요.
삭제 메서드
clear: 셋의 모든 요소를 제거하고self를 반환해요.delete: 주어진 객체를 셋에서 제거하고self를 반환해요.delete?: 주어진 객체가 셋에 있으면 제거하고self를, 없으면nil을 반환해요.subtract: 주어진 각 객체를 셋에서 제거하고self를 반환해요.delete_if: 주어진 블록이 지정한 요소들을 제거해요.select!(filter!의 별칭): 주어진 블록이 지정하지 않은 요소들을 제거해요.keep_if: 주어진 블록이 지정하지 않은 요소들을 제거해요.reject!: 주어진 블록이 지정한 요소들을 제거해요.
변환 메서드
classify: 주어진 블록에 따라 요소를 분류한 해시를 반환해요.collect!(map!의 별칭): 각 요소를 블록 반환값으로 바꿔요.divide: 주어진 블록에 따라 요소를 분류한 해시를 반환해요.classify와 달리 블록이 인자를 하나 또는 둘 받을 수 있어요.flatten:self를 재귀적으로 평탄화한 새 셋을 반환해요.flatten!:self안의 각 중첩 셋을 그 셋의 요소들로 교체해요.inspect(to_s의 별칭): 요소를 보여주는 문자열을 반환해요.join: 모든 요소를 필요에 따라 문자열로 변환해 주어진 레코드 구분자로 연결한 문자열을 반환해요.to_a: 셋의 모든 요소를 담은 배열을 반환해요.to_set: 인자와 블록이 없으면self를 반환해요. 블록이 있으면 블록 반환값들로 이루어진 새 셋을 반환해요.
반복 메서드
each: 셋의 각 요소마다 블록을 호출하고self를 반환해요.
기타 메서드
reset: 내부 상태를 초기화해요. 셋의 요소가 된 뒤 객체가 수정됐을 때 유용해요.
Public Class Methods
Set[*objects] → new_set
주어진 객체들로 채워진 새 Set 객체를 반환해요. Set::new를 참고하세요.
new → new_set
주어진 enumerable 객체의 요소를 담은 새 셋을 만들어요.
블록을 주면 enum의 요소를 블록으로 전처리해요.
Set.new([1, 2]) #=> Set[1, 2]
Set.new([1, 2, 1]) #=> Set[1, 2]
Set.new([1, 'c', :s]) #=> Set[1, "c", :s]
Set.new(1..5) #=> Set[1, 2, 3, 4, 5]
Set.new([1, 2, 3]) { |x| x * x } #=> Set[1, 4, 9]
중복인 1이 하나만 들어가는 것, 블록으로 제곱값을 넣는 걸 확인할 수 있어요.
json_create(hash)
JSON 직렬화에서 Set 객체를 역직렬화할 때 쓰는 메서드예요. Set#as_json과 함께 쓰이며 Marshal을 참고하세요.
Public Instance Methods
set & enum → new_set
셋과 주어진 enumerable 객체에 공통인 요소를 담은 새 셋을 반환해요.
Set[1, 3, 5] & Set[3, 2, 1] #=> Set[3, 1]
Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> Set["a", "b"]
set - enum → new_set
셋을 복제한 뒤 주어진 enumerable 객체에 나타나는 모든 요소를 제거해서 만든 새 셋을 반환해요.
Set[1, 3, 5] - Set[1, 5] #=> Set[3]
Set['a', 'b', 'z'] - ['a', 'c'] #=> Set["b", "z"]
set <=> other → -1, 0, 1, or nil
셋이 같으면 0, 셋이 주어진 셋의 진부분집합이면 -1, 진상위집합이면 1, 둘 다 고유한 요소를 가질 때는 nil을 반환해요.
set == other → true or false
두 셋이 같으면 true를 반환해요.
set ^ enum → new_set
셋과 주어진 enumerable 객체 사이에서 한쪽에만 있는(배타적) 요소를 담은 새 셋을 반환해요. (set ^ enum)은 ((set | enum) - (set & enum))과 같아요.
Set[1, 2] ^ Set[2, 3] #=> Set[3, 1]
Set[1, 'b', 'c'] ^ ['b', 'd'] #=> Set["d", 1, "c"]
set | enum → new_set
셋과 주어진 enumerable 객체의 요소를 합쳐 만든 새 셋을 반환해요.
Set[1, 2, 3] | Set[2, 4, 5] #=> Set[1, 2, 3, 4, 5]
Set[1, 5, 'z'] | (1..6) #=> Set[1, 5, "z", 2, 3, 4, 6]
add(obj) → self
주어진 객체를 셋에 추가하고 self를 반환해요. 여러 요소를 한 번에 추가하려면 Set#merge를 쓰세요.
Set[1, 2].add(3) #=> Set[1, 2, 3]
Set[1, 2].add([3, 4]) #=> Set[1, 2, [3, 4]]
Set[1, 2].add(2) #=> Set[1, 2]
세 번째 예시에서 이미 있는 2를 추가해도 중복이 안 생기는 걸 볼 수 있어요.
add?(obj) → self or nil
주어진 객체를 셋에 추가하고 self를 반환해요. 이미 셋에 있으면 nil을 반환해요.
Set[1, 2].add?(3) #=> Set[1, 2, 3]
Set[1, 2].add?([3, 4]) #=> Set[1, 2, [3, 4]]
Set[1, 2].add?(2) #=> nil
as_json
Set 객체를 직렬화·역직렬화하는 데 Set#as_json과 Set.json_create 메서드를 쓸 수 있어요. Marshal을 참고하세요.
Set#as_json은 self를 직렬화해서 self를 나타내는 2-요소 해시를 반환해요:
require 'json/add/set'
x = Set.new(%w/foo bar baz/).as_json
# => {"json_class"=>"Set", "a"=>["foo", "bar", "baz"]}
JSON.create는 그런 해시를 역직렬화해서 Set 객체를 반환해요:
Set.json_create(x) # => #<Set: {"foo", "bar", "baz"}>
classify { |o| ... } → hash
주어진 블록의 반환값으로 셋을 분류하고 {값 => 요소 집합} 쌍의 해시를 반환해요. 블록은 셋의 각 요소마다 한 번씩 호출되며 요소를 인자로 받아요.
files = Set.new(Dir.glob("*.rb"))
hash = files.classify { |f| File.mtime(f).year }
hash #=> {2000 => Set["a.rb", "b.rb"],
# 2001 => Set["c.rb", "d.rb", "e.rb"],
# 2002 => Set["f.rb"]}
블록이 없으면 enumerator를 반환해요.
clear → self
모든 요소를 제거하고 self를 반환해요.
set = Set[1, 'c', :s] #=> Set[1, "c", :s]
set.clear #=> Set[]
set #=> Set[]
collect! { |o| ... } → self
요소를 collect가 반환한 값들로 교체해요. 블록이 없으면 enumerator를 반환해요.
compare_by_identity → self
셋이 요소를 정체성(identity)으로 비교하게 만들고 self를 반환해요.
compare_by_identity? → true or false
셋이 요소를 정체성으로 비교할 것이면 true를 반환해요. Set#compare_by_identity도 함께 보세요.
delete(obj) → self
주어진 객체를 셋에서 삭제하고 self를 반환해요. 여러 항목을 한 번에 삭제하려면 subtract를 쓰세요.
delete?(obj) → self or nil
주어진 객체를 셋에서 삭제하고 self를 반환해요. 셋에 없으면 nil을 반환해요.
delete_if { |o| ... } → self
블록이 true로 평가되는 셋의 모든 요소를 삭제하고 self를 반환해요. 블록이 없으면 enumerator를 반환해요.
disjoint?(set) → true or false
셋과 주어진 enumerable이 공통 요소가 하나도 없으면 true를 반환해요. intersect?의 반대예요.
Set[1, 2, 3].disjoint? Set[3, 4] #=> false
Set[1, 2, 3].disjoint? Set[4, 5] #=> true
Set[1, 2, 3].disjoint? [3, 4] #=> false
Set[1, 2, 3].disjoint? 4..5 #=> true
divide { |o1, o2| ... } → set
주어진 블록이 정의한 공통성에 따라 셋을 부분집합들의 셋으로 나눠요.
블록의 인자 수(arity)가 2이면, block.call(o1, o2)와 block.call(o2, o1)이 모두 true일 때 요소 o1, o2가 공통이라고 해요. 그 외에는 block.call(o1) == block.call(o2)일 때 공통이라고 해요.
numbers = Set[1, 3, 4, 6, 9, 10, 11]
set = numbers.divide { |i,j| (i - j).abs == 1 }
set #=> Set[Set[1],
# Set[3, 4],
# Set[6],
# Set[9, 10, 11]]
블록이 없으면 enumerator를 반환해요.
each { |o| ... } → self
셋의 각 요소마다 블록을 한 번씩 호출하며 요소를 인자로 넘겨요. 블록이 없으면 enumerator를 반환해요.
empty? → true or false
셋에 요소가 없으면 true를 반환해요.
flatten → set
셋의 복사본으로, 포함된 각 셋을 재귀적으로 평탄화한 새 셋을 반환해요.
flatten! → self
Set#flatten과 동일하지만 결과를 그 자리에서 수신자에 적용해요. 수정된 게 없으면 nil을 반환해요.
hash → integer
셋의 해시 코드를 반환해요.
include?(item) → true or false
셋에 주어진 객체가 있으면 true를 반환해요:
Set[1, 2, 3].include? 2 #=> true
Set[1, 2, 3].include? 4 #=> false
include?와 member?는 다른 Enumerable처럼 ==로 요소 동등성을 검사하지 않는다는 점을 주의하세요.
이 메서드는 ===의 별칭이라 case 표현식에서 쓸 수 있어요:
case :apple
when Set[:potato, :carrot]
"vegetable"
when Set[:apple, :banana]
"fruit"
end
# => "fruit"
Enumerable#include?도 함께 보세요.
inspect → new_string
셋의 항목을 담은 새 문자열을 반환해요:
s = Set.new
s.inspect # => "Set[]"
s.add(1)
s.inspect # => "Set[1]"
s.add(2)
s.inspect # => "Set[1, 2]"
intersect?(set) → true or false
셋과 주어진 enumerable이 공통 요소를 하나라도 가지면 true를 반환해요.
Set[1, 2, 3].intersect? Set[4, 5] #=> false
Set[1, 2, 3].intersect? Set[3, 4] #=> true
Set[1, 2, 3].intersect? 4..5 #=> false
Set[1, 2, 3].intersect? [3, 4] #=> true
join(separator=nil) → new_string
셋의 각 요소를 문자열로 변환해서 만든 문자열을 반환해요.
keep_if { |o| ... } → self
블록이 false로 평가되는 셋의 모든 요소를 삭제하고 self를 반환해요. 블록이 없으면 enumerator를 반환해요.
merge(*enums, **nil) → self
주어진 enumerable 객체들의 요소를 셋에 합치고 self를 반환해요.
proper_subset?(set) → true or false
셋이 주어진 셋의 진부분집합이면 true를 반환해요.
proper_superset?(set) → true or false
셋이 주어진 셋의 진상위집합이면 true를 반환해요.
reject! { |o| ... } → self
Set#delete_if와 동일하지만 변경이 없으면 nil을 반환해요. 블록이 없으면 enumerator를 반환해요.
replace(enum) → self
셋의 내용물을 주어진 enumerable 객체의 내용물로 바꾸고 self를 반환해요.
set = Set[1, 'c', :s] #=> Set[1, "c", :s]
set.replace([1, 2]) #=> Set[1, 2]
set #=> Set[1, 2]
reset → self
기존 요소를 수정한 뒤 내부 상태를 초기화하고 self를 반환해요. 요소가 다시 인덱싱되고 중복이 제거돼요.
select! { |o| ... } → self
Set#keep_if와 동일하지만 변경이 없으면 nil을 반환해요. 블록이 없으면 enumerator를 반환해요.
size → integer
요소의 개수를 반환해요.
subset?(set) → true or false
셋이 주어진 셋의 부분집합이면 true를 반환해요.
subtract(enum) → self
주어진 enumerable 객체에 나타나는 모든 요소를 삭제하고 self를 반환해요.
superset?(set) → true or false
셋이 주어진 셋의 상위집합이면 true를 반환해요.
to_a → array
셋의 모든 요소를 담은 배열을 반환해요.
Set[1, 2].to_a #=> [1, 2]
Set[1, 'c', :s].to_a #=> [1, "c", :s]
to_json
self를 나타내는 JSON 문자열을 반환해요:
require 'json/add/set'
puts Set.new(%w/foo bar baz/).to_json
Output:
{"json_class":"Set","a":["foo","bar","baz"]}
to_set(klass = Set, *args, &block) → self or new_set
인자 없이 호출하면 self를 반환해요("셋 또는 셋으로 변환 가능한" 인자를 받는 메서드에서 덕 타이핑용으로 쓰죠).
인자가 있는 형태는 비권장(deprecated) 이에요. klass.new(self, *args, &block)으로 셋을 다른 셋으로 변환해요.