perl user input into a data structure - perl

I am trying to take user input such as 4 2 1 4 and store that into an array. Then loop through the array looking for duplicates to store into a hash table.
#!/usr/bin/perl -w
print "Enter numbers into hash table :";
my #num = <STDIN>;
my %hash;
foreach $x (#num){
my $count =1;
foreach $y(#num){
if($x == $y){
my $count++;
$hash{'$x'} = $count;
}
}
}
map{print "$_\t$hash{$_}\n"}keys %hash;
I expect the result to be:
1 1
2 1
4 2
The actual result, I just continue to enter digits, after I hit enter the program continues to ask for more input. I hit CTRL D and nothing happens.

There are a great many problems with your code and I don't have time to explore them all
I hope it helps to offer you a working solution
#!/usr/bin/perl
use strict;
use warnings 'all';
my %counts;
print "Enter numbers into counts table: ";
++$counts{$_} for split ' ', <STDIN>;
print "$_\t$counts{$_}\n" for keys %counts;
output
E:\Perl\source>count.pl
Enter numbers into counts table: 4 2 1 4
1 1
2 1
4 2

Probably it is possible to do more nicely, but i would do this as:
use strict;
use warnings;
use Data::Dumper;
my $numbers;
print "Enter numbers and finish with CTRL-D\n";
while(my $line = <STDIN>) {
$numbers->{$_}++ for ( $line =~ m/(\b\d+\b)/g );
}
print Dumper $numbers;

Related

How to find the number of vowels in a string using Perl

sub Solution{
my $n=$_[0];
my $m=lc $_[1];
my #chars=split("",$m);
my $result=0;
my #vowels=("a","e","i","o","u");
#OUTPUT [uncomment & modify if required]
for(my $i=0;$i<$n;$i=$i+1){
for(my $j=0;$j<5;$j=$j+1){
if($chars[$i]==$vowels[$j]){
$result=$result+1;
last;
}
}
}
print $result;
}
#INPUT [uncomment & modify if required]
my $n=<STDIN>;chomp($n);
my $m=<STDIN>;chomp($m);
Solution($n,$m);
So I wrote this solution to find the number of vowels in a string. $n is the length of the string and $m is the string.
However, for the input 3 nam I always get the input as 3.
Can someone help me debug it?
== compares numbers. eq compares strings. So instead of $chars[$i]==$vowels[$j] you should write $chars[$i] eq $vowels[$j]. If you had used use warnings;, which is recommended, you'd have gotten a warning about that.
And by the way, there's no need to work with extra variables for the length. You can get the length of a string with length() and of an array for example with scalar(). Also, the last index of an array #a can be accessed with $#a. Or you can use foreach to iterate over all elements of an array.
A better solution is using a tr operator which, in scalar context, returns the number of replacements:
perl -le 'for ( #ARGV ) { $_ = lc $_; $n = tr/aeiouy//; print "$_: $n"; }' Use Perl to count how many vowels are in each string
use: 2
perl: 1
to: 1
count: 2
how: 1
many: 2
vowels: 2
are: 2
in: 1
each: 2
string: 1
I included also y, which is sometimes a vowel, see: https://simple.wikipedia.org/wiki/Vowel
Let me suggest a better approach to count letters in a text
#!/usr/bin/env perl
#
# vim: ai:ts=4:sw=4
#
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my $debug = 0; # debug flag
my %count;
my #vowels = qw/a e i o u/;
map{
chomp;
my #chars = split '';
map{ $count{$_}++ } #chars;
} <DATA>;
say Dumper(\%count) if $debug;
foreach my $vowel (#vowels) {
say "$vowel: $count{$vowel}";
}
__DATA__
So I wrote this solution to find the number of vowels in a string. $n is the length of the string and $m is the string. However, for the input 3 nam I always get the input as 3.
Can someone help me debug it?
Output
a: 7
e: 18
i: 12
o: 12
u: 5
Your code is slightly modified form
#!/usr/bin/env perl
#
# vim: ai:ts=4:sw=4
#
use strict;
use warnings;
use feature 'say';
my $input = get_input('Please enter sentence:');
say "Counted vowels: " . solution($input);
sub get_input {
my $prompt = shift;
my $input;
say $prompt;
$input = <STDIN>;
chomp($input);
return $input;
}
sub solution{
my $str = lc shift;
my #chars=split('',$str);
my $count=0;
my #vowels=qw/a e i o u/;
map{
my $c=$_;
map{ $count++ if $c eq $_} #vowels;
} #chars;
return $count;
}

Issue with nested loop

I got file called numbers.txt which is basically line with 5 numbers:
they look like this:
1 2 3 4 5
What I'm trying to achieve is I want to read those numbers from the line (which already works), then in each iteration I want to add +1 to every number which was read from that file and print them on screen with print, so the final result should look like:
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
.
#!/usr/bin/perl
use strict;
use warnings;
open("handle", 'numbers.txt') or die('unable to open numbers file\n');
$/ = ' ';
OUT: for my $line (<handle>) {
for (my $a = 0; $a < 5; $a++) {
chomp $line;
$line += 1;
print "$line ";
next OUT;
}
}
close("handle");
Haven't done looping in perl for a while now and would be great if someone could provide working example.
Also, it would be great if you could provide more than one working example, just to be future proof ;)
Thanks
You can try this on for size.
#!/usr/bin/perl
use strict;
use warnings;
open("handle", 'numbers.txt') or die('unable to open numbers file\n');
for my $line (<handle>) {
chomp $line;
for my $number (split /\s+/, $line) {
for (my $a = $number; $a < $number+5; $a++) {
print "$a ";
}
print "\n";
}
}
close("handle");
You can dispense with $/=' ' and instead let the outer loop iterate on lines of the file.
For each line you want to iterate for each number which is separated by white space, thus the split /\s+/, $line which gives you a list of numbers for the inner loop.
For your output $a starts at the number read from the file.
This will do what you're after:
use strict;
use warnings;
while(<DATA>) {
chomp;
print "$_\n";
my #split = split;
my $count = 0;
for (1..4){
$count++;
foreach (#split){
my $num = $_ + $count;
print "$num ";
}
print "\n";
}
}
__DATA__
1 2 3 4 5
Here no need to use nested loop it's always program make slower.
#!/usr/bin/perl
use strict;
use warnings;
my #num = split(" ",(<DATA>)[0]);
foreach my $inc (0..$#num)
{
print map{$inc+$_," "}#num; # Add one by one in array element
print "\n";
}
__DATA__
1 2 3 4 5
Update Added another method, this one in line with the posted approach.
Increment each number in the string, changing the string in place. Repeat that. Below are two ways to do that. Yet another method reads individual numbers and prints following integer sequences.
(1) With regular expressions. It also fits in one-liner
echo "1 2 3 4 5" | perl -e '$v = <>; for (1..5) { print $v; $v =~ s/(\d+)/$1+1/eg; }'
This prints the desired output. But better put it in a script
use warnings;
use strict;
my $file = 'numbers.txt';
open my $fh, '<', $file or die "can't open $file: $!";
while (my $line = <$fh>) {
# Add chomp($line) if needed for some other processing.
for (1..5) {
print $line;
$line =~ s/(\d+)/$1+1/eg;
}
}
The /e modifier is crucial for this. It makes the replacement side of the regex be evaluated as code instead of as a double-quoted string. So you can actually execute code there and here we add to the captured number, $1+1, for each matched number as /g moves down the string. This changes the string so the next iteration of the for (1..5) increments those, etc. I match multiple digits, \d+, which isn't necessary in your example but makes far more sense in general.
(2) Via split + map + join, also repeatedly changing the line in place
while (my $line = <$fh>) {
for (1..5) {
print $line;
$line = join ' ', map { $_+1 } split '\s+', $line;
}
}
The split gets the list of numbers from $line and feeds it to map, which increments each, feeding its output list to join. The joined string is assigned back to $line, and this is repeated. I split by \s+ to allow multiple white space but this makes it very 'relaxed' in what input format it accepts, see perlrecharclass. If you know it's one space please change that to ' '.
(3) Take a number at a time and print the integer sequence starting from it.
open my $fh, '<', $file or die "can't open $file: $!";
local $/ = ' ';
while (my $num = <$fh>) {
print "$_ " for $num..$num+4;
print "\n";
}
The magical 4 can be coded by pre-processing the whole line to find the sequence length, say by
my $len = () = $line =~ /(\d+)/g;
or by split-ing into an array and taking its scalar, then using $len-1.
Additional comments.
I recommend the three-argument open, open my $fh, '<', $file
When you check a call print the error, die "Your message: $!", to see the reason for failure. If you decide to quit, if ($bad) { die "Got $bad" }, then you may not need $!. But when an external call fails you don't know the reason so you need the suitable error variable, most often $!.
Your program has a number of problems. Here is what's stopping it working
You are setting the record separator to a single space. Your input file contains "1 2 3 4 5\n", so the while loop will iterate five times setting $line to "1 ", "2 ", "3 ", "4 ", "5\n"
Your for loop is set up to iterate five times. It does chomp $line which removes the space after the number, then increments $line and prints it. Then you jump out of the for loop, having executed it only once, with next OUT. This results in each value in the file being incremented by one and printed, so you get 2 3 4 5 6
Removing the unnecessary next OUT, produces something closer
2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 6 7 8 9 10
There are now five numbers being printed for each number in the input file
Adding print "\n" after the for loop help separate the lines
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
6 7 8 9 10
Now we need to print the number before it is incremented instead of afterwards. If we swap $line += 1 and print "$line " we get this
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5
6 7 8 9
What is happening here is that the 5 is still followed be a newline, which now appears in the output. The chomp won't remove this because it removes the value of $/ from the end of a string. You've set that to a space, so it will remove only spaces. The fix is to replace chomp with a substitution s/\s+//g which removes *all whitespace from the string. You also need to do that only once so I've put it outside the for loop at the top
Now we get this
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
And this is your code as it ended up
use strict;
use warnings;
open( "handle", 'numbers.txt' ) or die('unable to open numbers file\n');
$/ = ' ';
for my $line (<handle>) {
$line =~ s/\s+//g;
for ( my $a = 0; $a < 5; $a++ ) {
print "$line ";
$line += 1;
}
print "\n";
}
close("handle");
There are a few other best practices that could improve your program
Use use warnings 'all'
Use lexical file handles, and the three-parameter form of open
Use local if you are changing Perl's built-in variables
Put $! into your die string so that you know why the open failed
Avoid the C-style for loop, and iterate over a list instead
Making these fixes as well looks like this. The output is identical to the above
use strict;
use warnings 'all';
open my $fh, '<', 'numbers.txt'
or die qq{Unable to open "numbers.txt" for input: $!};
local $/ = ' ';
for my $line ( <$fh> ) {
$line =~ s/\s+//g;
for my $a ( 0 .. 4 ) {
print "$line ";
++$line;
}
print "\n";
}

Perl: Iterating through large hash, runs out of memory

I have been trying to find values that match between two columns (columns a and column b) of a large file and print the common values, plus the corresponding column d. I have been doing this by interating through hashes, however, because the file is so large, there is not enough memory to produce the output file. Is there any other way to do the same thing using less memory resources.
Any help is much appreciated.
The script I have written thus far is below:
#!usr/bin/perl
use warnings;
use strict;
open (FILE1, "<input.txt") || die "$!\n Couldn't open input.txt\n";
open (Output, ">output.txt")||die "Can't Open output.txt ";
my $hash1={};
my $hash2={};
while (<FILE1>) {
chomp (my $line=$_);
my ($a, $b, $c, $d) = split (/\t/, $line);
if ($a) {
$hash1->{$a}{info1} = "$d"; #original_ID-> YOB
}
if ($b) {
$hash2->{$b}{info2} = "$a"; #original_ID-> sire
}
foreach my $key (keys %$hash2) {
if (exists $hash1{$a}) {
$info1 = $hash1->{$a}->{info1};
print "$a\t$info1\n";
}
}
}
close FILE1;
close Output;
print "Done\n";
To clarify, the input file is a large pedigree file. An example is:
1 2 3 1977
2 4 5 1944
3 4 5 1950
4 5 6 1930
5 7 6 1928
An example of the output file is:
2 1944
4 1950
5 1928
Does the below work for you ?
#!/usr/local/bin/perl
use strict;
use warnings;
use DBM::Deep;
use List::MoreUtils qw(uniq);
my #seen;
my $db = DBM::Deep->new(
file => "foo.db",
autoflush => 1
);
while (<>) {
chomp;
my #fields = split /\s+/;
$$db{$fields[0]} = $fields[3];
push #seen, $fields[1];
}
for (uniq #seen) {
print $_ . " " . $$db{$_} . "\n" if exists $$db{$_};
}

$array can't print anything

This is my program , I want to let user type a matrix line by line and print the while matrix , but I can't see the matrix
The user will type
1 2 3
4 5 6
7 8 9
like this
and I want to let it show
1 2 3
4 5 6
7 8 9
Perl program
$Num = 3;
while($Num > 0 )
{
$Row = <STDIN>;
$Row = chomp($Row);
#Row_array = split(" ",$Row);
push #P_matrix , #Row_array;
#Row_array = ();
$Num = $Num - 1;
}
for($i=0;$i<scalar(#P_matrix);$i++)
{
for($j=0;$j<scalar(#P_matrix[$i]);$j++)
{
printf "$d ",$P_matrix[$i][$j];
}
print "\n";
}
I change the expression => printf "$d ",$P_matrix[$i][$j]; to print $P_matrix[$i][$j]
but still don't work.
To create a multi-dimensional array, you have to use references. Use
push #P_matrix, [ #Row_array ];
to create the desired structure.
Also, chomp does not return the modified string. Simply use
chomp $Row;
to remove a newline from $Row. Moreover, chomp is not needed at all if you split on ' '.
printf uses % as the formatting character, not $.
You can use Data::Dumper to inspect complex data structures. Use strict and warnings to help you avoid common problems. Here is how I would write your program:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my #p_matrix;
push #p_matrix , [ split ' ' ] while <>;
warn Dumper \#p_matrix;
for my $i (0 .. $#p_matrix)
{
for my $j (0 .. $#{ $p_matrix[$i] })
{
printf '%d ', $p_matrix[$i][$j];
}
print "\n";
}
First and foremost please use use strict; use warnings;
Issues in your code:
You have a single dimensional array, but your are trying to access
it like two dimensional array. In order to make 2 dimensional array push the array reference of Row_array in #P_matrix as [#Row_array].
Where is $d defined? declare $d as my $d or our $d if you mean $d as scalar variable.
OR
For using %d, use need sprintf. Please read this.

100 Most Used Strings in File

How can I find the top 100 most used strings (words) in a .txt file using Perl? So far I have the following:
use 5.012;
use warnings;
open(my $file, "<", "file.txt");
my %word_count;
while (my $line = <$file>) {
foreach my $word (split ' ', $line) {
$word_count{$word}++;
}
}
for my $word (sort keys %word_count) {
print "'$word': $word_count{$word}\n";
}
But this only counts each word, and organizes it in alphabetical order. I want the top 100 most frequently used words in the file, sorted by number of occurrences. Any ideas?
Related: Count number of times string repeated in files perl
From reading the fine perlfaq4(1) manpage, one learns how to sort hashes by value. So try this. It’s rather more idiomatically “perlian” than your approach.
#!/usr/bin/env perl
use v5.12;
use strict;
use warnings;
use warnings FATAL => "utf8";
use open qw(:utf8 :std);
my %seen;
while (<>) {
$seen{$_}++ for split /\W+/; # or just split;
}
my $count = 0;
for (sort {
$seen{$b} <=> $seen{$a}
||
lc($a) cmp lc($b) # XXX: should be v5.16's fc() instead
||
$a cmp $b
} keys %seen)
{
next unless /\w/;
printf "%-20s %5d\n", $_, $seen{$_};
last if ++$count > 100;
}
When run against itself, the first 10 lines of output are:
seen 6
use 5
_ 3
a 3
b 3
cmp 2
count 2
for 2
lc 2
my 2