9

I'm looking at the following Data (in JSON)

{
  "FValidation_pipelineMTHXT_v4.5.1_refLibV2": "concordance2/f",
  "FValidation_pipelineLPJL": "concordance2/c",
  "FCompetenceRuns": "concordance2/b",
  "FWGS": "concordance2/a",
  "Falidation_pipelineMTHXT": "concordance2/e",
  "FValidation_pipelineLPJL_v4.5.1_refLibV2": "concordance2/d"
}

and I'm trying to sort by the hash value

for %files.kv -> $key, $value {

gives the required data, but I want it sorted. I've tried about 20 different methods, which don't work

for %files.sort.kv  -> ($key, $value) {

and

for %files.sort: *.value.kv  -> ($key, $value) {

which was inspired from https://docs.perl6.org/routine/sort#(Map)_method_sort and on and on but none are working :(

How can I sort this hash by value?

jjmerelo
  • 19,108
  • 5
  • 33
  • 72
con
  • 4,303
  • 5
  • 19
  • 42
  • 1
    @raiph `sort(*.value)` returns a sequence, and thus `.kv` will return `$index, $element` not `$element-key, $element-value`. You could try to turn it back into a hash e.g. `sort(*.value).hash.kv` but then hash randomization loses the sorting. – ugexe May 10 '19 at 02:44
  • 1
    @ugexe Thx. I've deleted my bogus comment. So your answer is spot on. `sort` returns a `Seq`. If the invocant is a hash the `Seq` elements are `Pair`s. A block can then use `.key` and `.value`. Simple! Lessons learned: **1** I've still failed to learn the lesson that "*Always* test code, *no exceptions*" includes the words *always* and *no exceptions*. **2** A hash stores its keys in random order -- "sorted hash" is an oxymoron. Think "sorted pairs" instead. **3** `sort` doesn't return a sorted version of its invocant. It returns a `Seq` of the elements of that invocant in some sorted order. – raiph May 10 '19 at 12:20

2 Answers2

9

.kv returns a flat sequence.

my %h = (
  a => 3,
  b => 2,
  c => 1,
);

say %h.kv.perl;
# ("a", 3, "c", 1, "b", 2).Seq

If you sort it, then you do so without keeping the key with its associated value.

say %h.kv.sort.perl;
# (1, 2, 3, "a", "b", "c").Seq

So you want to sort it before splitting up the pairs.

# default sort order (key first, value second)
say %h.sort.perl;
# (:a(3), :b(2), :c(1)).Seq

say %h.sort: *.value;       # sort by value only (tied values are in random order)
# (:c(1), :b(2), :a(3)).Seq
say %h.sort: *.invert;      # sort by value first, key second
# (:c(1), :b(2), :a(3)).Seq
say %h.sort: *.kv.reverse;  # sort by value first, key second
# (:c(1), :b(2), :a(3)).Seq

Once it is sorted you can take it as a sequence of Pair objects:

# default $_
for %h.sort: *.invert {
  say .key ~ ' => ' ~ .value
}

# extract as named attributes
for %h.sort: *.invert -> (:$key, :$value) {
  say "$key => $value"
}

# more explicit form of above
for %h.sort: *.invert -> Pair $ (:key($key), :value($value)) {
  say "$key => $value"
}

Or you could pull apart the pairs after the sort:
(Notice the two-level structure.)

say %h.sort(*.invert).map(*.kv).perl;
# (("c", 1).Seq, ("b", 2).Seq, ("a", 3).Seq).Seq
say %h.sort(*.invert)».kv.perl;
# (("c", 1).Seq, ("b", 2).Seq, ("a", 3).Seq).Seq

# default $_
for %h.sort(*.invert).map(*.kv) {
  say .key ~ ' => ' ~ .value
}

# extract inner positional parameters
for %h.sort(*.invert).map(*.kv) -> ($k,$v) {
  say "$k => $v"
}

# `».kv` instead of `.map(*.kv)`
for %h.sort(*.invert)».kv -> ($k,$v) {
  say "$k => $v"
}

You can even flatten it after pulling apart the pair objects.

say %h.sort(*.invert).map(*.kv).flat.perl;
# ("c", 1, "b", 2, "a", 3).Seq
say %h.sort(*.invert)».kv.flat.perl;
# ("c", 1, "b", 2, "a", 3).Seq

for %h.sort(*.invert).map(*.kv).flat -> $k, $v {
  say "$k => $v"
}

for %h.sort(*.invert)».kv.flat -> $k, $v {
  say "$k => $v"
}

(Note that ».method only maps over one method call. To map over two you need ».method1».method2, or just use map .map(*.method1.method2).
So in the ».kv.flat above, only the .kv method is mapped over the values.)

Brad Gilbert
  • 32,263
  • 9
  • 73
  • 122
  • ` %files.sort(*.invert)».kv -> ($key,$value) {` seems to work the best for me. Thanks! – con May 10 '19 at 13:48
8
my %hash = :a<z>, :b<y>, :c<x>;

for %hash.sort(*.value) {
    say $_.key;
    say $_.value;
}
ugexe
  • 5,090
  • 1
  • 19
  • 39
  • 2
    I know it is just an example, but just to note alternatives -- instead of `say $_.key` could just `say .key` or even `.key.say`. – Curt Tilmes May 10 '19 at 03:09