Bash obtain multiple number from result - bash

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!).

Related

So I have changed my script to find the product of two numbers in succession to each other below users maxvalue now this happens

So this script is supposed to allow a user to enter a maximum value that they wish to find all the numbers who are the product of two numbers in succession to each other less than or equal to the maximum value. What ends up happening is it mostly works but seems to go one number over the maximum value instead of stopping at or before the maximum value and I am not sure why it is behaving that way. Any help is appreciated. Below are the code and output of two different maximum values I tried.
Code
#!/bin/bash
#Task 3.
#This part allows the user to set the range of numbers they wish to find the products of two nonnegative number in succession to each other
count=0
echo "Input a maximum value."
read -r maxval
count=1
val1=0
val2=1
term=$((val1*val2))
echo "Ok all the products of two numbers in succession to each smaller than or equal to $maxval are as follows."
while [ "$term" -le "$maxval" ]
do
term=$(($val1*$val2))
echo "$term"
((val1++))
((val2++))
((count++))
done
echo "You have now reached the end of your range of products with integers in succession to each other smaller than or equal to $maxval."
echo "The total amount of products found in your range of products with integers in succesion to each other was $count"
Output 1
Input a maximum value.
13
Ok all the products of two numbers in succession to each smaller than or equal to 13 are as follows.
0
2
6
12
20
You have now reached the end of your range of products with integers in succession to each other smaller than or equal to 13.
The total amount of products found in your range of products with integers in succesion to each other was 6
Output 2
Input a maximum value.
154
Ok all the products of two numbers in succession to each smaller than or equal to 154 are as follows.
0
2
6
12
20
30
42
56
72
90
110
132
156
You have now reached the end of your range of products with integers in succession to each other smaller than or equal to 154.
The total amount of products found in your range of products with integers in succesion to each other was 14
The gist of the problem is that term is being calculated (and printed) after the test; you want to switch it around so that you calculate before the test.
One idea:
while [ "$term" -le "$maxval" ] # if we pass the test ...
do
echo "$term" # dislplay and ...
((val1++))
((val2++))
((count++)) # optimistically +1 assuming next test is successful and ...
term=$(($val1*$val2)) # calculate for the next test
done
((count--)) # last test was unsuccessful so -1
Test runs:
$ ./count.bash
Input a maximum value.
13
Ok all the products of two numbers in succession to each smaller than or equal to 13 are as follows.
0
2
6
12
You have now reached the end of your range of products with integers in succession to each other smaller than or equal to 13.
The total amount of products found in your range of products with integers in succesion to each other was 4
$ ./count.bash
Input a maximum value.
154
Ok all the products of two numbers in succession to each smaller than or equal to 154 are as follows.
0
2
6
12
20
30
42
56
72
90
110
132
You have now reached the end of your range of products with integers in succession to each other smaller than or equal to 154.
The total amount of products found in your range of products with integers in succesion to each other was 12

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.

How to use random number in shell script? [duplicate]

This question already has answers here:
Random number from a range in a Bash Script
(19 answers)
Closed 5 years ago.
With the shell script, I wish to generate five files, and I wish to put different random number range from 50000~150000 in each file. I tried something like following,
for i in 01 02 03 04 05; do
A=$((50000+100000))
B=$(($B%$A))
cat > ${i}.dat << EOF
AArandom=$A
EOF
done
But this does not work.... How can I make random numbers and print out for each file?
Each time you read the value of the variable $RANDOM,
it gives you a random number between 0 and 2^15 - 1,
that is 0 and 32767. So that doesn't give you enough range.
You could use two $RANDOM as two digits of base-15,
and then take appropriate modulo and apply appropriate range normalization.
Here's the logic wrapped in a function:
randrange() {
min=$1
max=$2
((range = max - min))
((maxrand = 2**30))
((limit = maxrand - maxrand % range))
while true; do
((r = RANDOM * 2**15 + RANDOM))
((r < limit)) && break
done
((num = min + r % range))
echo $num
}
And then you can generate the files in a loop like this:
for i in 01 02 03 04 05; do
echo "AArandom=$(randrange 50000 150000)" > $i.dat
done
Note that there is a caveat in the implementation of randrange:
there is a loop to re-roll in case the value would be biased,
but theoretically this may prevent the function from terminating.
In practice, that's extremely unlikely, but deserves a mention.
shuf is probably what you want:
$ shuf -i 50000-150000 -n 1
148495

while loop , sum of list of numbers ,sum of each number

Using a "while" loop how i can display the sum of the following list of numbers 1 8 4 3 6 5 7 2.
i must create a sum variable to collect the sum of each value as the number is processed by the loop. bash script
If the numbers are stored in a file called list_of_numbers and that file in the current directory (your question does not state where the numbers are coming from), then you could calculate and output the sum like this:
sum=0
while read num
do
echo "$sum + $num = $((sum+num))";
((sum += num))
done < ./list_of_numbers
echo $sum

Computation time of a bash script scales horribly

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.

Resources