Mercurial > hg > Others > Rakudo
view src/core.c/Baggy.pm6 @ 0:c341f82e7ad7 default tip
Rakudo branch in cr.ie.u-ryukyu.ac.jp
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Thu, 26 Dec 2019 16:50:27 +0900 |
parents | |
children |
line wrap: on
line source
my role Baggy does QuantHash { # A Bag/BagHash/Mix/MixHash consists of a single hash with Pairs. # The keys of the hash, are the .WHICH strings of the original object key. # The values are Pairs containing the original object key and value. has Rakudo::Internals::IterationSet $!elems; # key.WHICH => (key,value) # The Baggy role takes care of all mutable and immutable aspects that are # shared between Bag,BagHash,Mix,MixHash. Any specific behaviour for # mutable and immutable aspects of Mix/MixHash need to live in Mixy. # Immutables aspects of Bag/Mix, need to live to Bag/Mix respectively. #--- interface methods method of() { UInt } multi method ACCEPTS(Baggy:U: \other --> Bool:D) { other.^does(self) } multi method ACCEPTS(Baggy:D: Baggy:D \other --> Bool:D) { nqp::hllbool( nqp::unless( nqp::eqaddr(self,other), nqp::if( # not same object (my \araw := $!elems) && nqp::elems(araw), nqp::if( # something on left nqp::isconcrete(my \braw := other.RAW-HASH) && nqp::elems(braw), nqp::if( # something on both sides nqp::iseq_i(nqp::elems(araw),nqp::elems(braw)), nqp::stmts( # same size (my \iter := nqp::iterator(araw)), nqp::while( iter, nqp::unless( nqp::getattr( nqp::ifnull( nqp::atkey(braw,nqp::iterkey_s(nqp::shift(iter))), BEGIN nqp::p6bindattrinvres( # virtual Pair with 0 nqp::create(Pair),Pair,'$!value',0) ),Pair,'$!value') == nqp::getattr(nqp::iterval(iter),Pair,'$!value'), return False # missing/different: we're done ) ), True # all keys identical/same value ) ) ), # true -> both empty nqp::isfalse( (my \raw := other.RAW-HASH) && nqp::elems(raw) ) ) ) ) } multi method ACCEPTS(Baggy:D: \other --> Bool:D) { self.ACCEPTS(other.Bag) } multi method AT-KEY(Baggy:D: \k) { # exception: ro version for Bag/Mix nqp::if( $!elems, nqp::getattr( nqp::ifnull( nqp::atkey($!elems,k.WHICH), BEGIN nqp::p6bindattrinvres(nqp::create(Pair),Pair,'$!value',0) ), Pair, '$!value' ), 0 ) } multi method DELETE-KEY(Baggy:D: \k) { nqp::if( $!elems && nqp::existskey($!elems,(my $which := k.WHICH)), nqp::stmts( (my \value := nqp::getattr(nqp::atkey($!elems,$which),Pair,'$!value')), nqp::deletekey($!elems,$which), value ), 0 ) } multi method EXISTS-KEY(Baggy:D: \k) { nqp::hllbool( $!elems ?? nqp::existskey($!elems,k.WHICH) !! 0 ) } #--- object creation methods # helper method to create Bag from iterator, check for laziness method !create-from-iterator(\type, \iterator --> Baggy:D) { nqp::if( iterator.is-lazy, Failure.new(X::Cannot::Lazy.new(:action<coerce>,:what(type.^name))), nqp::create(type).SET-SELF( Rakudo::QuantHash.ADD-ITERATOR-TO-BAG( nqp::create(Rakudo::Internals::IterationSet),iterator,self.keyof ) ) ) } multi method new(Baggy:_: --> Baggy:D) { nqp::create(self) } multi method new(Baggy:_: \value --> Baggy:D) { nqp::if( nqp::istype(value,Iterable) && nqp::not_i(nqp::iscont(value)), self!create-from-iterator(self, value.iterator), nqp::stmts( nqp::bindkey( (my $elems := nqp::create(Rakudo::Internals::IterationSet)), value.WHICH, Pair.new(value,1) ), nqp::create(self).SET-SELF($elems) ) ) } multi method new(Baggy:_: **@args) { self!create-from-iterator(self, @args.iterator) } method new-from-pairs(Baggy:_: *@pairs --> Baggy:D) { nqp::if( (my \iterator := @pairs.iterator).is-lazy, Failure.new(X::Cannot::Lazy.new(:action<coerce>,:what(self.^name))), nqp::create(self).SET-SELF( Rakudo::QuantHash.ADD-PAIRS-TO-BAG( nqp::create(Rakudo::Internals::IterationSet),iterator,self.keyof ) ) ) } #--- iterator methods multi method iterator(Baggy:D:) { Rakudo::Iterator.Mappy-values($!elems) } my class Keys does Rakudo::Iterator::Mappy { method pull-one() { $!iter ?? nqp::getattr(nqp::iterval(nqp::shift($!iter)),Pair,'$!key') !! IterationEnd } method push-all(\target --> IterationEnd) { nqp::while( # doesn't sink $!iter, target.push( nqp::getattr(nqp::iterval(nqp::shift($!iter)),Pair,'$!key') ) ) } } multi method keys(Baggy:D:) { Seq.new(Keys.new($!elems)) } multi method kv(Baggy:D:) { Seq.new(Rakudo::Iterator.Mappy-kv-from-pairs($!elems)) } my class Values does Rakudo::Iterator::Mappy { method pull-one() is raw { nqp::if( $!iter, nqp::getattr(nqp::iterval(nqp::shift($!iter)),Pair,'$!value'), IterationEnd ) } method push-all(\target --> IterationEnd) { nqp::while( # doesn't sink $!iter, target.push( nqp::getattr( nqp::iterval(nqp::shift($!iter)), Pair, '$!value' ) ) ) } } multi method values(Baggy:D:) { Seq.new(Values.new($!elems)) } my class AntiPairs does Rakudo::Iterator::Mappy { method pull-one() { nqp::if( $!iter, nqp::iterval(nqp::shift($!iter)).antipair, IterationEnd ) } method push-all(\target --> IterationEnd) { nqp::while( $!iter, target.push(nqp::iterval(nqp::shift($!iter)).antipair), ) } } multi method antipairs(Baggy:D:) { Seq.new(AntiPairs.new($!elems)) } my class KxxV does Rakudo::Iterator::Mappy { has Mu $!key; has int $!times; method pull-one() is raw { nqp::if( $!times, nqp::stmts( ($!times = nqp::sub_i($!times,1)), $!key ), nqp::if( $!iter, nqp::stmts( ($!key := nqp::getattr( (my \pair := nqp::iterval(nqp::shift($!iter))), Pair, '$!key' )), ($!times = nqp::sub_i(nqp::getattr(pair,Pair,'$!value'),1)), $!key ), IterationEnd ) ) } method skip-one() { # the default skip-one, too difficult to handle nqp::not_i(nqp::eqaddr(self.pull-one,IterationEnd)) } method push-all(\target --> IterationEnd) { nqp::while( $!iter, nqp::stmts( ($!key := nqp::getattr( (my \pair := nqp::iterval(nqp::shift($!iter))), Pair, '$!key' )), ($!times = nqp::add_i(nqp::getattr(pair,Pair,'$!value'),1)), nqp::while( # doesn't sink ($!times = nqp::sub_i($!times,1)), target.push($!key) ) ) ) } } proto method kxxv(|) {*} multi method kxxv(Baggy:D:) { Seq.new(KxxV.new($!elems)) } multi method invert(Baggy:D:) { Seq.new(Rakudo::Iterator.Invert(Rakudo::Iterator.Mappy-values($!elems))) } #--- introspection methods multi method elems(Baggy:D: --> Int:D) { nqp::istrue($!elems) && nqp::elems($!elems) } multi method Bool(Baggy:D: --> Bool:D) { nqp::hllbool($!elems ?? nqp::elems($!elems) !! 0) } method !HASHIFY(\type) { nqp::stmts( (my \hash := Hash.^parameterize(type,Any).new), (my \descriptor := nqp::getattr(hash,Hash,'$!descriptor')), nqp::if( $!elems && nqp::elems($!elems), nqp::stmts( (my \storage := nqp::clone($!elems)), (my \iter := nqp::iterator(storage)), nqp::while( iter, nqp::bindkey( storage, nqp::iterkey_s(nqp::shift(iter)), nqp::p6bindattrinvres( nqp::clone(nqp::iterval(iter)),Pair,'$!value', (nqp::p6scalarfromdesc(descriptor) = nqp::getattr(nqp::iterval(iter),Pair,'$!value')) ) ) ), nqp::bindattr(hash,Map,'$!storage',storage) ) ), hash ) } multi method hash(Baggy:D: --> Hash:D) { self!HASHIFY(UInt) } multi method Hash(Baggy:D: --> Hash:D) { self!HASHIFY(Any) } method default(Baggy:D: --> 0) { } multi method Str(Baggy:D: --> Str:D) { nqp::join(' ',Rakudo::QuantHash.RAW-VALUES-MAP(self, { nqp::if( (my \value := nqp::getattr($_,Pair,'$!value')) == 1, nqp::getattr($_,Pair,'$!key').gist, "{nqp::getattr($_,Pair,'$!key').gist}({value})" ) })) } multi method gist(Baggy:D: --> Str:D) { nqp::concat( nqp::concat( nqp::concat(self.^name,'('), nqp::join(', ', Rakudo::Sorting.MERGESORT-str( Rakudo::QuantHash.RAW-VALUES-MAP(self, { nqp::if( (my \value := nqp::getattr($_,Pair,'$!value')) == 1, nqp::getattr($_,Pair,'$!key').gist, "{nqp::getattr($_,Pair,'$!key').gist}({value})" ) }) ) ) ), ')', ) } multi method perl(Baggy:D: --> Str:D) { nqp::if( $!elems && nqp::elems($!elems), nqp::stmts( (my \pairs := nqp::join(',', Rakudo::QuantHash.RAW-VALUES-MAP(self, { nqp::concat( nqp::concat( nqp::getattr($_,Pair,'$!key').perl, '=>' ), nqp::getattr($_,Pair,'$!value').perl ) }) )), nqp::if( nqp::eqaddr(self.keyof,Mu), nqp::concat( nqp::concat('(',pairs), nqp::concat(').',self.^name) ), nqp::concat( nqp::concat(self.^name,'.new-from-pairs('), nqp::concat(pairs,')') ) ) ), nqp::if( nqp::eqaddr(self,bag()), 'bag()', nqp::if( nqp::eqaddr(self,mix()), 'mix()', nqp::concat('().',self.^name) ) ) ) } #--- selection methods proto method grabpairs (|) {*} multi method grabpairs(Baggy:D:) { nqp::if( $!elems && nqp::elems($!elems), nqp::stmts( (my \iter := Rakudo::QuantHash.ROLL($!elems)), (my \pair := nqp::iterval(iter)), nqp::deletekey($!elems,nqp::iterkey_s(iter)), pair ), Nil ) } multi method grabpairs(Baggy:D: Callable:D $calculate) { self.grabpairs( $calculate(self.elems) ) } multi method grabpairs(Baggy:D: Whatever $) { self.grabpairs(Inf) } my class GrabPairsN does Rakudo::QuantHash::Pairs { method pull-one() is raw { nqp::if( nqp::elems($!picked), nqp::stmts( (my \pair := nqp::atkey( $!elems, (my \key := nqp::pop_s($!picked)) )), nqp::deletekey($!elems,key), pair ), IterationEnd ) } } multi method grabpairs(Baggy:D: \count) { Seq.new(GrabPairsN.new($!elems,count)) } proto method pickpairs(|) {*} multi method pickpairs(Baggy:D:) { nqp::if( $!elems && nqp::elems($!elems), nqp::iterval(Rakudo::QuantHash.ROLL($!elems)), Nil ) } multi method pickpairs(Baggy:D: Callable:D $calculate) { self.pickpairs( $calculate(self.total) ) } multi method pickpairs(Baggy:D: Whatever $) { self.pickpairs(Inf) } my class PickPairsN does Rakudo::QuantHash::Pairs { method pull-one() is raw { nqp::if( nqp::elems($!picked), nqp::atkey($!elems,nqp::pop_s($!picked)), IterationEnd ) } } multi method pickpairs(Baggy:D: \count) { Seq.new(PickPairsN.new($!elems,count)) } proto method grab(|) {*} multi method grab(Baggy:D: |c) { X::Immutable.new( method => 'grab', typename => self.^name ).throw; } proto method pick(|) {*} multi method pick(Baggy:D:) { self.roll } multi method pick(Baggy:D: Callable:D $calculate) { self.pick( $calculate(self.total) ) } multi method pick(Baggy:D: Whatever) { self.pick(Inf) } my class PickN does PredictiveIterator { has $!raw; # the IterationSet of the Baggy has $!weights; # clone of raw, but with just the weights has $!todo; # number of draws to do has $!total; # total number of draws possible # Return the .WHICH key of a randomly picked object. Updates # the weight of the picked object and the total number of draws # still possible. method BAG-PICK() { nqp::stmts( (my Int $rand := $!total.rand.Int), (my Int $seen := 0), (my \iter := nqp::iterator($!weights)), nqp::while( iter && nqp::isle_I( ($seen := nqp::add_I( $seen, nqp::iterval(nqp::shift(iter)), Int )), $rand ), nqp::null ), nqp::bindkey( # iter now contains picked one $!weights, nqp::iterkey_s(iter), nqp::sub_I(nqp::iterval(iter),1,Int) ), ($!total := nqp::sub_I($!total,1,Int)), nqp::iterkey_s(iter) ) } method !SET-SELF(\raw, \todo, \total) { nqp::stmts( ($!weights := nqp::clone($!raw := raw)), (my \iter := nqp::iterator($!weights)), nqp::while( iter, nqp::bindkey( $!weights, nqp::iterkey_s(nqp::shift(iter)), nqp::getattr(nqp::iterval(iter),Pair,'$!value') ) ), ($!todo := nqp::if(todo > total,total,todo)), ($!total := total), self ) } method new(\raw, \todo, \total) { nqp::create(self)!SET-SELF(raw, todo, total) } method pull-one() is raw { nqp::if( $!todo, nqp::stmts( ($!todo := nqp::sub_I($!todo,1,Int)), nqp::getattr(nqp::atkey($!raw,self.BAG-PICK),Pair,'$!key') ), IterationEnd ) } method skip-one() { nqp::if( $!todo, nqp::stmts( ($!todo := nqp::sub_I($!todo,1,Int)), self.BAG-PICK ) ) } method push-all(\target --> IterationEnd) { nqp::stmts( (my $todo = $!todo), nqp::while( $todo, nqp::stmts( --$todo, target.push(nqp::getattr( nqp::atkey($!raw,self.BAG-PICK), Pair, '$!key' )) ) ), ($!todo := nqp::decont($todo)) ) } method count-only(--> Int:D) { $!todo } method sink-all() { $!todo := 0 } } multi method pick(Baggy:D: \count) { Seq.new( (my \total := self.total) < 1 || (my \todo := count == Inf ?? total !! count.Int) < 1 ?? Rakudo::Iterator.Empty # nothing to do !! PickN.new($!elems,todo,total) ) } proto method roll(|) {*} multi method roll(Baggy:D:) { nqp::if( $!elems && (my \total := self.total), nqp::getattr( nqp::iterval(Rakudo::QuantHash.BAG-ROLL($!elems,total)), Pair, '$!key' ), Nil ) } multi method roll(Baggy:D: Whatever) { Seq.new(nqp::if( $!elems && (my \total := self.total), Rakudo::Iterator.Callable( { nqp::getattr( nqp::iterval(Rakudo::QuantHash.BAG-ROLL($!elems,total)), Pair, '$!key' ) }, True ), Rakudo::Iterator.Empty )) } multi method roll(Baggy:D: Callable:D $calculate) { nqp::if( (my \total := self.total), self.roll($calculate(total)), Seq.new(Rakudo::Iterator.Empty) ) } multi method roll(Baggy:D: \count) { nqp::if( count == Inf, self.roll(*), # let Whatever handle it Seq.new(nqp::if( # something else as count (my $todo = count.Int) < 1, # also handles NaN Rakudo::Iterator.Empty, # nothing to do nqp::if( $!elems && (my \total := self.total) && ++$todo, Rakudo::Iterator.Callable( { # need to do a number of times nqp::if( --$todo, nqp::getattr( nqp::iterval(Rakudo::QuantHash.BAG-ROLL($!elems,total)), Pair, '$!key' ), IterationEnd ) }), Rakudo::Iterator.Empty # nothing to roll for ) )) ) } #--- classification method proto method classify-list(|) {*} multi method classify-list( &test, \list) { fail X::Cannot::Lazy.new(:action<classify>) if list.is-lazy; my \iter = (nqp::istype(list, Iterable) ?? list !! list.list).iterator; until nqp::eqaddr((my $value := iter.pull-one),IterationEnd) { my $tested := test($value); if nqp::istype($tested, Iterable) { # multi-level classify X::Invalid::ComputedValue.new( :name<mapper>, :method<classify-list>, :value<an Iterable item>, :reason(self.^name ~ ' cannot be nested and so does not ' ~ 'support multi-level classification'), ).throw; } else { ++self{$tested}; } } self; } multi method classify-list( %test, |c ) { self.classify-list( { %test{$^a} }, |c ); } multi method classify-list( @test, |c ) { self.classify-list( { @test[$^a] }, |c ); } multi method classify-list(&test, **@list, |c) { self.classify-list(&test, @list, |c); } proto method categorize-list(|) {*} multi method categorize-list( &test, \list ) { fail X::Cannot::Lazy.new(:action<categorize>) if list.is-lazy; my \iter = (nqp::istype(list, Iterable) ?? list !! list.list).iterator; my $value := iter.pull-one; unless nqp::eqaddr($value,IterationEnd) { my $tested := test($value); # multi-level categorize if nqp::istype($tested[0],Iterable) { X::Invalid::ComputedValue.new( :name<mapper>, :method<categorize-list>, :value<a nested Iterable item>, :reason(self.^name ~ ' cannot be nested and so does not ' ~ 'support multi-level categorization'), ).throw; } # simple categorize else { loop { ++self{$_} for @$tested; last if nqp::eqaddr(($value := iter.pull-one),IterationEnd); nqp::istype(($tested := test($value))[0], Iterable) and X::Invalid::ComputedValue.new( :name<mapper>, :method<categorize-list>, :value('an item with different number of elements ' ~ 'in it than previous items'), :reason('all values need to have the same number ' ~ 'of elements. Mixed-level classification is ' ~ 'not supported.'), ).throw; }; } } self; } multi method categorize-list( %test, |c ) { self.categorize-list( { %test{$^a} }, |c ); } multi method categorize-list( @test, |c ) { self.categorize-list( { @test[$^a] }, |c ); } multi method categorize-list( &test, **@list, |c ) { self.categorize-list( &test, @list, |c ); } #--- coercion methods sub SETIFY(\raw, \type) { nqp::if( raw && nqp::elems(raw), nqp::stmts( (my \elems := nqp::clone(raw)), (my \iter := nqp::iterator(elems)), nqp::while( iter, nqp::bindkey( elems, nqp::iterkey_s(nqp::shift(iter)), nqp::getattr(nqp::iterval(iter),Pair,'$!key'), ) ), nqp::create(type).SET-SELF(elems) ), nqp::if( nqp::eqaddr(type,Set), set(), nqp::create(type) ) ) } multi method Set(Baggy:D:) { SETIFY($!elems,Set) } multi method SetHash(Baggy:D:) { SETIFY($!elems,SetHash) } sub MIXIFY(\raw, \type) { nqp::if( raw && nqp::elems(raw), nqp::create(type).SET-SELF(Rakudo::QuantHash.BAGGY-CLONE(raw)), nqp::if( nqp::istype(type,Mix), mix(), nqp::create(MixHash) ) ) } multi method Mix(Baggy:D:) { MIXIFY($!elems, Mix) } multi method MixHash(Baggy:D:) { MIXIFY($!elems, MixHash) } method RAW-HASH() is raw { $!elems } } multi sub infix:<eqv>(Baggy:D \a, Baggy:D \b --> Bool:D) { nqp::hllbool( nqp::eqaddr(a,b) || (nqp::eqaddr(a.WHAT,b.WHAT) && a.ACCEPTS(b)) ) } # vim: ft=perl6 expandtab sw=4