Computation time of a bash script scales horribly - bash

I have the following code that is addressing the Project Euler problem below:
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?
My script works fine, generates me 2520 as it should do for 1-10, I also have an answer for 1-17 of 12252240, it looks like so:
#!/bin/bash
for ((i=1; i<10000000000; i++))
do
if (( i%2 == 0 )) && (( i%3 == 0 )) && (( i%4 == 0 )) && (( i%5 == 0 )) &&
(( i%6 == 0 )) && (( i%7 == 0 )) && (( i%8 == 0 )) && (( i%9 == 0 )) &&
(( i%10 == 0 )) && (( i%11 == 0 )) && (( i%12 == 0 )) && (( i%13 == 0 )) &&
(( i%14 == 0 )) && (( i%15 == 0 )) && (( i%16 == 0 )) && (( i%17 == 0 )); then
# remaning terms to factor && (( i%18 == 0 )) && (( i%19 == 0 )) && (( i%20 == 0 )); then
int=$i
fi
if [[ $int ]]; then
echo "Lowest integer = '$int'"
break
else
continue
fi
done
However, the jump from factoring around 12 terms (about 3/4th of a second real time), to factoring 17 (6 mins real time), in computational time is huge.
I've yet to let the full 20 factors run, but all Project Euler problems are supposed to be solvable in a few minutes on medium power home computers.
So my question is 2 fold: 1) Am I on the right track in terms of how I approached programming this, and 2) how else could/should I have done it to make it as efficient as possible?

Without abandoning the brute-force approach, running the inner loop in reverse order roughly halves the running time.
for ((i=1; i<100000000; ++i)); do
for ((j=17; j>1; --j)); do
(( i%j != 0 )) && break
done
((j==1)) && echo "$i" && break
done
Informally speaking, almost no numbers are divisible by 17, and out of those, almost no numbers are divisible by 16. Thus, running the inner loop in reverse order removes 16 iterations of the inner loop for most numbers, and 15 for most of the rest.
Additional optimizations are obvious; for example, the inner loop could end at 4, because 2, 3, and 4 are already covered by their respective squares (all numbers which are divisible by 9 are also divisible by 3, etc). However, that's small potatoes compared to the main optimization.
(You did not have an explicit inner loop, and in fact, unrolling the loop like you did probably achieves a small performance gain. I rolled it into an explicit loop mainly out of laziness as well as for aesthetic reasons.)

So my question is 2 fold:
1) Am I on the right track in terms of how I approached programming this, and
I'm afraid you're not. You're using the wrong tools, namely a shell scripting language, to solve mathematical problems, and wonder why that doesn't perform well. "being solvable in a couple of minutes on a home computer" doesn't mean it's supposed to be like that, no matter how unusual your choice of tool is.
2) how else could/should I have done it to make it as efficient as possible?
Don't use bash's arithmetics. Bash is a shell, which means it's an interpreter to its core. Which means that it'll spend very little time calculating, and very much time understanding what it should do. To illustrate: Your complicated formula first has to be parsed into a tree that tells bash in which order to execute things, then these things have to be identified, then bash needs to work through that tree and save all the results for the next level of the tree. The few arithmetic instructions that it does cost next to no computational time.
Have a look at numpy, which is a python module for mathematics; it does things faster. If you're not afraid to compile your stuff, look at C++ or C, both for which very very fast math libraries exist.

Arithmetic conditions support logical operators. The speed gain is not huge, but there's some:
if (( i % 2 == 0 && i % 3 == 0 && ... ))
Also note, that testing i % 10 == 0 when you already know that i % 2 == 0 and i % 5 == 0 is not needed.
There's a much faster way how to get the number without iterating over all the numbers.

The answer is not a faster programming language. The answer is a more clever algorithm.
You know your end answer has to be divisible by all of the numbers, so start with your largest number and only check multiples of it. Find the smallest number that is a multiple of your two biggest numbers, and then check only multiples of that for the next number.
Let's look at how this works for 1 to 10:
10 // not divisible by 9, keep adding 10's until divisible by 9
20
30
40
50
60
70
80
90 // divisible by 9, move on to 8, not divisible by 8, keep adding 90's
180
270
360 // divisible by 8, not divisible by 7, keep adding 360's
720
1080
1440
1800
2160
2520 // divisible by 7, 6, 5, 4, 3, 2, 1 so you're done!
So in only 17 steps, you have your answer.
This algorithm implemented in Ruby (not known for its speed) found the answer for 1-5000 in 4.5 seconds on a moderately fast laptop.

Related

Bash obtain multiple number from result

I have these variables maybe this can up until 100 ( minimun two variables)
(Maximun +50)
1=10
2=21
3=44
4=36
...
and need find which variables sum up to 57
In this case is variable 4 + 2.
Or maybe the result was 90 and this case is 1+3+4.
I think need some random code, maybe some like this.
#!/bin/bash
array[0]=10
array[1]=21
array[2]=44
array[3]=36
Next add random until this fits to result
But but if I have 100 variables and need to find a result is it possible?
I read some links to randomize but I never seen anything like this.
This recursive Bash function tries to find and print sums using a brute-force, check all possible sums, approach:
function print_sums
{
local -r target=$1 # Number for which sums are to be found
local -r pre_sum=$2 # Sum built up for an outer target
local -r values=( "${#:3}" ) # Values available to use in sums
if (( target == 0 )) ; then
printf '%s\n' "$pre_sum"
elif (( ${#values[*]} == 0 )) ; then
:
else
# Print any sums that include the first in the list of values
local first_value=${values[0]}
if (( first_value <= target )) ; then
local new_pre_sum
[[ -z $pre_sum ]] && new_pre_sum=$first_value \
|| new_pre_sum="$pre_sum+$first_value"
local new_target=$((target - first_value))
print_sums "$new_target" "$new_pre_sum" "${values[#]:1}"
fi
# Print any sums that don't include the first in the list of values
print_sums "$target" "$pre_sum" "${values[#]:1}"
fi
return 0
}
Example usage, with an extended list of possible values to use in sums is:
values=(10 21 44 36 85 61 69 81 76 39 95 22 30 4 29 47 80 18 40 44 )
print_sums 90 '' "${values[#]}"
This prints:
10+21+30+29
10+44+36
10+36+22+4+18
10+36+4+40
10+36+44
10+76+4
10+22+18+40
10+4+29+47
10+80
21+36+4+29
21+69
21+39+30
21+22+29+18
21+22+47
21+4+47+18
21+29+40
61+29
39+22+29
39+4+29+18
39+4+47
It takes less than a second to do this on an oldish Linux machine. However, the exponential explosion (each addition to the list of values doubles the potential number of sums to try) means that it is not a practical solution for significantly larger numbers of values. I haven't tried 50, but it's hopeless unless the target value is small so you get a lot of early returns.
The question asked for the indices of the values in the sum to be printed, not the values themselves. That can be done with minor modifications to the code (which are left as an exercise for anybody who is interested!).

How to generate a random decimal number from 0 to 3 with bash?

I want to generate a random decimal number from 0 to 3, the result should look like this:
0.2
1.5
2.9
The only command I know is:
echo "0.$(( ($RANDOM%500) + 500))"
but this always generates 0.xxx. How do I do that ?
Bash has no support for non-integers. The snippet you have just generates a random number between 500 and 999 and then prints it after "0." to make it look like a real number.
There are lots of ways to do something similar in bash (generating the integer and decimal parts separately). To ensure a maximally even distribution, I would just decide how many digits you want after the decimal and pick a random integer with the same precision, then print the digits out with the decimal in the right place. For example, if you just want one digit after the decimal in the half-open range [0,3), you can generate an integer between 0 and 30 and then print out the tens and ones separated by a period:
(( n = RANDOM % 30 ))
printf '%s.%s\n' $(( n / 10 )) $(( n % 10 ))
If you want two digits after the decimal, use % 300 in the RANDOM assignment and 100 in the two expressions on the printf. And so on.
Alternatively, see the answer below for a number of solutions using other tools that aren't bash builtins:
https://stackoverflow.com/a/50359816/2836621
$RANDOM gives random integers in the range 0..32767
Knowing this, you have many options. Here are two:
Using bc:
$ bc <<< "scale=3; 3 * $RANDOM / 32767"
2.681
Constructing a number with two $RANDOMs:
$ echo "$(( $RANDOM % 3 )).$(( $RANDOM % 999 ))"
0.921
I have limited the precision to 3 decimal digits. Increasing/decreasing it should be trivial.

Check if a number N is sum of multiple of 3 and 5 given that N could be as big as 100,000 [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
How do I check if a number is sum of multiples of 3 and 5 given that the number could be as big as 100,000 . I need an optimized way to break a number into two parts such that the two parts are multiple of 3 and 5 only and the part which is multiple of 3 is greater than the part which is multiple of 5 and if that kind of splitting is not possible then I need to reject that number .
Eg:
1 => cant be split so rejected ,
35 => 30 + 5 ,
65 => 60 + 5 (Though 30 + 35 could be a split but since part which is multiple of 3 has to be greater than the part which is multiple of 5),
11 => 6+5
Every (integer) number modulo 3 yields 0, 1 or 2.
So let's examine all cases (n > 3 must yield for obvious reasons):
n % 3 == 0. Easy: We just take 0 == 0 * 5 and n / 3 as splitting.
n % 3 == 2. Easy again: One number will be 5 and the other (n-5) / 3. When subtracting 5 from n, we will create a second number (n-5), which falls under the first case.
n % 3 == 1. Same as in case 2, but this time we substract 10 == 2*5.
A small problem is the property that the multiple of 3 has to be larger than the one of 5. For this to hold true, n has to be at least 22. ( 22 == 2 * 5 + 3 * 4).
So all numbers smaller than 22 with the property n % 3 == 1 have to be rejected: 4, 7, 10, 13, 16 and 19. (As long as the factor for the multiples have to be non-negative).
If you mean to find a way to split a number to two parts, where the first part is a multiple of 3 and the second is a multiple of 5, with the extra requirement that the first (multiple of 3) part is greater than than the second (multiple of 5) part, then it's rather trivial:
Every number from 20 and above can be split that way.
Proof: For given number N, exactly one of the three numbers, N, N-5, N-10 will be a multiple of 3 (consider modulo 3 arithmetic.) So, one of these three splits satisfy the requirements:
N 0
N-5 5
N-10 10
and since N >= 20, the 1st part is greater (or equal) than the 2nd.
Off the top of my head --
Make Q = N / 3, integer division, rounding down. Make R the remainder.
If R = 0 you're done.
If R == 2, decrement Q.
Else R must be 1, subtract 2 from Q.
Your answer is Q * 3 and N - (Q * 3). Check that all results are positive and that the 3s multiple > 5s multiple restriction is satisfied.
(Note that this is essentially the same as Sirko's answer, but I felt it worthwhile to think it through separately, so I didn't attempt to analyze his first.)
max divisor of 3 and 5 is 1.
so when N = 3, or N >= 5, it can be sum of multiple of 3 and 5.
Just use this code:-
Enjoy :)
$num = 0; // Large Number
$arr = array();
if(isset($_POST['number']) $num = $_POST['number'];
// Assuming you post the number to be checked.
$j=0;
for($i=0;$i<$num;$i++)
{
if(($num-$i)%3==0 || ($num-$i)%5==0) { $arr[j] = $num - $i; $j++; }
}
//This creates an array of all possible numbers.
$keepLooping = true;
while($keepLooping)
{
$rand = array_rand($arr,2);
if(($rand[0] + $rand[1]) == $num)
{
//Do whatever you like with them. :)
}
}
I haven't tested it though but just for your idea. Instead of the for loop to select the possibilities, you can choose some other way whichever suits you.

Unix shell bit select

How can I calculate bit selection from shell?
Suppose I've got something like: i[m:l]
i is an integer, m is the MSB portion of the bit select and l is the LSB portion of the bit select, e.g.:
250[1:0] - would return the 2 LSB bits of "250", and answer will be "2"
250[7:2] - would return the 6 MSB bits of "250", and answer will be "62"
Not sure how portable this is, but Bash and KSH at least support bitwise operations (left & right shift, bitwise AND and OR), and exponentiation. So you can use those directly to do bitmasks.
#! /bin/sh
extract_bits() {
msb=$1 ; lsb=$2 ; num=$3
# Number of bits required
len=$(( $msb + 1 - $lsb ))
# Bitmask == 2^len - 1
mask=$(( 2 ** $len - 1 ))
# Left-shift mask, bitand, right-shift result
echo $(( ( num & ( $mask << $lsb ) ) >> $lsb ))
}
extract_bits 1 0 250
extract_bits 7 2 250
(As to whether it's a good idea to be doing this at all in a shell script, well, I'm not convinced.)

Fastest algorithm of getting precise answer (not approximated) when square-rooting

Sorry for unclear title, but I don't know how to state it properly (feel free to edit), so I will give example:
sqrt(108) ~ 10.39... BUT I want it to be like this sqrt(108)=6*sqrt(3) so it means expanding into two numbers
So that's my algorithm
i = floor(sqrt(number)) //just in case, floor returns lowest integer value :)
while (i > 0) //in given example number 108
if (number mod (i*i) == 0)
first = i //in given example first is 6
second = number / (i*i) //in given example second is 3
i = 0
i--
Maybe you know better algorithm?
If it matters I will use PHP and of course I will use appropriate syntax
There is no fast algorithm for this. It requires you to find all the square factors. This requires at least some factorizing.
But you can speed up your approach by quite a bit. For a start, you only need to find prime factors up to the cube root of n, and then test whether n itself is a perfect square using the advice from Fastest way to determine if an integer's square root is an integer.
Next speed up, work from the bottom factors up. Every time you find a prime factor, divide n by it repeatedly, accumulating out the squares. As you reduce the size of n, reduce your limit that you'll go to. This lets you take advantage of the fact that most numbers will be divisible by some small numbers, which quickly reduces the size of the number you have left to factor, and lets you cut off your search sooner.
Next performance improvement, start to become smarter about which numbers you do trial divisions by. For instance special case 2, then only test odd numbers. You've just doubled the speed of your algorithm again.
But be aware that, even with all of these speedups, you're just getting more efficient brute force. It is still brute force, and still won't be fast. (Though it will generally be much, much faster than your current idea.)
Here is some pseudocode to make this clear.
integer_sqrt = 1
remainder = 1
# First we special case 2.
while 0 == number % 4:
integer_sqrt *= 2
number /= 4
if 0 == number / 2:
number /= 2
remainder *= 2
# Now we run through the odd numbers up to the cube root.
# Note that beyond the cube root there is no way to factor this into
# prime * prime * product_of_bigger_factors
limit = floor(cube_root(number + 1))
i = 3
while i <= limit:
if 0 == number % i:
while 0 == number % (i*i):
integer_sqrt *= i
number /= i*i
if 0 == number % (i*i):
number /= i
remainder *= i
limit = floor(cube_root(number + 1))
i += 2
# And finally check whether we landed on the square of a prime.
possible_sqrt = floor(sqrt(number + 1))
if number == possible_sqrt * possible_sqrt:
integer_sqrt *= possible_sqrt
else:
remainder *= number
# And the answer is now integer_sqrt * sqrt(remainder)
Note that the various +1s are to avoid problems with the imprecision of floating point numbers.
Running through all of the steps of the algorithm for 2700, here is what happens:
number = 2700
integer_sqrt = 1
remainder = 1
enter while loop
number is divisible by 4
integer_sqrt *= 2 # now 2
number /= 4 # now 675
number is not divisible by 4
exit while loop
number is not divisible by 2
limit = floor(cube_root(number + 1)) # now 8
i = 3
enter while loop
i < =limit # 3 < 8
enter while loop
number is divisible by i*i # 9 divides 675
integer_sqrt *= 3 # now 6
number /= 9 # now 75
number is not divisible by i*i # 9 does not divide 75
exit while loop
i divides number # 3 divides 75
number /= 3 # now 25
remainder *= 3 # now 3
limit = floor(cube_root(number + 1)) # now 2
i += 2 # now 5
i is not <= limit # 5 > 2
exit while loop
possible_sqrt = floor(sqrt(number + 1)) # 5
number == possible_sqrt * possible_sqrt # 25 = 5 * 5
integer_sqrt *= possible_sqrt # now 30
# and now answer is integer_sqrt * sqrt(remainder) ie 30 * sqrt(3)
It's unlikely that there is a fast algorithm for this. See https://mathoverflow.net/questions/16098/complexity-of-testing-integer-square-freeness especially https://mathoverflow.net/questions/16098/complexity-of-testing-integer-square-freeness/16100#16100
List all prime divisors in increasing order e.g. 2700 = 2*2*3*3*3*5*5. This is the slowest step and requires sqrt(N) operations.
Create an accumulator (start with 1). Scan this list. For every pair of numbers, multiply the accumulator by (one of) them. So after scanning the list above, you get 2*3*5.
Accumulator is your multiplier. The rest remains under square root.

Resources