Condition in ternary operator doesn't cause any change - perl

Here is my code sample:
for $i(1..100){
if ($i%15==0){$s="Divisible by 15"}
elsif($i%5==0){$s="Divisible by 5"}
else {$i%3==0 ? $s="Divisible by 3" : $s=$i};
print $s."\n"}
This displays partial correct results, if a number if divisible by 3, it displays the "number" and not "Divisible by 3".
Example of output:
1
2
3
4
Divisible by 5
6
7
8
9
Divisible by 5
PS: I have to write this code in the minimum no. of characters possible. (The reason why the code is so sluggish)

The fourth line is parsed as
(((($i % 3) == 0) ? ($s = 'Divisible by 3') : $s) = $i)
meaning that even when $i is divisible by 3, the assignment looks like
($s = 'Divisible by 3') = $i
Some fixes:
$i%3==0 ? ($s="Divisible by 3") : ($s=$i)
$s = ($i%3==0 ? "Divisible by 3" : $i)
Pro-tip: B::Deparse is very helpful for figuring this stuff out. I ran the command
perl -MO=Deparse,-p -e '$i%3==0 ? $s="Divisible by 3" : $s=$i'
to see exactly what the problem was.

The ternary ?: has higher precedence than assignment. Use parens to sort that out:
for my $i (1 .. 100) {
my $s = "Divisible by ";
$i%15==0 ? ($s .= 15)
: $i%5==0 ? ($s .= 5)
: $i%3==0 ? ($s .= 3)
: ($s =$i);
print "$s\n";
}
Output:
1
2
Divisible by 3
4
Divisible by 5
Divisible by 3
7
8
Divisible by 3
Divisible by 5

If you want a version with less characters,
for $i(1..100){print(($i%3&&$i%5?$i:"Divisible by ".($i%5?3:$i%15?5:15))."\n");}
Parenthesis after the print function means call the print function, not group the expression. Caused by the fact that parenthesis are optional for that function.

Related

need to use pop function twice to remove last element from the array (perl)

I've just started to learn Perl and joined Euler project to practice coding. This is the first exercise I did. The task was: "If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000." My solution:
use strict;
use warnings;
my #numbers = (1..1000);
my $counter = 0;
my #all_array = ();
my $total = 0;
foreach $counter (#numbers) {
if (($numbers[$counter] % 3 == 0) or ($numbers[$counter] % 5 == 0)) {
push (#all_array, $numbers[$counter]);
}
}
pop (#all_array); #after that the last digit is still in place
pop (#all_array); # only now the number 1000 is removed
my $tot = eval join '+', #all_array; #returns correct value
print $tot;
The final element of the array is 1000. It seems as if it is followed by a space so to remove the number and get the correct result I have to use the pop function twice. The use of local $"='' changes nothing. Besides, I'm getting a message: Use of uninitialized value within #numbers in modulus (%) at C:\Users\Greg\Documents\perl\unt.pl line 10.
What am I doing wrong and how to fix it?
Let's go through your code:
#numbers is an array with the numbers from 1 to 1000
Why do you include 1000 in the list when the exercise says "less than N"?
the for loop
assigns each of the numbers to $counter, i.e. 1, 2, ...
you use $counter as index into #numbers
why do you do that when $counter is already the number you are looking for?
Perl arrays start at index 0, so you have an off-by-one error
you never check 1 because your first number will be $numbers[1] == 2 (OK, doesn't cause an incorrect result for the task at hand...)
you access one element behind the array, i.e. $numbers[1000] == undef
calculating with undef will cause a warning
undef % 3 == 0 is true, hence...
the first pop() will remove undef (from $counter == 1000)
the second pop() will remove 1000 (from $counter == 999)
then you use eval on a string 3 + 5 + 6 + ... a very inefficient way to do a sum :-)
Wouldn't it be just a simpler approach to calculate the sum while running over the numbers from 1 to N-1? F.ex.:
#!/usr/bin/perl
use strict;
use warnings;
foreach my $arg (#ARGV) {
my $sum = 0;
foreach my $number (1..$arg - 1) {
$sum += $number
if ($number % 3 == 0) || ($number % 5 == 0);
}
print "${arg}: ${sum}\n";
}
exit 0;
Test run:
$ perl dummy.pl 10 100 1000 10000
10: 23
100: 2318
1000: 233168
10000: 23331668

Perl Range Operator with Letters and Numbers

Is there a way to use Perl's range operator .. to use letters AND numbers?
For example, have:
for my $i ('X'..'9') {
print "$i ";
}
output X Y Z 1 2 3 4 5 6 7 8 9
Not unless for my $i ('X' .. 'Z', 1 .. 9) counts. "Z" will never increment to 1.

How can I square a number in Perl?

I have code to print out the first 9 squared numbers:
#!/usr/local/bin/perl
for($i=1;$i<10 ;$i++ )
{
printf $i^2 . "\n";
}
but for some reason this just outputs 30167451011. How do I properly square a number?
To square you have to use $i**2
#!/usr/local/bin/perl
for ( my $i = 1; $i < 10; $i++ ) {
print $i**2 . "\n";
}
This will output:
1
4
9
16
25
36
49
64
81
To explain what happened in the original code, you need to know 3 things: first, ^ is the XOR operator in perl, because it is the XOR operator in C.
1 ^ 2 = 3
2 ^ 2 = 0
3 ^ 2 = 1
4 ^ 2 = 6
...
Second, the ^ operator has lower precedence than the string concatenation operator . so $i^2 . "\n" is equivalent to $i ^ (2 . "\n")
Third, perl converts between strings and numbers as necessary. The . operator requires strings on both sides, so the 2 is converted to "2" and concatenated with the "\n" to become the string "2\n".
Then the ^ operator requires numbers on both sides, so the string "2\n" is converted to a number - by taking the leading number-looking portion and throwing away the rest. So the result of $i ^ 2 . "\n" is ultimately the same as $i ^ 2. Your "\n" didn't have any effect at all, so all the results are printed with nothing between them. 3, 0, 1, 6, ... became 3016...
^ is the Bitwise Xor operator
To square a number, you want the Exponentiation operator **
for my $i ( 1 .. 9 ) {
print $i**2, "\n";
}
Outputs:
1
4
9
16
25
36
49
64
81
foreach my $i (1..9){
say $i**2;
}
It can be achieve by doing this way also:
for (1..9) {
print $_*$_,"\n"
}
Output:
1
4
9
16
25
36
49
64
81
You can use the code below to get the squares.
print $_**2,"\n" for 1..10;

What does 'foreach(1..4)' mean in Perl?

In Perl, what does foreach(1..4) mean? Specifically the two periods. I've been having trouble finding an answer.
In Perl, 1..4 produces 1 2 3 4 so you're looping through all the numbers from 1 to 4, assigning each value in turn to $_.
.. is one of the Range Operators
It is known as the range operator.
It creates a range between the two operands...
foreach(1..4)
It means your loop runs from 1 to 4
or
creates a range between the two operands
Example equals to
my $min = 999;
foreach $item (1, 2, 3, 4)
{
$min = $item if $min > $item;
}
print "Min = $min\n"; # expects Min = 1

Perl One liner for 3 conditions

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.