How to make 3D array in BASH? - arrays

I want to write/make/use a 3D array of [m][n][k] in BASH. From what I understand, BASH does not support array that are not 1D.
Any ideas how to do it?

Fake multi-dimensionality with a crafted associative array key:
declare -A ary
for i in 1 2 3; do
for j in 4 5 6; do
for k in 7 8 9; do
ary["$i,$j,$k"]=$((i*j*k))
done
done
done
for key in "${!ary[#]}"; do printf "%s\t%d\n" "$key" "${ary[$key]}"; done | sort
1,4,7 28
1,4,8 32
1,4,9 36
1,5,7 35
1,5,8 40
1,5,9 45
1,6,7 42
1,6,8 48
1,6,9 54
2,4,7 56
2,4,8 64
2,4,9 72
2,5,7 70
2,5,8 80
2,5,9 90
2,6,7 84
2,6,8 96
2,6,9 108
3,4,7 84
3,4,8 96
3,4,9 108
3,5,7 105
3,5,8 120
3,5,9 135
3,6,7 126
3,6,8 144
3,6,9 162
I used sort because keys of an assoc.array have no inherent order.

You can use associative arrays if your bash is recent enough:
unset assoc
declare -A assoc
assoc["1.2.3"]=x
But, I'd rather switch to a language that supports multidimensional arrays (e.g. Perl).

As in C, you can simulate multidimensional array using an offset.
#! /bin/bash
xmax=100
ymax=150
zmax=80
xymax=$((xmax*ymax))
vol=()
for ((z=0; z<zmax; z++)); do
for ((y=0; y<ymax; y++)); do
for ((x=0; x<xmax; x++)); do
((t = z*xymax+y*xmax+x))
if ((vol[t] == 0)); then
((vol[t] = vol[t-xymax] + vol[t-ymax] + vol[t-1]))
fi
done
done
done

Related

how can we find the complexity of merge sort with an array of size 16

I have an array of size 16 and have to find its theta and big Oh
general case is nlogn but what will it be for specific case.
73 3 69 88 36 56 44 63 14 60 80 84 6 80 55 62
Array size or its composition/pattern don't affect the merge sort technique. So it is going to be same for 16-elements array as well. Mergesort will anyway at first divide the array, then compare and merge.

Array manipulation in Perl

The Scenario is as follows:
I have a dynamically changing text file which I'm passing to a variable to capture a pattern that occurs throughout the file. It looks something like this:
my #array1;
my $file = `cat <file_name>.txt`;
if (#array1 = ( $file =~ m/<pattern_match>/g) ) {
print "#array1\n";
}
The array looks something like this:
10:38:49 788 56 51 56 61 56 59 56 51 56 80 56 83 56 50 45 42 45 50 45 50 45 43 45 54 10:38:51 788 56 51 56 61 56 59 56 51 56 80 56 83 56 50 45 42 45 50 45 50 45 43 45 54
From the above array1 output, the pattern of the array is something like this:
T1 P1 t1(1) t1(2)...t1(25) T2 P2 t2(1) t2(2)...t2(25) so on and so forth
Currently, /g in the regex returns a set of values that occur only twice (only because the txt file contains this pattern that number of times). This particular pattern occurrence will change depending on the file name that I plan to pass dynamically.
What I intend to acheive:
The final result should be a csv file that contains these values in the following format:
T1,P1,t1(1),t1(2),...,t1(25)
T2,P2,t2(1),t2(2),...,t2(25)
so on and so forth
For instance: My final CSV file should look like this:
10:38:49,788,56,51,56,61,56,59,56,51,56,80,56,83,56,50,45,42,45,50,45,50,45,43,45,54
10:38:51,788,56,51,56,61,56,59,56,51,56,80,56,83,56,50,45,42,45,50,45,50,45,43,45,54
The delimiter for this pattern is T1 which is time in the format \d\d:\d\d:\d\d
Example: 10:38:49, 10:38:51 etc
What I have tried so far:
use Data::Dumper;
use List::MoreUtils qw(part);
my $partitions = 2;
my $i = 0;
print Dumper part {$partitions * $i++ / #array1} #array1;
In this particular case, my $partitions = 2; holds good since the pattern occurrence in the txt file is only twice, and hence, I'm splitting the array into two. However, as mentioned earlier, the pattern occurrence number keeps changing according to the txt file I use.
The Question:
How can I make this code more generic to achieve my final goal of splitting the array into multiple equal sized arrays without losing the contents of the original array, and then converting these mini-arrays into one single CSV file?
If there is any other workaround for this other than array manipulation, please do let me know.
Thanks in advance.
PS: I considered Hash of Hashes and Array of Hashes, but that kind of a data structure did not seem to be healthy solution for the problem I'm facing right now.
As far as I can tell, all you need is splice, which will work fine as long as you know the record size and it's constant
The data you showed has 52 fields, but the description of it requires 27 fields per record. It looks like each line has T, P, and t1 .. t24, rather than ending at t25
Here's how it looks if I split the data into 26-element chunks
use strict;
use warnings 'all';
my #data = qw/
10:38:49 788 56 51 56 61 56 59 56 51 56 80 56 83 56 50 45 42 45 50 45 50 45 43 45 54 10:38:51 788 56 51 56 61 56 59 56 51 56 80 56 83 56 50 45 42 45 50 45 50 45 43 45 54
/;
while ( #data ) {
my #set = splice #data, 0, 26;
print join(',', #set), "\n";
}
output
10:38:49,788,56,51,56,61,56,59,56,51,56,80,56,83,56,50,45,42,45,50,45,50,45,43,45,54
10:38:51,788,56,51,56,61,56,59,56,51,56,80,56,83,56,50,45,42,45,50,45,50,45,43,45,54
If you wanted to use List::MoreUtils instead of splice, the the natatime function returns an iterator that will do the same thing as the splice above
Like this
use List::MoreUtils qw/ natatime /;
my $iter = natatime 26, #data;
while ( my #set = $iter->() ) {
print join(',', #set), "\n";
}
The output is identical to that of the program above
Note
It is very wrong to start a new shell process just to use cat to read a file. The standard method is to undefine the input record separator $/ like this
my $file = do {
open my $fh, '<', '<file_name>.txt' or die "Unable to open file for input: $!";
local $/;
<$fh>;
};
Or if you prefer you could use File::Slurper like this
use File::Slurper qw/ read_binary /;
my $file = read_binary '<file_name>.txt';
although you will probably have to install it as it is not a core module

comparing multiple column files using python3

input_file1:
a 1 33
a 34 67
a 68 78
b 1 99
b 100 140
c 1 70
c 71 100
c 101 190
input file2:
a 5 23
a 30 72
a 76 78
b 5 30
c 23 88
c 92 98
I want to compare these two files such that for every value of 'a' in file2 the two integers (boundary) fall in the range (boundaries) of 'a' in file1 or between two ranges.
Instead of storing values like this 'a 1 33', you can make one structure (like 'a:1:33') for your data while writing into file. So that it will become easy to read data also.
Then, you can read each line and can split it based on ':' separator and you can compare with another file easily.

How can I create an array of ratios inside a for loop in MATLAB?

I would like to create an array or vector of musical notes using a for loop. Every musical note, A, A#, B, C...etc is a 2^(1/12) ratio of the previous/next. E.G the note A is 440Hz, and A# is 440 * 2^(1/12) Hz = 446.16Hz.
Starting from 27.5Hz (A0), I want a loop that iterates 88 times to create an array of each notes frequency up to 4186Hz, so that will look like
f= [27.5 29.14 30.87 ... 4186.01]
So far, I've understood this much:
f = [];
for i=1:87,
%what goes here
% f = [27.5 * 2^(i/12)]; ?
end
return;
There is no need to do a loop for this in matlab, you can simply do:
f = 27.5 * 2.^((0:87)/12)
The answer:
f =
Columns 1 through 13
27.5 29.135 30.868 32.703 34.648 36.708 38.891 41.203 43.654 46.249 48.999 51.913 55
Columns 14 through 26
58.27 61.735 65.406 69.296 73.416 77.782 82.407 87.307 92.499 97.999 103.83 110 116.54
Columns 27 through 39
123.47 130.81 138.59 146.83 155.56 164.81 174.61 185 196 207.65 220 233.08 246.94
Columns 40 through 52
261.63 277.18 293.66 311.13 329.63 349.23 369.99 392 415.3 440 466.16 493.88 523.25
Columns 53 through 65
554.37 587.33 622.25 659.26 698.46 739.99 783.99 830.61 880 932.33 987.77 1046.5 1108.7
Columns 66 through 78
1174.7 1244.5 1318.5 1396.9 1480 1568 1661.2 1760 1864.7 1975.5 2093 2217.5 2349.3
Columns 79 through 88
2489 2637 2793.8 2960 3136 3322.4 3520 3729.3 3951.1 4186
maxind = 87;
f = zeros(1, maxind); % preallocate, better performance and avoids mlint warnings
for ii=1:maxind
f(ii) = 27.5 * 2^(ii/12);
end
The reason I named the loop variable ii is because i is the name of a builtin function. So it's considered bad practice to use that as a variable name.
Also, in your description you said you want to iterate 88 times, but the above loop only iterates 1 through 87 (both inclusive). If you want to iterate 88 times change maxind to 88.

Bash random number generator where number is not in array

I am attempting to create a random number generator that generates a number between 1 and 99 but not any number that has already been generated.
In the script array1 contains the numbers already generated. To make it easier to test I have reduced the random number range to 0 - 14 and manually created an array.
I am quite new to bash scripting and am picking it up with a couple of books and the internet.
I have tried a mixture of ideas, the one that seems to make most sense is
array1=( 1 2 3 6 7 8 9 10 11 12 13 )
func1() {
for var in "${array1[#]}"
do
echo $var
done
}
rnd=$[ $RANDOM % 14 ]
until [ $rnd != func1 ]
do
rnd=$[ $RANDOM % 14 ]
done
echo $rnd
however I know the problem is on line 9 the shell sees the following code:
until [ $rnd != 1 2 3 6 7 8 9 10 11 12 13 ]
I know that the solution is that line 9 needs to be:
until [ $rnd != 1 ] && [ $rnd != 2 ] && [ $rnd != 3 ] && ...
I just don't know how to make this happen automatically from the array. The array does vary in length depending on how many numbers have been generated.
Any help will be greatly appreciated!
This is something that I found difficulty doing in bash. The approach I came up with is to have func1() return true or false and modify the array to remove the number that has been picked.
array=( {1..15} )
func1() {
local pick="$1"
found=1
total=${#array[#]}
for ((i=0;i<total;i++)); do
if (( pick == ${array[i]} )); then
echo $pick
array=( ${array[#]:0:i} ${array[#]:((i + 1)):$total})
found=0
break
fi
done
return $found
}
numbers=3
for ((x=0;x<numbers;x++)); do
until func1 $(( $RANDOM % ( ${#array[#]} ) )); do
continue
done
done
As noted in one of the comments, using the Knuth Shuffle is an excellent way to do this
#!/bin/bash
shuffle() {
local i tmp size max rand
# Code from http://mywiki.wooledge.org/BashFAQ/026
# $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
}
# Fill an array with values 1 to 99
array=({1..99});
# Shuffle the array at random
shuffle
# Echo shuffled array
echo ${array[#]}
Output
$ ./knuth
58 78 6 37 84 79 81 43 50 25 49 56 99 41 26 15 86 11 96 90 76 46 92 70 87 27 33 91 1 2 73 97 65 69 42 32 39 67 72 52 36 64 24 88 60 35 83 89 66 30 4 53 57 28 75 48 40 74 18 23 45 61 20 31 21 16 68 80 62 8 98 14 7 19 47 55 22 85 59 17 77 10 63 93 51 54 95 82 94 9 44 38 13 71 34 29 5 3 12
You can also use the -R switch to sort, if your version of sort supports it:
for x in {1..99} ; do echo $x ; done | sort -R

Resources