Generate lowest possible number from array in bash - arrays

Let's say I have an array with some numbers, ordered from the lowest to the highest numeral:
root#blubb:~# min=1
root#blubb:~# echo $array
1 2 3 6 16 26 27
I want a bash script to always try the minimum number defined first (in this case min=1) and if that is not possible add 1 and try it again, until it finally works (in this case it would take 4)
I tried a lot with shuf and while-/for loops but could not get it to run properly.

min=1
array="1 2 3 6 16 26 27"
found=0
res=$min
for elem in $array; do
if [[ $elem = $res ]]; then
found=1
continue
fi
if [[ $found = 1 ]]; then
((res+=1))
if ((elem>res)); then
break
fi
fi
done
echo $res
Or with a function
function min_array {
local min array res found
min=$1
shift
array=$*
found=0
res=$min
for elem in $array; do
if [[ $elem = $res ]]; then
found=1
continue
fi
if [[ $found = 1 ]]; then
((res+=1))
if ((elem>res)); then
break
fi
fi
done
echo $res
}
$ min_array 1 1 2 3 6 16 26
4
$ min_array 6 1 2 3 6 16 26
7
$ min_array 8 1 2 3 6 16 26
8
EDIT one case was missing, another version
function min_array {
local min array res found
min=$1
shift
array=$*
found=0
res=$min
for elem in $array; do
if [[ $elem = $res ]]; then
found=1
((res+=1))
continue
fi
if [[ $found = 1 ]]; then
if ((elem>res)); then
break
else
((res+=1))
fi
fi
done
echo $res
}

Try this.
array=(7 5 3 1)
min()
{
local min=$1; shift
local n
for n in "$#"; do
if ((n<min)); then
min=$n
fi
done
echo "$min"
}
min "${array[#]}"

Related

How can I get what element is a value in an array from whether it matches a value?

I have a variable value which is 25. I want to search for that variable in an array with several elements and get the position in my array that matches the value of my variable. In this example, I should get that what I am looking for is position 3 as the 25 matches my variable. When it finds the value I want to store it in a variable so my result variable should be 3.
value=25
array=( 11 13 17 25 36 )
result=0
My idea is to go through the array with a for loop and if it finds the value it stores it in another variable. This is what I tried:
for i in ${array[#]}; do
if [[ $i -eq $value ]]; then
result=$i
fi
done
But this returns 25 and not its position, how can I return its position in the array?
You can do something like this:
Assuming your array has only positive integers, declared result=-1 so that it prints -1 if no match is found.
Use a separate variable like index.
And increment its value by 1 as the loop progresses forward.
value=25
array=( 11 13 17 25 36 )
result=-1
index=0
for i in ${array[#]}; do
if [[ $i -eq $value ]]; then
result=$index
fi
index=$((index+1))
done
echo $result
You can iterate through the indexes of an array :
value=25
array=( 11 13 17 25 36 )
result=-1
for i in "${!array[#]}"; do
if [[ "${array[i]}" -eq "$value" ]]; then
result=$i
fi
done
echo $result
$ value=25
$ array=( 11 13 17 25 36 )
$ result=$(sed -En "s/.*\[([0-9]*)\]=\"$value\".*/\1/p" <(declare -p array))
$ echo $result
3
If you'll be doing that kind of lookup more than once, and assuming all of your values are unique (e.g. 25 doesn't appear multiple times), consider creating and using an associative array that contains the reverse mapping of values to indices so you only need to loop once to create that array and then every time you need to find the index of a value, it's just a fast hash lookup instead of a slow loop:
$ cat tst.sh
#!/usr/bin/env bash
value=$1
array=( 11 13 17 25 36 )
result=0
for i in "${!array[#]}"; do
rev_array["${array[$i]}"]="$i"
done
[[ -v rev_array["$value"] ]] && result=${rev_array["$value"]}
echo "$result"
$ ./tst.sh 25
3
$ ./tst.sh foo
0
It sounds like you might be doing something that would be better written in awk anyway though.

Bash find unique values in array1 not in array2 (and vice-versa)

In bash, I know to be able to find the unique values between two arrays can be found by:
echo "${array1[#]} ${array2[#]}" | tr ' ' '\n' | sort | uniq -u
However, this gives the unique values between BOTH arrays. What if I wanted something about the elements that are unique only to array1 and elements that are unique only to array2? For example:
array1=(1 2 3 4 5)
array2=(2 3 4 5 6)
original_command_output = 1 6
new_command_output1 = 1
new_command_output2 = 6
You could use the comm command.
To get elements unique to the first array:
comm -23 \
<(printf '%s\n' "${array1[#]}" | sort) \
<(printf '%s\n' "${array2[#]}" | sort)
and elements unique to the second array:
comm -13 \
<(printf '%s\n' "${array1[#]}" | sort) \
<(printf '%s\n' "${array2[#]}" | sort)
Or, more robust, allowing for any character including newlines to be part of the elements, split on the null byte:
comm -z -23 \
<(printf '%s\0' "${array1[#]}" | sort -z) \
<(printf '%s\0' "${array2[#]}" | sort -z)
comm is probably the way to go but if you're running bash >= 4 then you can do it with associative arrays:
#!/bin/bash
declare -a array1=(1 2 3 4 5) array2=(2 3 4 5 6)
declare -A uniq1=() uniq2=()
for e in "${array1[#]}"; do uniq1[$e]=; done
for e in "${array2[#]}"; do
if [[ ${uniq1[$e]-1} ]]
then
uniq2[$e]=
else
unset "uniq1[$e]"
fi
done
echo "new_command_output1 = ${!uniq1[*]}"
echo "new_command_output2 = ${!uniq2[*]}"
new_command_output1 = 1
new_command_output2 = 6
BASH builtins can handle this cleaner and quicker. This will read both arrays for each element, comparing if they exist in either. If no match is found, output unique elements
arr1=(1 2 3 4 5)
arr2=(1 3 2 4)
for i in "${arr1[#]}" "${arr2[#]}" ; do
[[ ${arr2[#]} =~ $i ]] || echo $i
[[ ${arr1[#]} =~ $i ]] || echo $i
done
output: 5
If one of yours arrays have multiple character elements, e.g. 152, then you must convert the arrays, adding a literal character before and after. This way regex can identify an exact match
arr1=(1 2 3 4 5)
arr2=(1 3 2 4 152)
for i in "${arr1[#]}" ; do
var1+="^$i$"
done
for i in "${arr2[#]}" ; do
var2+="^$i$"
done
for i in "${arr1[#]}" "${arr2[#]}" ; do
[[ $var1 =~ "^$i$" ]] || echo $i
[[ $var2 =~ "^$i$" ]] || echo $i
done
output: 5 152

Print an element from a sorted array

I wrote a program to sort an array and print the element where the unsorted array and sorted array matches. In theory, this should work, but it isn't. All the elements in the sorted array for some reason combine it all and output an array element of 1.
#!/bin/bash
arr=(6 2 15 90 9 1 4 30 1 3)
function sort(){
local array=($#) max=$(($# - 1))
while ((max > 0))
do
local i=0
while ((i < max)); do
if [ ${array[$i]} \> ${array[$((i + 1))]} ]
then
local t=${array[$i]}
array[$i]=${array[$((i + 1))]}
array[$((i + 1))]=$t
fi
((i++))
done
((max--))
done
echo ${array[#]}
}
arr_sort=($(sort ${arr[#]}))
for ((j=0; j<(( ${#arr[#]} -1 )); j++)); do
for ((k=0; k<(( ${#arr[#]} -1 )); k++)); do
if (( ${arr[j]:-0} == ${arr_sort[k]:-0} )); then
echo ${arr[j]}
break
fi
done
Try this:
#!/bin/bash
arr=(6 2 15 90 9 1 4 30 1 3)
arr_sort=( $(echo ${arr[#]} | tr ' ' '\n' | sort -n) )
for ((j=0; j<${#arr[#]}; j++)); do
if (( ${arr[i]} == ${arr_sort[i]} )); then
echo "Match ${arr[j]} at position $j (starting from 0)"
fi
done
Since there are no matches between the unsorted array
6 2 15 90 9 1 4 30 1 3
and the sorted one
1 1 2 3 4 6 9 15 30 90
in the example you gave, you will have no output.

Bash Array Manipulation

I doing a project for a Unix/Linux class and have a few Arrays in another file, like this
All the arrays should store is a 1 or 0
T1=( 1 1 1 1 1 )
T2=( 1 1 1 1 1 )
T3=( 1 1 1 1 1 )
T4=( 1 1 1 1 1 )
T5=( 1 1 1 1 1 )
However i'm having difficulty editing the arrays to 0 and making changes stick
#!/bin/bash
i=0
for line in `cat JeopardyOut`
do
let i=i+1
#Creating one big Array with all the values from the arrays file
Array[i]=$line
done
cat JeopardyOut
#Parsing the giant Array into the main 5 arrays I'm using
createArray()
{
y=0
for y in 0 1 2 3 4
do
for i in 2 3 4 5 6
do
#echo "${Array[i]}"
T1[$y]=${Array[i]}
done
for i in 9 10 11 12 13
do
#echo "${Array[i]}"
T2[$y]=${Array[i]}
done
for i in 16 17 18 19 20
do
#echo "${Array[i]}"
T3[$y]=${Array[i]}
done
for i in 23 24 25 26 27
do
#echo "${Array[i]}"
T4[$y]=${Array[i]}
done
for i in 30 31 32 33 34
do
#echo "${Array[i]}"
T5[$y]=${Array[i]}
done
done
}
createArray
ArrayNum=$1
Index=$2
There's likely way better ways to do this, However this is what ended up working for me.
#Changing the necessary indexes, this will be used by a completely
#different script
ChangeArray()
{
if [[ $ArrayNum == "1" ]]; then
T1[ $Index ]=0
elif [[ $ArrayNum == "2" ]]; then
T2[ $Index ]=0
elif [[ $ArrayNum == "3" ]]; then
T3[ $Index ]=0
elif [[ $ArrayNum == "4" ]]; then
T4[ $Index ]=0
elif [[ $ArrayNum == "5" ]]; then
T5[ $Index ]=0
else
echo "Invalid Parameters"
fi
}
if [[ $ArrayNum -ne "" || $Index -ne "" ]]; then
if [[ $ArrayNum == "5" && $Index == "5"]]; then
reset
else
ChangeArray
fi
fi
# And the part that's likely at fault for my issue but don't know how I
# should fix it
echo "T1=( ${T1[*]} )" > JeopardyOut
echo "T2=( ${T2[*]} )" >> JeopardyOut
echo "T3=( ${T3[*]} )" >> JeopardyOut
echo "T4=( ${T4[*]} )" >> JeopardyOut
echo "T5=( ${T5[*]} )" >> JeopardyOut
cat JeopardyOut
Something is wrong with the way I am trying to edit the Arrays...
While I can get any index of any of the arrays to 0, I do not know why the 1s I change to 0 turn back into 1 when I rerun the script.
PS. This is a basis class for Linux programming in Sierra, I don't really understand a lot of the bash script other than what I've learned through trial and error.
Maybe this example can help you
#!/bin/bash
while read -r line; do
#Creating one big Array with all the values from the arrays file
Array+=(`grep -oP '\(\K(.+?)(?=\))' <<< "$line"`)
done < JeopardyOut
echo "Giant Array value: ${Array[#]}"
# Or you can clone the arrays in the file
i=0
while read -r line; do
((i++))
eval "T$i=(`grep -oP '\(\K(.+?)(?=\))' <<< "$line"`)"
done < JeopardyOut
echo "This is the contents of array T1: ${T1[#]}"
echo "This is the contents of array T2: ${T2[#]}"
echo "This is the contents of array T3: ${T3[#]}"
echo "This is the contents of array T4: ${T4[#]}"
echo "This is the contents of array T5: ${T5[#]}"
# This can help you to make a new file
# I change some value to our arrays
T1[2]=0
T2[1]=true
# Now let's go to make a new arrays file
echo "T1=(${T1[#]})" > JeopardyOut2
echo "T2=(${T2[#]})" >> JeopardyOut2
echo "T3=(${T3[#]})" >> JeopardyOut2
echo "T4=(${T4[#]})" >> JeopardyOut2
echo "T5=(${T5[#]})" >> JeopardyOut2
echo "This is the content of JeopardyOut2:"
cat JeopardyOut2
Output
$ bash example
Giant Array value: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
This is the contents of array T1: 1 1 1 1 1
This is the contents of array T2: 1 1 1 1 1
This is the contents of array T3: 1 1 1 1 1
This is the contents of array T4: 1 1 1 1 1
This is the contents of array T5: 1 1 1 1 1
This is the content of JeopardyOut2:
T1=(1 1 0 1 1)
T2=(1 true 1 1 1)
T3=(1 1 1 1 1)
T4=(1 1 1 1 1)
T5=(1 1 1 1 1)
$

How to add a value into the middle of an array?

Hello I have the following array:
array=(1 2 3 4 5 7 8 9 12 13)
I execute this for loop:
for w in ${!array[#]}
do
comp=$(echo "${array[w+1]} - ${array[w]} " | bc)
if [ $comp = 1 ]; then
/*???*/
else
/*???*/
fi
done
What I would like to do is to insert a value when the difference between two consecutive elements is not = 1
How can I do it?
Many thanks.
Just create a loop from the minimum to the maximum values and fill the gaps:
array=(1 2 3 4 5 7 8 9 12 13)
min=${array[0]}
max=${array[-1]}
new_array=()
for ((i=min; i<=max; i++)); do
if [[ " ${array[#]} " =~ " $i " ]]; then
new_array+=($i)
else
new_array+=(0)
fi
done
echo "${new_array[#]}"
This creates a new array $new_array with the values:
1 2 3 4 5 0 7 8 9 0 0 12 13
This uses the trick in Check if an array contains a value.
You can select parts of the original array with ${arr[#]:index:count}.
Select the start, insert a new element, add the end.
To insert an element after index i=5 (the fifth element)
$ array=(1 2 3 4 5 7 8 9 12 13)
$ i=5
$ arr=("${array[#]:0:i}") ### take the start of the array.
$ arr+=( 0 ) ### add a new value ( may use $((i+1)) )
$ arr+=("${array[#]:i}") ### copy the tail of the array.
$ array=("${arr[#]}") ### transfer the corrected array.
$ printf '<%s>' "${array[#]}"; echo
<1><2><3><4><5><6><7><8><9><12><13>
To process all the elements, just do a loop:
#!/bin/bash
array=(1 2 3 4 5 7 8 9 12 13)
for (( i=1;i<${#array[#]};i++)); do
if (( array[i] != i+1 ));
then arr=("${array[#]:0:i}") ### take the start of the array.
arr+=( "0" ) ### add a new value
arr+=("${array[#]:i}") ### copy the tail of the array.
# echo "head $i ${array[#]:0:i}" ### see the array.
# echo "tail $i ${array[#]:i}"
array=("${arr[#]}") ### transfer the corrected array.
fi
done
printf '<%s>' "${array[#]}"; echo
$ chmod u+x ./script.sh
$ ./script.sh
<1><2><3><4><5><0><7><8><9><10><0><0><13>
There does not seem to be a way to insert directly into an array. You can append elements to another array instead though:
result=()
for w in ${!array[#]}; do
result+=("${array[w]}")
comp=$(echo "${array[w+1]} - ${array[w]} " | bc)
if [ $comp = 1 ]; then
/* probably leave empty */
else
/* handle missing digits */
fi
done
As a final step, you can assign result back to the original array.

Resources