perl program to validate user input by comparing existing array - perl

Im trying to write a Perl script to validate user input.
If the user provide wrong value then it should show which is wrong value among the provided user input.
Example:
I have any array #arr=qw/cat rat mat sat/;
If user provide the input as perl validate_user_input.pl cat sot bat then, my script should show sot bat is wrong inputs to the script.
below is the script im trying .
#!/usr/bin/perl
use strict;
use warnings;
my (#not,#arr,$flag);
#arr=qw/cat rat mat sat/;
my $count=#arr;
foreach (#ARGV)
{
my $i=1;
foreach my $existing (#arr)
{
until ( "$existing" eq "$_" )
{
$flag = 1;
$i++;
last;
}
}
print "$i\n";
if ( ($count==$i) && ($flag == 1))
{
push(#not,"$_");
}
}
print "#not\n";
Logic i tried to implement:
Element in #ARGV will be checked against each element of #arr. If $flag=1 and the iteration $i is equal to number of elements in #arr (i.e checked against all the elements in #arr) then input not found in #arr.
Can anyone tell me what need to be done for this script to work.

Too complicated. Turn your 'valid' args into a hash, then use grep:
#!/usr/bin/env perl
use strict;
use warnings;
my #allowed = qw/cat rat mat sat/;
my %is_allowed = map { $_ => 1 } #allowed;
foreach my $arg ( grep { not $is_allowed{$_} } #ARGV ) {
print "$arg is not valid\n";
}
You can set a flag in that foreach loop if you like, if you want to proceed. Or just use grep in a scalar context:
if ((grep { not $is_allowed{$_} } #ARGV) > 0 ) {
die "invalid args found, exiting\n"
}

Well, a short search of this site would lead me to believe you are over-complicating things:
#!/usr/bin/env perl
use strict;
use warnings;
my (#not,#arr);
#arr=qw/cat rat mat sat/;
foreach (#ARGV)
{
push(#not,"$_") unless ( "$_" ~~ #arr );
}
print "#not\n";
The smart match operator (~~) still comes back as 'experimental' on my 5.24 version of bash, but it would seem it has been around since at least 5.10.
You may also want to add some kind of test at the end to check if #not has anything in it as all words may have been found. You could re-use your flag :)

Put the valid arguments into a hash %valid (instead of an array #arr). Then you can do:
for (#ARGV) {
push #not, $_ if !exists $valid{$_};
}

TIMTOWDI
use strict;
use warnings;
my #arr=qw(cat rat mat sat);
my $ok = { map{ $_ => 0 } #arr};
my $wrong;
exists $ok->{$_} ? $ok->{$_}++ : $wrong->{$_}++ for #ARGV;
print "OK $_ : $ok->{$_}\n" for #arr;
print "NO $_ : $wrong->{$_}\n" for (sort keys %$wrong);
using
perl arg.pl bat cat ded rat mat foo bar foo cat
output
OK cat : 2
OK rat : 1
OK mat : 1
OK sat : 0
NO bar : 1
NO bat : 1
NO ded : 1
NO foo : 2

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;
}

How can I read content file, do some instructions, and then copy out all the interesting content to another file line by line?

File 1 :
1. "a:1 b c:10 d e f g:2 a:1 a:1 a:1"
2. "h i l m"
3. "e:1 b"
4. "f:2 e:5 a"
File 2 should be
1. "a:1 c:10 g:2"
2. "f:2 e:5"
So I would like to:
save just one sample in case of repeated elements (for ex. line 1 "a:1"),
check if the line includes the element I'd like to save (so it must be for ex. "a:1" and not just "a")
if there's just one interesting element (line 3) I'll not evaluate the line.
I've tried to put the file content into an array inside a while cycle but when I printed Out my content it was all printed in line 1.
this is my attempt:
use List::MoreUtils qw(uniq);
$in = "in.txt";
$out = "out.txt";
open (IN, "<", $in);
open (OUT, ">", $out);
while(defined ( $l = <IN>)){
#a = split (/\s/, $l);
#c= uniq(#a);
for ($i = 0; $i < #c; $i++){
if ($c[$i] =~ /.*:-?\d\.\d+/) {
print OUT $c[$i];
}
}
}
This will do what you ask.
It isn't clear whether the line numbers and quotation marks are part of your data, but I have written it so that it doesn't matter either way
The program expects the path to the input file as a parameter on the command line
use strict;
use warnings;
while (<>) {
my %seen;
my #wanted = grep { /:/ and not $seen{$_}++ } /[^\s"]+/g;
print "#wanted\n" if #wanted > 1;
}
output
a:1 c:10 g:2
f:2 e:5
This ugly one-liner also uses a hash but in a way that preserves the order of fields.
perl -ne '
%h=();
print qq($nr. "$_"\n)
if $_=join " ", grep !$h{$_}++, /\w+:\d+/g and / / and ++$nr
' in.txt > out.txt
output:
1. "a:1 c:10 g:2"
2. "f:2 e:5"
Here's one way you could do it:
#!/usr/bin/env perl
use strict;
use warnings;
my $i = 1;
while (<>) {
my %h; # create an empty hash every line
foreach (split /["\s]/) { # split on double quotes and spaces
$h{$_}++ if /:/; # if colon found, add element to hash
}
if (keys %h > 1) { # if more than one element in hash
print (($i++), q/. "/, (join " ", (keys %h)), qq/"\n/);
}
}
Usage: file.pl in.txt > out.txt
I wasn't sure what your exact criterion for including the line was but the above code works for your sample data. Because a hash is being used, the contents isn't necessarily in the right order. If you wanted to sort the values, that would be a minor modification.
output:
1. "c:10 a:1 g:2"
2. "f:2 e:5"
I had a bit of fun playing with this problem.
It may not help you that much as it is a little tricky to read, but this is what I ended up with:
use List::MoreUtils qw(uniq);
$in = "in.txt";
$out = "out.txt";
open (IN, "<", $in);
open (OUT, ">", $out);
foreach (<IN>) {
#result = map /.\:\d*/ ? $_ : (), uniq ( split ) ;
print OUT join(" ", #result) . "\n" unless ($#result < 1);
}
Output:
a:1 c:10 g:2
f:2 e:5
Here is a version that doesn't use uniq and uses the -n option to handle the while loop.
#!/usr/bin/perl -n
my %seen;
#result = map /.\:\d*/ ? $_ : (), grep {! $seen{$_}++ } ( split ) ;
print join(" ", #result) . "\n" unless ($#result < 1);
Output:
./myscript.pl in.txt
a:1 c:10 g:2
f:2 e:5
I just noticed its not supposed to print if there is only one result.
That's easy to fix by changing the $#result test.

Using goto LABEL for comparing two files

I am unable to get desired output.
Please help to correct my errors.
file1
A
B
C
D
E
F
file2
A
D
C
Desired Output (if found print '1' at relative position in larger file and if not print '0')
1
0
1
1
0
0
code
#!/usr/bin/perl -w
open(FH,$file);
#q=<FH>;
open(FH1,$file2);
#d=<FH1>;
open(OUT,">out.txt");
foreach $i(#q) {
foreach $j(#d) {
if ($i eq $j) {
$id=1 ;
goto LABEL;
} elsif ($i ne $j) {
$id=1;
goto LABEL;
}
}
}
print OUT "1\t";
LABEL:
print OUT "0\t";
}
close FH;
close FH1;
close OUT;
note: actual files are much much larger and contain uneven number of elements.
You were looking for
for $q (#q) {
my $found = 0;
for $d (#d) {
if ($q eq $d) {
$found = 1;
goto LABEL;
}
}
LABEL: print "$found\n";
}
The above is better written as follows:
for $q (#q) {
my $found = 0;
for $d (#d) {
if ($q eq $d) {
$found = 1;
last;
}
}
print "$found\n";
}
But those solutions perform poorly. You can avoid iterating over #d repeatedly by using a hash.
my %d = map { $_ => 1 } #d;
for $q (#q) {
print $d{$q} ? "1" : "0", "\n";
}
Consider the following approach:
use strict;
use warnings;
use autodie;
use feature 'say';
open my $fh1, '<', 'file1';
open my $fh2, '<', 'file2';
say <$fh1> eq <$fh2> ? '1' : '0'
until eof $fh1 or eof $fh2;
Notes:
use strict; use warnings; to maintain sanity
autodie to take care of failed file opens
Lexical filehandles are preferred to bareword filehandles
say for syntactic sugar to automatically append a newline at the end of every 1 or 0
Diamond operator to read in each filehandle line-by-line
eq to string-compare the two lines
Ternary operator (COND ? TRUE : FALSE) to decide whether to print 1 or 0
until is a negated while
eof to tell the loop when either of the two filehandles has been exhausted
As it was said don't use LABEL. And to be honest you don't need perl for that, because join and sed do the job (may be you need to sort the files first):
join -a1 -a2 -e "0" -o 2.1 file1.txt file2.txt | sed "s/[^0]/1/g"
May be you need to sort your files first - in this case have a look at this post: comparing to unsorted files.
To be honest LABEL is not your friend - don't do that. For me it sounds more like a job for the join. But if you want to solve it using Perl I would try the following:
If the input files are sorted (otherwise you can use sort to achieve that) compare them line by line and print the result:
while ($line_from_f1 = <F1>)
{
$line_from_f2=<F2>;
if ($line_from_f1 eq $line_from_f2)
{
print "1\n";
}
else
{
print "0\n";
}
}
Shorter version (untested):
while (<F1>)
{
print ($_ eq <F2>)."\n";
}
Note: These versions compare the files line by line - if a line is missing in the middle it does not work properly.

extract every nth number

i want to extract every 3rd number ( 42.034 , 41.630 , 40.158 as so on ) from the file
see example-
42.034 13.749 28.463 41.630 12.627 28.412 40.158 12.173 30.831 26.823
12.596 32.191 26.366 13.332 32.938 25.289 12.810 32.419 23.949 13.329
Any suggestions using perl script ?
Thanks,
dac
You can split file's contents to separate numbers and use the modulo operator to extract every 3rd number:
my $contents = do { local $/; open my $fh, "file" or die $!; <$fh> };
my #numbers = split /\s+/, $contents;
for (0..$#numbers) {
$_ % 3 == 0 and print "$numbers[$_]\n";
}
use strict;
use warnings;
use 5.010; ## for say
use List::MoreUtils qw/natatime/;
my #vals = qw/42.034 13.749 28.463 41.630 12.627 28.412 40.158 12.173 30.831
26.823 12.596 32.191 26.366 13.332 32.938 25.289 12.810 32.419 23.949 13.329/;
my $it = natatime 3, #vals;
say while (($_) = $it->());
This is probably the shortest way to specify that. If #list is your list of numbers
#list[ grep { $_ % 3 == 0 } 0..$#list ]
It's a one-liner!
$ perl -lane 'print for grep {++$i % 3 == 1} #F' /path/to/your/input
-n gives you line-by-line processing, -a autosplitting for field processing, and $i (effectively initialized to zero for our purposes) keeps count of the number of fields processed...
This method avoids reading the entire file into memory at once:
use strict;
my #queue;
while (<>) {
push #queue, / ( \d+ (?: \. \d* ) ? ) /gx;
while (#queue >= 3) {
my $third = (splice #queue, 0, 3)[2];
print $third, "\n"; # Or do whatever with it.
}
}
If the file has 10 numbers in every line you can use this:
perl -pe 's/([\d.]+) [\d.]+ [\d.]+/$1/g;' file
It's not a clean solution but it should "do the job".
Looks like this post lacked a solution that didn't read the whole file and used grep.
#!/usr/bin/perl -w
use strict;
my $re = qr/-?\d+(?:\.\d*)/; # Insert a more precise regexp here
my $n = 3;
my $count = 0;
while (<>) {
my #res = grep { not $count++ % $n } m/($re)/go;
print "#res\n";
};
I believe you’ll find that this work per spec, behaves politely, and never reads in more than it needs to.
#!/usr/bin/env perl
use 5.010_001;
use strict;
use autodie;
use warnings qw[ FATAL all ];
use open qw[ :std IO :utf8 ];
END { close STDOUT }
use Regexp::Common;
my $real_num_rx = $RE{num}{real};
my $left_edge_rx = qr{
(?: (?<= \A ) # or use \b
| (?<= \p{White_Space} ) # or use \D
)
}x;
my $right_edge_rx = qr{
(?= \z # or use \b
| \p{White_Space} # or use \D
)
}x;
my $a_number_rx = $left_edge_rx
. $real_num_rx
. $right_edge_rx
;
if (-t STDIN && #ARGV == 0) {
warn "$0: reading numbers from stdin,"
. " type ^D to end, ^C to kill\n";
}
$/ = " ";
my $count = 0;
while (<>) {
while (/($a_number_rx)/g) {
say $1 if $count++ % 3 == 0;
}
}

Perl - the fastest way to read a range of lines from a file into a variable

Given a start and end line number, what's the fastest way to read a range of lines from a file into a variable?
Use the range operator .. (also known as the flip-flop operator), which offers the following syntactic sugar:
If either operand of scalar .. is a constant expression, that operand is considered true if it is equal (==) to the current input line number (the $. variable).
If you plan to do this for multiple files via <>, be sure to close the implicit ARGV filehandle as described in the perlfunc documentation for the eof operator. (This resets the line count in $..)
The program below collects in the variable $lines lines 3 through 5 of all files named on the command line and prints them at the end.
#! /usr/bin/perl
use warnings;
use strict;
my $lines;
while (<>) {
$lines .= $_ if 3 .. 5;
}
continue {
close ARGV if eof;
}
print $lines;
Sample run:
$ ./prog.pl prog.pl prog.c main.hs
use warnings;
use strict;
int main(void)
{
import Data.Function (on)
import Data.List (sortBy)
--import Data.Ord (comparing)
You can use flip-flop operators
while(<>) {
if (($. == 3) .. ($. == 7)) {
push #result, $_;
}
The following will load all desired lines of a file into an array variable. It will stop reading the input file as soon as the end line number is reached:
use strict;
use warnings;
my $start = 3;
my $end = 6;
my #lines;
while (<>) {
last if $. > $end;
push #lines, $_ if $. >= $start;
}
Reading line by line isn't going to be optimal. Fortunately someone has done the hardwork already :)
use Tie::File; it present the file as an array.
http://perldoc.perl.org/Tie/File.html
# cat x.pl
#!/usr/bin/perl
my #lines;
my $start = 2;
my $end = 4;
my $i = 0;
for( $i=0; $i<$start; $i++ )
{
scalar(<STDIN>);
}
for( ; $i<=$end; $i++ )
{
push #lines, scalar(<STDIN>);
}
print #lines;
# cat xxx
1
2
3
4
5
# cat xxx | ./x.pl
3
4
5
#
Otherwise, you're reading a lot of extra lines at the end you don't need to. As it is, the print #lines may be copying memory, so iterating the print while reading the second for-loop might be a better idea. But if you need to "store it" in a variable in perl, then you may not be able to get around it.
Update:
You could do it in one loop with a "continue if $. < $start" but you need to make sure to reset "$." manually on eof() if you're iterating over or <>.