Baggy — 가중치 있는 객체 컬렉션을 위한 역할

Baggy — 가중치 있는 객체 컬렉션을 위한 역할

bag이나 mix처럼 '요소마다 가중치를 붙여 모아 두는' 컬렉션들이 공통으로 갖는 동작을 묶어 둔 역할이 Baggy예요. Bag, BagHash, Mixy 등이 이 역할을 구현해요.

role Baggy does QuantHash { }

메서드

method new-from-pairs

method new-from-pairs(Baggy: *@pairs --> Baggy:D)

위치 인자로 주어진 Pair 객체 리스트로부터 Baggy 객체를 만들어요.

say Mix.new-from-pairs: 'butter' => 0.22, 'sugar' => 0.1, 'sugar' => 0.02;
# OUTPUT: «Mix(butter(0.22) sugar(0.12))␤»

주의: Pairs를 실수로 위치 인자로 넘기지 않도록 하세요. 위 예시에서 키 주변의 따옴표가 중요한 이유예요.

method grab

multi method grab(Baggy:D: --> Any)
multi method grab(Baggy:D: $count --> Seq:D)

pick처럼 가중치에 따라 무작위로 요소를 선택하는데, pick과 달리 변경 가능한 구조(예: BagHash)에서만 동작해요. 불변 구조에 grab을 쓰면 X::Immutable 예외가 나요. *$count로 넘기거나 $count가 invocant의 total 이상이면, invocant에서 total개 요소를 무작위 순서(섞인 상태)로 돌려줘요.

grab은 그립은 키의 가중치를 1씩 줄여요(0이 되면 키 삭제). 따라서 total도 1 줄어들고, 이후 grab 연산에서도 확률이 일관되게 유지돼요.

my $cars = ('Ford' => 2, 'Rover' => 3).BagHash;
say $cars.grab;                                   # OUTPUT: «Ford␤»
say $cars.grab(2);                                # OUTPUT: «(Rover Rover)␤»
say $cars.grab(*);                                # OUTPUT: «(Rover Ford)␤»

my $breakfast = ('eggs' => 2, 'bacon' => 3).Bag;
say $breakfast.grab;
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::Immutable: Cannot call 'grab' on an immutable 'Bag'␤»

method grabpairs

multi method grabpairs(Baggy:D: --> Any)
multi method grabpairs(Baggy:D: $count --> Seq:D)

호출된 메서드 버전에 따라 Pair 하나 또는 Pair들의 Seq를 돌려줘요. 각 Pair의 키는 invocant의 요소, 값은 그 요소의 가중치예요. pickpairs와 달리 변경 가능한 구조에서만 동작해요. 불변 구조에서 쓰면 X::Immutable 예외가 나요. *$count로 넘기거나 $count가 요소 수 이상이면 모든 요소/가중치 Pair를 무작위 순서로 돌려줘요.

grabpairspickpairs와 다른 점은, '잡힌' 요소가 실제로 invocant에서 제거된다는 거예요.

my $breakfast = (eggs => 2, bacon => 3).BagHash;
say $breakfast.grabpairs;                         # OUTPUT: «bacon => 3␤»
say $breakfast;                                   # OUTPUT: «BagHash.new(eggs(2))␤»
say $breakfast.grabpairs(1);                      # OUTPUT: «(eggs => 2)␤»
say $breakfast.grabpairs(*);                      # OUTPUT: «()␤»

my $diet = ('eggs' => 2, 'bacon' => 3).Bag;
say $diet.grabpairs;
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::Immutable: Cannot call 'grabpairs' on an immutable 'Bag'␤»

method pick

multi method pick(Baggy:D: --> Any)
multi method pick(Baggy:D: $count --> Seq:D)

일반 리스트 pick과 같지만, 키가 값에 대응하는 횟수만큼 복제된 뒤 리스트 pick을 쓴 것처럼 가중치를 반영해 invocant의 키를 돌려줘요. 비유하자면 가방에서 색깔 구슬을 꺼내는 셈이에요. ('복원하며 꺼내기'는 roll을 쓰세요.) *$count로 넘기거나 $counttotal 이상이면 total개 요소를 무작위 순서로 돌려줘요.

pick 호출은 자기만의 private 상태를 유지하며, 이후 pick 호출에는 영향을 주지 않아요.

my $breakfast = bag <eggs bacon bacon bacon>;
say $breakfast.pick;                              # OUTPUT: «eggs␤»
say $breakfast.pick(2);                           # OUTPUT: «(eggs bacon)␤»

say $breakfast.total;                             # OUTPUT: «4␤»
say $breakfast.pick(*);                           # OUTPUT: «(bacon bacon bacon eggs)␤»

method pickpairs

multi method pickpairs(Baggy:D: --> Pair:D)
multi method pickpairs(Baggy:D: $count --> Seq:D)

호출 버전에 따라 Pair 하나 또는 Pair들의 Seq를 돌려줘요. 각 Pair의 키는 invocant의 요소, 값은 가중치예요. 요소는 '복원 없이' 선택돼요. *를 넘기거나 $count가 요소 수 이상이면 모든 요소/가중치 Pair를 무작위 순서로 돌려줘요.

pickpairs 호출은 자기만의 private 상태를 유지해요.

my $breakfast = bag <eggs bacon bacon bacon>;
say $breakfast.pickpairs;                         # OUTPUT: «eggs => 1⤇»
say $breakfast.pickpairs(1);                      # OUTPUT: «(bacon => 3)⤇»
say $breakfast.pickpairs(*);                      # OUTPUT: «(eggs => 1 bacon => 3)⤇»

method roll

multi method roll(Baggy:D: --> Any:D)
multi method roll(Baggy:D: $count --> Seq:D)

일반 리스트 roll과 같지만, 값에 대응하는 횟수만큼 키가 복제된 뒤 리스트 roll을 쓴 것처럼 가중치를 반영해요. 비유하자면 서로 독립적인 $count개의 주사위를 던지는 셈이에요. bag 용어로는 색깔 구슬을 꺼내고 다시 넣기를 $count번 하는 것과 같아요. ('복원 없이 꺼내기'는 pick을 쓰세요.)

*$count로 넘기면 invocant에서 무작위로 선택된 요소들의 lazy하고 무한한 시퀀스를 돌려줘요.

my $breakfast = bag <eggs bacon bacon bacon>;
say $breakfast.roll;                                  # OUTPUT: «bacon␤»
say $breakfast.roll(3);                               # OUTPUT: «(bacon eggs bacon)␤»

my $random_dishes := $breakfast.roll(*);
say $random_dishes[^5];                               # OUTPUT: «(bacon eggs bacon bacon bacon)␤»

method pairs

method pairs(Baggy:D: --> Seq:D)

모든 요소와 그 가중치를 Pair들의 Seq로 돌려줘요. 키는 요소 자체, 값은 가중치예요.

my $breakfast = bag <bacon eggs bacon>;
my $seq = $breakfast.pairs;
say $seq.sort;                                    # OUTPUT: «(bacon => 2 eggs => 1)⤇»

method antipairs

method antipairs(Baggy:D: --> Seq:D)

pairs와 반대로, 요소 자체가 값이고 가중치가 키예요.

my $breakfast = bag <bacon eggs bacon>;
my $seq = $breakfast.antipairs;
say $seq.sort;                                    # OUTPUT: «(1 => eggs 2 => bacon)⤇»

method invert

method invert(Baggy:D: --> Seq:D)

pairs와 반대입니다. 특수한 경우를 제외하면 Baggy 타입에서 invertantipairs와 같은 결과를 돌려줘요.

my $breakfast = bag <bacon eggs bacon>;
my $seq = $breakfast.invert;
say $seq.sort;                                    # OUTPUT: «(1 => eggs 2 => bacon)⤇»

method classify-list

multi method classify-list(&mapper, *@list --> Baggy:D)
multi method classify-list(%mapper, *@list --> Baggy:D)
multi method classify-list(@mapper, *@list --> Baggy:D)

주어진 mapper로 (비어 있을 수 있는) @list 값을 분류해서 변경 가능한 Baggy를 채워요. @list는 lazy일 수 없어요.

say BagHash.new.classify-list: { $_ %% 2 ?? 'even' !! 'odd' }, ^10;
# OUTPUT: BagHash(even(5) odd(5))

my @mapper = <zero one two three four five>;
say MixHash.new.classify-list: @mapper, 1, 2, 3, 4, 4, 6;
# OUTPUT: MixHash((Any) two three four(2) one)

mapper는 단일 인자를 받는 Callable, Associative, Iterable 중 하나일 수 있어요. @list의 값은 각각 mapper의 키/인덱스를 나타내요. Callable mapper는 @list의 각 항목에 대해 한 번 실행되고 그 반환값이 mapper의 값으로 쓰여요.

mapper의 값은 Baggy의 키가 되고 1씩 증가해요. 한 번에 여러 범주로 분류하고 싶으면 .categorize-list를 보세요.

참고: Hash.classify-list와 달리 Iterable를 반환하는 mapper 값은 예외를 던져요. Baggy 타입은 중첩 분류를 지원하지 않기 때문이에요. 같은 이유로 Baggy.classify-list:&as 인자를 받지 않아요.

method categorize-list

multi method categorize-list(&mapper, *@list --> Baggy:D)
multi method categorize-list(%mapper, *@list --> Baggy:D)
multi method categorize-list(@mapper, *@list --> Baggy:D)

주어진 mapper로 (비어 있을 수 있는) @list 값을 범주화해서 변경 가능한 Baggy를 채워요. @list는 lazy일 수 없어요.

say BagHash.new.categorize-list: {
    gather {
        take 'largish' if $_ > 5;
        take .is-prime ?? 'prime' !! 'non-prime';
        take $_ %% 2   ?? 'even'  !! 'odd';
    }
}, ^10;
# OUTPUT: BagHash(largish(4) even(5) non-prime(6) prime(4) odd(5))

my %mapper = :sugar<sweet white>, :lemon<sour>, :cake('sweet', 'is-a-lie');
say MixHash.new.categorize-list: %mapper, <sugar lemon cake>;
# OUTPUT: MixHash(is-a-lie sour white sweet(2))

mapperCallable, Associative, Iterable 중 하나일 수 있어요. mapper의 값은 Baggy의 (비어 있을 수 있는) 키 리스트로 쓰이고 1씩 증가해요.

참고: Hash.categorize-list와 달리 Iterable 리스트를 mapper 값으로 반환하면 예외가 나요. Baggy 타입은 중첩 범주화를 지원하지 않기 때문이에요. 그래서 :&as 인자도 받지 않아요.

method keys

method keys(Baggy:D: --> Seq:D)

kxxv와 달리 개별 가중치를 고려하지 않고 Baggy 객체의 모든 키를 Seq로 돌려줘요.

my $breakfast = bag <eggs spam spam spam>;
say $breakfast.keys.sort;                        # OUTPUT: «(eggs spam)␤»

my $n = ("a" => 5, "b" => 2).BagHash;
say $n.keys.sort;                                # OUTPUT: «(a b)␤»

method values

method values(Baggy:D: --> Seq:D)

Baggy 객체의 모든 값(가중치)을 Seq로 돌려줘요.

my $breakfast = bag <eggs spam spam spam>;
say $breakfast.values.sort;                      # OUTPUT: «(1 3)␤»

my $n = ("a" => 5, "b" => 2, "a" => 1).BagHash;
say $n.values.sort;                              # OUTPUT: «(2 6)␤»

method kv

method kv(Baggy:D: --> Seq:D)

키와 값을 교차 배열한 Seq를 돌려줘요.

my $breakfast = bag <eggs spam spam spam>;
say $breakfast.kv;                                # OUTPUT: «(spam 3 eggs 1)␤»

my $n = ("a" => 5, "b" => 2, "a" => 1).BagHash;
say $n.kv;                                        # OUTPUT: «(a 6 b 2)␤»

method kxxv

method kxxv(Baggy:D: --> Seq:D)

invocant의 각 키를 가중치만큼 곱한 Seq를 돌려줘요. 정수 가중치를 가진 Baggy 타입, 즉 BagBagHash에서만 동작해요.

my $breakfast = bag <spam eggs spam spam bacon>;
say $breakfast.kxxv.sort;                         # OUTPUT: «(bacon eggs spam spam spam)␤»

my $n = ("a" => 0, "b" => 1, "b" => 2).BagHash;
say $n.kxxv;                                      # OUTPUT: «(b b b)␤»

method elems

method elems(Baggy:D: --> Int:D)

개별 요소의 가중치를 고려하지 않고 Baggy 객체의 요소 수를 돌려줘요.

my $breakfast = bag <eggs spam spam spam>;
say $breakfast.elems;                             # OUTPUT: «2␤»

my $n = ("b" => 9.4, "b" => 2).MixHash;
say $n.elems;                                     # OUTPUT: «1␤»

method total

method total(Baggy:D:)

Baggy 객체에 있는 모든 요소의 가중치 합을 돌려줘요.

my $breakfast = bag <eggs spam spam bacon>;
say $breakfast.total;                             # OUTPUT: «4␤»

my $n = ("a" => 5, "b" => 1, "b" => 2).BagHash;
say $n.total;                                     # OUTPUT: «8␤»

method default

method default(Baggy:D: --> 0)

0을 돌려줘요.

my $breakfast = bag <eggs bacon>;
say $breakfast.default;                           # OUTPUT: «0␤»

method hash

method hash(Baggy:D: --> Hash:D)

invocant의 요소를 키로, 가중치를 값으로 하는 Hash를 돌려줘요.

my $breakfast = bag <eggs bacon bacon>;
my $h = $breakfast.hash;
say $h.^name;                    # OUTPUT: «Hash[Any,Any]␤»
say $h;                          # OUTPUT: «{bacon => 2, eggs => 1}␤»

method Bool

method Bool(Baggy:D: --> Bool:D)

invocant가 요소를 하나 이상 담고 있으면 True를 돌려줘요.

my $breakfast = ('eggs' => 1).BagHash;
say $breakfast.Bool;                              # OUTPUT: «True␤»
                                                  # (since we have one element)
$breakfast<eggs> = 0;                             # weight == 0 will lead to element removal
say $breakfast.Bool;                              # OUTPUT: «False␤»

method Set

method Set(--> Set:D)

invocant의 keys를 요소로 하는 Set을 돌려줘요.

my $breakfast = (eggs => 2, bacon => 3).BagHash;
say $breakfast.Set;                               # OUTPUT: «Set(bacon eggs)␤»

method SetHash

method SetHash(--> SetHash:D)

invocant의 keys를 요소로 하는 SetHash를 돌려줘요.

my $breakfast = (eggs => 2, bacon => 3).BagHash;
my $sh = $breakfast.SetHash;
say $sh.^name;                            # OUTPUT: «SetHash␤»
say $sh.elems;                            # OUTPUT: «2␤»

method ACCEPTS

method ACCEPTS($other --> Bool:D)

오른쪽이 Baggy일 때 스마트매치에 쓰여요.

오른쪽이 타입 객체(즉 Baggy)이면, $otherBaggydoes할 때 True, 아니면 False를 돌려줘요.

오른쪽이 Baggy 객체이면, $other가 invocant와 같은 요소·같은 가중치를 가질 때만 True를 돌려줘요.

my $breakfast = bag <eggs bacon>;
say $breakfast ~~ Baggy;                            # OUTPUT: «True␤»
say $breakfast.does(Baggy);                         # OUTPUT: «True␤»

my $second-breakfast = (eggs => 1, bacon => 1).Mix;
say $breakfast ~~ $second-breakfast;                # OUTPUT: «True␤»

my $third-breakfast = (eggs => 1, bacon => 2).Bag;
say $second-breakfast ~~ $third-breakfast;          # OUTPUT: «False␤»

같이 보기

세트, Bag, Mix (Sets, Bags, and Mixes)

출처: Raku 공식 문서 — Baggy