I have created the following subroutine gender to randomly print string MALE or FEMALE. When subroutine is invoked, the print command suffixes a "1" at the end of the string. See the sample code and output below:
sub gender {
if ( (int rand(100)) >50) {
print "MALE ";
}
else {
print "FEMALE";
}
}
foreach (1..5) {
print &gender, "\n";
}
Notice a "1" is suffixed to "MALE" OR "FEMALE"
OUTPUT:
FEMALE1
FEMALE1
MALE 1
MALE 1
FEMALE1
MALE 1
I am using perl v5.8.9 v5.8.9 built for MSWin32-x86-multi-thread
Binary build 826 [290470] provided by ActiveState http://www.ActiveState.com
Built May 24 2009 09:21:05
print &gender
calls the gender function and prints what it returns. gender itself, as the last thing it does in either branch, prints a string. Implicitly, it returns the result of the last expression in it (the print "MALE" or print "FEMALE"), and print, when it succeeds, returns 1.
So either do this:
sub gender { if ( rand(100) >= 50 ) {print "MALE ";} else {print "FEMALE";}}
foreach (1..5) { &gender(); print "\n"; }
or this:
sub gender { if ( rand(100) >= 50 ) {return "MALE ";} else {return "FEMALE";}}
foreach (1..5) { print &gender(), "\n"; }
Also, note that &gender, with & but without parentheses, is a special form of function invocation that isn't usually what people mean to use; either drop the & or add empty parentheses to your call.
I've also corrected the if test to return male 50% of the time and female 50% of the time, instead of 49% and 51% respectively.
Let's get idiomatic with your code:
print gender(), "\n"
for 1..5;
sub gender {
return int rand(100) > 50 ? 'MALE' : 'FEMALE';
}
So, what did I do?
First:
The gender sub should not be called with the & and no parens. This invokes the subroutine on the arguments passed to its caller. This is handy when you have a bunch of common argument sanitizing code. But it is not desirable or needed here.
I put the sub after the other code because I like to read my code from high level to specific--the opposite of how C forces you to organize things. I don't like reading my code from the bottom up, so I did it this way. This is purely a personal preference. Do whatever makes you happy. Or if you have to work with others, follow the standard you've agreed upon.
I shortened foreach to for. They do exactly the same thing, one takes fewer characters.
I used for as a statement modifier. In other words I took a simple statement print $_, "\n"; and tacked the for onto the end. For simple tasks it is nicer than using a block. Again, this is my opinion. Some people decry statement modifiers as evil and unwelcome. If you decide to use them, keep it simple. YMMV.
I got rid of the extra unneeded print ysth mentioned.
Instead of using a big if/else block, I used the ternary operator (OK, it's really just a ternary operator, but people call it the ternary operator). It computes a test value and depending on the boolean value of the test, returns the result one of two expressions. It is handy when you want if/else logic in an assignment.
Without an explicit return, the Perl sub will return the last evaluated value. gender returns a 1 because in both execution paths, it calls print which returns a 1.
You should either be having gender return a string, which the caller then prints, or have gender do the printing, and have the caller not do anything with the return value.
Thank You everyone for helping me out with this. I found a way to make a chart I wanted.
Here is how I finally did it;
print "GENDER NAME AGE HEIGHT WEIGHT \n";
foreach (1..10) { ## Starting foreach loop
$age = int(rand( 50))+10;
$height = int (rand(40)) + 50;
$weight = int (rand (100)) + 100;
sub randchar4bit {(chr int rand(25)+65).(chr int rand(25)+65). (chr int rand(25)+65).(chr int rand(25)+65)};
sub gender { return (int rand(100)>50)? "MALE " : "FEMALE ";} ;
print gender(), " ", &randchar4bit, " $age $height $weight style 1\n";
}; ## closing foreach loop
It generates a nice output:
GENDER NAME AGE HEIGHT WEIGHT
FEMALE HHRN 41 67 165 style 1
MALE HNMF 27 63 187 style 1
MALE NLDB 26 54 165 style 1
FEMALE REMB 33 71 118 style 1
MALE TWEW 10 57 122 style 1
MALE OCSC 35 80 168 style 1
FEMALE TKTR 25 64 179 style 1
MALE GMYN 47 73 123 style 1
MALE YKUG 50 79 148 style 1
FEMALE HDFW 47 73 159 style 1
Related
I am trying to calculate the absolute values of line 2 - values of line 1
and then the horizontal absolute values of every line in my input file. Here's a part of that input.
43 402 51 360
63 60 69 63
65 53 89 55
103 138 135 135
109 36 123 38
To be more precise about what im trying to do I made the following example
initial data
0 2 0 0
0 1 1 1
next stage (absolute value after subscription the second line minus the first line)
2 2 0
1 0 0
final stage (horizontal application of abs values until one column remained)
0
1
The below code was a failed attempt to obtain the final stage of the single column. My problem here is that I don't know how to obtain the final (desired) stage by using subroutine, as I believe that it is a better way to solving my problem. Of course, every idea or better approach is welcome.
#!/usr/bin/perl
use feature qw(say);
use strict;
use warnings;
use Data::Dumper;
my #rows = 'table_only_numbers';
open(my $fh, '<:encoding(UTF-8)', $rows)
sub ori {
for ($num_cols=#{ $rows[$r-1]}; $num_cols=1; $num_cols-- ){
my #diff_diffs = map { abs($diffs[$_-1] - $diffs[$_]) } 1..$num_cols-1;
#final=#diff_diffs;
say join ' ',#final;
return (final) }
my $num_cols = #{ $rows[0] };
for my $r (1..$#rows) {
die "Bad format!" if #{ $rows[$r] } != $num_cols;
my #diffs = map { abs($rows[$r-1][$_] - $rows[$r][$_]) } 0..$num_cols-1;
while ($num_cols>1)
{
$final_output = ori(#{ $rows[0] })
say "final_output";
}
}
close $fh;
Finally, I figure it by myself without subroutines!!! Im posting it in case someone face the same issue in the future.I know that it is an easiest way to do it but as I am newbie in Perl it is the easiest way for me.
So I used:
for the first abs of the line 2 minus the line 1
my #data = map { abs($current[$_]-$previous[$_]) } 0..$#current;
push #final, \#data;
To obtain the absolute value of row 2 minus the row 1
And after I used 3 times as I had 3 columns left (in my case) the following coding line and each time I Substituted the #xxx with a new variable. and I have desired output of I column.
foreach my $row (#XXX) {
my #data = map { abs(#{$row}[$_]-#{$row}[$_+1]) } 0..$#{$row}-1;
say join ' ', #data;
push #XXX, \#data;}
Input to my script is this file which contains data as below.
A food 75
B car 136
A car 69
A house 179
B food 75
C car 136
C food 85
For each distinct value of the second column, I want to print any line where the number in the third column is different.
Example output
C food 85
A car 69
Here is my Perl code.
#! /usr/local/bin/perl
use strict;
use warning;
my %data = ();
open FILE, '<', 'data.txt' or die $!;
while ( <FILE> ) {
chomp;
$data{$1} = $2 while /\s*(\S+),(\S+)/g;
}
close FILE;
print $_, '-', $data{$_}, $/ for keys %data;
I am able to print the hash keys and values, but not able to get the desired output.
Any pointers on how to do that using Perl?
As far as I can tell from your question, you want a list of all the lines where there is an "odd one out" with the same item type and a different number in the third column from all the rest
I think this is what you need
It reads all the data into hash %data, so that $data{$type}{$n} is a (reference to an) array of all the data lines that use that object type and number
Then the hash is scanned again, looking for and printing all instances that have only a single line with the given type/number and where there are other values for the same object type (otherwise it would be the only entry and not an "odd one out")
use strict;
use warnings 'all';
use autodie;
my %data;
open my $fh, '<', 'data.txt';
while ( <$fh> ) {
my ( $label, $type, $n) = split;
push #{ $data{$type}{$n} }, $_;
}
for my $type ( keys %data ) {
my $items = $data{$type};
next unless keys %$items > 1;
for my $n ( keys %$items ) {
print $items->{$n}[0] if #{ $items->{$n} } == 1;
}
}
output
C food 85
A car 69
Note that this may print multiple lines for a given object type if the input looks like, say
B car 22
A car 33
B car 136
C car 136
This has two "odd ones out" that appear only once for the given object type, so both B car 22 and A car 33 will be printed
Here are the pointers:
First, you need to remember lines somewhere before outputting them.
Second, you need to discard previously remembered line for the object according to the rules you set.
In your case, the rule is to discard when the number for the object differs from the previous remembered.
Both tasks can be accomplished with the hash.
For each line:
my ($letter, $object, $number)=split /\s+/, $line;
if (!defined($hash{$object}) || $hash{$object}[0]!=$number) {
$hash{$object}=[$number, $line];
}
Third, you need to output the hash:
for my $object(keys %hash) {
print $hash{$object}[1];
}
But there is the problem: a hash is an unordered structure, it won't return its keys in the order you put them into the hash.
So, the fourth: you need to add the ordering to your hash data, which can be accomplished like this:
$hash{$object}=[$number,$line,$.]; # $. is the row number over all the input files or STDIN, we use it for sorting
And in the output part you sort with the stored row number
(see sort for details about $a, $b variables):
for my $object(sort { $hash{$a}[2]<=>$hash{$b}[2] } keys %hash) {
print $hash{$object}[1];
}
Regarding the comments
I am certain that my code does not contain any errors.
If we look at the question before it was edited by some high rep users, it states:
[cite]
Now where if Numeric column(Third) has different value (Where in 2nd column matches) ...Then print only the mismatched number line. example..
A food 75
B car 136
A car 69
A house 179
B food 75
B car 136
C food 85
Example output (As number columns are not matching)
C food 85
[/cite]
I can only interpret that print only the mismatched number line as: to print the last line for the object where the number changed. That clearly matches the example the OP provided.
Even so, in my answer I addressed the possibility of misinterpretation, by stating that line omitting is done according to whatever rules the OP wants.
And below that I indicated what was the rule by that time in my opinion.
I think it well addressed the OP problem, because, after all, the OP wanted the pointers.
And now my answer is critiqued because it does not match the edited (long after and not by OP) requirements.
I disagree.
Regarding the whitespace: specifying /\s+/ for split is not an error here, despite of some comments trying to assert that.
While I agree that " " is common for split, I would disagree that there are a lot of cases where you must use " " instead of /\s+/.
/\s+/ is a regular expression which is the conventional argument for split, while " " is the shorthand, that actually masks the meaning.
With that I decided to use explicit split /\s+/, $line in my example instead of just split " ", $line or just split specifically to show the innerworkings of perl.
I think it is important to any one new to perl.
It is perfectly ok to use /\s+/, but be careful if you expect to have leading whitespace in your data, consult perldoc -f split and decide whether /\s+/ suits your needs or not.
I have a hash
%grades{$subject}{$student}=$score
I am trying to extract top 30 scores for each subject with students from the hash which requires sorting, but I'm not sure how to sort the multiple key hash.
So far I have this, but this gives me every single one of the scores instead of the top 30 that I need for each subject. Also, is there a faster way to perform the query since I have almost 200K students.
foreach my $subject(sort keys %grades) {
foreach my $student(keys %{ $grades{$subject} }) {
print "$subject, $student: $grades{$subject}{$student}\n";
}
}
This sorts the top 2 scores for each subject (just for illustrative purposes). You should change 0 .. 1 to 0 .. 29 for the top 30:
use warnings;
use strict;
my %grades = (
math => {bill=>55, joe=>66, mike=>77},
hist => {bill=>72, joe=>33, mike=>99},
read => {bill=>95, joe=>24, mike=>22},
);
for my $subject (sort keys %grades) {
my %gr = %{ $grades{$subject} };
for my $student ((reverse sort { $gr{$a} <=> $gr{$b} } keys %gr)[0 .. 1]) {
print "$subject $student $gr{$student}\n";
}
}
__END__
hist mike 99
hist bill 72
math mike 77
math joe 66
read bill 95
read joe 24
Refer to perldoc perldsc and How do I sort a hash (optionally by value instead of key)?
Count them.
$count++;
last if $count > 30;
I have this
if($x<10){
print "child";
}elseif($x>10 && $x<18){
print "teenage"
}else{
print "old"
}
I want to put in a perl one liner how could i do this please help me
You may use the conditional operator. You also need only say print once - and I'm also going to change your conditions around, because 10 is neither >10 nor <10, but your code thinks 10 is old.
print $x<10 ? 'child' : $x<18 ? 'teenage' : 'old';
Conditional operator in Perl
You're looking for the conditional operator (a form of ternary operator which acts as a shorthand if-statement, not Perl-specific):
print $age < 10 ? "child" : $age < 18 ? "teenage" : "old";
Also, your code treats 10 as old, as it's neither less than nor greater than 10, so I've switched the function to what I think you wanted it to do.
Reusing the code
You can turn this into a subroutine for easy reuse:
sub determineAgeGroup {
my $age = $_[0];
return $age < 10 ? "a child" : $age < 18 ? "a teenager" : "old";
}
my #ages = (5,10,15,20);
foreach my $age (#ages) {
print "If you're $age you're " . determineAgeGroup($age) . "\n";
}
Output to this is:
If you're 5 you're a child
If you're 10 you're a teenager
If you're 15 you're a teenager
If you're 20 you're old
Link to working demo.
for my $x ( 5, 15, 55 ) {
print "$x is ";
print (($x<10) ? 'child' : ($x>10 && $x<18) ? 'teenage' : 'old');
print "\n";
}
No idea why you'd want to but this should work:
print (($x<10)?("child"):(($x>10 && $x<18)?("teenage"):("old")))
But just because it's short doesn't mean it's better than the original -- compare the difficulty in supporting/debugging the two options.
If you're just playing around the you could also define the strings in an appropriate array and do some maths on the value of $x to get a valid array entry.
I am trying to inter-convert some decimal and binary numbers. I am working with data produced in the following format:
Example decimal: 163, Corresponding binary: 10100011
Binary table key:
...and the corresponding description for the binary number in question:
I want to be able to take a decimal number, convert it to binary, and then use this look-up table to print a list of attributes for a given decimal. I'm able to convert my decimal to binary using this code:
sub dec2bin {
my $str = unpack("B32", pack("N", shift));
$str =~ s/^0+(?=\d)//; # otherwise you'll get leading zeros
return $str;
}
But then don't see how to use the lookup table. The problem is, I have binary numbers specifically designed to be compatible for this table, such as 1000011, 10000011, 101110011, but I just don't see how to use these binaries to pull our their descriptions. They are even different lengths!
Can someone please help me understand what is going on here?
EDIT: here is another look-up table I found... perhaps this is more accurate/helpful? It looks identical to me, but is from the software's offiical website.
Once the number is converted, its base as expressed in the input is irrelevant. Internally, think of it as just a number.
The value 163 expresses a bitfield, that is, each of its bits is the answer to some yes-no question, and the table tells you how the questions are arranged.
You might give the bits human-readable names with subs, as in
sub read_is_paired { $_[0] & 0x0001 }
sub read_is_mapped { $_[0] & 0x0002 }
sub strand_of_mate { $_[0] & 0x0020 }
sub read_is_2nd { $_[0] & 0x0080 }
Then decoding the bitfield resembles
my $flags = 163;
print "read is paired? ", read_is_paired($flags) ? "YES" : "NO", "\n",
"read is mapped? ", read_is_mapped($flags) ? "YES" : "NO", "\n",
"strand of mate = ", strand_of_mate($flags) ? "1" : "0", "\n",
"read is second? ", read_is_2nd($flags) ? "YES" : "NO", "\n";
Output:
read is paired? YES
read is mapped? YES
strand of mate = 1
read is second? YES
Any easier method might just be to examine each key in your map and compare it directly to your converted number.
sub get_descriptions {
my $binary_num = shift;
my #descriptions;
for my $k (keys %description_map) {
# bitwise comparison
if( $k & $binary_num ) {
# add description because this bit is set
push #descriptions, $description_map{$k};
}
}
# full listing of all descriptions for the set bits
return #descriptions;
}
The table is in base 16, so simply convert to base 2 (I copied/pasted the table from another forum, please fix if it's different from your screenshots):
0000000001 the read is paired in sequencing
0000000010 the read is mapped in a proper pair
0000000100 the query sequence itself is unmapped
0000001000 the mate is unmapped
0000010000 strand of the query (1 for reverse)
0000100000 strand of the mate
0001000000 the read is the first read in a pair
0010000000 the read is the second read in a pair
etc...
To get the correcy description in your format would then be the following code:
my #descriptions = (
"the read is paired in sequencing"
,"the read is mapped in a proper pair"
#...
);
check_number(163); # Note that you don't need to convert to binary :)
sub check_number {
my $number = shift;
my $bitmask = 1; # will keep incrementing it by *2 every time
for($i=0; $i < #descriptions; $i++) {
my $match = $bitmask & $number ? 1 : 0; # is the bit flipped on?
print "|$match| $descriptions[$i] | \n";
$bitmask *= 2; # or bit-shift - faster but less readable.
}
}
The output from my test code was (Sorry, got lazy copy/pasting description strings so faked them):
$ perl5.8 17152880.pl
|1| the read is paired in sequencing |
|1| the read is mapped in a proper pair |
|0| 3 |
|0| 4 |
|0| 5 |
|1| 6 |
|0| 7 |
|1| 8 |
|0| 9 |
If you ONLY want to print descriptions that match, change the print statement in the loop to print "$descriptions[$i]\n" if $match;
The benefit of this approach is that it's easily extended to longer description table