Remove multiple elements from array based on index - bash

I would like to remove multiple elements from an array based on their indexes.
array=("a" "b" "c" "d")
indexes=(1 3)
Output should be
array=("a" "c")
I know how to remove an element from an array knowing the index of the element:
If $i is the index:
array=("${(#)array[1,$i-1]}" "${(#)array[$i+1,$#array]}")
But what if I have multiple elements to remove? If I loop over the array of indexes, once I have removed one element the other indexes won't correspond anymore to the elements to be removed. So how is it possible to do that?

Using BASH arrays you can do this easily:
# original array
array=("a" "b" "c" "d")
# indexes array
indexes=(1 3)
# loop through indexes array and delete from element array
for i in "${indexes[#]}"; do
unset "array[$i]"
done
# check content of original array
declare -p array
declare -a array=([0]="a" [2]="c")
As per Chepner's comments below if OP wants an array of contiguous indices then loop through differential array and populate a new array
# result array
out=()
# loop through differential array and populate result
for i in "${array[#]}"; do
out+=("$i")
done
declare -p out
declare -a out=([0]="a" [1]="c")

Assuming indices is sorted, keep a counter of how many items you have already removed, and subtract that from each index in your loop.
count=0
for i in $indices; do
c=$((i - count))
array=("${(#)array[1,$c-1]}" "${(#)array[$c+1,$#array]}")
count=$((count + 1))
done
In bash, the same approach looks like
count=0
for i in "${indices[#]}"; do
c=$((i - count))
array=( "${array[#]:0:c-1}" "${array[#]:c+1}" )
count=$((count + 1))
done

Related

Looping over associative array groups

I have an associative array asc with its keys in the following form
asc[1-0]=dlc[0]
asc[2-1]=dlc[1]
asc[3-2]=dlc[2]
asc[1-3]=dlc[3]
asc[2-4]=dlc[4]
asc[3-5]=dlc[5]
asc[1-6]=dlc[6]
asc[2-7]=dlc[7]
asc[3-8]=dlc[8]
asc[1-9]=dlc[9]
asc[2-10]=dlc[10]
asc[3-11]=dlc[11]
asc[1-12]=dlc[12]
asc[2-13]=dlc[13]
...
I would like to group the elements by the first number when I call a function fn.
loop over i
fn asc[i-*] $ pass all elements with i as first number
You'll have to iterate over the keys of the associative array and build up an ordinary array of the values associated with the desired subset of keys.
for i in 1 2 3; do
group=()
for k in "${!asc[#]}"; do
[[ $k = $i-* ]] || continue
group+=("${asc[$k]}")
done
fn "${group[#]}"
done

How do I get the unique values of a list in bash preserving order and keep the last value for each unique?

I have a list in bash that can have repeated values in it. I would like to remove duplicates and get a list with only the unique values in it. Order must be preserved and the last occurrence of the unique values is the one I wish to keep.
For example, if I have this list:
A=( D B A C D )
I'm looking for this:
result=( B A C D )
I've seen solutions for this when the data is a list in a file, but I'd prefer to keep the list in-memory without jumping through any hoops.
I think I can use an associative array and loop through the list adding the entries as keys in the array and then just dump the keys into the unique list but I'm not an expert with associative arrays across platforms -- do they sort themselves on key value sort of like a lot of C++ STL containers do or do they preserve the order of insertion regardless of key values?
I'd like to avoid a reliance on associative arrays though, because not all systems I may need to run on have bash 4.x or higher... some will be bash 3.x...
Any help would be great.
Without Associative Arrays
You can do it with indexed arrays by using an intermediate indexed array to hold unique values from A. This requires a nested loop over values stored in c[] for each element of A, e.g.
#!/bin/bash
declare -a result # declare result indexed array
declare -a c # declare temp intermediate indexed array
A=( D B A C D ) # original with duplicates
## loop decending over A, reset found flag, loop over c, if present continue,
# otherwise store A at index in c
for ((i = $((${#A[#]}-1)); i >= 0; i--)); do
found=0;
for j in ${c[#]}; do
[ "$j" = "${A[i]}" ] && { found=1; break; }
done
[ "$found" -eq '1' ] && continue
c[i]=${A[i]}
done
## loop over c testing if index for A exists, add from c to result
for ((i = 0; i < ${#A[#]}; i++)); do
[ "${c[i]}" ] && result+=(${c[i]})
done
declare -p result # output result
Example Use/Output
$ bash lastuniqindexed.sh
declare -a result='([0]="B" [1]="A" [2]="C" [3]="D")'
Using Associative Arrays with BASH_VERSION Test
You can do it with a combination of indexed and associative arrays making only a single pass though each array. You use an associative array B keyed with the value of A using B as a frequency array indicating whether an element of A has been seen. You then store the element of A in a temporary indexed array c[] so that the unique values can be added to result preserving the original order.
You can address whether associative array functionality is present with a bash version test at the beginning, e.g.
#!/bin/bash
case $BASH_VERSION in
## empty or beginning with 1, 2, 3
''|[123].*) echo "ERROR: Bash 4.0 needed" >&2
exit 1;;
esac
declare -A B # declare associative array
declare -a result # declare indexed array
A=( D B A C D ) # original with duplicates
## loop decending over A, if B[A] doesn't exist, set B[A]=1, store in c[]
for ((i = $((${#A[#]}-1)); i >= 0; i--)); do
[ -n "${B[${A[i]}]}" ] || { B[${A[i]}]=1; c[i]=${A[i]};}
done
## loop over c testing if index for A exists, add from c to result
for ((i = 0; i < ${#A[#]}; i++)); do
[ "${c[i]}" ] && result+=(${c[i]})
done
declare -p result # output result
Without the use of associative arrays, the nested loops looping over the original checking against each entry in c[] will be much less efficient as the size of the array grows.
Example Use/Output
$ bash lastuniq.sh
declare -a result='([0]="B" [1]="A" [2]="C" [3]="D")'
Look things over and let me know if you have further questions.

How to add 5 to every number of an array with Bash?

I am trying to create an array of user inputs and then add to each element in the array:
read number
for i in 1 2 3
read array[$i]
done
let position=0
for i in "${array[#]}"
do
let array[position]+=($i+$number)
let "position++"
done
for (( i=0; $i<3; i=$1+1 ))
do
echo ${array[$1]}
So, the user will enter "5" for number and then three more numbers for the array(90, 80, 70). The results should be array(95, 85, 75), but the output I'm getting is array(95, 175, 155).
A saner way to write this would be:
read -r number
read -r -a array
for idx in "${!array[#]}"; do
(( array[$idx] += number ))
done
printf '%s\n' "${array[#]}"
Instead of assuming that the indexes start at 0 (which they didn't, originally, because you were explicitly assigning to positions 1, 2 and 3), using "${!array[#]}" finds the actual indexes, and thus works correctly even with sparse arrays or ones not indexed starting at position 0.
Instead of duplicating $i (aka the values from your array) on the right-hand side of +=, it only adds the number itself.
Instead of iterating over indexes (again) to print the values, it just asks the array to dump all its values in index order with "${array[#]}".
See this in operation at https://ideone.com/WTLJSu
There is a behavioral difference insofar as it expects all the array values to be passed on a single line of input. If you don't want that, see the version at https://ideone.com/3OQtt3 instead.

'for' loop with dynamic array size

I have an array that gets elements added to it when it calls the function findVar. The problem seems to be on the for loop that does not update the number of elements once started running.
When I do echo at the end of the for loop I get the correct number of elements and the last element but it seems not to be checking on the for conditions once started.
for var in "${tempV[#]}"
do
num_words=${#tempV[#]}
let i=i+1
if ! [ $i -gt $num_words ]
then
findVar $objBKP $var
fi
done
Your attempt is looping over every original element in the array and then ensuring that you haven't looped more times than that and calling your function.
That doesn't work because the original expansion of tempV happens once and so the added entries are never seen. But that also doesn't make sense since, by definition, if you are looping over the elements of the array you can't loop more times then there are elements in the array.
What you want to be doing (assuming a non-sparse, integer-indexed array that is only appended to) is looping numerically and checking that you haven't exceeded the array size as the loop condition.
Something like this (untested):
i=0
while [ "$i" -lt "${#tempV[#]}" ]; do
var=${tempV[i]}
findVar "$objBKP" "$var"
i=$((i + 1))
done
You're not using $i for anything other than an iteration counter. It's completely unnecessary in your posted example. Instead, just iterate over the contents of the variable expansion. For example:
for var in "${tempV[#]}"; do
findVar "$objBKP" "$var"
done

Iterate through a PlatonScript array

I am developing on the top of the jPlaton platform and I want to declare a 10-element array of integers in PlatonScript.
Then, I want to assign a number to each array position, lets say numbers 1 to 10.
Finally, I want to iterate through this array and calculate and print the double of each array element.
Array
|1|2|3|4|5|6|7|8|9|10|
Output
|2|4|6|8|10|12|14|16|18|20|
How do I do those "for" loops?
Thanks
#ind:INTEGER
#arr:INTEGER[]
#ind=1
LOOP
setIndex arr #ind
IF (#ind>10)
BREAK
#arr = #ind
#ind=#ind+1
ENDLOOP
#dint:INTEGER
#dint=0
#ind=1
LOOP
setIndex arr #ind
IF (#ind>10)
BREAK
#dint=2*#arr
#ind=#ind+1
HTML
<p>#dint</p>
ENDHTML
ENDLOOP
Useful array methods
setIndex ObjectName IndexValue
Used in variable of array type
Set the current index of the variable
If index set to -1 then method returns the element count
of the array If index set to 0 then current index set to the last plus one (next empty). If index set to a number smaller or equal to the element count the currennt index is set to the specified number First position index is 1, next is 2 and so on. After we set the current array index we are able to access the current array element using the name of the variable as it was a simple (elementary) one
clear ObjectName
Clear the value of the variable. For arrays all the elements of the array are cleared and the array size is set to 0.
sort ObjectName
Sort the elements of an array type variable

Resources