Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting hash kv pairs

my %hash =
    two   => 2,
    three => 3,
    one   => 1,
;

for %hash.sort(*.key)>>.kv -> ($key, $value) {
    say "'$key' => '$value'";
}

Is %hash.sort({.key})>>.kv equivalent to above sort?

Why this sort doesn't work without hyper >> hint?

like image 953
mpapec Avatar asked Jul 31 '15 11:07

mpapec


2 Answers

The sort method is returning a List of Pairs.

Since calling .kv on the list returns a list of index, Pair lists, which you don't want; you can't just call .kv. So you have to pull out the key and value from the Pair objects in the list individually by calling the .kv method on each of them, which >>.kv does.

You could also have used .map(*.kv) instead.

The >>.kv syntax allows an implementation to spread the work over multiple threads if it makes sense to do so.
( Currently Rakudo just does the work in a semi-random order to prevent people from using the feature wrong )


There is an alternate way of writing the loop by extracting the attributes using adverbs in the sub-signature:

for %hash.sort -> (:$key, :$value) {
  say "'$key' => '$value'";
}

for %hash.sort -> $pair (:$key, :$value) {
  say $pair;
  say $key === $pair.key and $value === $pair.value; # True␤
}

# :$key is short for :key($key)
for %hash.sort -> (:key($k), :value($v)) {
  say "'$k' => '$v'";
}

This can be useful on other objects which don't have a method for creating a list of their public attributes

class C { has $.a; has $.b; has $.c; has $!private-value }
my $c = 5;
my $obj = C.new(:a<A>,:b(1),:$c);

given $obj -> ( :$a, :b($b), :$c) ) {
  say "$a $b $c";
}

# ignore $.a by using an unnamed scalar
given $obj -> ( :a($), :$b, :$c ) { ... }

# places any unspecified public attributes in %others
given $obj -> ( :$a, :$b, *%others ) {
  .say for keys %others; # c␤
}

# ignores any unspecified attributes
# useful to allow subclasses to add more attributes
# or to just drop any values you don't care about
given $obj -> ( :$a, :$b, *% ) { ... }

# fails because it doesn't handle the public c attribute
# in the sub-signature
given $obj -> ( :$a, :$b ) { ... }

That is only the beginning of what's possible with signatures.

All of the following is also allowed in subroutine and method signatures, optional, and completely overkill for this example. It is really useful in multi subs and multi methods for restricting the possible candidates.

for 'one' => 1, 1/3
->
  # Type is an alias to the object type
  ::Type Any $_ # Any is the default type requirement

  # the public attributes of the object
  (
    ::A-Type Any :key(   :numerator(   $a ) ),
    ::B-Type Any :value( :denominator( $b ) ) where $b >= 1,
  )
{
  my Type $obj = $_; # new variable declared as having the same type
  my A-Type $new-a = $a;
  my B-Type $new-b = $b;

  # could have used $_.^name or .^name instead of Type.^name
  # so you don't actually have to add the alias to the signature
  # to get the name of the arguments type
  say Type.^name, ' ', $_;
  say '  ', A-Type.^name, ' ', $a;
  say '  ', B-Type.^name, ' ', $b;
}
Pair one => 1
  Str one
  Int 1
Rat 0.333333
  Int 1
  Int 3

As to using .sort({.key}), yes that is basically the same thing, as sort accepts anything Callable there.

I would like to point out that you didn't even need to provide an argument to sort because it's default is even smarter than what you gave it.

Perl 6 has many ways of creating and accessing Callable things. So any of the following would have worked:

*.key
{ .key } # { $_.key }
-> $_ { .key } # basically what the previous line turns into
{ $^placeholder-var.key }
sub ($_) { .key }
&a-subroutine-reference # you would have to create the subroutine though

Also since all of the normal operators are actually subroutines, you could use them in other places where you need a Callable. ( I can't think of one that works in that spot though )

&infix:<+> # the subroutines responsible for the numeric addition operator
&[+] # ditto

&prefix:<++>
&postfix:<++>

# etc
like image 73
Brad Gilbert Avatar answered Sep 28 '22 08:09

Brad Gilbert


As far as I can see, the ony difference between the two versions is use of a block with implicit $_ parameter instead of using a Whatever-Star, so they are indeed equivalent.

This is Perl, so There Is More Than One Way to Do It:

*.key
{ .key }
{ $^arg.key }
-> $arg { $arg.key }

Why this sort doesn't work without hyper >> hint?

sort coerces the hash to a list of pairs, and that's what you'll get:

say %hash.sort(*.key).perl;
# ("one" => "1", "three" => "3", "two" => "2")

To get rid of the pairs, you need to iterate over the list and call .kv on each one:

say %hash.sort(*.key)>>.kv.perl;
# (("one", "1"), ("three", "3"), ("two", "2"))

say %hash.sort(*.key).map(*.kv).perl;
# (("one", "1"), ("three", "3"), ("two", "2"))

You could coerce to Hash before calling .kv:

say %hash.sort(*.key).hash.kv.perl;
# ("one", "1", "three", "3", "two", "2")

but this would of course defeat the purpose of the excercise as hash ordering cannot be relied on.

You may have noticed that you'll get different results depending on how exactly you write the code. If there no trailing .list, what you get is actually a Parcel and not a List, but semantics have not been finalized.

Note that even though the returned objects all perlify with simple parentheses, some are parcels and some are lists, which you can check by calling .WHAT. This is still a work in progress.

Also note the inner parentheses in some of these variants, which you can get rid of with a call to .flat. If you do so, you can use -> $key, $value as signature of your for loop instead of -> ($key, $value) (or, more explicitly, -> $anon ($key, $value)) which uses signature binding to unpack the parcels.

Instead of using .kv, you could use the same approach to unpack the pair objects instead:

for %hash.sort(*.key) -> (:$key, :$value) {
    say "'$key' => '$value'";
}
like image 24
Christoph Avatar answered Sep 28 '22 09:09

Christoph