How do I make the STDIN in the Array stop? (PERL) [duplicate] - arrays

This question already has answers here:
Perl script does not print <STDIN> multiple times
(2 answers)
Closed 5 years ago.
My #array will not stop taking in STDIN...
my #array = undef;
while (#array = undef){
#array = <STDIN>;
for (#array[x]=5){
#array = defined;
}
}

As clarified, limit the STDIN to five lines
use warnings;
use strict;
use feature 'say';
my #input;
while (<STDIN>) {
chomp;
push #input, $_;
last if #input == 5;
}
say for #input;
There are other things to comment on in the posted code. While a good deal of it is cleared up in detail in Dave Cross answer, I'd like to address the business of context when reading from a filehandle.
The "diamond" operator <> is context-aware. From I/O Operators (perlop)
If a <FILEHANDLE> is used in a context that is looking for a list, a list comprising all input lines is returned, one line per list element. It's easy to grow to a rather large data space this way, so use with care.
In the usual while loop the <> is in the scalar context
while (my $line = <$fh>)
and it is the same with while (<$fh>) since it assigns to $_ variable, a scalar, by default.
But if we assign to an array, say from a filehandle $fh with which a file was opened
my #lines = <$fh>;
then <> operator works in the list context. It reads all lines until it sees EOF (end-of-file), at which point it returns all lines, which are assigned to #lines. Remember that each line has its newline. You can remove them all by
chomp #lines;
since chomp works on a list as well.
With STDIN this raises an issue when input comes from keyboard, as <> waits for more input since EOF isn't coming on its own. It is usually given as Ctrl+D† on Unixy systems (Ctrl+Z on Windows).
So you can, in principle, have #array = <STDIN> and quit input with Ctrl+D but this may be a little awkward for input expected from keyboard, as it mostly implies the need for line by line processing. It is less unusual if STDIN comes from a file,
script.pl < input.txt
or a pipe on the command line
some command with output | script.pl
where we do get an EOF (courtesy of EOT).
But I'd still stick to a customary while when reading STDIN, and process it line by line.
† The Ctrl+D is how this is usually referred to but one actually types a low-case d with Ctrl. Note that Ctrl and c (labeled as Ctrl+C) does something entirely different; it sends the SIGINT signal, which terminates the whole program if not caught.

my #array = undef;
while (#array = undef){
These two lines don't do what (I assume) you think they are doing.
my #array = undef;
This defines an array with a single element which is the special value undef. I suspect that what you actually wanted was:
my #array = ();
which creates an empty array. But Perl arrays are always empty when first created, so this can be simplified to:
my #array;
The second line repeats that error and adds a new one.
while (#array = undef) {
I suspect you want to check for an empty array here and you were reaching for something that meant something like "if #array is undefined). But you missed the fact that in Perl, assignment operators (like =) are different to comparison operators (like ==). So this line assigns undef to #array rather than comparing it. You really wanted #array == undef - but that's not right either.
You need to move away from this idea of checking that an array is "defined". What you're actually interested in is whether an array is empty. And Perl has a clever trick that helps you work that out.
If you use a Perl array in a place where Perl expects to see a single (scalar) value, it gives you the number of elements in the array. So you can write code like:
my $number_of_elements = #an_array;
The boolean logic check in an if or while condition is a single scalar value. So if you want to check if an array contains any elements, you can use code like this:
if (#array) {
# #array contains data
} else {
# #array is empty
}
And to loop while an array contains elements, you can simply write:
while (#array) {
# do something
}
But here, you want to do something while your array is empty. To do that, you can either invert the while condition logic (using ! for "not"):
while (!#array) {
# do something
}
Or you can switch to using an until test (which is the opposite of while):
until (#array) {
# do something
}
I'm going to have to stop there. I hope this gives you some insight into what is wrong with your code. I'm afraid that this level of wrongness permeates the rest of your code too.

Related

Creating a random generated array with a fixed number of elements, but still less than a fixed amount

newbie here, as the title says I'm trying to generate an array that has to contain random numbers (including negative ones), but no more than 19.
I came up with this:
use strict;
use warnings;
use feature qw/say/;
my #rand=();
my $all_the_numbers=int(rand(19));
my $i=0;
while ($all_the_numbers<20)
{
my $number=20-int(rand(30)); #in order to randomize negative numbers too
push #rand, $number;
$i++;
last if $i=$all_the_numbers;
}
say "#rand";
But I always end up with an array that has only one element. Any idea why?
UPDATE
First of all, thanks for helping me with the previous problem.
Now I'm trying to delete every positive number in the array, making it contain only the negative ones; the whole program looks like this:
#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;
use 5.10.1;
my #rand=();
my $all_the_numbers=int(rand(19));
my $i=0;
while ($all_the_numbers<20)
{
my $number=20-int(rand(30));
push #rand, $number;
$i++;
last if $i==$all_the_numbers;
}
say "#rand";
my $h=0;
while ($all_the_numbers<20)
{
for (#rand)
{
if ($rand[0]<0)
{
next; #in order to make it skip the negative ones
}
shift #rand;
}
$h++;
last if $h==$all_the_numbers;
}
say "#rand";
However, this resoults in deleting only the positive numbers that come before the first negative one in the array, leaving what comes right after untouched...
So, how is it that the while loop doesn't keep on deleting the positives?
Here's a picture to better explain myself: https://i.stack.imgur.com/qi7sf.png
Thanks in advance.
Try this:
last if $i==$all_the_numbers;
Observe the operator ==.
You probably knew it already, but for the benefit of other readers that might visit:
The operator == compares for equality.
The operator = assigns the value on its right to the variable on its left.

perl: More concise way to branch based on whether split succeeded?

I know split returns the number of fields parsed, if it assigned to a scalar; and returns an array if assigned to an array.
Is there a way to check whether a line is successfully parsed without having to call split twice (once to check how many fields were parsed, and, if the correct number of fields were parsed, a second time to return the fields in an array)?
foreach (#lines) {
if ( split ) {
my ($ipaddr, $hostname) = split;
}
}
.. I need to check whether the split succeeded in order to avoid later uninitialized references to $ipaddr and $hostname. Just seems like I ought to be able to combine the two calls to split into a single call.
Sure:
foreach (#lines) {
if (2 == (my ($ipaddr, $hostname) = split)) {
# Got exactly two fields
}
}
So if you just want to skip bad lines, you can simply use:
foreach (#lines) {
2 == (my ($ipaddr, $hostname) = split)
or next;
# Got exactly two fields
}
Don't forget to remove trailing whitespace from your lines first (such as by using chomp to remove line feeds) or it will mess up your field count.
You can change the == to <= if there might be more fields.
I think I would prefer a regex match:
for ( #lines ) {
next unless my ($ipaddr, $hostname) = /(\S+)\s+(\S+)/;
# use $ipaddr & $hostname
}
This is different from the original in that it will succeed if more than two non-space substrings are found, but a fix is simple if it is necessary.

Dereferencing an array from an array of arrays in perl

I have various subroutines that give me arrays of arrays. I have tested them separately and somehow when i write my main routine, I fail to make the program recognize my arrays. I know it's a problem of dereferencing, or at least i suspect it heavily.
The code is a bit long but I'll try to explain it:
my #leaderboard=#arrarraa; #an array of arrays
my $parentmass=$spect[$#spect]; #scalar
while (scalar #leaderboard>0) {
for my $i(0..(scalar #leaderboard-1)) {
my $curref=$leaderboard[$i]; #the program says here that there is an uninitialized value. But I start with a list of 18 elements.
my #currentarray=#$curref; #then i try to dereference the array
my $w=sumaarray (#currentarray);
if ($w==$parentmass) {
if (defined $Leader[0]) {
my $sc1=score (#currentarray);
my $sc2=score (#Leader);
if ($sc1>$sc2) {
#Leader=#currentarray;
}
}
else {#Leader=#currentarray;}
}
elsif ($w>$parentmass) {splice #leaderboard,$i,1;} #here i delete the element if it doesn't work. I hope it's done correctly.
}
my $leadref= cut (#leaderboard); #here i take the first 10 scores of the AoAs
#leaderboard = #$leadref;
my $leaderef=expand (#leaderboard); #then i expand the AoAs by one term
#leaderboard= #$leaderef; #and i should end with a completely different list to work with in the while loop
}
So I don't know how to dereference the AoAs correctly. The output of the program says:
"Use of uninitialized value $curref in concatenation (.) or string at C:\Algorithms\22cyclic\cyclospectrumsub.pl line 183.
Can't use an undefined value as an ARRAY reference at C:\Algorithms\22cyclic\cyclospectrumsub.pl line 184."
I would appreciate enormously any insight or recommendation.
The problem is with the splice that modifies the list while it is being processed. By using the 0..(scalar #leaderboard-1) you set up the range of elements to process at the beginning, but when some elements are removed by the splice, the list ends up shorter than that and once $i runs off the end of the modified list you get undefined references.
A quick fix would be to use
for (my $i = 0; $i < #leaderboard; $i++)
although that's neither very idiomatic nor efficient.
Note that doing something like $i < #leaderboard or #leaderboard-1 already provides scalar context for the array variable, so you don't need the scalar() call, it does nothing here.
I'd probably use something like
my #result;
while(my $elem = shift #leaderboard) {
...
if ($w==$parentmass) {
# do more stuff
push #result, $elem;
}
}
So instead of deleting from the original list, all elements would be taken off the original and only the successful (by whatever criterion) ones included in the result.
There seem to be two things going on here
You're removing all arrays from #leaderboard whose sumaarray is greater than $parentmass
You're putting in #Leader the array with the highest score of all the arrays in #leaderboard whose sumaarray is equal to $parentmass
I'm unclear whether that's correct. You don't seem to handle the case where sumaarray is less than $parentmass at all. But that can be written very simply by using grep together with the max_by function from the List::UtilsBy module
use List::UtilsBy 'max_by';
my $parentmass = $spect[-1];
my #leaderboard = grep { sumaarray(#$_) <= $parentmass } #arrarraa;
my $leader = max_by { score(#$_) }
grep { sumaarray(#$_) == $parentmass }
#leaderboard;
I'm sure this could be made a lot neater if I understood the intention of your algorithm; especially how those elements with a sumarray of less that $parentmass

How to determine the length of a variable in an array in Perl

I am trying to print to the terminal in all upper-case any word that at is least five characters long. My code is:
if (substr(#vdata, length(#vdata)-5, 5)) {
print "#vdata";
}
It does not seem to be working. What am I doing wrong?
Assuming that #vdata contains your list of words:
my #upper = map {length $_ > 5 ? uc $_ : ()} #vdata;
print "#upper";
You are making simple things complicated. Try something like:
print uc $_ if length $_ > 5
Let's start by making you snippet into a complete, runnable program.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
my #vdata = qw[this arrays has strings of different lengths];
if (substr(#vdata, length(#vdata)-5, 5)) {
print "#vdata";
}
I've put some data into #vdata, but also (and probably more importantly) I've turned on strict and warnings. You should get into the habit of doing that for all of your code.
So what happens if we try to run this program?
$ ./vdata
length() used on #vdata (did you mean "scalar(#vdata)"?) at ./vdata line 8.
So there's your first problem. You're using the length function on an array. I get the idea that you're not thinking particularly clearly about what you're thinking. Your description of the problem seems rather vague.
I am trying to print to the terminal in all upper-case any word that at least five characters long.
I assume that you are storing the words in the array #vdata. So instead of getting the length of the array, you want to get the length of each individual element of the array. Let's start by setting up a loop to look at each element in the array.
foreach my $word (#vdata) {
# Do something with $word
}
The code within the block is run once for each element in #vdata. And each time it is executed, the element is in $word. So let's print $word if it's five characters or more.
foreach my $word (#vdata) {
say $word if length($word) >= 5;
}
(Actually, we've used say, not print, to get an automatic newline character.)
Running that prints:
arrays
strings
different
lengths
So we're on the right lines.
Now you want to print these words in upper case. That's easy, we just use the uc function.
foreach my $word (#vdata) {
say uc $word if length($word) >= 5;
}
And now the program shouts at us.
ARRAYS
STRINGS
DIFFERENT
LENGTHS
I think perhaps you were trying to do too much in one go. Programming is far easier if you break your problem down into smaller chunks and work on solving one smaller problem at a time.

How do I create an array of hashes and loop through them in Perl?

I'm trying to create an array of hashes, but I'm having trouble looping through the array. I have tried this code, but it does not work:
for ($i = 0; $i<#pattern; $i++){
while(($k, $v)= each $pattern[$i]){
debug(" $k: $v");
}
}
First, why aren't you useing strict and warnings? The following lines should be at the top of every Perl program you create, right after #!/usr/bin/perl. Always.
use strict;
use warnings;
And I know you aren't because I'm pretty sure you'd get some nice error messages out of strict and warnings from this, and from many other places in your code as well, judging by your variable use.
Second, why aren't you doing this:
for my $i (#pattern) {
..
}
That loops through every element in #pattern, assigning them to $i one at a time. Then, in your loop, when you want a particular element, just use $i. Changes to $i will be reflected in #pattern, and when the loop exits, $i will fall out of scope, essentially cleaning up after itself.
Third, for the love of Larry Wall, please declare your variables with my to localize them. It's really not that hard, and it makes you a better person, I promise.
Fourth, and last, your array stores references to hashes, not hashes. If they stored hashes, your code would be wrong because hashes start with %, not $. As it is, references (of any kind) are scalar values, and thus start with $. So we need to dereference them to get hashes:
for my $i (#pattern) {
while(my($k, $v) = each %{$i}) {
debug(" $k: $v");
}
}
Or, your way:
for (my $i = 0; $i<#pattern; $i++) { # added a my() for good measure
while(my($k, $v) = each %{$pattern[$i]}) {
debug(" $k: $v");
}
}
Try this instead:
for my $hashref (#pattern) {
for my $key (keys %$hashref) {
debug "$key: $hashref->{$key}";
}
}
The biggest problem with what you were trying was each $pattern[$i]. The each function expects a hash to work on, but $pattern[$i] returns a hashref (i.e. a reference to a hash). You could fix your code by dereferencing $pattern[$i] as a hash:
while(my($k, $v) = each %{$pattern[$i]}) {
Also, beware of the each function, it can leave the hash iterator in an incomplete state.
See the documentation for the perl data structures cookbook:
perldoc perldsc

Resources