2

I have an associative array that acts like it's usual double array.

Structure is similar to this: [ [0,1], [0,1,2] ]. Code:

declare -A array
array[0,0]=0
array[0,1]=1
array[1,0]=0
array[1,1]=1
array[1,2]=2

How do I get lengths of array[0] and array[1]? In this example: 2 and 3.

Thank you.

P.S. I tried to search for duplicates. No success. And if it's not clear: I don't know the length of array.


Answer was chosen after efficiency testing. Here is example of function based on @RenaudPacalet's answer:

function getLength() {
    local k=$(eval "echo \${!$1[@]}")
    local re="(\<$2,[0-9])"
    echo $k | grep -Eo $re | wc -l
}

Usage example: getLength array 1 returns 3 in this question's case.

Keep in mind that using $(eval "echo \${!$1[@]}") is much slower than ${!array[@]}.

MOPO3OB
  • 383
  • 3
  • 16
  • "that acts like it's usual double array" No, it doesn't, It *always* acts as an associative array. – Ignacio Vazquez-Abrams Aug 08 '17 at 12:13
  • 1
    echo ${#array[@]} – py9 Aug 08 '17 at 12:16
  • @Ignacio Vazquez-Abrams, I mean that it reminds of double array. Not acts, if You will :) – MOPO3OB Aug 08 '17 at 12:25
  • @py9, how does this help? – MOPO3OB Aug 08 '17 at 12:27
  • 2
    bash has no support for multi-dimensional arrays ( array[0,0] is just a list element with index "0,0"). See https://stackoverflow.com/questions/12317483/array-of-arrays-in-bash – Wayne Vosberg Aug 08 '17 at 12:32
  • @WayneVosberg I know, of course! I wonder, what is the best way to get values of lengths, as if it was multidimensional array ;) – MOPO3OB Aug 08 '17 at 12:35
  • If you need to do this kind of operation efficiently and your keys are restricted to values allowed in variable names, have you considered using multiple 1D arrays? `array0=( 0 1 ); array1=( 0 1 2 )` -- you can iterate over variables with a given name prefix, and bash 4.3's namevars give you a reliable mechanism for address and modification that doesn't require `eval`. – Charles Duffy Aug 23 '17 at 18:16
  • @CharlesDuffy To be honest, I don't need that much speed in this. Efficiency tests were just for choosing correct answer. Although, they both are good. I was thinking about implementing Your suggestion. But wanted to struggle with this solution. Anyway, it's working good now. Thank You. – MOPO3OB Aug 23 '17 at 18:34

2 Answers2

3

You'll have to iterate over the array keys and count the ones you care about: there's no syntax for something like ${array[0,*])

n0=0
n1=0
for key in "${!array[@]}"; do 
    [[ $key == 0,* ]] && ((n0++))
    [[ $key == 1,* ]] && ((n1++))
done
echo $n0
echo $n1

Or, use an array to keep count of all the "first level" indices

n=()
for key in "${!array[@]}"; do (( n[${key%%,*}]++ )); done
# then, print out the counts
for ind in "${!n[@]}"; do printf "%s\t%s\n" $ind "${n[$ind]}"; done
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
0
k=${!array[@]}
n=0
re="(\<$n,[0-9]+)"
echo $k | grep -Eo $re | wc -l
  1. get the keys of your array,
  2. set the row index,
  3. create a regular expression for the matching keys,
  4. filter the keys using the regular expression and count the number of matches.

And repeat with other row indexes, if needed. The regular expression is a bit tricky. \< is a beginning of word (to avoid that 10,10 matches 0,). $n,[0-9]+ is the current row index, followed by a comma and one or more digits. The enclosing parentheses delimit a sub-expression.

The -Eo options of grep put it in extended regular expression mode and separate the matching strings with new lines, such that wc -l can count them.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • it only works without * in RegEx. grep's version 2.5.1. – MOPO3OB Aug 23 '17 at 17:11
  • 1
    This implementation is really unfortunate. Consider if you have a literal asterisk as a key -- the `echo *` will emit a list of filenames. And `grep -o` is a GNUism, not guaranteed by [the POSIX spec for `grep`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html). – Charles Duffy Aug 23 '17 at 17:45
  • @CharlesDuffy for case of example in the question, this is just okay. But I got what You mean. Appreciate both answers. That's why I updated question with specifying criterion of choosing answer. – MOPO3OB Aug 23 '17 at 18:12
  • @MOPO3OB, the efficiency varies on the test scenario -- this will be faster on large arrays, the other will be faster on small ones (because starting the pipeline, the external `grep` command, etc. has a very nontrivial constant cost). If you were showing this answer being higher-performance with small array sizes, I'd very much like to review your test procedure. – Charles Duffy Aug 23 '17 at 18:13
  • @CharlesDuffy agreed. Efficiency for small data is almost the same for both answers. Could feel the difference in arrays with more than 10k elements. – MOPO3OB Aug 23 '17 at 18:19
  • @RenaudPacalet can You explain why asterisk in regular expression doesn't work for me. Please, take a look at function in updated question. – MOPO3OB Aug 24 '17 at 09:06
  • @MOPO3OB I see. I do not know why it does not work with your version of grep but as it is not needed at all, I removed it. Beware, I think there is a typo in your updated question: `we -l` instead of `wc -l`. – Renaud Pacalet Aug 24 '17 at 09:12
  • @RenaudPacalet thank You, there was a typo. Your answer is all good now. – MOPO3OB Aug 24 '17 at 09:21