Collatz Conjecture - Iteration rather than Recursion - perl

I am working on something for learning purposes where I have tackled Collatz using recursion. If you see below I make use of #_ and $_ to keep the for alive.
#!/usr/bin/env perl
sub collatz {
my ($num, $count) = #_;
$count++;
if ($num == 1) {
return $count;
} elsif ($num % 2 == 0) {
return collatz($num/2, $count);
} else {
return collatz($num*3 + 1, $count);
}
}
my $max = 0;
my $saved = 0;
for (1..1000) {
my $length = collatz($_, 0);
print "Num: " . $_ . " Length: " . $length . "\n";
if ($length > $max) {
$max = $length;
$saved = $_;
}
}
print "The longest sequence starts with " . $saved . "\n";
I am trying to use iteration instead of recursion but I just can't think of how to tackle this. I am not after the code in the question, I just want some tips / hints on how to tackle this to get the same result.
I suspect I will need to use a while or an until field.
Any help would be appreciated, again I don't want the exact answer.
Update
Here is my second attempt, which is giving me an error of
Can't return outside a subroutine at answer2.pl line 38.
my $number = 0;
my $counter = 0;
while ($number != 1000) {
$counter++;
if ($number == 1) {
return $counter;
}
elsif ($number % 2 == 0) {
return ($number / 2, $counter);
}
else {
return ($number * 3 + 1, $counter);
}
$number++;
}
print "number" . $number . "counter" . $counter . "\n";

Basically you have tail recursion, which is nice and simple to eliminate.
Instead of collatz calling itself to generate the next step in the sequence, you simply change the variables in-place and loop back to the top.
In its crudest form this would be
sub collatz2 {
my ($num, $count) = #_;
NEXT:
$count++;
if ($num == 1) {
return $count;
}
elsif ($num % 2 == 0) {
$num = $num / 2;
}
else {
$num = $num * 3 + 1;
}
goto NEXT;
}
but it should be written much more nicely than that.
I ended up with this
sub collatz {
my ($num) = #_;
my $count = 1;
while ($num > 1) {
$num = $num % 2 ? $num * 3 + 1 : $num / 2;
++$count;
}
$count;
}

Consider adding the logic that returns when the condition is met in the while.
Spoiler:
my $iter = 0;
while($num != 1){ #do stuff; $iter++ }

Just use a for or while loop with the end condition that your number == 1.
Spoiler:
use strict;
use warnings;
my $max_num = 0;
my $max_steps = 0;
for my $num (1..1000) {
my $steps = 0;
for (my $i = $num; $i != 1; $steps++) {
$i = $i % 2 ? 3 * $i + 1 : $i / 2;
}
print "Num: " . $num . " Length: " . $steps . "\n";
if ($steps > $max_steps) {
$max_num = $num;
$max_steps = $steps;
}
}
print "The longest sequence starts with " . $max_num . "\n";

Related

Unable to modify array parameter

This is supposed to put the contents of nums into decreasing order, however sort does not change the contents of nums. Many sites I read have said to pass by reference, but I don't think I'm dereferencing the argument correctly. Bear with me, this is my first Perl program :)
#! /usr/bin/env perl
sub sort {
my #arr = #_;
my $len = scalar #arr;
for (my $i = 1; $i < $len-1; $i = $i + 1) {
my $max = $i;
for (my $j = $i + 1; $j < $len; $j = $j + 1) {
if ($arr[$j] > $arr[$max]) {
$max = $j
}
}
$temp = $arr[$max];
$arr[$max] = $arr[$i];
$arr[$i] = $temp;
}
}
print "Enter 10 numbers: ";
my $numbers = <STDIN>;
my #nums = split ' ', $numbers;
print "Unsorted: #nums\n";
sort \#nums;
print "Sorted: #nums\n";
#Matt Jacob helped me out with this one. This is what I was looking for.
#! /usr/bin/env perl
sub selection_sort {
my $arr = shift;
my $len = scalar #$arr;
for (my $i = 0; $i < $len-1; $i++) {
my $max = $i;
for (my $j = $i + 1; $j < $len; $j++) {
if (#$arr[$j] > #$arr[$max]) {
$max = $j
}
}
my $temp = #$arr[$max];
#$arr[$max] = #$arr[$i];
#$arr[$i] = $temp;
}
}
print "Enter 10 numbers: ";
my $input = <STDIN>;
my #integers = split ' ', $input;
print "Unsorted: #integers\n";
selection_sort \#integers;
print "Sorted: #integers\n";

how to output into a tree like structure via Tree::DAG_Node?

I'm trying to write a program that can read from a file then put into a heap sort then output in a tree along with the sorted list. But I got stuck, instead of having the put be the numbers used in the heap sort, it just post 1-10 on one line then 1-9 on the next line. I'm really confused and I barely understand this language as it is. I put a example of the out below alone with the output I am hoping to have it draw.
#!/usr/bin/perl
use 5.006;
use strict;
use warnings;
use Tree::DAG_Node;
process_data(read_file('testing.txt'));
process_data((3,1,4,1,5,9,2,6,5,3,6));
sub read_file{
my($filename)=#_;
my #data=();
my #words;
open(my $fh, "<", $filename)
or die "Could not open file: $!\n";
while(<$fh>){
chomp;
#words = split(' ');
foreach my $word(#words){
push #data, $word;
}
}
close $fh;
return #data;
}
sub heap_sort {
my ($a) = #_;
my $n = #$a;
for (my $i = ($n - 2) / 2; $i >= 0; $i--) {
down_heap($a, $n, $i);
}
for (my $i = 0; $i < $n; $i++) {
my $t = $a->[$n - $i - 1];
$a->[$n - $i - 1] = $a->[0];
$a->[0] = $t;
down_heap($a, $n - $i - 1, 0);
}
}
sub down_heap {
my ($a, $n, $i) = #_;
while (1) {
my $j = max($a, $n, $i, 2 * $i + 1, 2 * $i + 2);
last if $j == $i;
my $t = $a->[$i];
$a->[$i] = $a->[$j];
$a->[$j] = $t;
$i = $j;
}
sub max {
my ($a, $n, $i, $j, $k) = #_;
my $m = $i;
$m = $j if $j < $n && $a->[$j] > $a->[$m];
$m = $k if $k < $n && $a->[$k] > $a->[$m];
return $m;
}
}
sub draw_tree{
my(#data)=#_;
my $root = Tree::DAG_Node->new;
$root->name($_[0]);
$root->new_daughter->name($_) for (1..10);
my #names = #data;
my $count = 50;
for my $n ($root->daughters) {
for (split //, $names[$count++]) {
$n->new_daughter->name($_)
}
}
print map "$_\n", #{$root->draw_ascii_tree};
}
sub process_data{
my(#data)=#_;
my #a = #data;
print "#a\n";
print "\n";
heap_sort(\#a);
draw_tree(#a);
print "\n";
print "#a\n";
print "\n";
}
here is the output I get
CMD output of my code
Output I am expecting:
(using example data)
1 1 9 4 5
1
|
1 ----- 9
|
/\
4 5
1 1 4 5 9

How to make this Perl program print in descending order?

This code works but it prints in ascending order. Do I need to change my whole formula?
print "Enter an integer \n";
my $root = <STDIN>;
my #nums = (100..200);
my $i = 0;
# code in while loop executes as long as condition is true
while ( $i < $#nums )
{
print "$nums[$i]\n",if($nums[$i] % $root == 0); $i++;
}
Just set $i to $#nums instead of 0 and decrement it -- instead of incrementing. You'll need to change the loop condition to $i >= 0 (it should be $i <= $#nums in your code, otherwise it skips 200 when 10 was entered).
#!/usr/bin/perl
use warnings;
use strict;
print "Enter an integer \n";
my $root = <>;
my #nums = (100 .. 200);
my $i = $#nums;
while ( $i >= 0 ) {
print "$nums[$i]\n" if $nums[$i] % $root == 0;
--$i;
}
There are more than a few ways to do it ... not all equally good:
#!/usr/bin/env perl
use strict;
use warnings;
run(#ARGV);
sub run {
my $root = $_[0] // get_root();
my #nums = (100 .. 200);
my #functions = (
sub {
my ($root, $nums) = #_;
my $i = #$nums;
while ($i--) {
print "$nums->[$i]\n" unless $nums->[$i] % $root;
}
return;
},
sub {
my ($root, $nums) = #_;
for my $n ( reverse #$nums ) {
print "$n\n" unless $n % $root;
}
return;
},
sub {
my ($root, $nums) = #_;
my $i;
while ($i++ < #$nums) {
print "$nums->[#$nums - $i]\n" unless $nums->[#$nums - $i] % $root;
}
return;
},
sub {
my ($root, $nums) = #_;
my #multiples = reverse grep !($_ % $root), #$nums;
print "$_\n" for #multiples;
return;
},
);
for my $i ( 0 ... $#functions ) {
print "=== Function $i ===\n";
$functions[$i]->($root, \#nums);
}
}
sub get_root {
return scalar <STDIN>;
}
print "Enter an integer \n";
my $root = <STDIN>;
my #nums = (100..200);
#nums = reverse #nums; #Just reverse the arrays
my $i = 0;
# code in while loop executes as long as condition is true
while ( $i < $#nums )
{
print "$nums[$i]\n",if($nums[$i] % $root == 0); $i++;
}
May be it will helps you.
for (my $i = $#nums; $i >= 0; --$i) { ... }
for (my $i = #nums; $i--; ) { ... }
for my $i (reverse 0 .. $#nums) { ... }
for (1 .. #nums) { my $i = -$_; ... } # Or: my $i = #nums-$_;
for my $num (reverse #nums) { ... }

Perl Script not running correctly

When ever I run this bit of code. it doesn't display any output. Anyone see anything wrong?
I am trying to display this in the out put:
A
AA
AAA
AAAB
AAABA
AAABAA
AAABAAA
AAABAAAB
etc.
#!/usr/local/bin/perl
$A = 3;
$B = 1;
$i = 1;
$output = "";
$j = 1;
while ($i <= $ARGV[0]) {
while ($j <= $i) {
if ($A == 0 && $B == 0) {
$A = 3;
$B = 1;
}
if ($A > 0) {
$output.= "A";
$A--;
}
else {
$output.= "B";
$B--;
}
$j++;
}
print($output . "\n");
$i++;
}
It works for me when I run it with a numeric argument (number of lines).
An idea how to simplify the code:
#!/usr/bin/perl
use warnings;
use strict;
my $count = shift;
my $A = 3;
my $B = 1;
my $string = q();
$string .= ('A' x $A) . ('B' x $B) while $count > length $string;
print substr($string, 0, $_), "\n" for 1 .. $count;
It uses a different algorithm - it creates the longest possible string, and then outputs parts of it.
if there is no #ARGV, while ($i <= $ARGV[0]) never runs.
#ARGV is an array of the command line arguments provided when the script is executed. you did not provide any command line arguments. if you had use warnings in effect, you would be warned that $ARGV[0] is uninitialized.
As from ikegami comment. You cann't pass the input at when the program is compile. For example, consider your file name is algo.pl. Can you run your program with
perl algo.pl 10
Here 10 is the input value of the program. In program value is retrieve by the $ARGV[0]
so in your program looks like while ($i <= $ARGV[0]).
If you want pass the several values like perl filename.pl 12 data1 data2In your data retrieve by $ARGV[0] $ARGV[1] $ARGV[2] for more information see here.
If you want pass the input at the time of execution used STDIN
use warnings;
use strict;
my $A = 3;
my $B = 1;
my $i = 1;
my $output = "";
my $j = 1;
print "Enter the value: ";
chomp(my $value = <STDIN>);
while ($i <= $value) {
while ($j <= $i) {
if ($A == 0 && $B == 0) {
$A = 3;
$B = 1;
}
if ($A > 0) {
$output.= "A";
$A--;
}
else {
$output.= "B";
$B--;
}
$j++;
}
print($output . "\n");
$i++;
}

Binary representation of float to decimal conversions in Perl

I read Stack Overflow question How do I convert a binary string to a number in Perl? on how to convert binary integers to decimal or vice versa in Perl. But how do I do this for float as well?
For example, conversion from 5.375 to 101.011 and vice versa.
sub number_to_binary_string {
my $in = shift;
my $sign = $in < 0 and $in = abs $in;
my $out = sprintf "%b.", int $in;
substr $out, 0, 0, '-' if $sign;
$in -= int $in;
do {
if ($in >= .5) {
$out .= '1';
$in -= .5;
}
else {
$out .= '0';
}
$in *= 2;
} while $in > 0;
return $out;
}
sub binary_string_to_number {
my $in = shift;
my ($int,$frac) = split /\./, $in;
my $sign = $int =~ s/^-//;
my $out = oct "0b$int";
my $mult = 1;
for my $digit (split //, $frac) {
$mult *= .5;
$out += $mult * $digit;
}
$out = -$out if $sign;
return $out;
}
Below is a machine- and build-specific implementation (NV = little-endian double).
It returns the number stored exactly, and it supports NaN, Infinity, -Infinity and -0 and subnormals. It trims leading zeros and trailing decimal zeroes.
sub double_to_bin {
my ($n) = #_;
my ($s, $e, $m) = unpack 'a a11 a52', unpack 'B64', "".reverse pack 'F', $n;
$s = $s ? '-' : '';
$e = oct("0b$e");
if ($e == 0x7ff) {
return ($m =~ /1/) ? 'NaN' : $s . 'Infinity'
} elsif ($e == 0x000) {
$m = "0$m"; $e -= 52;
} else {
$m = "1$m"; $e -= 1075;
}
if ($e >= 0) {
$m .= ('0' x $e);
} elsif ($e >= -52) {
substr($m, $e+53, 0, '.');
} else {
$m = '0.' . ('0' x (-$e-53)) . $m;
}
$m =~ s/^0+(?!\.)//;
$m =~ s/(?:\..*1\K|\.)0+\z//;
return $s . $m;
}
Here's a sketch of an interesting "portable" implementation. It doesn't handle any of the interesting edge-cases like integers, NaNs, infinities, or even negative numbers because I'm lazy, but extending it wouldn't be so hard.
(my $bin = sprintf "%b.%032b", int($num), 2**32 * ($num - int($num)))
=~ s/\.?0+$//;
The 2**32 seems like an architecture-specific magic number but in fact it's basically just how many bits of precision you want after the dot. Too small and you get harmless truncation; too large and there's potential for overflow (since %b probably casts to UV sometime before doing its formatting).
$TO_BIN = '-b';
$TO_DEC = '-d';
($op, $n ) = #ARGV;
die("USAGE: $0 -b <dec_to_convert> | -d <bin_to_convert>\n") unless ( $op =~ /^($TO_BIN|$TO_DEC)$/ && $n );
for (split(//,$n)) {
if ($_ eq ".") {
$f=".";
} else {
if (defined $f) { $f.=$_ } else { $i.=$_ }
}
}
$ci = sprintf("%b", $i) if $op eq $TO_BIN;
$ci = sprintf("%d", eval "0b$i") if $op eq $TO_DEC;
#f=split(//,$f) if $f;
if ($op eq $TO_BIN) {
while( $f && length($cf) < 16 ) {
($f *= 2) =~ s/(\d)(\.?.*)/$2/;
$cf .= $1 ? '1' : '0';
}
} else {
for ($i=1;$i<#f;$i++) {
$cf = ($cf + $f[#f-$i])/2;
}
}
$cf=~s/^.*\.|^/./ if $cf;
print("$ci$cf\n");