Can I delete an element of SetHash and use its value in Perl 6? - raku

I would like to delete any element of a SetHash, so that its value be returned:
my SetHash $a;
$a<m n k> = 1..*;
my $elem = $a.mymethod;
say $elem; # n
say $a; # SetHash(m k)
I can do it in two steps as follows. Is there a better way?
my $elem = $a.pick;
$a{$elem}--;
And by the way, is there a more idiomatic way of adding several elements to a SetHash?
Is the following any better?
my SetHash $a;
$a{$_}++ for <m n k>;
or
my SetHash $a;
$a<m n k> X= True;

Since you want to remove any random element from the set and return the value you got, I'd recommend using the grab method on Setty things (only works properly on mutable sets like SetHash): https://docs.perl6.org/type/SetHash#(Setty)_method_grab
Depending on what exact return value you need, maybe grabpairs is better.

UPD I'm leaving this answer up for now rather than deleting it, but see timotimo's answer.
I would like to delete an element of a SetHash, so that its value be returned
Use the :delete adverb.
Per the Subscript doc page,
the :delete adverb will delete the element(s) from the collection or, if supported by the collection, create a hole at the given index(es), in addition to returning their value(s):
my %associative = << :a :b :c >> ;
my $deleted = %associative<< a c >> :delete ;
say $deleted ; # (True True)
say %associative ; # {b => True}
UPD Integrating #piojo's and #EugeneBarsky's comments and answers:
my %associative = << :a :b :c >> ;
my $deleted = .{.keys.pick } :delete :k with %associative ;
say $deleted ; # b
say %associative ; # {a => True, c => True}
is there a more idiomatic way of adding several elements to a SetHash?
Plain assignment with a list on the right hand side works, e.g.
$a<m n k> = True, True, True;
$a<m n k> = True xx *;
$a<m n k> = True ... *;
I've used and seen others using both the formulations in your examples too.

If I understand your comments correctly, you want to delete an element from a hash without knowing whether it exists, and get the element as a return value if it does exist. This can be done by combining the :delete and :k adverbs.
my %set := SetHash.new: ('a'..'g');
my #removed = %set<a e i o u>:delete :k;
say #removed; # output: [a e]

Another approach to deleting the element and returning it, is to augment the SetHash class with a custom method:
use v6;
use MONKEY-TYPING;
augment class SetHash {
method delete-elem(Any $elem) {
self.DELETE-KEY( $elem );
return $elem;
}
}
my SetHash $a = <m n k>.SetHash;
$a<a b c>»++; # add some more elements to the SetHash...
my $elem = $a.delete-elem( $a.pick );
say "Deleted: $elem";
say $a;
Output:
Deleted: m
SetHash(a b c k n)

Using a combination of the answers, it seems I've managed to do what I wanted:
my SetHash $s;
$s = <a b c d e f>.SetHash;
my $deleted = $s{ $s.pick } :delete :k;
say $deleted; # f
say $s; # SetHash(a b c d e)

Related

confusion about lists contained in an aggregate, maybe context problem?

Rakudo version 2020.01
I was writing some throw-away code and did not bother to implement a class, just used a Hash as work-alike. I found some surprising behaviour with lists.
class Q1 {}
class R1 {
has Str $.some-str is required;
has #.some-list is required;
}
my $r1 = R1.new(
some-str => '…',
some-list => (Q1.new, Q1.new, Q1.new)
);
# hash as poor man's class
my $r2 = {
some-str => '…',
some-list => (Q1.new, Q1.new, Q1.new)
};
multi sub frob(R1 $r1) {
for #`(Array) $r1.some-list -> $elem {
$elem.raku.say;
}
}
multi sub frob(Hash $r2) {
for #`(List) $r2<some-list> -> $elem {
$elem.raku.say;
}
}
frob $r1;
# OK.
# Q1.new
# Q1.new
# Q1.new
frob $r2;
# got:
# (Q1.new, Q1.new, Q1.new)
# expected:
# Q1.new
# Q1.new
# Q1.new
frob(Hash …) works as expected when I call .flat or .list on the list (even though it is already a list‽).
I tried to make a minimal test case, but this works identical AFAICT.
for [Q1.new, Q1.new, Q1.new] -> $elem {
$elem.raku.say;
}
for (Q1.new, Q1.new, Q1.new) -> $elem {
$elem.raku.say;
}
I have read the documentation on List and Scalar several times, but I still cannot make sense out of my observation. Why do I have to special treat the list in the Hash, but not in the class?
for doesn't loop over itemized values.
When you place something in a scalar container it gets itemized.
sub foo ( $v ) { # itemized
for $v { .say }
}
sub bar ( \v ) {
for v { .say }
}
foo (1,2,3);
# (1 2 3)
bar (1,2,3);
# 1
# 2
# 3
An element in a Hash is also a scalar container.
my %h = 'foo' => 'bar';
say %h<foo>.VAR.^name;
# Scalar
So if you place a list into a Hash, it will get itemized.
my %h;
my \list = (1,2,3);
%h<list> = list;
say list.VAR.^name;
# List
say %h<list>.VAR.^name;
# Scalar
So if you want to loop over the values you have to de-itemize it.
%h<list>[]
%h<list><>
%h<list>.list
%h<list>.self
#(%h<list>)
given %h<list> -> #list { … }
my #list := %h<list>;
(my # := %h<list>) # inline version of previous example
You could avoid this scalar container by binding instead.
%h<list> := list;
(This prevents the = operator from working on that hash element.)
If you noticed that in the class object you defined it with an # not $
class R1 {
has Str $.some-str is required;
has #.some-list is required;
}
If you changed it to an $ and mark it rw it will work like the Hash example
class R2 {
has Str $.some-str is required;
has List $.some-list is required is rw;
}
my $r2 = R2.new(
some-str => '…',
some-list => (1,2,3),
);
for $r2.some-list { .say }
# (1 2 3)
It has to be a $ variable or it won't be in a Scalar container.
It also has to be marked rw so that the accessor returns the actual Scalar container rather than the de-itemized value.
This has nothing to do with [] versus (). This has to do with the difference between $ (indicating an item) and % (indicating an Associative):
sub a(%h) { dd %h } # a sub taking an Associative
sub b(Hash $h) { dd $h } # a sub taking an item of type Hash
a { a => 42 }; # Hash % = {:a(42)}
b { a => 42 }; # ${:a(42)}
In the "b" case, what is received is an item. If you try to iterate over that, you will get 1 iteration, for that item. Whereas in the "a" case, you've indicated that it is something Associative that you want (with the % sigil).
Perhaps a clearer example:
my $a = (1,2,3);
for $a { dd $_ } # List $a = $(1, 2, 3)␤
Since $a is an item, you get one iteration. You can indicate that you want to iterate on the underlying thing, by adding .list:
for $a.list { dd $_ } # 1␤2␤3␤
Or, if you want to get more linenoisy, prefix a #:
for #$a { dd $_ } # 1␤2␤3␤
Not strictly an answer, but an observation: in Raku, it pays to use classes rather than hashes, contrary to Perl:
my %h = a => 42, b => 666;
for ^10000000 { my $a = %h<a> }
say now - INIT now; # 0.4434793
Using classes and objects:
class A { has $.a; has $.b }
my $h = A.new(a => 42, b => 666);
for ^10000000 { my $a = $h.a }
say now - INIT now; # 0.368659
Not only is using classes faster, it also prevents you from making typos in initialization if you add the is required trait:
class A { has $.a is required; has $.b is required }
A.new(a => 42, B => 666);
# The attribute '$!b' is required, but you did not provide a value for it.
And it prevents you from making typos when accessing it:
my $a = A.new(a => 42, b => 666);
$a.bb;
# No such method 'bb' for invocant of type 'A'. Did you mean 'b'?

Why does Perl 6's Map give me a value in one case and a list in another?

I'm playing with Map and I get a result I don't understand.
First, I construct the Map. No big whoop:
> my $m = Map.new: '1' => :1st, '2' => :2nd;
Map.new(("1" => :st(1),"2" => :nd(2)))
I access a single element by the literal key and get back a Pair:
> $m<1>.^name
Pair
> $m<<1>>.^name
Pair
That's all fine.
If I try it with the key in a variable, I get back a List instead:
> my $n = 1
1
> $m<<$n>>.^name
List
That list has the right value, but why do I get a List in that case and not the $m<<1>> case?
And, once I have the list, I seem unable to chain another subscript to it:
> $m<<$n>>.[0]
===SORRY!=== Error while compiling:
Unable to parse quote-words subscript; couldn't find right double-angle quote
at line 2
When you access an associative value like this, the compiler can tell that it need only ever return one value.
$m< 1 >
$m<< 1 >>
In Perl 6, a singular value will in many cases behave just like a list of one value.
42.elems == 1 # True
42.[0] =:= 42 # True
In the following case, the compiler can't immediately tell that it will only produce one value:
my $n = 1;
$m<< $n >>;
As it could produce 2 values:
my $o = '1 2';
$m<< $o >>;
If you want the string to be a single key, you have to use quotation marks.
$m<< "$o" >>
Or use the more appropriate {}
$m{ $n }
The $m<1> is just a combination of two features.
Quotewords: ( qw<> and qqww<<>> )
< a b c > eqv ("a", "b", "c")
< "a b" c > eqv (「"a」, 「b"」, "c") # three strings
<< a b c >> eqv ("a", "b", "c")
<< "a b" c >> eqv ("a b", "c") # two strings
Associative indexing:
%h< a b c > eqv %h{ < a b c > }
%h<< "a b" c >> eqv %h{ << "a b" c >> }
Also I now get back different values.
$m< 1 >.WHAT =:= Pair
$m<< 1 >>.WHAT =:= Pair
$m<< $n >>.WHAT =:= Pair # different
$m<< $o >>.WHAT =:= List
The reason $m<<$n>>.[0] doesn't work is the compiler thinks you are using a hyper postfix >>.[0].
There are a couple ways of working around that.
Actually using a hyper postfix
$m<<$n>>>>.[0]
$m<<$n>>».[0]
Use an unspace. (can never be inside of an operator so will split them up)
$m<<$n>>\.[0]
$m<<$n>>\ .[0]
I think this is a bug, as it doesn't make much sense to be matching a hyper postfix inside of a quotewords statement.
(It doesn't affect $m<<1>>.elems)

Object Hash Lookup with `eqv`

Is there a way to use eqv to lookup a hash value without looping over the key-value pairs when using object keys?
It is possible to use object keys in a hash by specifying the key's type at declaration:
class Foo { has $.bar };
my Foo $a .= new(:bar(1));
my %h{Foo} = $a => 'A', Foo.new(:bar(2)) => 'B';
However, key lookup uses the identity operator === which will return the value only if it is the same object and not an equivalent one:
my Foo $a-prime .= new(:bar(1));
say $a eqv $a-prime; # True
say $a === $a-prime; # False
say %h{$a}; # A
say %h{$a-prime}; # (Any)
Looking at the documentation for "===", the last line reveals that the operator is based on .WHICH and that "... all value types must override method WHICH." This is why if you create two separate items with the same string value, "===" returns True.
my $a = "Hello World";
my $b = join " ", "Hello", "World";
say $a === $b; # True even though different items - because same value
say $a.WHICH ; # "Str|Hello World"
say $b.WHICH ; # (same as above) which is why "===" returns True
So, instead of creating your own container type or using some of the hooks for subscripts, you could instead copy the way "value types" do it - i.e. butcher (some what) the idea of identity. The .WHICH method for strings shown above simply returns the type name and contents joined with a '|'. Why not do the same thing;
class Foo {
has $.bar;
multi method WHICH(Foo:D:) { "Foo|" ~ $!bar.Str }
}
my Foo $a .= new(:bar(1));
my %h{Foo} = $a => 'A', Foo.new(:bar(2)) => 'B';
my Foo $a-prime .= new(:bar(1));
say $a eqv $a-prime; # True
say $a === $a-prime; # True
say %h{$a}; # A
say %h{$a-prime}; # A
There is a small cost, of course - the concept of identity for the instances of this class is, well - let's say interesting. What are the repercussions? The only thing that comes immediately to mind, is if you plan to use some sort of object persistence framework, its going to squish different instances that now look the same into one (perhaps).
Different objects with the same attribute data are going to be indistinguishable - which is why I hesitated before posting this answer. OTOH, its your class, its your app so, depending on the size/importance etc, it may be a fine way to do it.
If you don't like the buildin postcircumfix, provide your own.
class Foo { has $.bar };
my Foo $a .= new(:bar(1));
my %h{Foo} = $a => 'A', Foo.new(:bar(2)) => 'B';
multi sub postcircumfix:<{ }>(\SELF, WhateverCode $c) is raw {
gather for SELF.keys -> $k {
take $k if $c($k)
}
}
dd %h;
dd %h{* eqv $a};
OUTPUT
Hash[Any,Foo] %h = (my Any %{Foo} = (Foo.new(bar => 1)) => "A", (Foo.new(bar => 2)) => "B")
(Foo.new(bar => 1),).Seq

TCL multiple assignment (as in Perl or Ruby)

In Ruby or Perl one can assign more than variable by using parentheses. For example (in Ruby):
(i,j) = [1,2]
(k,m) = foo() #foo returns a two element array
Can one accomplish the same in TCL, in elegant way? I mean I know that you can
do:
foreach varname { i j } val { 1 2 } { set $varname $val }
foreach varname { k m } val [ foo ] { set $varname $val }
But I was hoping for something shorter/ with less braces.
Since Tcl 8.5, you can do
lassign {1 2} i j
lassign [foo] k m
Note the somewhat unintuitive left-to-right order of value sources -> variables. It's not a unique design choice: e.g. scan and regexp use the same convention. I'm one of those who find it a little less readable, but once one has gotten used to it it's not really a problem.
If one really needs a Ruby-like syntax, it can easily be arranged:
proc mset {vars vals} {
uplevel 1 [list lassign $vals {*}$vars]
}
mset {i j} {1 2}
mset {k m} [foo]
Before Tcl 8.5 you can use
foreach { i j } { 1 2 } break
foreach { k m } [ foo ] break
which at least has fewer braces than in your example.
Documentation: break, foreach, lassign, list, proc, uplevel

perl6/rakudo: Unable to parse postcircumfix:sym<( )>

Why do I get this error-message?
#!perl6
use v6;
my #a = 1..3;
my #b = 7..10;
my #c = 'a'..'d';
for zip(#a;#b;#c) -> $nth_a, $nth_b, $nth_c { ... };
# Output:
# ===SORRY!===
# Unable to parse postcircumfix:sym<( )>, couldn't find final ')' at line 9
Rakudo doesn't implement the lol ("list of lists") form yet, and so cannot parse #a;#b;#c. For the same reason, zip doesn't have a form which takes three lists yet. Clearly the error message is less than awesome.
There isn't really a good workaround yet, but here's something that will get the job done:
sub zip3(#a, #b, #c) {
my $a-list = flat(#a.list);
my $b-list = flat(#b.list);
my $c-list = flat(#c.list);
my ($a, $b, $c);
gather while ?$a-list && ?$b-list && ?$c-list {
$a = $a-list.shift unless $a-list[0] ~~ ::Whatever;
$b = $b-list.shift unless $b-list[0] ~~ ::Whatever;
$c = $c-list.shift unless $c-list[0] ~~ ::Whatever;
take ($a, $b, $c);
}
}
for zip3(#a,#b,#c) -> $nth_a, $nth_b, $nth_c {
say $nth_a ~ $nth_b ~ $nth_c;
}
The multi-dimensional syntax (the use of ; inside parens) and zip across more than two lists both work, so the code originally posted now works (if you provide some real code rather than the { ... } stub block).