Shift Elements of 2D VBA Array - arrays

All -
I'm wondering if there's an efficient way to "shift" elements of a 2-dimensional array. Effectively, what I have is triangular data, saved in a VBA array (n x m, where n <= m):
0 1 2 3 4 5
----------------
0 | A B C D E F
1 | G H I J
2 | K L
I'd like to "restructure" this array to:
0 1 2 3 4 5
----------------
0 | A B C D E F
1 | G H I J
2 | K L
The blank values in the array are actually empty strings (""). I'd imagine there's some looping that I could do to perform this with some compute cost, but I'm wondering if there's an efficient approach for subset "shifting" within VBA...

As #TimWilliams commented correctly, you won't do it without any loops. - A possible approach, however reducing loops would be to
write the initial array (named e.g. v) row wise to an empty target range (applying individual offsets you can calculate before) and eventually
assign them back as so called data field array.
The following example code should give you an idea. - Little draw back: in any case you get the array back as 1-based array.
'~~> assign initial (variant) array v as in OP
'...
'~~> calculate column offsets per array row, e.g. {0,2,4}
'...
'~~> shift array rows and write them to any empty target area
Dim startCell As Range: Set startCell = Sheet1.Range("X1")
Dim i As Long, j As Long, tmp As Variant
For i = 1 To UBound(v)
'~~> get individual column offset per row, e.g. {0,2,4}
j = Array(0, 2, 4)(i - 1)
'~~> write next row to target range
startCell.Offset(i, j).Resize(1, UBound(v, 2)) = Application.Index(v, i, 0)
Next i
'~~> get rearranged 1-based 2-dim datafield array
v = startCell.Offset(1).Resize(UBound(v), UBound(v, 2))
If you shift elements within a greater array, you could write the entire array to the target and overwrite only rows you need rearranged (considering to clear these single row ranges before:-)

Related

Aggregating part of a 2d array in a column in said array

I have a 2d array, with flexible dimensions:
arr_emissions(1 to n, 0 to m)
Where n is 22 or larger, and m is 6 or larger.
In the smallest case column m = 6 should contain the sum of columns m = 2 - 5.
I could ofcourse simply add them, but as the dimensions of the array are flexible I would like to implement a more robust method, that preferly doesn't loop over the entire array.
I was hoping to implement the native application.WorksheetFormula.Sum(). I saw an implementation in this answer, but that only works for complete rows or columns.
Example:
I have arr_emissions(0 to 111,1 to 6). It is populated in a loop from 1 to 111.
The data in the array is as follows:
(1,1) #3-4-2020# 'a date value
(1,2) 1,379777
(1,3) 0
(1,4) Empty
(1,5) Empty
Don't know if this helps, but this takes a source array v and then populates a new array w with the sum of columns 2-4 of the corresponding row of v.
Sub x()
Dim v, i As Long, w()
'just to populate source array
v = Range("A1").CurrentRegion.Value
ReDim w(1 To UBound(v, 1))
For i = 1 To UBound(w)
'each element of w is sum of columns 2-4 of corresponding row of v
w(i) = Application.Sum(Application.Index(v, i, Array(2, 3, 4)))
Next i
'write w to sheet
Range("G1").Resize(UBound(w)) = Application.Transpose(w)
End Sub
Thanks to the answer from SJR I found myself a working solution. This is all within a larger piece of code, but for this example I filled some variables with fixed numbers to match my example from my question.
Dim days as Integer
days = 111
Dim emissions_rows as Integer
emissions_cols = 6
ReDim arr_emissions(0 To days, 1 To emissions_cols) As Variant
Dim arr_sum As Variant
Dim sum_str As String
sum_str = "Transpose(row(2:" & emissions_rows - 1 & "))"
arr_sum = Application.Evaluate(sum_str) '= Array(2,3,4,5)
arr_emissions(emissions_index, emissions_cols) = Application.Sum(Application.Index(arr_emissions, emissions_index + 1, arr_sum))
The code writes a string to include the variables, so to take the second column untill the second to last column, which is then evaluated into an array.
That array is then used within the sum function, to only sum over those columns.
The result is then written to the last column of arr_emissions().
emissions_index is an index that is used to loop over the array.

Julia / Cellular Automata: efficient way to get neighborhood

I'd like to implement a cellular automaton (CA) in Julia. Dimensions should be wrapped, this means: the left neighbor of the leftmost cell is the rightmost cell etc.
One crucial question is: how to get the neighbors of one cell to compute it's state in the next generation? As dimensions should be wrapped and Julia does not allow negative indices (as in Python) i had this idea:
Considered a 1D CA, one generation is a one-dimensional array:
0 0 1 0 0
What if we create a two dimensional Array, where the first row is shifted right and the third is shifted left, like this:
0 0 0 1 0
0 0 1 0 0
0 1 0 0 0
Now, the first column contain the states of the first cell and it's neighbors etc.
i think this can easily be generalized for two and more dimensions.
First question: do you think this is a good idea, or is this a wrong track?
EDIT: Answer to first question was no, second Question and code example discarded.
Second question: If the approach is basically ok, please have a look at the following sketch:
EDIT: Other approach, here is a stripped down version of a 1D CA, using mod1() for getting neighborhood-indices, as Bogumił Kamiński suggested.
for any cell:
- A array of all indices
- B array of all neighborhood states
- C states converted to one integer
- D lookup next state
function digits2int(digits, base=10)
int = 0
for digit in digits
int = int * base + digit
end
return int
end
gen = [0,0,0,0,0,1,0,0,0,0,0]
rule = [0,1,1,1,1,0,0,0]
function nextgen(gen, rule)
values = [mod1.(x .+ [-1,0,1], size(gen)) for x in 1:length(gen)] # A
values = [gen[value] for value in values] # B
values = [digits2int(value, 2) for value in values] # C
values = [rule[value+1] for value in values] # D
return values
end
for _ in 1:100
global gen
println(gen)
gen = nextgen(gen, rule)
end
Next step should be to extend it to two dimensions, will try it now...
The way I typically do it is to use mod1 function for wrapped indexing.
In this approach, no matter what dimensionality of your array a is then when you want to move from position x by delta dx it is enough to write mod1(x+dx, size(a, 1)) if x is the first dimension of an array.
Here is a simple example of a random walk on a 2D torus counting the number of times a given cell was visited (here I additionally use broadcasting to handle all dimensions in one expression):
function randomwalk()
a = zeros(Int, 8, 8)
pos = (1,1)
for _ in 1:10^6
# Von Neumann neighborhood
dpos = rand(((1,0), (-1,0), (0,1), (0,-1)))
pos = mod1.(pos .+ dpos, size(a))
a[pos...] += 1
end
a
end
Usually, if the CA has cells that are only dependent on the cells next to them, it's simpler just to "wrap" the vector by adding the last element to the front and the first element to the back, doing the simulation, and then "unwrap" by taking the first and last elements away again to get the result length the same as the starting array length. For the 1-D case:
const lines = 10
const start = ".........#........."
const rules = [90, 30, 14]
rule2poss(rule) = [rule & (1 << (i - 1)) != 0 for i in 1:8]
cells2bools(cells) = [cells[i] == '#' for i in 1:length(cells)]
bools2cells(bset) = prod([bset[i] ? "#" : "." for i in 1:length(bset)])
function transform(bset, ruleposs)
newbset = map(x->ruleposs[x],
[bset[i + 1] * 4 + bset[i] * 2 + bset[i - 1] + 1
for i in 2:length(bset)-1])
vcat(newbset[end], newbset, newbset[1])
end
const startset = cells2bools(start)
for rul in rules
println("\nUsing Rule $rul:")
bset = vcat(startset[end], startset, startset[1]) # wrap ends
rp = rule2poss(rul)
for _ in 1:lines
println(bools2cells(bset[2:end-1])) # unwrap ends
bset = transform(bset, rp)
end
end
As long as only the adjacent cells are used in the simulation for any given cell, this is correct.
If you extend this to a 2D matrix, you would also "wrap" the first and last rows as well as the first and last columns, and so forth.

build excel array from data items and a multiplier

first question on this site.
Been coming here to bask in the warm glow of the knowledge on offer for years! Please be gentle with me. ;)
I'm not a programmer but can muddle my way around excel but I have a problem in excel that I'm struggling to find a solution to.
I need to take a set of data and turn it into an array (or list) of all the occurrences of that data. For example a set of data (A,B,C) and an instances value for each item (2,1,3).
What I need to do is take those two items and create an array of all occurrences.
Like this:-
Data,Instances
A,2
B,1
C,3
Total 6
Result
1,A
2,B
3,C
4,A
5,C
6,C
(I hope that's clear - my rating isn't high enough to post a screenshot)
So, in this example I have 2 As, 1 B and 3 Cs giving a total of 6 items. To create the result I've run through the list 6 times listing each data item if it still has an occurrence (but an array/list that was AABCCC would be just as valid). For the full data set there could be as many as 12 different data items with any number of occurrences each from 1 to 12.
Somehow I think I'm overcomplicating a simple process but for the life of me I can't get my head around achieving the result I need.
Say we put your data in column A:
and run this short macro:
Sub croupier()
Dim N As Long, K As Long, i As Long, ary(), bry()
Dim v As String
N = Cells(Rows.Count, "A").End(xlUp).Row
ReDim ary(1 To N)
ReDim bry(1 To N)
For i = 1 To N
v = Cells(i, "A").Value
cry = Split(v, ",")
ary(i) = cry(0)
bry(i) = CLng(cry(1))
Next i
K = 1
While Application.WorksheetFunction.Sum(bry) > 0
For i = 1 To N
If bry(i) <> 0 Then
Cells(K, "B").Value = ary(i)
bry(i) = bry(i) - 1
K = K + 1
End If
Next i
Wend
End Sub
Our result is this:
We repeatedly run down column A placing the values in column B until the count of an item reaches zero.
When the overall count of items is zero, we stop.

Identify missing values from array

I am striving to create a function in VBA that calculates the number of missing values in each column of a matrix of nxn dimensions.
Each column should contain the numbers 1 to n only once.
However if this is not the case I want to the function to state how many values are missing. For example in a column of 4x4 matrix (1,2,1,3) there is one missing value which is 4, and the function should return the value 1, for the 1 missing value.
I am very new to VBA and by no means a master, but this is what I have done so far...
Function calcCost(sol() As Integer, n As Integer) As Integer
Dim ArrayOfTruth(1 To n) As Boolean
For Row = 1 To n
For i = 1 To n
If ProbMatrix(Column, Row) = i Then
ArrayOfTruth(i) = True
cost = 0
For i = 1 To n
If ArrayOfTruth(i) = True Then
cost = cost + 1
Assuming that the requirement of a square range of cells supersedes the description of the 'matrix's' values, I'm not sure why an array is needed at all.
Function calcCost(rTopLeft As Range, n As Long)
Dim c As Long, r As Long
For c = 1 To n
If Not CBool(Application.CountIf(rTopLeft.Resize(n, n), c)) Then _
r = r + 1
Next c
calcCost = r
End Function
Syntax:
    =calcCost(<top left corner of desired range>, <number of cells both right and down>)
Example:
=calcCost(E9, 18)
The above implementation could also be written as,
=18-SUMPRODUCT(--SIGN(COUNTIF(OFFSET(E9,0,0,18,18), ROW(1:18))))

Populate VBA array, one row at a time

I want to be able to populate an array with rows of data at a time instead of element by element. For example, I'd like to get a final product like the 3x3 array:
1 2 3
4 5 6
7 8 9
by populating it with the row 1 2 3, then with 4 5 6, then with 7 8 9.
In Excel, I have formulas set up in cells F1:Z1 that change based on inputs in cells A1:D1. The macro loads the case in A1:D1, and then I want to be able to select cells F1:Z1 and insert them into an array at once, instead of looping through individual elements in F1, G1, H1, ..., Z1. (I have 10,000 cases, so I know the array would have 10,000 rows and 21 columns.)
Is there any way to do this, or would I have to loop through each element individually?
You can actually populate an array in one shot. Suppose your Range of interest is A1:C200 in Sheet1. You can populate the array using the following
Dim v as Variant
v = ThisWorkbook.Worksheets("Sheet1").Range("A1:C200").Value
This will give you a 2D array with 3 columns and 200 rows.
If any one need help, I was looking for a solution to the same need too, and I found this right now.
Here is a solution using "Index" function :
Sub Test()
Dim varArray As Variant
Dim varTemp As Variant
varArray = ThisWorkbook.Worksheets("Sheet1").Range("A1:E10")
varTemp = Application.Index(varArray, 2, 0)
End Sub
In this example, varTemp contains the values of the row number "2" in the range "A1:E10".
Say we have, in the columns A, B and C, from row 1 to 10, this values :
1 a q
2 b w
3 c e
4 d r
5 e t
6 f y
7 g u
8 h i
9 i o
10 j p
then :
Debug.Print varTemp(1)
Debug.Print varTemp(2)
Debug.Print varTemp(3)
will display :
2
b
w
Have a look here : https://usefulgyaan.wordpress.com/2013/06/12/vba-trick-of-the-week-slicing-an-array-without-loop-application-index/
You can actually store the entire row at once. I was trying to do exactly what you are doing here
Consider you have formulae in "A3:Z3" which you want to loop got 1000 times and store values. below are steps
declare a variant
Dim myarray(1000) As Variant
for i = 1 to 1000
myarray(i) = Range("A3:Z3").value2
next i
use the same loop to paste wherever you want

Resources