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를 무작위 순서로 돌려줘요.
grabpairs가 pickpairs와 다른 점은, '잡힌' 요소가 실제로 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로 넘기거나 $count가 total 이상이면 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 타입에서 invert는 antipairs와 같은 결과를 돌려줘요.
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))
mapper는 Callable, 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 타입, 즉 Bag과 BagHash에서만 동작해요.
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)이면, $other가 Baggy를 does할 때 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)