Multiple standard input in array-like format - arrays

I need to get my code to produce the following output, but I can't get it to work.
Example program output
Please enter your 3 numbers: 12 45 78
Your numbers forward:
12
45
78
Your numbers reversed:
78
45
12
My Perl code
#!/usr/bin/perl
#use strict;
use warnings;
use 5.010;
print "Please enter your 3 numbers: \n";
my $n1 = <STDIN>;
my $n2 = <STDIN>;
my $n3 = <STDIN>;
print "Your numbers forward: \n";
print $n1;
print $n2;
print $n3;
#Possible Backup Idea
# my #names = (n1, n2, n3);
# foreach my $n (#names) {
# say $n;
# }
#2nd Possible Backup Idea
# print "$coins[0]\n"; #Prints the first element
# print "$coins[-1]\n"; #Prints the last element
# print "$coins[-2]"; #Prints 2nd to last element
print "Your numbers reversed: \n";
print $n3;
print $n2;
print $n1;
But when it runs it doesn't take the input all in one line like I
need, and it must be input three times to work.
Please enter your 3 numbers:
12
23
34
Your numbers forward:
12
23
34
Your numbers reversed:
34
23
12

You can use stdin to take the input but you'll want to split according to the space character as delimiter.
#!/usr/bin/perl
use warnings;
use strict;
use feature qw(say);
say "Pick 3 numbers";
my $input = <STDIN>;
my #numbers = split(/\s+/, $input);
my #reverse_numbers = reverse(#numbers);
say "Your numbers forward:";
say join("\n", #numbers);
say "Your numbers backwards:";
say join("\n", #reverse_numbers);

my $n1 = <STDIN>; reads one full line at a time. So doing it three times will read three lines.
Instead, you want to read one line and split it up on whitespace into an array of numbers.
my $input = <STDIN>;
my #numbers = split /\s+/, $input;
/\s+/ is a regular expression to match any number of whitespace characters. See the Perl Regex Tutorial for more.
Then you can work with the list of numbers using for loops.
print "Your numbers forward:\n";
for my $number (#numbers) {
print "$number\n";
}
print "Your numbers reversed: \n";
for my $number (reverse #numbers) {
print "$number\n";
}

Related

Perl output successive string from array

I have an array I can print out as "abcd" however I am trying to print it as "a>ab>abc>abcd". I can't figure out the nested loop I need within the foreach loop I have. What loop do I need within it to print it this way?
my $str = "a>b>c>d";
my #words = split />/, $str;
foreach my $i (0 .. $#words) {
print $words[$i], "\n";
}
Thank you.
You had the right idea, but instead of printing the word at position i, you want to print all the words between positions 0 and i (inclusive). Also, your input can contain multiple strings, so loop over them.
use warnings;
while (my $str = <>) { # read lines from stdin or named files
chomp($str); # remove any trailing line separator
my #words = split />/, $str; # break string into array of words
foreach my $i (0 .. $#words) {
print join '', #words[0 .. $i]; # build the term from the first n words
print '>' if $i < $#words; # print separator between terms (but not at end)
}
print "\n";
}
There are many other ways to write it, but hopefully this way helps you understand what's happening and why. Good luck!
one liner:
perl -e '#a=qw(a b c d); for(#a) {$s.=($h.=$_).">"} $s=substr($s,0,-1);print $s'
I would do it like this:
#!/usr/bin/perl
use strict;
use warnings;
my $str = "a>b>c>d>e>f>g";
my #words = split />/, $str;
$" = '';
my #new_words;
push #new_words, "#words[0 .. $_]" for 0 .. $#words;
print join '>', #new_words;
A few things to explain.
Perl will expand array variables in a double-quoted string. So something like this:
#array = ('x', 'y', 'z');
print "#array";
will print x y z. Notice there are spaces between the elements. The string that is inserted between the elements is controlled by the $" variable. So by setting that variable to an empty string we can remove the spaces, so:
$" = '';
#array = ('x', 'y', 'z');
print "#array";
will print xyz.
The most complex line is:
push #new_words, "#words[0 .. $_]" for 0 .. $#words;
That's just a compact way to write:
for (0 .. $#words) {
my $new_word = "#words[0 .. $_]";
push #new_words, $new_word;
}
We iterate across the integers from zero to the last index in #words. Each time around the loop, we use an array slice to get a list of elements from the array, convert that to a string (by putting it in double-quotes) and then push that string onto #new_words.
This is what I ended up with, It's the only way I could understand and get the output I was looking for.
use strict;
use warnings;
my $str = "a>b>c>d>e>f>g";
my #words = split />/, $str;
my $j = $#words;
my $i = 0;
my #newtax;
while($i <= $#words){
foreach my $i (0 .. $#words - $j){
push (#new, $words[$i]);
}
if($i < $#words){
push(#new, ">");
}
$j--;
$i++;
}
print #new;
This output "a>ab>abc>abcd>abcde>abcdef>abcdefg"

how to split a string into arrays using perl?

There is a string as:
$string= 123456-9876;
Need to split it in array as follows:
$string = [12,34,56,98,76]
trying to split it as split('-',$string) is not serving the purpose.
How could i do that in perl?
Extract pairs of digits: (e.g. "1234-5678" ⇒ [12,34,56,78])
$string = [ $string =~ /\d\d/g ];
Extract pairs of digits, even if separated by non-digits: (e.g. "1234-567-8" ⇒ [12,34,56,78])
$string = [ $string =~ s/\D//rg =~ /../sg ];
Rather than splitting, you can capture all 2 digit numbers with this perl code,
$str = "123456-9876";
my #matches = $str =~ /\d{2}/g;
print "#matches\n";
Prints,
12 34 56 98 76
Another solution, that just groups two digits no matter whatever, wherever non-digits are present in the string, without mutating the original string
$string = "1dd23-dsd--456-9-876";
while($string =~ /(\d).*?(\d)/g) {
print "$1$2 ";
}
Prints,
12 34 56 98 76

Trying to print one element of an array somehow prints all elements

I am writing a script to take a list of integers from file aryData, sort them, print the sorted array, the highest value, and the lowest value.
aryData
89 62 11 75 8 33 95 4
But when printing the highest or lowest value, all elements of the array are printed.
This is my Perl code
#!/bin/perl
use strict;
use warnings;
print "Enter filename to be sorted: ";
my $filename = <STDIN>;
chomp( $filename );
open( INFILE, "<$filename" );
my #nums = <INFILE>;
close INFILE;
my #sorted = sort { $a cmp $b } #nums;
open my $outfile, '>', "HighLow.txt";
print $outfile "Sorted numbers: #sorted";
print $outfile "Smallest number: $sorted[0] \n";
print $outfile "Largest number: $sorted[-1] \n";
output HighLow.txt
Sorted numbers: 89 62 11 75 8 33 95 4
Smallest number: 89 62 11 75 8 33 95 4
Largest number: 89 62 11 75 8 33 95 4
This answer will have a large portion of code review and explain concepts that are not directly related to the question.
Let's look at the part of your code that reads in the array.
open(INFILE, "<$filename");
my #nums = <INFILE>;
close INFILE;
This code is fine for what you are doing, but it has a few security and style issues that I will get into further below.
So you have a file name, and you read in a file line by line. Each line goes into one element in the array #nums. Since your stuff is not working the way you want, the first step you need to take to debug this is to try to look at the array.
Your attempt to do this was not a bad idea.
print "Sorted numbers: #sorted";
Interpolating an array in a double quoted "" string in Perl joins the the elements of the array with the variable $,, which is also known as the output field separator. By default, it's a blank space .
my #foo = (1, 2, 3);
print "#foo";
This will give the following output
1 2 3
Unfortunately your input file already had spaces as separators, and all numbers were on one line. So you couldn't really see that the array wasn't properly set up. That's one of those facepalm moments when you notice it yourself. You could have noticed it by looking at the sorted numbers. You did sort them, but they were not sorted.
Sorted numbers: 89 62 11 75 8 33 95 4
A better way to figure out what's in the array would be to use Data::Dumper, which lets you serialize data structures. It's included with Perl.
use Data::Dumper;
my #foo = (1, 2, 3);
print Dumper \#foo;
The module gives you a Dumper function. It likes works better on references, so you need to add the backslash to create a reference to #foo. What that means exactly is not relevant at this point. Just remember that if your variable does not have a $, you put a backslash in front.
$VAR1 = [
1,
2,
3
];
This is useful. It tells us the three elements. Now lets look at your code. Instead of an actual file, I am using the pseudo-filehandle DATA, which reads from the __DATA__ section at the end of the program. This is great for testing and for examples.
use Data::Dumper;
my #nums = <DATA>;
my #sorted = sort { $a cmp $b } #nums;
print Dumper \#sorted;
__DATA__
89 62 11 75 8 33 95 4
This prints
$VAR1 = [
'89 62 11 75 8 33 95 4
'
];
We can see two things here. First, all numbers are on one line, and thus they went into the first element. Second, the line has a newline at the end. You already know that you can remove that with chomp.
So lets try to fix this. We now know that we need to split the line of numbers. There are many different ways to accomplish this task. I will go with a very verbose one to explain the steps involved.
use Data::Dumper;
my $line = <DATA>; # only read one line
chomp $line; # remove the line ending
my #nums = split / /, $line;
my #sorted = sort { $a cmp $b } #nums;
print Dumper \#sorted;
__DATA__
89 62 11 75 8 33 95 4
We use split with an empty pattern / / to turn the string of numbers into a list of numbers, and put that in an array. Then we sort.
$VAR1 = [
'11',
'33',
'4',
'62',
'75',
'8',
'89',
'95'
];
As you can see, we now have a sorted list of numbers. But they are not sorted numerically. Instead, they are sorted asciibetically. That's because cmp is the operator that sorts by ASCII character number. It's also the default behavior of Perl's sort, so you could have omitted that whole { $a cmp $b } block. It's the same as just saying sort #nums.
But we want to sort numbers by their numerical value, so we need to use the <=> sorting operator.
use Data::Dumper;
my $line = <DATA>; # only read one line
chomp $line; # remove the line ending
my #nums = split / /, $line;
my #sorted = sort { $a <=> $b } #nums;
print Dumper \#sorted;
__DATA__
89 62 11 75 8 33 95 4
Now the program prints the right output.
$VAR1 = [
'4',
'8',
'11',
'33',
'62',
'75',
'89',
'95'
];
I'll leave it to you to put this back into your actual program.
Finally, a word about your open. You are using what's called glob filehandles. Those things like INFILE are global identifiers. They are valid throughout your program, even in other modules that you might load. While in this tiny program that doesn't really make a difference, it might cause problems in the future. If for example the Data::Dumper module was to open a file and use the same identifier INFILE, and you had not called close INFILE, your program might either crash or do very weird things, because it would reuse the same handle.
Instead, you can use a lexical file handle. A lexical variable is only valid inside of a certain scope, like a function or the body of a loop. It's just a regular variable, declared with my. It will automatically call close for you when it goes out of scope.
open my $fh, "<foo";
my #nums = <$fh>;
close $fh;
You are calling open with two arguments. That's also not a good idea. Right now you have the mode <, but if you leave that out and do open my $fh, "$file" and read the $file from the user, they might pass in bad things, like | rm -rf slash. Perl will then treat the pipe | as the mode, open a pipe and delete all your stuff. Instead, use three-argument open.
open my $fh, '<', 'foo';
Now that you explicitly set the mode, you're safe.
The last point is that you should always check if open worked. That's easy.
open my $fh, '<', 'foo' or die $!;
The variable $! contains the error that open encountered. The or will only trigger if the return value of the open call was false. And die makes the program terminate. The error you might receive could look like this.
No such file or directory at /home/foo/code/scratch.pl line 6154.
So the full file reading should look something like this.
open my $fh, '<', $filename or die "Could not read $filename: $!";
my #nums = <$fh>;
close $fh;
As you've seen from the comments, the problem here is that you don't populate your array correctly. You end up with only one element in #nums - it's a single element that contains all of your data.
You could confirm that by using something like Data::Dumper, which... er... dumps your data :-)
At the top of your program, just after the use warnings; you can add this:
use Data::Dumper;
Then after you have loaded up #nums, try dumping it:
print Dumper(\#nums), "\n";
You'll see this:
$VAR1 = [
'89 62 11 75 8 33 95 4
'
];
Compare that to what you see when we fix your problem and you'll see an obvious difference.
So we have a line of data that contains the numbers you're interested in separated by spaces. To convert that into a list of numbers which we can store in your array, we can use the split() function. split() takes two arguments - a regular expression to split the string on, and the string to split.
You have this code to read from the file and assign to your array:
my #nums = <INFILE>;
You can replace that with:
my #nums = split / /, <INFILE>;
Now our data dump looks like this:
$VAR1 = [
'89',
'62',
'11',
'75',
'8',
'33',
'95',
'4
'
];
I hope the difference is obvious. Your program basically works at this point, but we can clean things up a bit by dealing with the new-line at the end of the record in the file (you can see it after the 4 above).
We'll need to split the line into two.
chomp(my $input = <INFILE>);
my #nums = split / /, $input;
Now our data dump looks like this:
$VAR1 = [
'89',
'62',
'11',
'75',
'8',
'33',
'95',
'4'
];
At this point, your program still has a bug left in it. I'm going to leave that for you to investigate (hint: what does sort() actually do? Read the documentation) - if you have more problems, please ask another question.
But I'd like to finish by suggesting some improvements to your general coding style. I'm not sure where you're learning Perl from, but some of the stuff you're doing looks pretty dated.
When you open a file in Perl, you should always check the results from your call to open and take appropriate action if it fails. In many cases, killing the program is the appropriate action, so I'd use die() in your open statement.
open( INFILE, "<$filename" )
or die "Can't open $filename: $!\n");
The $! in the error message will tell you why Perl couldn't open the file.
It's also regarded as best practice these days to avoid "bareword filehandles" (like your INFILE) and also split the file name from the mode indicators (> or <). Putting that all together, your file handling code becomes:
open( my $in_fh, '<', $filename )
or die "Can't open $filename: $!\n";
chomp(my $input = <$in_fh>);
my #nums = split / /, $input;
close $in_fh;
I see you are already using this style for the output file. Seems strange to mix the styles within the same program.
Maybe You can try this to find out the max and min:
#a=qw(1 3 2 8 7 5 4 10 9);
#a=sort {$a<=>$b}#a;
print "the max number=$a[0]\nthe min number=$a[$#a]\n";

Use push and pop to reverse and print out an array in Perl?

Trying a few lines of code to manually reverse an array by character and print it out. (yes, i know this can be done by the reverse function).
To begin with, I have a dummy file called dummy.txt
abcdefg1234567890
My perl script here:
#! /usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my $file = 'dummy.txt';
my #reverse_array;
open (FH, '<', $file)
or die ("Couldn't open $file : $!");
#split with empty string to get individual characters
my #chars = split("", <FH>);
print "$#chars\n"; #prints 16, meaning there are 17 elements in #chars
print "#chars\n"; #prints a b c d e f g 1 2 3 4 5 6 7 8 9 0
for(my $i=0; $i <= $#chars; $i++) #iterate number of elements' times
{
push #reverse_array, pop #chars; #pop the elements out of #chars into #reverse_array
}
print "$#reverse_array \n"; #prints 8, meaning there are 9 elements in #reverse_array
print "#reverse_array \n"; #prints 0 9 8 7 6 5 4 3 2, not as expected, but this is indeed 17 elements including the white space
close (FH)
or die ("Failed to close $file: $!");
So pop is popping out whitespaces. So split is adding white spaces when I try to split it with empty string? I'm still thinking how to get around this. Would love your input, and modify on my code would be be best for me.
It is always preferable to iterate over a list in Perl, rather than using the ugly C-style for loop
Your loop
for (my $i=0; $i <= $#chars; $i++) {
push #reverse_array, pop #chars;
}
is equivalent to
my $i = 0;
while ( $i <= $#chars ) {
push #reverse_array, pop #chars;
++$i;
}
so $#chars starts at 16. The loop is executed once, and now $i is 1 while $#chars is 15 because of the pop. This process will continue until after the eighth iteration of the loop -- when $i is 8 and $#chars is 7, and the test now fails so the loop exits
This has the effect that you are reversing only the last eight elements of #chars, as you have seen
If instead you write
for my $i ( 0 .. $#chars ) {
push #reverse_array, pop #chars;
}
then it's not only clearer and easier to read, but it also does what you intend
The whitespace comes from Perl's behaviour when stringifying arrays. If you write "#array\n" then Perl builds a string containing the elements of #array, separated by a single space. (This is the default behaviour. You can change the string used to separate the elements, but I advise you don't go there yet.)
To print just the contents of the array then you can write
print #chars, "\n"
and you will see what you expect
Your problem is that you're using $#chars in your loop construct, and it's changing as you go. You need to save it first:
use strict;
use warnings;
my $str = 'abcdefghijklmnopqrstuvwxyz';
my #reversed;
my #chars = split //, $str;
my $elt_count = scalar #chars;
for ( 1 .. $elt_count ) {
push( #reversed, pop #chars );
}
print #reversed;
But you could tackle it a little more easily with a while loop:
use strict;
use warnings;
my $str = 'abcdefghijklmnopqrstuvwxyz';
my #reversed;
my #chars = split //, $str;
while ( #chars ) {
push( #reversed, pop #chars );
}
print #reversed;
Which relies on the fact that you're emptying #chars as you go, and bails out when there's nothing left.
You should also be aware - there's a difference between:
print "#reversed";
and
print #reversed;
In the former case, perl is inserting spaces because you've converted the array to a string. That's why you're getting the apparent behaviour that your are.

Proper way to string interpolate an array element within printf

While working on Schwartz's Learning Perl, I came across an exercise where I am supposed to accept a number of user input strings, where the first input is to be the width determining right justified output for the other strings.
In other words, inputs:
10
apple
boy
Output should be:
10
apple
boy
where output is right justified by 10.
I tried using arrays to approach the problem:
#!/usr/bin/perl
use strict;
use warnings;
my #array;
while (<>) {
chomp($_);
push #array, $_ ;
}
while (#array) {
printf ("%$array[0]s \n", shift #array);
}
But after formatting and printing '10' correctly, I get errors:
$ perl test.pl
10
apple
boy
10
Invalid conversion in printf: "%a" at test.pl line 11, <> line 3.
%apples
Argument "boy" isn't numeric in printf at test.pl line 11, <> line 3.
0oys
I tried a variety of methods to force interpolation of the array element by enclosing it in braces, but all these have resulted in errors. What's the proper way to string interpolate array elements within printf (if that's the right term)?
Here's a more Perlish way to write it that avoids having to perform an explicit shift. It's a lot more do-what-I-mean since the format control variable is not part of #array from the start:
use strict;
use warnings;
my ( $length, #array ) = <>;
chomp( $length, #array );
printf "%${length}s\n", $_ for ( $length, #array );

Resources