I am trying to iterate through an n-dimensional space with a series of nested for-loops in bash.
VAR1="a b c d e f g h i"
VAR2="1 2 3 4 5 6 7 8 9"
VAR3="a1 b2 b3 b4 b5 b6"
for i1 in $VAR1; do
for i2 in $VAR2; do
for i3 in $VAR3; do
echo "$i1 $i2 $i3"
done
done
done
Now as I get more dimensions to iterate through, I realize it would be easier/better to be able to specify an arbitrary number of variables to loop through.
If I were using a more sophisticated programming language, I might use recursion to pass a list of lists to a function, pop one list off, iterate through it, recursively calling the function each time through the loop, passing the now reduced list of lists, and assembling the n-tuples as I go.
(I tried to pseudocode what that would look like, but it hurt my head thinking about recursion and constructing the lists.)
function iterate_through(var list_of_lists)
this_list=pop list_of_lists
var new_list = []
for i in this_list
new_list.push(i)
new_list.push(iterate_through(list_of_lists))
# return stuff
# i gave up about here, but recursion may not even be necessary
Anyone have a suggestion for how to accomplish iterating through an arbitrary number of vars in bash? Keeping in mind the goal is to iterate through the entire n-dimensional space, and that iteration is not necessarily part of the solution.
If parallel is acceptable, then one could simplify the nested for loop as
parallel -P1 echo {1} {2} {3} ::: $VAR1 ::: $VAR2 ::: $VAR3
In the general case, it could be perhaps feasible to first assemble this command and then execute it...
You can use recursion to compute cartesian product
The following script will do the job with variable length input vector :
#!/bin/bash
dim=("a b c d e f g h i" "1 2 3 4 5 6 7 8 9" "a1 b2 b3 b4 b5 b6")
function iterate {
local index="$2"
if [ "${index}" == "${#dim[#]}" ]; then
for (( i=0; i<=${index}; i++ ))
do
echo -n "${items[$i]} "
done
echo ""
else
for element in ${dim[${index}]}; do
items["${index}"]="${element}"
local it=$((index+1))
iterate items[#] "$it"
done
fi
}
declare -a items=("")
iterate "" 0
The following gist will take as input arguments all your dimensions array (with space separated items) : https://gist.github.com/bertrandmartel/a16f68cf508ae2c07b59
Related
This question already has answers here:
How to generate random number in Bash?
(25 answers)
Closed 11 months ago.
Normally, I may use the following expression to define a variable in bash as the number
var='3'
How could I associate the variable with the random value, e.g. from 1 to 6, which could be assigned each time in the for loop:
for var ...; do
print $var
done
Assuming that in each iteration, the var should be randomly selected from 1 to 6.
I'm not sure if your question is about generating the random number
$ echo $(( RANDOM % 6 + 1 ))
4 # results may vary
or getting a sequence of random numbers. A C-style for loop would probably be simplest.
# Roll a 6-sided dice 5 times.
for ((n=0; var=RANDOM%6+1, n<5; n++)); do
echo $var
done
The second expression makes use of the , operator, so that both var is assigned to just before the beginning of each loop iteration.
(Or course, there's not much reason to write the loop this way. Be clear, and put the assignment at the top of the body instead.
for ((n=0; n < 5; n++)); do
var=$((RANDOM%6 + 1))
echo $var
done
)
There is no need for for loop actually, you can do it by using RANDOM.
If we take your example into consideration;
To create random number between 1 and 6, you can use something like;
$(( ( $RANDOM % 6 ) + 1))
You can try it with;
random_number=$(( ( $RANDOM % 6 ) + 1)); echo $random_number
Basically, what I'm trying to understand is that how to reassign a variable that was already declared before to a new value.
I want to reassign the variable to a different value in the loop. Then print that sum.
For example in JavaScript,
sum = 0;
for... (loop)
sum = sum + start-point; (loop body)
console.log(sum)
Now I don't seem to be able to get that in bash.
This is my code in bash
echo Enter a number:
read NUMBER
echo Enter a startpoint:
read STARTPOINT
echo Enter how many terms:
read TERMS
sum=0;
for ((n=0;n<$TERMS;n++));
do
STARTPOINT=$((STARTPOINT + NUMBER))
sum=$(($sum + $STARTPOINT))
echo $STARTPOINT
echo $sum
done
All the code is correct except the sum part, and because of it the code doesn't run properly. if I remove that part, it works fine. I just need to sum the outputs of the variable STARTPOINT.
Example
Input
NUMBER = 3 (means to increment 3 in the startpoint)
STARTPOINT = 2
TERMS = 5 (means to add 3 to 2 five times)
Expected output
5
8
11
14
17
And the part that I am having difficulty with is how to add all these numbers, when all added, it should print 55.
In this answer I changed your variable names to be lowercase. This is the conventional naming scheme in bash to avoid accidental name collisions with built-in variables and environment variables.
If I understood correctly, you want to build the following sequence and also sum it up:
startpoint + 1*number + startpoint + 2*number + ... + startpoint+ term*number
In that case you should not change startpoint inside your loop. Use another variable to store the result of startpoint + n*number.
startpoint=2 number=3 terms=5 sum=0
echo "$terms times increment $startpoint by $number:"
for ((n=1; n<=terms; n++));
do
((addend = startpoint + n*number))
echo "$addend"
((sum += addend))
done
echo "The sum is $sum"
However, instead of using a slow loop you could printing the sequence using seq and then calculate its sum using the closed formula for triangular numbers:
startpoint=2 number=3 terms=5
seq $((startpoint+number)) $number $((startpoint+terms*number))
((sum = terms*startpoint + terms*(terms+1)/2 * number))
echo "The sum is $sum"
I'm trying to create a fractal tree in bash, provided that the user enters N where N is the number of branches.
I need to write the following sequence that gets N as an input:
N = 1; sequence = 50
N = 2; sequence = (50-16),(50+16)
N = 3; sequence = (50-16-8),(50-16+8),(50+16-8),(50+16+8)
N = 4; sequence = (50-16-8-4),(50-16-8+4),(50-16+8-4),(50-16+8+4),(50+16-8-4),(50+16-8+4),(50+16+8-4),(50+16+8+4)
N = 5; sequence = (50-16-8-4-2),(50-16-8-4+2),(50-16-8+4-2),(50-16-8+4+2),(50-16-8+4-2),(50-16-8+4+2),(50-16+8-4-2),(50-16+8-4+2),(50-16+8+4-2),(50-16+8+4+2),(50+16-8-4-2),(50+16-8-4+2),(50+16-8+4-2),(50+16-8+4+2),(50+16+8-4-2),(50+16+8-4+2),(50+16+8+4-2),(50+16+8+4+2)
I'm trying to use for loops and basic mathematics to get this sequence as an array but I'm still failing to get the accurate output, here is my code so far:
#!/bin/bash
N=$1
declare -a sequence=()
temp1=50
temp2=50
for i in $(eval echo "{1..$N}");do
for j in $(eval echo "{1..$N}");do
temp1=$((temp1+2**(5-j)))
temp2=$((temp2-2**(5-j)))
done
sequence+=($temp1)
sequence+=($temp2)
temp1=50
temp2=50
done
echo ${sequence[#]}
I don't know how to alternate between summation and subtraction, how can I approach this?
Ok so I am not really sure what it is that you are doing haha, but I wrote a script that generates the output you described..
N=${1}
sequence=()
math_sequence=()
if [ $N -eq 1 ]
then
math_sequence+=(50)
sequence+=(50)
else
for i in `seq 0 $(bc <<< "(2^(${N}-1)) - 1")`
do
X=50
Y=32
SIGNS=$(echo "obase=2;${i}" | bc | xargs printf "%0$((${N}-1))d\n" | sed 's/0/-/g; s/1/+/g')
MATH="$X"
VAL=$Y
for (( i=0; i<${#SIGNS}; i++ )); do
MATH+="${SIGNS:$i:1}"
VAL=$(bc <<< "$VAL / 2")
MATH+="${VAL}"
done
math_sequence+=( "(${MATH}), " )
sequence+=( $(bc <<< "${MATH}") )
done
fi
echo ${math_sequence[#]}
echo "----------------"
echo ${sequence[#]}
Some tricks I used here..
I saw that the +/- pattern kinda looked like binary counting: ----,---+,--+-,--++...+++-,++++ So I just made a binary counter and used the 0's and 1's as - and +.
bc <<< "${EQUATION}" is much more reliable than $(( ${EQUATION} )). At least I like it better. Works for larger numbers, uses ^ instead of ** for exponents. My fav
I generate two arrays for ya... math_sequence which contains the list of equations, and sequence which contains the actual values. I was not sure which one you actually wanted so I gave you both.
The script is pretty configurable. Just change X and Y in the for loop and you can tweak this thing to make all sorts of numbers.
bash thisScript.sh <N> Will generate the output you described:
N = 1; sequence = 50
N = 2; sequence = (50-16),(50+16)
N = 3; sequence = (50-16-8),(50-16+8),(50+16-8),(50+16+8)
N = 4; sequence = (50-16-8-4),(50-16-8+4),(50-16+8-4),(50-16+8+4),(50+16-8-4),(50+16-8+4),(50+16+8-4),(50+16+8+4)
N = 5; sequence = (50-16-8-4-2),(50-16-8-4+2),(50-16-8+4-2),(50-16-8+4+2),(50-16-8+4-2),(50-16-8+4+2),(50-16+8-4-2),(50-16+8-4+2),(50-16+8+4-2),(50-16+8+4+2),(50+16-8-4-2),(50+16-8-4+2),(50+16-8+4-2),(50+16-8+4+2),(50+16+8-4-2),(50+16+8-4+2),(50+16+8+4-2),(50+16+8+4+2)
This script seems to give inconsistent results. For example, when the if statement sees its first string that is greater, it works fine. But, sometimes, later strings that are larger get ignored completely:
ITEM[0]="XX"
ITEM[1]="XXXXXXX"
ITEM[2]="X"
ITEM[3]="XXXXXXXXXXXX"
ITEM[4]="XXXX"
SETPOINT=0
for i in "${!ITEM[#]}"; do
STRING="${ITEM[$i]}"
LENGTH=${#STRING}
echo "String length = $LENGTH"
if [ $LENGTH \> $SETPOINT ]; then
SETPOINT=$LENGTH
echo "Setpoint was updated to $SETPOINT"
fi
echo "Loop again"
done
echo "Final setpoint = $SETPOINT"
Here is the example output:
String length = 2
Setpoint was updated to 2
Loop again
String length = 7
Setpoint was updated to 7
Loop again
String length = 1
Loop again
String length = 12 <--- Why didn't it catch this one?????
Loop again
String length = 4
Loop again
Final setpoint = 7
Also, originally I had tried to do the variable expansion and string counting inside the if statement, so I didn't have to create "STRING" and "LENGTH", but I could not figure out the syntax to both expand the array variable and count the string at same time inside the if. So, if you have a thought on that too in order to shorten the code, that would be amazing!
Thanks!
Replace the \> with -gt.
man test explains that:
s1 > s2 True if string s1 comes after s2 based on the binary value of their characters.
n1 -gt n2 True if the integer n1 is algebraically greater than the integer n2.
I have an array which contains strings. Several of those strings can be the same and that is fine. They can be in any order to start of, but most likely they are in alphabetical order. I have the following shuffle function which will shuffle all the elements. However, I want to add a condition that no two of the same string can be adjacent in the array.
For example, this is fine: ook eek ook monkey ook but this is not: ook ook eek ook monkey as two ook are adjacent. It is assumed that the input has been checked so that any duplicates are less than half the total number of elements so a set of non-adjacent solutions exists. For example, ook ook ook eek would be rejected. The strings could contains spaces and UTF-8 characters but not new lines -- the strings are actually file name of images.
How can I modify the shuffle function to achieve this goal?
Or is there a better way to do that?
shuffle() {
# This function shuffles the elements of an array in-place using the
# Knuth-Fisher-Yates shuffle algorithm.
local i tmp size max rand
# $RANDOM % (i+1) is biased because of the limited range of $RANDOM
# Compensate by using a range which is a multiple of the array size.
size=${#array[*]}
max=$(( 32768 / size * size ))
for ((i=size-1; i>0; i--)); do
while (( (rand=$RANDOM) >= max )); do :; done
rand=$(( rand % (i+1) ))
tmp=${array[i]} array[i]=${array[rand]} array[rand]=$tmp
done
}
Given the rejection pre-condition, it is possible to split the word list into several «equivalence classes» (ECs) — special word groups in each of which words are the same by whatever criterion. Rejection implies that there is no more than one EC that is partly in one half of the list and partly in the other.
We lay a part of this EC aside, so that (1) the remaining part is contained in no more than one of the remaining halves of the list, and (2) the halves are strictly equal in size. Then we shuffle the halves, each one separately. After that, we merge them, the first half occupying odd elements, while evens are for the second half. Then we randomly insert the remaining elements previously laid-aside. It is quite simple, since all of them belong to one EC and thus it is easy to mark places where they can be and where they cannot.
By construction, there can be no two adjacent elements belonging to one EC.
[EDIT:] Finally, the implementation of what`s above.
shuffle() {
local sort="$(sort <<<"$1" | sed "s/^/+/g")"
local size="$(grep -c ^ <<<"$sort")"
local take cntr head tail
if [ "$sort" == "${sort%%$'\n'*}" ]; then
# single line: nothing to shuffle
echo "${sort#+}"
return
fi
if [ $[size & 1] == 1 ]; then
# enforcing equal halves, beginning to extract the middle
take="$(head -$[size / 2 + 1] <<<"$sort" | tail -1)"
fi
cntr="$take"
size=$[size / 2]
head="$(head -$size <<<"$sort")"
tail="$(tail -$size <<<"$sort")"
while [ "$(head -1 <<<"$tail")" == "$(tail -1 <<<"$head")" ]; do
# continue extracting the middle, if still left
if [ -n "$cntr" -a "$cntr" != "$(tail -1 <<<"$head")" ]; then
break
else
cntr="$(tail -1 <<<"$head")"
fi
((--size))
head="$(head -$size <<<"$head")"
tail="$(tail -$size <<<"$tail")"
take="${take:+$take$'\n'}$cntr"$'\n'"$cntr"
done
sort=()
for cntr in $(seq $size); do
# transforming two line sets into a single interlaced array
sort[$[cntr * 4 - 3]]="$(head -$cntr <<<"$head" | tail -1)"
sort[$[cntr * 4 - 1]]="$(head -$cntr <<<"$tail" | tail -1)"
done
for cntr in $(seq $[size - 1]); do
# shuffling each of the interlaced halves by Fisher-Yates
head="${sort[$[cntr * 4 - 3]]}"
tail=$[RANDOM % (size - cntr + 1) + cntr]
sort[$[cntr * 4 - 3]]="${sort[$[tail * 4 - 3]]}"
sort[$[tail * 4 - 3]]="$head"
head="${sort[$[cntr * 4 - 1]]}"
tail=$[RANDOM % (size - cntr + 1) + cntr]
sort[$[cntr * 4 - 1]]="${sort[$[tail * 4 - 1]]}"
sort[$[tail * 4 - 1]]="$head"
done
if [ -n "$take" ]; then
# got a remainder; inserting
tail=($(seq 0 $[size * 2]))
for cntr in $(seq $[size * 2]); do
# discarding potential places with remainder in proximity
if [ "${sort[$[cntr * 2 - 1]]}" \
== "${take%%$'\n'*}" ]; then
tail[$[cntr - 1]]=""
tail[$[cntr]]=""
fi
done
tail=(${tail[#]})
for cntr in $(seq 0 $[${#tail[#]} - 2]); do
# shuffling the remaining places, also by Fisher-Yates
head="${tail[$cntr]}"
size=$[RANDOM % (${#tail[#]} - cntr) + cntr]
tail[$cntr]="${tail[$size]}"
tail[$size]="$head"
done
size="$(grep -c ^ <<<"$take")"
while ((size--)); do
# finally inserting remainders
sort[$[${tail[$size]} * 2]]="${take%%$'\n'*}"
done
fi
head=0
size="${#sort[#]}"
while ((size)); do
# printing the overall result
if [ -n "${sort[$head]}" ]; then
echo "${sort[$head]#+}"
((size--))
fi
((head++))
done
}
# a very simple test from the original post
shuffle \
"ook
ook
eek
ook
monkey"