in reply to Alternative to sort Hash

G'day IB2017,

The first code statement you presented has problems. You would have seen this had you used strict and warnings. Your code basically has this form:

$ perl -e '%x = { a => 1, b => 2 }' $

With warnings you would have found this problem:

$ perl -e 'use warnings; %x = { a => 1, b => 2 }' Name "main::x" used only once: possible typo at -e line 1. Reference found where even-sized list expected at -e line 1. $

With strict you would have found this problem:

$ perl -e 'use strict; %x = { a => 1, b => 2 }' Global symbol "%x" requires explicit package name (did you forget to d +eclare "my %x"?) at -e line 1. Execution of -e aborted due to compilation errors. $

All your Perl scripts should start with:

use strict; use warnings;

Your next problem seems to be that you think hashes are ordered collections: they are not. If you look at keys, values or each, you'll find the same information:

"Hash entries are returned in an apparently random order. ..."

Read any of those for more information about this.

Using the empty string as a key into %voices will result in an uninitialised value. That's another potential problem; although, you don't show any code that deals with that.

Here's a general solution:

#!/usr/bin/env perl use strict; use warnings; my %all_voices = ( 'Microsoft Hedda Desktop - German' => 3, 'Microsoft Haruka Desktop - Japanese' => 4, 'Microsoft Zira Desktop - English (United States)' => 0, 'Microsoft Hazel Desktop - English (Great Britain)' => 1, 'Microsoft David Desktop - English (United States)' => 2, 'Microsoft Huihui Desktop - Chinese (Simplified)' => 5 ); for my $language (qw{German Japanese French English Chinese}) { my @available_voices = grep /^[^-]+-\s+$language/, keys %all_voice +s; if (@available_voices) { my $first_voice = ( sort { $all_voices{$a} <=> $all_voices{$b} } @available_voices )[0]; printf "%8s : [%d] %s\n", $language, $all_voices{$first_voice}, $first_voice; } else { printf "%8s : No voice found\n", $language; } }

Output:

German : [3] Microsoft Hedda Desktop - German Japanese : [4] Microsoft Haruka Desktop - Japanese French : No voice found English : [0] Microsoft Zira Desktop - English (United States) Chinese : [5] Microsoft Huihui Desktop - Chinese (Simplified)

To remove the English voice with value 0, when there are more than 1 English voices, you can simply add a filter like this:

... if (@available_voices) { if (@available_voices > 1) { @available_voices = grep $all_voices{$_}, @available_voice +s; } ...

Now output is this:

German : [3] Microsoft Hedda Desktop - German Japanese : [4] Microsoft Haruka Desktop - Japanese French : No voice found English : [1] Microsoft Hazel Desktop - English (Great Britain) Chinese : [5] Microsoft Huihui Desktop - Chinese (Simplified)

Finally, you seem (as far as I can tell) to be asking for a "compact" solution. Less code is not necessarily better code: it's often less readable and therefore hard to maintain and potentially error-prone; and, after Perl's optimisations, there's no guarantee it's any more efficient. What exactly are you requirements for this code? Why do you think a "compact" solution would be preferable?

— Ken

Replies are listed 'Best First'.
Re^2: Alternative to sort Hash
by soonix (Chancellor) on Dec 06, 2019 at 14:11 UTC
    Less code is not necessarily better code
    True, but OP's %voices looks like it's generated from something like {map {$_ => $n++} @list_of_voices} which (in my eyes) is unnecessary, especially as he uses List::Util, already.