How does incrementation operator work within a loop - perl

I've just started off with perl, and while trying out a few compound statements, I wrote this:
my $ct;
while ($ct++ < 10) {
print $ct;
}
It prints out:
12345678910
I was not expecting it to print 10. How does the logic for the loop really work?
According to perdoc, a TERM operator has the highest precedence. $ct gets incremented to 10 after iterating the loop where it is 9. When it becomes 10, while loop is supposed to exit. So why is 10 still printed out?

Think of it like
while ($ct < 10) {
$ct += 1;
print $ct;
}
(increment after comparison)
On the other hand, ++ on the left side of the variable will increment first, and then do comparison,
while (++$ct < 10) {
print $ct;
}
This is quite intuitive for someone with C background; from perldoc:
"++" and "--" work as in C. That is, if placed before a variable, they increment or decrement the variable by one before returning the value, and if placed after, increment or decrement after returning the value.

Its because you are using the postfix operator. its first compared then incremented.

Related

Raku: Trouble Accessing Value of a Multidimensional Hash

I am having issues accessing the value of a 2-dimensional hash. From what I can tell online, it should be something like: %myHash{"key1"}{"key2"} #Returns value
However, I am getting the error: "Type Array does not support associative indexing."
Here's a Minimal Reproducible Example.
my %hash = key1-dim1 => key1-dim2 => 42, key2-dim1 => [42, 42];
say %hash{'key1-dim1'}{'key1-dim2'}; # 42
say %hash{'key2-dim1'}{'foo bar'}; # Type Array does not support associative indexing.
Here's another reproducible example, but longer:
my #tracks = 'Foo Bar', 'Foo Baz';
my %count;
for #tracks -> $title {
$_ = $title;
my #words = split(/\s/, $_);
if (#words.elems > 1) {
my $i = 0;
while (#words.elems - $i > 1) {
my %wordHash = ();
%wordHash.push: (#words[$i + 1] => 1);
%counts.push: (#words[$i] => %wordHash);
say %counts{#words[$i]}{#words[$i+1]}; #===============CRASHES HERE================
say %counts.kv;
$i = $i + 1;
}
}
}
In my code above, the problem line where the 2-d hash value is accessed will work once in the first iteration of the for-loop. However, it always crashes with that error on the second time through. I've tried replacing the array references in the curly braces with static key values in case something was weird with those, but that did not affect the result. I can't seem to find what exactly is going wrong by searching online.
I'm very new to raku, so I apologize if it's something that should be obvious.
After adding the second elements with push to the same part of the Hash, the elment is now an array. Best you can see this by print the Hash before the crash:
say "counts: " ~ %counts.raku;
#first time: counts: {:aaa(${:aaa(1)})}
#second time: counts: {:aaa($[{:aaa(1)}, {:aaa(1)}])}
The square brackets are indicating an array.
Maybe BagHash does already some work for you. See also raku sets without borders
my #tracks = 'aa1 aa2 aa2 aa3', 'bb1 bb2', 'cc1';
for #tracks -> $title {
my $n = BagHash.new: $title.words;
$n.raku.say;
}
#("aa2"=>2,"aa1"=>1,"aa3"=>1).BagHash
#("bb1"=>1,"bb2"=>1).BagHash
#("cc1"=>1).BagHash
Let me first explain the minimal example:
my %hash = key1-dim1 => key1-dim2 => 42,
key2-dim1 => [42, 42];
say %hash{'key1-dim1'}{'key1-dim2'}; # 42
say %hash{'key2-dim1'}{'key2-dim2'}; # Type Array does not support associative indexing.
The problem is that the value associated with key2-dim1 isn't itself a hash but is instead an Array. Arrays (and all other Positionals) only support indexing by position -- by integer. They don't support indexing by association -- by string or object key.
Hopefully that explains that bit. See also a search of SO using the [raku] tag plus 'Type Array does not support associative indexing'.
Your longer example throws an error at this line -- not immediately, but eventually:
say %counts{...}{...}; # Type Array does not support associative indexing.
The hash %counts is constructed by the previous line:
%counts.push: ...
Excerpting the doc for Hash.push:
If a key already exists in the hash ... old and new value are both placed into an Array
Example:
my %h = a => 1;
%h.push: (a => 1); # a => [1,1]
Now consider that the following code would have the same effect as the example from the doc:
my %h;
say %h.push: (a => 1); # {a => 1}
say %h.push: (a => 1); # {a => [1,1]}
Note how the first .push of a => 1 results in a 1 value for the a key of the %h hash, while the second .push of the same pair results in a [1,1] value for the a key.
A similar thing is going on in your code.
In your code, you're pushing the value %wordHash into the #words[$i] key of the %counts hash.
The first time you do this the resulting value associated with the #words[$i] key in %counts is just the value you pushed -- %wordHash. This is just like the first push of 1 above resulting in the value associated with the a key, from the push, being 1.
And because %wordHash is itself a hash, you can associatively index into it. So %counts{...}{...} works.
But the second time you push a value to the same %counts key (i.e. when the key is %counts{#words[$i]}, with #words[$i] set to a word/string/key that is already held by %counts), then the value associated with that key will not end up being associated with %wordHash but instead with [%wordHash, %wordHash].
And you clearly do get such a second time in your code, if the #tracks you are feeding in have titles that begin with the same word. (I think the same is true even if the duplication isn't the first word but instead later ones. But I'm too confused by your code to be sure what the exact broken combinations are. And it's too late at night for me to try understand it, especially given that it doesn't seem important anyway.)
So when your code then evaluates %counts{#words[$i]}{#words[$i+1]}, it is the same as [%wordHash, %wordHash]{...}. Which doesn't make sense, so you get the error you see.
Hopefully the foregoing has been helpful.
But I must say I'm both confused by your code, and intrigued as to what you're actually trying to accomplish.
I get that you're just learning Raku, and that what you've gotten from this SO might already be enough for you, but Raku has a range of nice high level hash like data types and functionality, and if you describe what you're aiming at we might be able to help with more than just clearing up Raku wrinkles that you and we have been dealing with thus far.
Regardless, welcome to SO and Raku. :)
Well, this one was kind of funny and surprising. You can't go wrong if you follow the other question, however, here's a modified version of your program:
my #tracks = ['love is love','love is in the air', 'love love love'];
my %counts;
for #tracks -> $title {
$_ = $title;
my #words = split(/\s/, $_);
if (#words.elems > 1) {
my $i = 0;
while (#words.elems - $i > 1) {
my %wordHash = ();
%wordHash{#words[$i + 1]} = 1;
%counts{#words[$i]} = %wordHash;
say %counts{#words[$i]}{#words[$i+1]}; # The buck stops here
say %counts.kv;
$i = $i + 1;
}
}
}
Please check the line where it crashed before. Can you spot the difference? It was kind of a (un)lucky thing that you used i as a loop variable... i is a complex number in Raku. So it was crashing because it couldn't use complex numbers to index an array. You simply had dropped the $.
You can use sigilless variables in Raku, as long as they're not i, or e, or any of the other constants that are already defined.
I've also made a couple of changes to better reflect the fact that you're building a Hash and not an array of Pairs, as Lukas Valle said.

Perl - least common multiple

This code should do the LCM from N numbers.
I tried to put prints wherever I can in order to see where is the mistake. and I think is in:
if($vec[0] == $vec[$n-1]){
$resultado = $vec[0];
last;
}
But I can not make it work.
Could you please help me?
I'm a rookie with Perl.
Hope you can solve this problem.
Also I tried to change the variables but it does not work. I mean
$u = 0 , $w = $n-1;
FULL CODE
To get the LCM, you can split the task into multiple subroutines:
is_prime # returns true if the value is prime
roots # returns the roots of a number (all prime numbers that make up a value. Ex: roots of 12 are: 2, 2, 3)
LCM # takes a list of values. Extract the roots while the number is not prime. Store in a hash like and increment everytime we see the root
So we'll have a hash like:
%sub_total = (
VALUE => TIMES_FOUND,
2 => 2,
3 => 1,
);
We have another hash which is the total. If the sub_total hash has a key that is used more often than in the total hash, we add it to the total.
Finally, we loop through the total hash and and find the product using the algorithm:
for (%total){
$prod *= $_ ** $total {$_};
}
Note
I'll shortly attach the code I wrote for getting the LCM. It's not with me now here.

Alternate for making such a thing work in perl : `for(10..0)`

This doesn't work in perl: for(10 .. 0) It essentially doesn't loop even once, because it checks that 10>0 initially.
Any alternate shorthand for creating a decreasing iterating for loop?
for (reverse 0 .. 10) {
say $_;
}
Use the reverse function.
Unfortunately, this forces evaluation of the range to a list, so this uses more memory than the loop without reverse.
I'm not sure brevity is a great criteria for doing this, but you don't need the map in the inverting solution, nor reverse in the reversing one:
# By Inverting without map, one of:
for(-10..0){$_=-$_;say}
for(-10..0){$_*=-1;say}
# Compare to similar length with map:
for(map-$_,-10..0){say}
# Can just use -$_ where $_ is used, if $_ is used < 6 times; that's shorter.
for(-10..0){say-$_}
# By Reversing without reverse (in a sub; in main use #ARGV or #l=...=pop#l)
#_=0..10;while($_=pop){say}
# More Pop Alternatives
for(#_=0..10;$_=pop;say){}
#_=0..10;for(;$_=pop;){say}
#_=0..10;do{say$_=pop}while$_
($_,#_)=(10,0..9);do{say}while($_=pop)
# Though, yeah, it's shorter with reverse
for(reverse 0..10){say}
for (map -$_,-10..0) { ... }
for (map 10-$_,0..10) { ... }
If any part of the range is negative, then the first one is shorter than using reverse.

Hash value is not re-initialized when loop is terminated with 'last' keyword

Consider the following nested loops:
my %deleted_documents_names = map { $_ => 1 }
$self->{MANUAL}->get_deleted_documents();
while($sth->fetch){
.....
.....
.....
while(my ($key, $value) = each(%deleted_documents_names)){
{
if($document_name eq $key){
$del_status=1;
last;
}
}
if($del_status==1){next;}
.....
.....
.....
.....
}
Now, I take a sample case where three values (A,B,C) will be compared against two values (B,C).
First scan:
A compared to B
A compared to C
Second scan:
B compared to B
Loop is terminated.
Third scan:
C is compared with C.
In this case, C should be compared first with B, being first value, but this comparison is skipped, and it only scans from the next element after the one that was found equal. If I remove last termination condition and let the loop run for total number of scans, then it works all fine, but I need to find out why in this case, $key refers to the next compared value and not to the first value once loop is restarted after getting terminated with last keyword.
Any help will be appreciated.
Use
keys %deleted_documents_names ; # Reset the "each" iterator.
See keys.
But, why are you iterating over the hash? Why don't you just
if (exists $deleted_documents_names{$document_name}) {
each() is a function that returns key-value pairs from a hash until it reaches the end. It is not aware of the scope it was called in, and doesn't know anything about your while loop logic. See the documentation here.
It can be reset by calling keys %hash or values %hash.
Update: however, as Choroba points out, you don't really need this loop. Your loop and accompanying logic could be replaced by this:
next if (exists $deleted_documents_names{$document_name});
(Hashes are designed with a structure that allows a key to be quickly found. In fact, this structure is what gives them the name "hashes". So doing it this way will be much more efficient than looping through all elements and testing each one).

How to control nestedness of loops in Perl 6?

This program should've written triples of indices that have a sum less or equal to 7:
for ((1..7) X (1..7)) X (1..7) {
.say if [+] $_ <= 7;
}
I thought it would only loop over the top level of the list (and the code would have an error in the loop body then, but it's not the point), but it just loops over individual numbers, which is frustrating :( Is there a neat trick to avoid it? And BTW, is there a way to make an n-ary direct product?
the easiest way to to name the reference
for (1..7) X (1..7) -> $a, $b { }