How to get positive sign for result from mod in bash - bash

When naively using the mod command in bash the residual gets the wrong sign (in my opinion) for negative numerators:
If i write:
for i in {-5..5}; do echo $(( $i % 3 )) ; done
i get the output (as a row)
-2 -1 0 -2 -1 0 1 2 0 1 2
How do i achieve the "correct" behavior
1 2 0 1 2 0 1 2 0 1 2

I know it's an old question, but rather than loop until the result is positive or launch perl or python consider the following:
for i in {-5..5}; do echo $(( (($i % 3) + 3) % 3)) ; done
this will result in the OP's desired output.
This works because the first modulo will bring the result into the range -3 to 3, adding 3, causes the result to be in the range 0 to 6, we can then perform modulo again (adding 3 has no effect on this).
in general: mod = ((a % b) + b) % b

Add 3 and then Mod 3 to the first set of results:
$ for i in {-5..5}; do printf "%d " $(( (($i % 3) + 3) % 3 )) ; done
1 2 0 1 2 0 1 2 0 1 2
If you know the maximum range, you can just add a significantly large enough multiple of 3 to make all numbers positive before the first modulo operation.
$ for i in {-5..5}; do printf "%d " $(( ($i + 3000000) % 3 )) ; done
However, the first approach is cleaner and more universal.
Lastly, for fun:
positive_mod() {
local dividend=$1
local divisor=$2
printf "%d" $(( (($dividend % $divisor) + $divisor) % $divisor ))
}
for i in {-5..5}; do
printf "%d " $(positive_mod $i 3)
done

According to wikipedia negative signs are allowed.
[The result of a mod n] this still leaves a sign ambiguity if the remainder is nonzero: two possible choices for the remainder occur, one negative and the other positive, and two possible choices for the quotient occur. Usually, in number theory, the positive remainder is always chosen, but programming languages choose depending on the language and the signs of a or n.
So it's up to the programming language to define this. As bash has obviously gone for the "negative remainder" way you might escape to e.g. perl like this:
for i in {-5..5}; do perl -le "print $i%3"; done
This is at the cost of launching the Perl interpreter individually for each integer.
Indeed! Since the OP seem to care about correct math, you might consider switching to something like python and do the looping and everything in there.

Related

Generate random number except values in bash

I want to generate a random nunmber between some values but except some values.
I have this:
shuf -i 1-9 -n 1
Or this:
$(( ( RANDOM % 10 ) + 1 ))
But i want to exclude the number 3 and the number 8, for example.
Practical General Solution
Instead of specifying a range of numbers you can specify each valid number individually using the -e option (at least for shuf from GNU coreutils) or you can specify the numbers in an (anonymous) file. Since the last option is more portable (works also for shuffle on BSD), we will use that. Now we only have to generate output with the numbers 1, 2, 4, 5, 6, 7, 9. Here are some examples:
shuf -n 1 <(seq 9 | grep -Fxv -e{3,8})
shuf -n 1 <(printf %s\\n 1 2 4 5 6 7 9)
shuf -n 1 <(printf %s\\n 1 2 {4..7} 9)
shuf -n 1 <(seq 2; seq 4 7; echo 9)
Just For Fun
For each finite set S={i1,i2,…in}⊂ℕ of integers you can come up (by hand) with a polynomial f such that f(x)=ix for all x∈{0,1,…,n-1}.
In general, polynomial f has degree n-1. Sometimes we can use rounding to reduce the degree. In your concrete case of S={1,2,4,5,6,7,9} we can use f(x) = floor(1.25x+1.5). Here is an implementation in bash using bc (the (…) / 1 rounds down).
bc <<< "($RANDOM % 7 * 1.25 + 1.5) / 1"
A benefit of this method is that it works purley with built-ins if we scale all numbers inside the formula such that they become integers (here we scaled by a factor of 4).
echo $(( ($RANDOM % 7 * 5 + 6) / 4 ))
Here is one way:
until N=$(( ( RANDOM % 10 ) + 1 )); (( $N != 3 && $N != 8 )); do true; done; echo $N
One could argue that this way is imperfectly efficient, but in the absence of an obvious, simple alternative, it should suit.
A function?
rnd() {
local n=$(( $RANDOM % 10 ))
while [[ $n =~ [038] ]]
do n=$(( $RANDOM % 10 ))
done
echo $n
}
Then you can just say x=$( rnd ).
A simple way to select a random value from any set of values is to put the values in an array and select a random item from the array. In this case:
values=(1 2 {4..7} 9)
random_value=${values[RANDOM % ${#values[*]}]}
Also see Select a random item from an array.
The code above has some limitations. First, it doesn't work if the number of values to select from is greater than 32768, because the maximum value of $RANDOM is 32767. (The size of the array could become a problem for numbers greater than that anyway.) Second, the value returned by RANDOM % ${#values[*]} is (usually slightly) biased towards the lower array indices. See Why do people say there is modulo bias when using a random number generator? for more information. If that matters to you, see the rand function in BashFAQ/026 (How can I randomize/shuffle the order of lines in a file? ...) for Bash code that generates unbiased (pseudo)random numbers in a restricted range.

How do I perform this calculation and output it to standard out?

I am trying to do this in Bash:
read n
echo int(math.ceil((math.sqrt(1 + 8 * n) - 1) / 2))
Of course this isn't working syntax but I am just putting it there so you can tell what I am trying to do.
Is there an easy way to actually make this into valid Bash?
Although you ask to do this in Bash, there's no native support for functions like square root or ceiling. It would be simpler to delegate to Perl:
perl -wmPOSIX -e "print POSIX::ceil((sqrt(1 + 8 * $n) - 1) / 2)"
Alternatively, you could use bc to calculate the square root, and some Bash to calculate the ceiling.
Let's define a function that prints the result of the formula with sqrt of bc:
formula() {
local n=$1
bc -l <<< "(sqrt(1 + 8 * $n) - 1) / 2"
}
The -l flag changes the scale from the default 0 to 20.
This affects the scale in the display of floating point results.
For example, with the default zero, 10 / 3 would print just 3.
We need the floating point details in the next step to compute the ceiling.
ceil() {
local n=$1
local intpart=${n%%.*}
if [[ $n =~ \.00*$ ]]; then
echo $intpart
else
echo $((intpart + 1))
fi
}
The idea here is to extract the integer part,
and if the decimal part is all zeros, then we print simply the integer part,
otherwise the integer part + 1, as that is the ceiling.
And a final simple function that combines the above functions to get the result that you want:
compute() {
local n=$1
ceil $(formula $n)
}
And a checker function to test it:
check() {
local n num
for n; do
num=$(formula $n)
echo $n $num $(compute $n)
done
}
Let's try it:
check 1 2 3 4 7 11 12 16 17
It produces:
1 1.00000000000000000000 1
2 1.56155281280883027491 2
3 2.00000000000000000000 2
4 2.37228132326901432992 3
7 3.27491721763537484861 4
11 4.21699056602830190566 5
12 4.42442890089805236087 5
16 5.17890834580027361089 6
17 5.35234995535981255455 6
You can use bc's sqrt function.
echo "(sqrt(1 + 8 * 3) - 1) / 2" | bc
Ceil function can be implemented using any of the methods described in this answer.
Getting Ceil integer
For eg:
ceiling_divide() {
ceiling_result=`echo "($1 + $2 - 1)/$2" | bc`
}
You can use bc for all the job
$>cat filebc
print "Enter a number\n";
scale=20
a=read()
b=((sqrt(1 + 8 * a) - 1) / 2)
scale=0
print "ceil = ";
((b/1)+((b%1)>0))
quit
Call it like that
bc -q filebc

Syntax error in BASH script near `%` operator

I am writing a simple bash script to count the number of occurrences of random draws of cards. I store those in an array, and when printing out the results, for every 10 times that card is pulled, I print one single '*' in a sort of Histogram style of output.
Although, I keep receiving this error when compiling on Terminal:
"task1.sh: line 29: % 10 : syntax error: operand expected (error token is "% 10 ")
task1.sh: line 33: % 10: syntax error: operand expected (error token is "% 10")"
Can't seem to figure out why though. Thank you in advance for any help.
#!/bin/bash
randomdraw(){
Suits="Clubs Diamonds Hearts Spades"
suit=($Suits)
Denominations="2 3 4 5 6 7 8 9 10 Jack Queen King Ace"
denomination=($Denominations)
num_suits=${#suit[*]}
num_denominations=${#denomination[*]}
declare -a numoccurences
declare -a suitoccurences
for ((x=0 ; $x<$loopnum ; x=$x+1));
do
(( numoccurences[$(( RANDOM%num_denominations ))]++ ))
(( suitoccurences[$(( RANDOM%num_suits ))]++ ))
done
}
echo "How Many Random Draws?: "
read loopnum
randomdraw loopnum
for ((x=0 ; $x<$num_denominations ; x=$x+1));
do
let "rounder=$(( ${numoccurences[x]} % 10 ))"
if [ $rounder -ge 5 ];
then
let "starnum=$(( $(( ${numoccurences[x]} / 10 )) + 1 ))"
else
let "starnum=$(( ${numoccurences[x]} / 10 ))"
fi
echo "${denomination[x]}: "
for ((k=0 ; $k<$starnum ; k=$k+1));
do
echo "*"
done
done
Your num_denominations array is mostly empty and the
let "rounder=$(( ${numoccurences[x]} % 10 ))"
is evaluated to
let "rounder=$(( % 10 ))"
Print numoccurences and suitoccurences before asking for loop number for debugging.
You should try to be consistent in the way you write arithmetic expressions in bash. You don't need to use $ to introduce a variable inside an arithmetic expression. And you don't need to use ${array[idx]} either. There's no reason to use let if you have arithmetic evaluation, either. So instead of
let "rounder=$(( ${numoccurences[x]} % 10 ))"
You could write:
(( rounder = numoccurences[x] % 10 ))
These don't quite do the same thing. In the first one, ${numoccurences[x]} will be substituted with nothing if numoccurrences doesn't have a value corresponding to the key $x. In the second one, numoccurrence[x] will be replaced by 0, which is what you actually want. (That has nothing to do with the unnecessary let, since the $((...)) arithmetic expression is evaluated before let is run.)
There are many other places in that script where you would be well advised to simplify your style. For example,
let "starnum=$(( $(( ${numoccurences[x]} / 10 )) + 1 ))"
would be more robust and more readable as
(( starnum = numoccurences[x] / 10 + 1 ))

Solving a (simple) numeric exercise in bash

Some of you are probably familiar with Project Euler, and I'm currently attempting a few of their problems to teach myself some more bash. They're a bit more mathematical than 'script-y' but it helps with syntax etc.
The problem currently asks me to solve:
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.
The code I have looks like so:
#!/bin/bash
i="1"
for i in `seq 1 333`
do
threes[$i]=`calc $i*3` # where 'calc' is a function written in bashrc
#calc actually looks like: calc() {awk "BEGIN { print "$*"} }
let "sumthrees = sumthrees + ${threes[$i]}"
done
for i in `seq 1 199`
do
fives[$i]=`calc $i*5`
let "sumfives = sumfives + ${fives[$i]}"
done
let "ans = $sumfives + $sumthrees"
echo "The sum of all 3 factors is $sumthrees and the sum of all five factors is $sumfives"
echo "The sum of both is $ans"
#So I can repeatedly run the script without bash remembering the variables between executions
unset i
unset fives
unset threes
unset sumfives
unset sumthrees
unset ans
So far I've not gotten the correct answer, but have run out of ideas as to where I'm going wrong. (FYI, the script currently gives me 266333, which I believe is close, but I don't know the answer yet.)
Can anyone spot anything? And for my own learning, if there are more elegant solutions to this that people might like to share that would be great.
EDIT
Thanks for all the answers, super informative. Since there are so many useful answers here I'll accept my favourite as the proper thread answer.
Blue Moon pointed out the actual problem with your logic.
You don't need to store all the threes and fives in arrays because you don't need them later.
You don't need to unset variables at the end of a script if you use ./yourscript or bash script because they'll disappear along with the
shell instance (better to initialize them first in any case).
You don't need awk to do math, bash does that just fine.
seq and let are not the best way to do anything in a bash script.
Here's a straight forward version:
#!/bin/bash
sum=0
for ((i=1; i<1000; i++))
do
if (( i%3 == 0 || i%5 == 0 ))
then
(( sum += i ))
fi
done
echo "$sum"
Your logic is almost right except that there are numbers which divide by both 3 and 5. So you are adding these numbers twice. Hence, you get wrong answer.
Use another loop similar to ones you have and subtract the ones that divide by both 3 and 5 from the result.
A few tips you might find useful:
In bash, you use let to give the shell a hint that a variable should be considered a number. All bash variables are strings, but you can do arithmetic on numerical strings. If I say let i=1 then i is set to 1, but if I say let i="taco" then $i will be 0, because it couldn't be read as a number. You can achieve a small amount of type-safety when doing mathematical work in the shell.
Bash also has $((this)) mechanism for doing math! You can check it out yourself: echo $((2 + 2)) -> 4, and even more relevant to this problem: echo $((6 % 3 == 0)) -> 1
In case you aren't familiar, % divides the first number by the second, and gives back the remainder; when the remainder is 0, it means that the first is divisible by the second! == is a test to see if two things are equal, and for logical tests like this 1 represents true and 0 represents false. So I'm testing if 6 is divisible by 3, which it is, and the value I get back is 1.
The test brackets, [ ... ] have a "test for equality" flag, -eq, which you can use to check if a math expression has a certain value (man test for more details)!
$ let i=6
$ echo $((i % 3 == 0 || i % 5 == 0))
1
$ if [ $((i % 3 == 0 || i % 5 == 0)) -eq 1 ]; then echo "yes"; fi
yes
(|| is another logical test - $((a || b)) will be 1 (true) when a is true or b is true).
Finally, instead of doing this for the number 6, you could do it in a for loop and increment a sum variable every time you find a multiple of 3 or 5:
let sum=0
for i in {1..1000}; do
if [ $((i % 3 == 0 || i % 5 == 0)) -eq 1 ]; then
let sum=$((sum + i))
fi
done
echo $sum
And there you'd have a working solution!
Bash has a lot of nice little tricks, (and a lot more mean ugly tricks), but it's worth learning at least a handful of them to make use of it as a scripting tool.
How about creative use of the modulus function & some checks. Then you have just 1 loop.
#!/bin/bash
i=1
while [ $i -lt 1000 ]
do
if [ $(($i % 3)) -eq 0 ] || [ $(($i % 5)) -eq 0 ]
then
sumall=$(($sumall+$i))
fi
i=$(($i+1))
done
echo "The sum of both is $sumall"
Answer: 233168
A different solution:
#!/bin/bash
sum=0
for n in {1..999}; do [ $(((n%5) * (n%3))) -eq 0 ] && sum=$((sum+n)); done
echo $sum
The script loops through all numbers below 1000, tests if the product of the number mod 3 and the number mod 5 is 0 (the product of two numbers can only be zero if one of them is zero). If that is the case, it adds the current number to a sum, which is printed out afterwards.
By the way, if I were you I'd include the definition of the calc function inside the script, to get a self-contained solution that doesn't need your specific configuration.

Shell script random number generating

var=$RANDOM creates random numbers but how can i specify a range like between 0 and 12 for instance?
If you already have your random number, you can say
var=$RANDOM
var=$[ $var % 13 ]
to get numbers from 0..12.
Edit:
If you want to produce numbers from $x to $y, you can easily modify this:
var=$[ $x + $var % ($y + 1 - $x) ]
Between 0 and 12 (included):
echo $((RANDOM % 13))
Edit: Note that this method is not strictly correct. Because 32768 is not a multiple of 13, the odds for 0 to 8 to be generated are slightly higher (0.04%) than the remaining numbers (9 to 12).
Here is shell function that should give a balanced output:
randomNumber()
{
top=32768-$((32768%($1+1)))
while true; do
r=$RANDOM
[ r -lt $top ] && break
done
echo $((r%$1))
}
Of course, something better should be designed if the higher value of the range exceed 32767.
An alternative using shuf available on linux (or coreutils to be exact):
var=$(shuf -i0-12 -n1)
Here you go
echo $(( $RANDOM % 12 ))
I hope this helps.
This document has some examples of using this like RANGE and FLOOR that might be helpful: http://tldp.org/LDP/abs/html/randomvar.html
On FreeBSD and possibly other BSDs you can use:
jot -r 3 0 12
This will create 3 random numbers from 0 to 12 inclusively.
Another option, if you only need a single random number per script, you can do:
var=$(( $$ % 13 ))
This will use the PID of the script as the seed, which should be mostly random. The range again will be from 0 to 12.

Resources