How can I print a Perl hash in specific order? - arrays

I have this code
#!/usr/bin/perl
use strict;
my #a = ("b","a","d","c");
my %h = ("a",1,"b",2,"c",3,"d",4);
#print '"' . join('","', #a), "\"\n";
print "\n";
foreach my $key (#a) {
print '"' . $h{$key} . '",';
}
print "\n";
that outputs
"2","1","4","3",
but I would like that it just outputted
"2","1","4","3"
Notice that last ',' isn't there.
Is it possible to e.g. print a hash in a specific order, or some other trick to get the output I want?
Update:
Based on friedo's answer I was able to get it right.
print '"' . join('","', #h{#a}), "\"\n";
Friedo's answer doesn't have the quotes around the values.

print join("," => map qq["$_"], #h{#a}), "\n";
At the heart of this line is #h{#a}, a hash slice that means the same as
($h{"b"}, $h{"a"}, $h{"d"}, $h{"c"})
The obvious advantage is economy of expression.
Moving out one level, we find the map operator: for each value from #h{#a}, wrap it in double quotes using qq["$_"]. Because you want double quotes in the result, the code uses qq// to switch delimiters. Otherwise, you'd be stuck with "\"$_\"" or a concatenation chain as in your question.
Finally, join inserts commas between the mapped values. In this case, => is identical to the comma operator, but I use it here instead of
join(",", ...)
which I find visually unpleasing because of the commas being crammed together.
You may be tempted to write the join without parentheses, but doing so would make "\n" an argument to join rather than print.

You can use a hash slice to get the values, then use join to stick them together with commas.
print join ",", #h{#a};

Use join to put commas between values and not at the end, and map to wrap each value in double quotes.
print join(",", map { qq|"$h{$_}"| } #a);

Related

Convert array to a string with newlines embeded in string

i need to convert a perl array to a single string variable, containing all array items separated with newlines.
my $content = "";
#names=('A','C','C','D','E');
$content = $content . join($", #names) . "\n";
print $content;
I intend the output to be like:
A
C
C
D
E
But i get:
A C C D E
Why isn't the newline \n character being honoured?
Since it appears that you want a newline not just between each line, but after the last one too, you can use any of the following:
join("\n", #names) . "\n"
join("", map "$_\n", #names)
join("\n", #names, "")
These are equivalent except when the array in empty. In that situation, the first results in a newline, and the other result in an empty string.
By the way,
$content = $content . EXPR;
can be written as
$content .= EXPR;
To join an array with newlines in between, use
join("\n", #array)
Your code uses the contents of the $" variable as the separator, which by default contains a space.
Do this instead:
$content = $content . join("\n", #names);
The $" variable holds the value that Perl uses between array elements when it interpolates an array in a double-quoted string. By default (and you haven't changed it), it's a space.
Perhaps you were thinking about the input or output record separators, $/ or $\.
But, you don't really want to play with those variables. If you want a newline, use a newline:
join "\n", #array;

How to type selected elements of an array in double quotes

Here, I'm trying to print the 2nd, 3rd and 4th elements of an array in double quotes and I've only been able to do it in single quotes.
my #fruits = ("apples", "oranges", "guavas", "passionfruits", "grapes");
my $a = 1;
while ($a<4){
print " '$fruits[$a]' \n";
$a += 1;
}
But I can't do this in single quotes. When I change the single quotes to double quotes and vice versa, it prints "$fruits[$a]"\n three times instead.
And when I change all quotes to double quotes, it gives an error which I understand why.
Please I really need help here.
And if I could get a way to print all three elements in double quotes without having to use a loop. Thanks!
To use " in a string delimited by ", escape it.
"foo: \"$bar\"\n"
You could also switch the delimiter (keeping in mind that "..." is short for qq"...").
qq{foo: "$bar"\n}
Always use
use strict;
use warnings;
even in the shortest Perl scripts.
In case of typos in the code, Perl will usually issue errors if you include them. Without, Perl will happily do weird, wrong and pointless things silently.
Your example will not print the entire array. Instead you will get:
'oranges'
'guavas'
'passionfruits'
The first index of an array is 0 and therefore 'apples' is skipped because $a is initialized with 1. The loop is also exited due to reaching the value 4 before printing out 'grapes'.
In order to print the entire array you would do:
if you need to use the index value $i somewhere:
for my $i (0 .. $#fruits) {
print " $i: '$fruits[$i]' \n";
}
($#fruits is the last index if #fruits, equal to the the size of the array minus 1. Since this array has 5 items, the index values range from 0 to 4)
otherwise:
foreach my $current_fruit (#fruits) {
print " '$current_fruit' \n";
}
where $current_fruit is set to each item in the array #fruits in its turn.
Quotes in Perl function as operators, and depending on which ones you use, they may or may not do various things with the included string.
In your examples, the double quotes will do interpolation on the string, substituting the value of $fruits[$a] and replacing the escape sequence \n. Therefore:
print " '$fruits[$a]' \n";
(for $a == 1) becomes:
'oranges'
Single quotes, in contrast, will not do interpolation.
Only single quotes themselves (and backslashes preceding single quotes) need to be escaped with a backslash. (Other backslashes can optionally be escaped.) All other character sequences will appear as they are, so the argument to print in:
print ' "$fruits[$a]" \n';
is considered entirely a literal string and thus
"$fruits[$a]" \n "$fruits[$a]" \n "$fruits[$a]" \n
is printed out.
To get the desired output, there are multiple ways to go about it:
The simplest way - but not easiest to read for complex strings - is to use double quotes and escape the included quotes:
print " \"$fruits[$a]\" \n";
You can use an generic notation for "...", which is qq{...} where {} are either a pair of braces (any of (), [], {} or <>) or the same other non-whitespace, non-word character, e.g.:
print qq{ "$fruits[$a]" \n};
or
print qq! "$fruits[$a]" \n!;
You can concatenate the string out of parts that you quote separately:
print ' "' . $fruits[$a] . '"' . " \n";
Which is easiest to read in code will depend on the complexity of the string and the variables contained: For really long strings with complex dereferences, indeces and hash keys you might want to use option 3, whereas for short ones 2 would be the best.
My entire edited code:
use strict;
use warnings;
my #fruits = ("apples", "oranges", "guavas", "passionfruits", "grapes");
for my $i (0 .. $#fruits) {
print " $i: \"$fruits[$i]\" \n";
print qq< $i: "$fruits[$i]" \n>;
print ' ' . $i . ': "' . $fruits[$i] . '"' . " \n";
}
foreach my $current_fruit (#fruits) {
print " \"$current_fruit\" \n";
print qq¤ "$current_fruit" \n¤;
print ' "' . $current_fruit . '"' . " \n";
}
You can learn more about the different quotes in Perl from the perldoc (or man on UNIX-like systems) page perlop and its section titled "Quote and Quote-like Operators".

Changing element's positions in Perl

So I have a problem and I can't solve it. If I read some words from a file in Perl, in that file the words aren't in order, but have a number (as a first character) that should be the element's position to form a sentence.The 0 means that position is correct, 1 means that the word should be in position [1] etc.
The file looks like: 0This 3a 4sentence 2be 1should, and the solution should look like 0This 1should 2be 3a 4sentence.
In a for loop I get through the words array that i get from the file, and this is how i get the first character(the number) $firstCharacter = substr $words[$i], 0, 1;, but i don't know how to properly change the array.
Here's the code that I use
#!/usr/bin/perl -w
$arg = $ARGV[0];
open FILE, "< $arg" or die "Can't open file: $!\n";
$/ = ".\n";
while($row = <FILE>)
{
chomp $row;
#words = split(' ',$row);
}
for($i = 0; $i < scalar #words; $i++)
{
$firstCharacter = substr $words[$i], 0, 1;
if($firstCharacter != 0)
{
}
}
Just use sort. You can use a match in list context to extract the numbers, using \d+ will work even for numbers > 9:
#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my #words = qw( 0This 3a 4sentence 2be 1should );
say join ' ', sort { ($a =~ /\d+/g)[0] <=> ($b =~ /\d+/g)[0] } #words;
If you don't mind the warnings, or you are willing to turn them off, you can use numeric comparison directly on the words, Perl will extract the numeric prefixes itself:
no warnings 'numeric';
say join ' ', sort { $a <=> $b } #words;
Assuming you have an array like this:
my #words = ('0This', '3a', '4sentence', '2be', '1should');
And you want it sorted like so:
('0This', '1should', '2be', '3a', '4sentence');
There's two steps to this. First is extracting the leading number. Then sorting by that number.
You can't use substr, because you don't know how long the number might be. For example, ('9Second', '12345First'). If you only looked at the first character you'd get 9 and 1 and sort them incorrectly.
Instead, you'd use a regex to capture the number.
my($num) = $word =~ /^(\d+)/;
See perlretut for more on how that works, particularly Extracting Matches.
Now that you can capture the numbers, you can sort by them. Rather than doing it in loop yourself, sort handles the sorting for you. All you have to do is supply the criterion for the sorting. In this case we capture the number from each word (assigned to $a and $b by sort) and compare them as numbers.
#words = sort {
# Capture the number from each word.
my($anum) = $a =~ /^(\d+)/;
my($bnum) = $b =~ /^(\d+)/;
# Compare the numbers.
$anum <=> $bnum
} #words;
There are various ways to make this more efficient, in particular the Schwartzian Transform.
You can also cheat a bit.
If you ask Perl to treat something as a number, it will do its damnedest to comply. If the string starts with a number, it will use that and ignore the rest, though it will complain.
$ perl -wle 'print "23foo" + "42bar"'
Argument "42bar" isn't numeric in addition (+) at -e line 1.
Argument "23foo" isn't numeric in addition (+) at -e line 1.
65
We can take advantage of that to simplify the sort by just comparing the words as numbers directly.
{
no warnings 'numeric';
#words = sort { $a <=> $b } #words;
}
Note that I turned off the warning about using a word as a number. use warnings and no warnings only has effect within the current block, so by putting the no warnings 'numeric' and the sort in their own block I've only turned off the warning for that one sort statement.
Finally, if the words are in a file you can use the Unix sort utility from the command line. Use -n for "numeric sorting" and it will do the same trick as above.
$ cat test.data
00This
3a
123sentence
2be
1should
$ sort -n test.data
00This
1should
2be
3a
123sentence
You should be able to split on the spaces, which will make the numbers the first character of the word. With that assumption, you can simply compare using the numerical comparison operator (<=>) as opposed to the string comparison (cmp).
The operators are important because if you compare strings, the first character is used, meaning 10, 11, and 12 would be out of order, and listed near the 1 (1,10,11,12,2,3,4… instead of 1,2,3,4…10,11,12).
Split, Then Sort
Note: #schwern commented an important point. If you use warnings -- and you should -- you will receive warnings. This is because the values of the internal comparison variables, $a and $b, aren't numbers, but strings (e.g., `"0this", "3a"). I've update the following Codepad and provided more suitable alternatives to avoid this issue.
http://codepad.org/xs2GH9xT
use strict;
use warnings;
my $line = q{0This 3a 4sentence 2be 1should};
my #words = split /\s/,$line;
my #sorted = sort {$a <=> $b} #words;
print qq{
Line: $line
Words: #words
Sorted: #sorted
};
Alternatives
One method is to ignore the warning using no warnings 'numeric' as in Schwern's answer. As he has shown, turning off the warnings in a block will re-enable it afterwards, which may be a little foolproof compared to Choroba's answer, which applies it to the broader scope.
Choroba's solution works by parsing the digits from the those values internally. This is much fewer lines of code, but I would generally advise against that for performance reasons. The regex isn't only run once per word, but multiple times over the sorting process.
Another method is to strip the numbers out and use them for the sort comparison. I attempt to do this below by creating a hash, where the key will be the number and the value will be the word.
Hash Mapping / Key Sort
Once you have an array where the values are the words prefixed by the numbers, you could just as easily split those number/word combo into a hash that has the key as the number and value as the word. This is accomplished by using split.
The important thing to note about the split statement is that a limit is passed (in this case 2), which limits the maximum number of fields the string is split into.
The two values are then used in the map to build the key/value assignment. Thus "0This" is split into "0" and "This" to be used in the hash as "0"=>"This"
http://codepad.org/kY8wwajc
use strict;
use warnings;
my $line = q{0This 3a 4sentence 2be 1should};
my #words = split /\s/, $line; # [ '0This', '3a', ... ]
my %mapped = map { split /(?=\D)/, $_, 2 } #words; # { '0'=>'This, '3'=>'a', ... }
my #sorted = #mapped{ sort { $a <=> $b } keys %mapped }; # [ 'This', 'should', 'be', ... ]
print qq{
Line: $line
Words: #words
Sorted: #sorted
};
This also can be further optimized, but uses multiple variables to illustrate the steps in the process.

Ways to Flatten A Perl Array in Scalar Context

I recently started learning perl and have a question that I'm not finding a clear answer to on the Internet. say I have something like this,
#arr = (1, 2, 3);
$scal = "#arr"
# $scal is now 123.
Is the use of quotes the only way to flatten the array so that each element is stored in the scalar value? It seems improbable but I haven't found any other ways of doing this. Thanks in advance.
The join function is commonly used to "flatten" lists. Lets you specify what you want between each element in the resulting string.
$scal = join(",", #arr);
# $scal is no "1,2,3"
In your example, you're interpolating an array in a double-quoted string. What happens in those circumstances is is controlled by Perl's $" variable. From perldoc perlvar:
$LIST_SEPARATOR
$"
When an array or an array slice is interpolated into a double-quoted string or a similar context such as /.../ , its elements are separated by this value. Default is a space. For example, this:
print "The array is: #array\n";
is equivalent to this:
print "The array is: " . join($", #array) . "\n";
Mnemonic: works in double-quoted context.
The default value for $" is a space. You can obviously change the value of $".
{
local $" = ':',
my #arr = (1, 2, 3);
my $scalar = "#arr"; # $scalar contains '1:2:3'
}
As with any of Perl's special variables, it's always best to localise any changes within a code block.
You could also use join without any seperator
my $scalar = join( '' , #array ) ;
There is more than one way to do it.
in the spirit of TIMTOWTDI:
my $scal;
$scal .= $_ foreach #arr;
Read section Context in perldata. Perl has two major contexts: scalar and list.
For example:
#a = (1, 1, 1); # list context
print #a; # list context
$count = #a; # scalar context, returns the number of elements in #a
etc.

How to print 'AND' between array elements?

If I have an array with name like below.
How do I print "Hi joe and jack and john"?
The algorithm should also work, when there is only one name in the array.
#!/usr/bin/perl
use warnings;
use strict;
my #a = qw /joe jack john/;
my $mesg = "Hi ";
foreach my $name (#a) {
if ($#a == 0) {
$mesg .= $name;
} else {
$mesg .= " and " . $name;
}
}
print $mesg;
Usually we use an array join method to accomplish this. Here pseudo code:
#array = qw[name1 name2 name2];
print "Hey ", join(" and ", #array), ".";
Untested:
{ local $, = " and "; print "Hi "; print #a; }
Just use the special variable $".
$"="and"; #" (this last double quote is to help the syntax coloring)
$mesg="Hi #a";
To collect the perldoc perlvar answers, you may do one of (at least) two things.
1) Set $" (list separator):
When an array or an array slice is interpolated into a double-quoted
string or a similar context such as /.../ , its elements are separated
by this value. Default is a space. For example, this:
print "The array is: #array\n";
is equivalent to this:
print "The array is: " . join($", #array) . "\n";
=> $" affects the behavior of the interpolation of the array into a string
2) Set $, (output field separator):
The output field separator for the print operator. If defined, this
value is printed between each of print's arguments. Default is undef.
Mnemonic: what is printed when there is a "," in your print statement.
=> $, affects the behavior of the print statement.
Either will work, and either may be used with local to set the value of the special variable only within an enclosing scope. I guess the difference is that with $" you are not limited to the print command:
my #z = qw/ a b c /;
local $" = " and ";
my $line = "#z";
print $line;
here the "magic" happens on the 3rd line not at the print command.
In truth though, using join is the most readable, and unless you use a small enclosing block, a future reader might not notice the setting of a magic variable (say nearer the top) and never see that the behavior is not what is expected vs normal performance. I would save these tricks for small one-offs and one-liners and use the readable join for production code.

Resources