Hash Key and Value in Perl - perl

I have the question in Perl:Read a series of last names and phone numbers from the given input. The names and numbers should be separated by a comma. Then print the names and numbers alphabetically according to last name. Use hashes.
#!usr/bin/perl
my %series = ('Ashok','4365654435' 'Ramnath','4356456546' 'Aniketh','4565467577');
while (($key, $value) = each(sort %series))
{
print $key.",".$value."\n";
}
I am not getting the output. Where am I going wrong? Please help. Thanks in advance
#!usr/bin/perl
my %series = ('Ashok','4365654435' 'Ramnath','4356456546' 'Aniketh','4565467577');
print $_.",".$series{$_}."\n" for sort keys %series;
If I execute any of the above 2 programs, I get the same output as:
String found where operator expected at line 2, near "'4365654435' 'Ramnath'" (Missing operator before 'Ramnath'?)
String found where operator expected at line 2, near "'4356456546' 'Aniketh'" (Missing operator before 'Aniketh'?)
syntax error at line 2, near "'4365654435' 'Ramnath'"
Execution aborted due to compilation errors
But according to the question, I think I cannot store the input as my %series = ('Ashok','4365654435','Ramnath','4356456546','Aniketh','4565467577');

each only operates on hashes. You can't use sort like that, it sorts lists not hashes.
Your loop could be:
foreach my $key (sort keys %series) {
print $key.",".$series{$key}."\n";
}
Or in shorthand:
print $_.",".$series{$_}."\n" for sort keys %series;

In your hash declaration you have:
my %series = ('Ashok','4365654435' 'Ramnath','4356456546' 'Aniketh','4565467577');
This is generating the warnings.
A hash is simply an even list of scalars. Therefore, you have to put a comma between each pair:
my %series = ('Ashok','4365654435', 'Ramnath','4356456546', 'Aniketh','4565467577');
# ^--- ^---
If you want visual distinction between the pairs, you can use the => operator. This behaves the same as the comma. Additionaly, if the left hand side is a legal bareword, it is viewed as a quoted string. Therefore, we could write any of these:
# it is just a comma after all, with autoquoting
my %series = (Ashok => 4365654435 => Ramnath => 4356456546 => Aniketh => 4565467577);
# using it as a visual "pair" constructor
my %series = ('Ashok'=>'4365654435', 'Ramnath'=>'4356456546', 'Aniketh'=>'4565467577');
# as above, but using autoquoting. Numbers don't have to be quoted.
my %series = (
Ashok => 4365654435,
Ramnath => 4356456546,
Aniketh => 4565467577,
);
This last solution is the best. The last coma is optional, but I consider it good style—it makes it easy to add another entry. You can use autoquoting whenever the bareword on the left would be a legal variable name. E.g. a_bc => 1 is valid, but a bc => 1 is not (whitespace is not allowed in variable names), and +/- => 1 is not allowed (reserved characters). However Ünıçøðé => 1 is allowed when your source code is encoded in UTF-8 and you use uft8 in your script.

Besides what amonand Mat said, I'd like to notice other issues in your code:
your shebang is wrong it should be #!/usr/bin/perl - notice the first /
you don't have use strict; and use warnings; in your code - although this is not strictly a mistake, I consider this to be an issue. Those 2 commands will save you from a lot of trouble later on.
PS: you have to use commas between your number and names also, not only between names and numbers - you have to, because otherwise you get a compile error

Related

Odd "Use of uninitialized value", regular expression error

I'm using Strawberry Perl 5 on Windows 10. It does seem like my regular expressions are broken or regex101 won't tell me the truth. I want to catch 'num km'. Even tho my array seems to be the right length it'd often say"Use of uninitialized value".
my $string = "^ˇ~ --_ 12 km aéeklwa 32 km | \|ġ^ 0 km 23-24 km";
if (#szelmatches = $string =~ /\d+(\-\d+)?\s+km/gm) {
my $number_of_elements = scalar(#szelmatches);
print "Elements in the array : $number_of_elements \n";
}
foreach (#szelmatches) {
print "$_\n";
}
OUTPUT:
Elements in the array : 4
Use of uninitialized value $_ in concatenation (.) or string at C:\misc\perlek\wttr\szel.pl line 16.
I've ran defined() checks but it seems like my array elements are all defined. Changing \- to .{1} occasionaly worked but it is quite annoying to write like this. regex101.com and regexr.com tells me everything is allright.
I know you could write it simpler/shorter/better/faster/nicer etc, but i honestly think this should work. Do you guys have any idea what am i doing wrong?
Firstly, I had to fix a syntax error in your code before I could run it (the closing ) was missing from your if statement). Please cut and paste code, rather than retyping it.
If Perl tells you that it's finding undefs then it's almost certainly right. Using Data::Dumper can show us what is going on.
use warnings;
use Data::Dumper;
my $string = "^ˇ~ --_ 12 km aéeklwa 32 km | \|ġ^ 0 km 23-24 km";
if (#szelmatches = $string =~ /\d+(\-\d+)?\s+km/gm) {
my $number_of_elements = scalar(#szelmatches);
print "Elements in the array : $number_of_elements \n";
}
print Dumper \#szelmatches;
foreach (#szelmatches) {
print "$_\n";
}
That gives us the following:
$VAR1 = [
undef,
undef,
undef,
'-24'
];
So, yes, there are three undefs in your results. Can we work out why?
Well, here's your match operator.
/\d+(\-\d+)?\s+km/gm
It's looking for digits followed by an optional dash and more digits. But it's only that optional part that you're capturing (as it has parentheses around it). And in the first three cases, that optional section doesn't appear. So you get undef for those first three matches.
Let's actually match what you want (the whole digits section, I think) by putting more parentheses around the whole thing.
/(\d+(\-\d+)?)\s+km/gm
Now we get this result:
$VAR1 = [
'12',
undef,
'32',
undef,
'0',
undef,
'23-24',
'-24'
];
That's better. We get all of the matches we want, alongside the original ones. So, that's twice as many matches as we want. That's because we now have two sets of parentheses for each match. We need the first set to match and capture the digit section and the second set to join together the "-" and the "\d+". But we don't need the second set to capture its contents.
If you read the section on "Extended Patterns" in the perlre manual page, you'll see that we can create non-capturing parentheses with (?:...). So let's use that.
/(\d+(?:\-\d+)?)\s+km/gm
And that gives us:
$VAR1 = [
'12',
'32',
'0',
'23-24'
];
Which is, I think, what you wanted.
Update: Re-reading your question, I realise that you wanted the 'km' as well. So I've moved the closing parentheses past that.
/(\d+(?:\-\d+)?\s+km)/gm
And that gives us:
$VAR1 = [
'12 km',
'32 km',
'0 km',
'23-24 km'
];
The warning you see is because $_ is undefined. In Perl, you can have variables that have no value at all. That's undef.
The first thing you want to do in this case is inspect your array. The core Data::Dumper module is good for that. Or you can install Data::Printer from CPAN, which I prefer.
print Dumper \#szelmatches;
foreach (#szelmatches) {
print "$_\n";
}
This will output
$VAR1 = [
undef,
undef,
undef,
'-24'
];
Clearly there are some undefs in the array. This is because you have a capture group (\-\d) that is optional ?. Each time the string gets matched successfully via the /g modifier, it will put all of the capture group results into your array. But the only group you have is optional, so the pattern matches even if there is no -\d going on.
You can visualise this on Debugex. If you want to have a more detailed play-around with it, try the Regexp::Debugger module, which will allow you to step-by-step debug your regex right in your terminal.
You will have to tell us which numbers you actually want to capture.
If all you are after is the second one after the dash (which you do not have to escape, it has no special meaning), then you should not make that capture group optional.
Two problems.
When a capture is conditional (e.g. (...)?) and it doesn't match anything, it captures undef.
When there's one or more captures, the match returns the capture text rather than the entire text matched.
The solution is to remove the useless and problem-causing capture. Replace
if ( my #szelmatches = $string =~ /\d+(\-\d+)?\s+km/g )
with
if ( my #szelmatches = $string =~ /\d+(?:\-\d+)?\s+km/g )

Global symbol "%formsequence" requires explicit package name at line 37

I am trying to execute a Perl CGI script, but I am getting an error:
Global symbol "%formsequence" requires explicit package name at line 37.
I did some research and found that use strict forces me to declare the variables before I use them or store any data, but in my program I have declared them and that is why I don't understand the error. Here is my script:
#!/usr/bin/perl -w
use strict;
my %errors;
my %form;
my #formsequence;
my %fields = (
"lname" => "Last Name",
"phone" => "Phone",
"fname" => "Fist Name"
);
my %patterns = (
"lname" => '[A-Z][a-z]{2,50}',
"phone" => '\d{3}-\d{3}-\d{4}',
"fname" => '[A-Z][A-Za-z]{2,60}'
);
#formsequence = ("lname", "phone", "phone");
print "content-type/html\n\n";
if ($ENV{REQUEST_METHOD} eq "POST") {
&readformdata;
if (&checkrequiredfields) {
print "Form Data validated successfully!";
}
else {
foreach (keys (%fields)) {
if ($fields{$_} != $formsequence{$_}) { <-- line 37
$errors{$_}="Not in correct sequence\n";
}
}
}
I suspect you may be viewing the concept of an 'array' from the perspective of a PHP developer. In Perl a hash and an array are separate data structures.
Arrays are declared using the # prefix and you refer to elements using square brackets around an integer index:
my #names = ('Tom', 'Dick', 'Larry');
say $names[0]; # 'Tom'
say $names[-1]; # 'Larry'
my $count = #names; # $count now equals 3
foreach my $i (0..$#names) {
say $names[$i];
}
Hashes are declared using the % prefix and you refer to elements using curly braces around a string key:
my %rgb = (
red => '#ff0000',
white => '#ffffff',
blue => '#0000ff',
);
say $rgb{'red'}; # '#ff0000'
say $rgb{blue}; # '#0000ff' quotes optional around bareword keys
foreach my $k (keys %rgb) {
say $rgb{$k};
}
You wouldn't normally use the keys function on an array - in fact older versions of Perl don't even support it, newer versions will return a range of integers (e.g.: 0..2).
When you call keys on a hash the keys have no inherent order, and the order may change.
Other things worth knowing:
Using & to call a function is really old style (i.e. early 90s), these days we'd use readformdata() instead of &readformdata.
The != operator is a numeric comparison operator so only use it when the values you're comparing are actually numbers. If you want to check two strings are 'not equal' then use ne instead (e.g.: if($thing1 ne $thing2) { ... }).
This seems to be some rather old Perl.
You use -won the shebang line rather than use warnings (which has been available since Perl 5.6.0 was released in 2000.
You use a (presumably) custom readformdata() function instead of CGI,pm's params() method. CGI.pm was added to the Perl core in 1997 (it was recently removed - but that's not a reason to not use it).
You use ampersands on function calls. These haven't been necessary since Perl 5 was released in 1994.
Your problem is caused by declaring an array, #formsequence, which you then try to access as a hash - $formsequence{$_} means, "look up the key $_ in the hash %formsequence. In Perl, arrays and hashes are two completely different data types and it is possible (although not recommended for, hopefully, obvious reasons) to have an array and a hash with the same name.
You declare arrays like this - using #:
my #array = ('foo', 'bar', 'baz');
And access individual element like this - using [...]:
print $array[0]; # prints 'foo'
You declare hashes like this - using `%':
my %hash = (foo => 'Foo', bar => 'Bar', baz => 'Baz');
And access individual elements like ths - using {...}:
print $hash{foo}; # prints 'Foo'
Arrays are indexed using integers and are ordered. Hashes are indexed using strings and are unordered.
I can't really suggest a fix for your code as it's not really clear what you are trying to do. It appears that you want to check that parameters appear in a certain order, but this is doomed to failure as a) you can't guarantee the order in which CGI parameters are transmitted from the browser to your web server and b) you can't guarantee the order in which keys(%fields) will return the keys from your %fields hash.
If you explain in a little more detail what you are trying to do, then we might be able to help you more.

How to let Perl recognize both lower and uppercase input?

I'm currently trying to figure out how I can make my perl script accept both the lowercase and uppercase variant of a letter.
For Example.
my %channels = (a => 'test', b => 'test2', c => 'test3', d => 'test4', e => 'test5', f => 'test6', g => 'test7');
So when I want to enter test I can either do a or A and it will accept it
To Sum up the problem:
When running the script I ran into an issue where you had to input a if you wanted test. This is all fine to me but other people wanted the option to do capital A instead. I am trying to give them the option to do either.
Thanks All
For your hash keys (single alphabet as a key) try the logical defined operator.
// => logical defined operator. If the left hand side is true give the result else if evaluate the right side.
print $channels{$input} // $channels{lc($input)};
One simple solution for your input
print $channels{lc($input)};
If the input is uppercase it will convert to lowercase. Don't get worry about lowercase characters.
It is not fully clear what your requirement is but from the example it looks like you are asking for a hash which is case insensitive regarding the keys, i.e. that $hash{foo}, $hash{FoO} etc all result in the same value.
This can be implemented with tied hashes by defining appropriate FETCH,STORE and DELETE methods for the hash. And there are already implementations which do this, like Hash::Case.
Of course you could also simply normalize all keys (lower case, upper case etc) before accessing the hash.
I would keep all the hash keys lower case and convert the input value keys to lower case:
for my $inputKey ('a', 'A', 'b', 'B') {
print $channels{lc($inputKey)}, "\n";
}
With a regex it's not shorter but another way to solve it:
my %hash = {a => 'test', b => 'test2'};
my $read_input;
foreach my $key (keys %hash){
if($key =~ /${read_input}/i){ #note the i for 'ignore case'
print $hash{$key};
}
}
You basically iterate through the keys of your hash and compare them against the input. If you found the right one you can print the value.
But nevertheless: You may just convert every input to lower case and then access the hash.

What is "Use of unitialized value $. in range (or flip)" trying to tell me in Perl

I have the following code snippet in Perl:
my $argsize = #args;
if ($argsize >1){
foreach my $a ($args[1..$argsize-1]) {
$a =~ s/(.*[-+*].*)/\($1\)/; # if there's a math operator, put in parens
}
}
On execution I'm getting "Use of unitialized value $. in range (or flip) , followed by Argument "" isn't numeric in array element at... both pointing to the foreach line.
Can someone help me decipher the error message (and fix the problem(s))? I have an array #args of strings. The code should loop through the second to n't elements (if any exist), and surround individual args with () if they contain a +,-, or *.
I don't think the error stems from the values in args, I think I'm screwing up the range somehow... but I'm failing when args has > 1 element. an example might be:
<"bla bla bla"> <x-1> <foo>
The long and short of it is - your foreach line is broken:
foreach my $a (#args[1..$argsize-1]) {
Works fine. It's because you're using a $ which says 'scalar value' rather than an # which says array (or list).
If you use diagnostics you get;
Use of uninitialized value $. in range (or flip) at
(W uninitialized) An undefined value was used as if it were already
defined. It was interpreted as a "" or a 0, but maybe it was a mistake.
To suppress this warning assign a defined value to your variables.
To help you figure out what was undefined, perl will try to tell you
the name of the variable (if any) that was undefined. In some cases
it cannot do this, so it also tells you what operation you used the
undefined value in. Note, however, that perl optimizes your program
and the operation displayed in the warning may not necessarily appear
literally in your program. For example, "that $foo" is usually
optimized into "that " . $foo, and the warning will refer to the
concatenation (.) operator, even though there is no . in
your program.
You can reproduce this error by:
my $x = 1..3;
Which is actually pretty much what you're doing here - you're trying to assign an array value into a scalar.
There's a load more detail in this question:
What is the Perl context with range operator?
But basically: It's treating it as a range operator, as if you were working your way through a file. You would be able to 'act on' particular lines in the file via this operator.
e.g.:
use Data::Dumper;
while (<DATA>) {
my $x = 2 .. 3;
print Dumper $x;
print if $x;
}
__DATA__
line one
another line
third line
fourth line
That range operator is testing line numbers - and because you have no line numbers (because you're not iterating a file) it errors. (But otherwise - it might work, but you'd get some really strange results ;))
But I'd suggest you're doing this quite a convoluted way, and making (potentially?) an error, in that you're starting your array at 1, not zero.
You could instead:
s/(.*[-+*].*)/\($1\)/ for #args;
Which'll have the same result.
(If you need to skip the first argument:
my ( $first_arg, #rest ) = #args;
s/(.*[-+*].*)/\($1\)/ for #rest;
But that error at runtime is the result of some of the data you're feeding in. What you've got here though:
use strict;
use warnings;
my #args = ( '<"bla bla bla">', '<x-1>', '<foo>' );
print "Before #args\n";
s/(.*[-+*].*)/\($1\)/ for #args;
print "After: #args\n";

What does the Perl split function return when there is no value between tokens?

I'm trying to split a string using the split function but there isn't always a value between tokens.
Ex: ABC,123,,,,,,XYZ
I don't want to skip the multiple tokens though. These values are in specific positions in the string. However, when I do a split, and then try to step through my resulting array, I get "Use of uninitialized value" warnings.
I've tried comparing the value using $splitvalues[x] eq "" and I've tried using defined($splitvalues[x]) , but I can't for the life of me figure out how to identify what the split function is putting in to my array when there is no value between tokens.
Here's the snippet of my code (now with more crunchy goodness):
my #matrixDetail = ();
#some other processing happens here that is based on matching data from the
##oldDetail array with the first field of the #matrixLine array. If it does
#match, then I do the split
if($IHaveAMatch)
{
#matrixDetail = split(',', $matrixLine[1]);
}
else
{
#matrixDetail = ('','','','','','','');
}
my $newDetailString =
(($matrixDetail[0] eq '') ? $oldDetail[0] : $matrixDetail[0])
. (($matrixDetail[1] eq '') ? $oldDetail[1] : $matrixDetail[1])
.
.
.
. (($matrixDetail[6] eq '') ? $oldDetail[6] : $matrixDetail[6]);
because this is just snippets, I've left some of the other logic out, but the if statement is inside a sub that technically returns the #matrixDetail array back. If I don't find a match in my matrix and set the array equal to the array of empty strings manually, then I get no warnings. It's only when the split populates the #matrixDetail.
Also, I should mention, I've been writing code for nearly 15 years, but only very recently have I needed to work with Perl. The logic in my script is sound (or at least, it works), I'm just being anal about cleaning up my warnings and trying to figure out this little nuance.
#!perl
use warnings;
use strict;
use Data::Dumper;
my $str = "ABC,123,,,,,,XYZ";
my #elems = split ',', $str;
print Dumper \#elems;
This gives:
$VAR1 = [
'ABC',
'123',
'',
'',
'',
'',
'',
'XYZ'
];
It puts in an empty string.
Edit: Note that the documentation for split() states that "by default, empty leading fields are preserved, and empty trailing ones are deleted." Thus, if your string is ABC,123,,,,,,XYZ,,,, then your returned list will be the same as the above example, but if your string is ,,,,ABC,123, then you will have a list with three empty strings in elements 0, 1, and 2 (in addition to 'ABC' and '123').
Edit 2: Try dumping out the #matrixDetail and #oldDetail arrays. It's likely that one of those isn't the length that you think it is. You might also consider checking the number of elements in those two lists before trying to use them to make sure you have as many elements as you're expecting.
I suggest to use Text::CSV from CPAN. It is a ready made solution which already covers all the weird edge cases of parsing CSV formatted files.
delims with nothing between them give empty strings when split. Empty strings evaluate as false in boolean context.
If you know that your "details" input will never contain "0" (or other scalar that evaluates to false), this should work:
my #matrixDetail = split(',', $matrixLine[1]);
die if #matrixDetail > #oldDetail;
my $newDetailString = "";
for my $i (0..$#oldDetail) {
$newDetailString .= $matrixDetail[$i] || $oldDetail[$i]; # thanks canSpice
}
say $newDetailString;
(there are probably other scalars besides empty string and zero that evaluate to false but I couldn't name them off the top of my head.)
TMTOWTDI:
$matrixDetail[$_] ||= $oldDetail[$_] for 0..$#oldDetail;
my $newDetailString = join("", #matrixDetail);
edit: for loops now go from 0 to $#oldDetail instead of $#matrixDetail since trailing ",,," are not returned by split.
edit2: if you can't be sure that real input won't evaluate as false, you could always just test the length of your split elements. This is safer, definitely, though perhaps less elegant ^_^
Empty fields in the middle will be ''. Empty fields on the end will be omitted, unless you specify a third parameter to split large enough (or -1 for all).