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 );
Related
I know this is a fairly simple question, but I cannot figure out how to store all of the values in my array the way I want to.
Here is a small portion what the .txt file looks like:
0 A R N D
A 2 -2 0 0
R -2 6 0 -1
N 0 0 2 2
D 0 -1 2 4
Each value is delimited by either two spaces - if the next value is positive - or a space and a '-' - if the next value is negative
Here is the code:
use strict;
use warnings;
open my $infile, '<', 'PAM250.txt' or die $!;
my $line;
my #array;
while($line = <$infile>)
{
$line =~ /^$/ and die "Blank line detected at $.\n";
$line =~ /^#/ and next; #skips the commented lines at the beginning
#array = $line;
print "#array"; #Prints the array after each line is read
};
print "\n\n#array"; #only prints the last line of the array ?
I understand that #array only holds the last line that was passed to it. Is there a way where I can get #array to hold all of the lines?
You are looking for push.
push #array, $line;
You undoubtedly want to precede this with chomp to snip any newlines, first.
If file is small as compared to available memory of your machine then you can simply use below method to read content of file in to an array
open my $infile, '<', 'PAM250.txt' or die $!;
my #array = <$infile>;
close $infile;
If you are going to read a very large file then it is better to read it line by line as you are doing but use PUSH to add each line at end of array.
push(#array,$line);
I will suggest you also read about some more array manipulating functions in perl
You're unclear to what you want to achieve.
Is every line an element of your array?
Is every line an array in your array and your "words" are the elements of this array?
Anyhow.
Here is how you can achieve both:
use strict;
use warnings;
use Data::Dumper;
# Read all lines into your array, after removing the \n
my #array= map { chomp; $_ } <>;
# show it
print Dumper \#array;
# Make each line an array so that you have an array of arrays
$_= [ split ] foreach #array;
# show it
print Dumper \#array;
try this...
sub room
{
my $result = "";
open(FILE, <$_[0]);
while (<FILE>) { $return .= $_; }
close(FILE);
return $result;
}
so you have a basic functionality without great words. the suggest before contains the risk to fail on large files. fastest safe way is that. call it as you like...
my #array = &room('/etc/passwd');
print room('/etc/passwd');
you can shorten, rename as your convinience believes.
to the kidding ducks nearby: by this way the the push was replaced by simplictiy. a text-file contains linebreaks. the traditional push removes the linebreak and pushing up just the line. the construction of an array is a simple string with linebreaks. now contain the steplength...
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.
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.
I am new to Perl and am trying to write a script that will only print the even numbered lines of an array. I have tried multiple different methods of finding the size to use as the condition for my while loop, but I always end up getting an infinite loop of the first line without the program terminating. The array being input is a text file, input with the form "program.pl < foo.txt". Have I made a logic or syntax error?
#input = <STDIN>;
$i = $1;
$size = $#input + $1;
while ($size >= $i) {
print "$input[$i]";
$i = ($i + $2);
}
Don't call your problem with
program.pl < foo.txt
Instead, just pass 'foo.txt' as a parameter:
program.pl foo.txt
Inside your script, rely on default reading from <> and the line number variable $.:
use strict;
use warnings;
while (<>) {
next if $. % 2; # Skip odd numbers.
print;
}
Assuming you already have an array with all of your input, in your example #input, you can get all of the even index entries into another array using an Array Slice like so:
my #input_even_entries_only = #input[grep { $_ % 2 == 0 } 0..$#input];
The expression inside the square brackets evaluates to all of the even numbers between 0 and $#input.
You can then use a regular for/foreach loop to go through the resulting array:
for my $val (#input_even_entries_only) {
print "$val";
}
If you are trying to print lines of an array indexed at even numbers then, try this:
use strict;
use warnings;
my #input = <DATA>;
for(my $i=0; $i<=$#input; $i+=2) {
print $input[$i];
}
__DATA__
1
2
3
4
5
6
Output:
1
3
5
I've no idea what you are doing with the $1 and $2 variables. Did you think they were just numbers?
When you use a variable that has not been assigned a value, it is undefined, which will be converted to 0 when used in numerical context. If you do not use use warnings, this is done silently, and will be rather confusing.
Other than that, your code is not too far off. It should be something like:
use strict;
use warnings;
my #input = <>; # <> is more flexible and does the same thing
my $i = 1;
while ($i <= $#input) {
print $input[$i];
$i += 2;
}
Though of course, storing the entire file in an array is not necessary, and most often you should just loop over it instead. Like Miller has shown in his answer, which is probably the solution I would suggest. Using a for loop like JS shows is an excellent way to control the loop.
I am reading a text file named, mention-freq, which has data in the following format:
1
1
13
2
I want to read the lines and store the values in an array like this: #a=(1, 1, 13, 2). The Perl push function gives the index values/line numbers, i.e., 1,2,3,4, instead of my desired output. Could you please point out the error? Here is what I have done:
use strict;
use warnings;
open(FH, "<mention-freq") || die "$!";
my #a;
my $line;
while ($line = <FH>)
{
$line =~ s/\n//;
push #a, $line;
print #a."\n";
}
close FH;
The bug is that you are printing the concatenation of #a and a newline. When you concatenate, that forces scalar context. The scalar sense of an array is not its contents but rather its element count.
You just want
print "#a\n";
instead.
Also, while it will not affect your code here, the normal way to remove the record terminator read in by the <> readline operator is using chomp:
chomp $line;