Bag — 순서 없는 가중치 컬렉션(다중집합)

Bag — 순서 없는 가중치 컬렉션(다중집합)

어떤 요소가 몇 개씩 들어 있는지 셀 때가 있어요. 예를 들어 아침식사에 스팸이 4개, 계란이 1개 들어갔다면 그걸 '스팸 4, 계란 1' 식으로 표현하고 싶죠. 그런 데이터를 담는 타입이 Bag이에요. '가방(bag)에 색깔 구슬이 몇 개씩 들어 있다'는 상상과 잘 맞는, 순서 없이 요소마다 정수 가중치를 붙여주는 불변(immutable) 다중집합(multiset)이에요. Associative를 구현해요.

class Bag does Baggy { }

Bag이 불변이라면, 변경 가능한 버전이 필요할 땐 BagHash를 써요.

본문

Bag은 각 요소에 정수 가중치를 매겨 '그 요소가 몇 복사본인지'를 나타내요. 가중치 있는 무작위 선택, 즉 .pick.roll을 수행할 때 자주 사용돼요.

어떤 타입의 객체/값도 bag 요소가 될 수 있어요. Bag 안에서는 === 연산자로 같다고 판정되는 항목들이 같은 요소로 취급되고, 그 개수가 가중치가 돼요. 그리고 확장된 항목 리스트(순서 없이)도 쉽게 되돌려 받을 수 있어요.

my $breakfast = bag <spam eggs spam spam bacon spam>;

say $breakfast.elems;      # OUTPUT: «3␤»
say $breakfast.keys.sort;  # OUTPUT: «bacon eggs spam␤»

say $breakfast.total;      # OUTPUT: «6␤»
say $breakfast.kxxv.sort;  # OUTPUT: «bacon eggs spam spam spam spam␤»

Bag{ } postcircumfix 연산자(또는 문자열 키용 <>)로 객체 해시처럼 다룰 수 있어요. bag의 요소인 키에 대응하는 정수 가중치를 돌려주고, 요소가 아닌 키에는 0을 돌려줘요.

my $breakfast = bag <spam eggs spam spam bacon spam>;
say $breakfast<bacon>;    # OUTPUT: «1␤»
say $breakfast<spam>;     # OUTPUT: «4␤»
say $breakfast<sausage>;  # OUTPUT: «0␤»

Bag 객체 만들기

bag 서브루틴(또는 그 단축형인 Bag.new)으로 Bag을 만들 수 있어요. 타입과 무관하게 위치 인자들이 bag의 요소가 돼요.

my $n = bag "a" => 0, "b" => 1, "c" => 2, "c" => 2;
say $n.keys.raku;        # OUTPUT: «(:c(2), :b(1), :a(0)).Seq␤»
say $n.keys.map(&WHAT);  # OUTPUT: «((Pair) (Pair) (Pair))␤»
say $n.values.raku;      # OUTPUT: «(2, 1, 1).Seq␤»

또는 기존 객체에 .Bag 강제 변환(또는 함수형 Bag())을 호출해 Bag으로 바꿀 수 있어요. 해시류 객체나 Pair 항목은 키만 요소가 되고 (누적된) 값이 정수 가중치가 돼요.

my $n = ("a" => 0, "b" => 1, "c" => 2, "c" => 2).Bag;
say $n.keys.raku;        # OUTPUT: «("b", "c").Seq␤»
say $n.keys.map(&WHAT);  # OUTPUT: «((Str) (Str))␤»
say $n.values.raku;      # OUTPUT: «(1, 4).Seq␤»

List 같은 다른 타입에 bag 연산자를 쓰면 내부적으로 .Bag을 호출한 것처럼 동작해요. 다만 이 연산자들의 우선순위가 타이트하므로 인자 주변에 괄호가 필요할 수 있어요.

say (1..5) (+) 4;  # OUTPUT: «Bag(1 2 3 4(2) 5)␤»

.new 메서드로도 만들 수 있어요.

my $breakfast = Bag.new( <spam eggs spam spam bacon spam> );

6.d(2019.03 이후)부터는 허용할 값의 타입을 지정하는 매개변수화 문법도 쓸 수 있어요.

# only allow strings (Str) in the Bag
my $breakfast = Bag[Str].new( <spam eggs spam spam bacon spam> );

# only allow whole numbers (Int) in the Bag
my $breakfast = Bag[Int].new( <spam eggs spam spam bacon spam> );
# Type check failed in binding; expected Int but got Str ("spam")

마지막으로 is 트레이트로 해시처럼 가장하는 Bag을 만들 수 있어요.

my %b is Bag = <a b c>;
say %b<a>;  # OUTPUT: «True␤»
say %b<d>;  # OUTPUT: «False␤»

6.d(2019.03 이후)부터는 이 문법에서도 허용할 값 타입을 지정할 수 있어요.

# limit to strings
my %b is Bag[Str] = <a b c>;
say %b<a>;  # OUTPUT: «True␤»
say %b<d>;  # OUTPUT: «False␤»

# limit to whole numbers
my %b is Bag[Int] = <a b c>;
# Type check failed in binding; expected Int but got Str ("a")

연산자

Bag에 적용 가능한 '집합 연산자(set operators)'의 전체 목록은 세트 시맨틱 연산자 문서에서 확인할 수 있어요.

예시:

my ($a, $b) = bag(2, 2, 4), bag(2, 3, 3, 4);

say $a (<) $b;   # OUTPUT: «False␤»
say $a (<=) $b;  # OUTPUT: «False␤»
say $a (^) $b;   # OUTPUT: «Bag(3(2) 2)␤»
say $a (+) $b;   # OUTPUT: «Bag(2(3) 4(2) 3(2))␤»

# Unicode versions:
say $a ⊂ $b;  # OUTPUT: «False␤»
say $a ⊆ $b;  # OUTPUT: «False␤»
say $a ⊖ $b;  # OUTPUT: «Bag(3(2) 2)␤»
say $a ⊎ $b;  # OUTPUT: «Bag(2(3) 4(2) 3(2))␤»

출처: Raku 공식 문서 — Bag