VBA: completing a matrix - excel

I have a 3 by 3 matrix, where elements (1,1), (2,1), (2,2), (3,1), (3,2), (3,3) are given:
X . .
X X .
X X X
I need to write a program that writes out the missing elements, where (1,2)=(2,1), (1,3)=(3,1) and (2,3)=(3,2). I have written the following code:
Function kiegeszito(a)
For i = 1 To 3
For j = 1 To 3
If i < j Then
a(i, j) = a(j, i)
Else
a(i, j) = a(i, j)
End If
Next j
Next i
kiegeszito = a
End Function
However, this does not seem to work, could anybody help me why is this not working?

Just remove the Else condition:
Function kiegeszito(a)
For i = 1 To 3
For j = 1 To 3
If i < j Then a(i, j) = a(j, i)
Next j
Next i
kiegeszito = a
End Function

Get twin data in 2-dim matrix avoiding extra n*(n-1)/2 condition checks
The following approach
reduces the number of unnecessary condition checks by incrementing the 2nd loop starts
accepts any wanted base of 2-dim data:
Sub CompleteMatrix(ByRef data)
'count row|=column elements
Dim cnt As Long: cnt = UBound(data) - LBound(data) + 1
'fill missing twin data (identified by inverted indices)
Dim i As Long, j As Long
For i = LBound(data) To cnt - 1
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'next column starts from incremented row index
'(thus avoiding n*(n-1)/2 IF-conditions)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For j = i + 1 To UBound(data, 2)
data(i, j) = data(j, i) ' assign twin data
Next j
Next i
End Sub
An example call creating e.g. a 1-based 2-dim datafield array might be
Sub ExampleCall()
Dim v: v = Tabelle3.Range("A1:C3").Value
CompleteMatrix v
End Sub
Further link
A practical example using such a mirrored array might be a distance array; a related post demonstrates how to apply the FilterXML() function thereon.

Fill Array
Using a method (fillArray) you could modify the array 'in place':
The Code
Option Explicit
Sub fillArrayTEST()
Dim Data As Variant: Data = Range("A1:C3").Value
debugPrint2D Data
fillArray Data
debugPrint2D Data
End Sub
Sub fillArray(ByRef Data As Variant)
Dim cCount As Long: cCount = UBound(Data, 2)
Dim i As Long, j As Long
For i = 1 To UBound(Data, 1)
For j = 1 To cCount
If i < j Then Data(i, j) = Data(j, i)
Next j
Next i
End Sub
Sub debugPrint2D(ByVal Data As Variant)
Dim i As Long, j As Long
For i = LBound(Data, 1) To UBound(Data, 1)
For j = LBound(Data, 2) To UBound(Data, 2)
Debug.Print "[" & i & "," & j & "]", Data(i, j)
Next j
Next i
End Sub
A Homage to T.M.'s Brilliant Solution
Sub completeMatrix(ByRef Data As Variant)
Dim rLower As Long: rLower = LBound(Data, 1)
Dim cLower As Long: cLower = LBound(Data, 2)
Dim iDiff As Long: iDiff = cLower - rLower
Dim cStart As Long: cStart = iDiff + 1
Dim cUpper As Long: cUpper = UBound(Data, 2)
Dim r As Long, c As Long
For r = rLower To UBound(Data, 1) - rLower
For c = cStart + r To cUpper
Data(r, c) = Data(c - iDiff, r + iDiff)
Next c
Next r
End Sub
Sub completeMatrixTEST()
Dim Data As Variant: ReDim Data(0 To 2, 2 To 4)
Data(0, 2) = 1
Data(1, 2) = 2
Data(1, 3) = 3
Data(2, 2) = 4
Data(2, 3) = 5
Data(2, 4) = 6
debugPrint2D Data
completeMatrix Data
'Range("G1").Resize(UBound(Data, 1) - LBound(Data, 1) + 1, _
UBound(Data, 2) - LBound(Data, 2) + 1).Value = Data
Debug.Print
debugPrint2D Data
End Sub

Related

Repeat range Nth times

I am trying to devise a code that enables me to repeat a range (of one column) to be repeated Nth times. This is my try (and it is working) but I need your ideas to improve the code if possible
Sub Test()
Const N As Integer = 3
Dim a, i As Long, ii As Long, k As Long
a = ActiveSheet.Range("A1:A" & ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row).Value
ReDim b(1 To UBound(a, 1) * N, 1 To 1)
For i = 1 To N
For ii = LBound(a, 1) To UBound(a, 1)
k = k + 1
b(k, 1) = a(ii, 1)
Next ii
Next i
Range("C1").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
I would do it similarly...
Sub Test()
Dim a, c&, i&, k&
Const n& = 3
a = [a1].Resize(Cells(Rows.Count, 1).End(xlUp).Row)
ReDim b(1 To n * UBound(a), 1 To 1)
For k = 1 To n
For i = 1 To UBound(a)
c = c + 1
b(c, 1) = a(i, 1)
Next
Next
[c1].Resize(UBound(b)) = b
End Sub
But it would be best to make it into an encapsulated procedure...
Sub Test()
CloneRange [a1], [c1], 3
End Sub
Sub CloneRange(rSrc As Range, rDst As Range, Optional n& = 1)
Dim a, c&, i&, k&
a = rSrc.Resize(Cells(Rows.Count, 1).End(xlUp).Row)
ReDim b(1 To n * UBound(a), 1 To 1)
For k = 1 To n
For i = 1 To UBound(a)
c = c + 1
b(c, 1) = a(i, 1)
Next
Next
rDst.Resize(UBound(b)) = b
End Sub

Why is my array breaking the column when I use TRANSPOSE to paste it into a worksheet?

In Excel, I'm using VBA to create an array to collect data and then pasting it back into a worksheet. This functioned excellently on a smaller dataset (~15,000 rows), but when I move to my larger dataset (~117,000 rows), something is happening at the "Transpose" step.
In the array, I have headers and data that I want to paste into 5 columns in a new sheet starting at cell B5. I define the range ("ListDestination"), then paste it in using this code:
shNew.Name = shName
Set ListDestination = shNew.Range("B5").Resize(UBound(arrList, 2), UBound(arrList, 1))
ListDestination = WorksheetFunction.Transpose(arrList)
When I check the ListDestination in the immediate window, it is correct ($B$5:$F$116771) and in the Watches window, I can see that the arrList is defined (1 to 5, 0 to 116767), which is correct. When expanding it, it also shows the data in the correct places. However, after the "Transpose" line, the result in the worksheet is:
...whereas it should be:
For what it's worth, it does paste through the entire "ListDestination" range, but after row 51236 all I get is #N/A:
I haven't changed anything in the code since this worked on the smaller dataset, so I'm thinking it must have something to do with the size of the dataset.
Thanks for any help you can provide.
Here is a simple function that will transpose the array.
Function my_transpose(arr As Variant) As Variant()
Dim tempArray() As Variant
ReDim tempArray(LBound(arr, 2) To UBound(arr, 2), LBound(arr, 1) To UBound(arr,1)) As Variant
Dim i As Long
For i = LBound(arr, 1) To UBound(arr, 1)
Dim j As Long
For j = LBound(arr, 2) To UBound(arr, 2)
tempArray(j, i) = arr(i, j)
Next j
Next i
my_transpose = tempArray
End Function
Then you would use in your line like this:
shNew.Name = shName
Set ListDestination = shNew.Range("B5").Resize(UBound(arrList, 2), UBound(arrList, 1))
ListDestination = my_transpose(arrList)
The Limited Transpose Function
The Solution
Using the transpose2D function you could do one of the following:
' Note the '+ 1' since the lower limit of the second dimension is '0'.
Set ListDestination _
= shNew.Range("B5").Resize(UBound(arrList, 2) + 1, UBound(arrList, 1))
ListDestination.Value = transpose2D(arrList)
' No need for '+ 1' since '1' is used with 'transpose2D'.
Dim Data As Variant: Data = transpose2D(arrList, 1)
Set ListDestination _
= shNew.Range("B5").Resize(UBound(Data, 1), UBound(Data, 2))
ListDestination.Value = Data
' No need for '+ 1' since '1' is used with 'transpose2D'.
arrList = transpose2D(arrList, 1)
Set ListDestination _
= shNew.Range("B5").Resize(UBound(arrList, 1), UBound(arrList, 2))
ListDestination.Value = arrList
The Function
Function transpose2D( _
ByVal TwoD As Variant, _
Optional ByVal FirstIndex As Variant) _
As Variant
Dim LB1 As Long: LB1 = LBound(TwoD, 1)
Dim UB1 As Long: UB1 = UBound(TwoD, 1)
Dim LB2 As Long: LB2 = LBound(TwoD, 2)
Dim UB2 As Long: UB2 = UBound(TwoD, 2)
Dim Data As Variant, r As Long, c As Long
If IsMissing(FirstIndex) Then ' just transpose
ReDim Data(LB2 To UB2, LB1 To UB1)
For r = LB2 To UB2
For c = LB1 To UB1
Data(r, c) = TwoD(c, r)
Next c
Next r
Else ' transpose with (possibly) modified limits: LB1 = LB2 = FirstIndex
Dim D1 As Long: D1 = FirstIndex - LB1
Dim D2 As Long: D2 = FirstIndex - LB2
ReDim Data(FirstIndex To UB2 + D2, FirstIndex To UB1 + D1)
For r = LB2 To UB2
For c = LB1 To UB1
Data(r + D2, c + D1) = TwoD(c, r)
Next c
Next r
End If
transpose2D = Data
End Function
A Simple Example
Sub transpose2DTEST()
Dim TwoD As Variant: ReDim TwoD(1 To 2, 0 To 3) ' Note the zero (0)
Dim r As Long, c As Long, n As Long
For r = 1 To UBound(TwoD, 1)
For c = 0 To UBound(TwoD, 2)
n = n + 1
TwoD(r, c) = n
Next c
Next r
' Contents of TwoD:
' TwoD(1, 0) = 1
' TwoD(1, 1) = 2
' TwoD(1, 2) = 3
' TwoD(1, 3) = 4
' TwoD(2, 0) = 5
' TwoD(2, 1) = 6
' TwoD(2, 2) = 7
' TwoD(2, 3) = 8
Dim Data As Variant
Data = transpose2D(TwoD) ' just tranpose (note the zero)
' Contents of Data:
' Data(0, 1) = 1
' Data(0, 2) = 5
' Data(1, 1) = 2
' Data(1, 2) = 6
' Data(2, 1) = 3
' Data(2, 2) = 7
' Data(3, 1) = 4
' Data(3, 2) = 8
Data = transpose2D(TwoD, 1) ' FirstIndex = 1
' Contents of Data:
' Data(1, 1) = 1
' Data(1, 2) = 5
' Data(2, 1) = 2
' Data(2, 2) = 6
' Data(3, 1) = 3
' Data(3, 2) = 7
' Data(4, 1) = 4
' Data(4, 2) = 8
End Sub
The Transpose Test
This was run on a 64bit Office version: no errors. As I recall the limit on a 32bit version was 65535 and afterward (>65535) it would raise an error.
Sub TransposeTest64bit()
Dim Data As Variant: ReDim Data(1 To 65536, 1 To 1)
Data = Application.Transpose(Data)
Debug.Print LBound(Data), UBound(Data) ' 1, 65536
ReDim Data(1 To 65537, 1 To 1)
Data = Application.Transpose(Data)
Debug.Print LBound(Data), UBound(Data) ' 1, 1
End Sub

Get all combinations of summing numbers

Column A in sheet1 has the values [1,2,3,4,5,6] in range("A1:A6") and what I am trying to do is to get all the combinations of summing each two numbers and each three numbers and each four numbers and each five numbers
This is what I did till now but the results are not as I expected
Sub Test()
Dim a, b, lr As Long, i As Long, j As Long, k As Long, ii As Long
lr = Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To lr
For j = i To lr
For ii = j To lr
Cells(i, ii + 1) = i & "+" & j & "+" & ii & "=" & i + j + ii
Next ii
Next j
Next i
With Range("A1").CurrentRegion
a = .Offset(, 1).Resize(, .Columns.Count - 1).Value
ReDim b(1 To UBound(a, 1) * UBound(a, 2), 1 To 1)
For i = LBound(a) To UBound(a)
For j = LBound(a, 2) To UBound(a, 2)
If a(i, j) <> "" Then
k = k + 1
b(k, 1) = a(i, j)
End If
Next j
Next i
.Cells(1, .Columns.Count + 2).Resize(k).Value = b
End With
End Sub
Example of the desired output:
Each two numbers together >>
Sub Test()
Dim a, b, lr As Long, i As Long, j As Long, k As Long, ii As Long
lr = Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To lr
For j = i To lr
Cells(i, j + 1) = i & "+" & j & "=" & i + j
Next j
Next i
With Range("A1").CurrentRegion
a = .Offset(, 1).Resize(, .Columns.Count - 1).Value
ReDim b(1 To UBound(a, 1) * UBound(a, 2), 1 To 1)
For i = LBound(a) To UBound(a)
For j = LBound(a, 2) To UBound(a, 2)
If a(i, j) <> "" Then
k = k + 1
b(k, 1) = a(i, j)
End If
Next j
Next i
.Cells(1, .Columns.Count + 2).Resize(k).Value = b
End With
End Sub
The results would be like that in column J
1+1=2
1+2=3
1+3=4
1+4=5
1+5=6
1+6=7
2+2=4
2+3=5
2+4=6
2+5=7
2+6=8
3+3=6
3+4=7
3+5=8
3+6=9
4+4=8
4+5=9
4+6=10
5+5=10
5+6=11
6+6=12
This is OK for each two numbers .. How can I get the results for each three numbers and for each four numbers and for each five numbers?
** #Vityata
Public Sub PrintArrayOnSingleLine(myArray As Variant)
Dim i As Long, x As Long
Dim textArray As String, temp As String
For i = LBound(myArray) To UBound(myArray)
textArray = textArray & myArray(i)
x = x + Val(myArray(i))
temp = temp & "+" & myArray(i)
Next i
Dim myLastRow As Long
myLastRow = LastRow(Worksheets(1).Name) + 1
ActiveSheet.Cells(myLastRow, 1) = Mid(temp, 2) & "=" & x
End Sub
I have edited the procedure as you told me, but just one note, I can't get the same number to be summed. Example: 1+1=2
Combination (not repeating same values):
Copy the code below and run it. Then change the variable in size = n. The given numbers are in the initialArray. In the end, instead of printing the array as a textArray, add a variable to sum it:
Sub Main()
Dim size As Long: size = 2
Dim initialArray As Variant: initialArray = Array(1, 2, 3, 4, 5, 6)
Dim arr As Variant: ReDim arr(size - 1)
Dim n As Long: n = UBound(arr) + 1
EmbeddedLoops 0, size, initialArray, n, arr
End Sub
Function EmbeddedLoops(index As Long, size As Long, initialArray As Variant, n As Long, arr As Variant)
Dim p As Variant
If index >= size Then
If Not AnyValueBiggerThanNext(arr) And Not AnyValueIsRepeated(arr) Then
PrintArrayOnSingleLine arr
End If
Else
For Each p In initialArray
arr(index) = p
EmbeddedLoops index + 1, size, initialArray, n, arr
Next p
End If
End Function
Public Function AnyValueBiggerThanNext(arr As Variant) As Boolean
Dim i As Long
For i = LBound(arr) To UBound(arr) - 1
If arr(i) > arr(i + 1) Then
AnyValueBiggerThanNext = True
Exit Function
End If
Next i
AnyValueBiggerThanNext = False
End Function
Public Function AnyValueIsRepeated(arr As Variant) As Boolean
On Error GoTo AnyValueIsRepeated_Error:
Dim element As Variant
Dim testCollection As New Collection
For Each element In arr
testCollection.Add "item", CStr(element)
Next element
AnyValueIsRepeated = False
On Error GoTo 0
Exit Function
AnyValueIsRepeated_Error:
AnyValueIsRepeated = True
End Function
Public Sub PrintArrayOnSingleLine(myArray As Variant)
Dim i As Long
Dim textArray As String
For i = LBound(myArray) To UBound(myArray)
textArray = textArray & myArray(i)
Next i
Debug.Print textArray
End Sub
Permutation (repeating same values)
Sub Main()
Static size As Long
Static c As Variant
Static arr As Variant
Static n As Long
size = 3
c = Array(1, 2, 3, 4, 5, 6)
n = UBound(c) + 1
ReDim arr(size - 1)
EmbeddedLoops 0, size, c, n, arr
End Sub
Function EmbeddedLoops(index, k, c, n, arr)
Dim i As Variant
If index >= k Then
PrintArrayOnSingleLine arr
Else
For Each i In c
arr(index) = i
EmbeddedLoops index + 1, k, c, n, arr
Next i
End If
End Function
Public Sub PrintArrayOnSingleLine(myArray As Variant)
Dim counter As Integer
Dim textArray As String
For counter = LBound(myArray) To UBound(myArray)
textArray = textArray & myArray(counter)
Next counter
Debug.Print textArray
End Sub
Sources (Disclaimer - from my blog):
VBA Nested Loop with Recursion
VBA All Combinations

Building an array by skipping blank values

I'm new to VBA and was surprised that there isn't a function to insert elements in an array (my previous question). So I rethought my approach a bit.
On screen I have the following example table 'allActualWeights'. There are a lot of blanks (no weight value) that I want to get rid of (the table is different everytime). So the end result should be 'actualWeights'.
In my code I tried the following:
Option Base 1
Dim allActualWeights
allActualWeights = Range("A6:E29").Value
Dim actualWeights
actualWeights = allActualWeights
For Index = 1 To 24
If allActualWeights(Index, 2) <> 0 Then
ReDim actualWeights(Index, 5)
actualWeights(Index, 1) = allActualWeights(Index, 1)
actualWeights(Index, 2) = allActualWeights(Index, 2)
actualWeights(Index, 3) = allActualWeights(Index, 3)
actualWeights(Index, 4) = allActualWeights(Index, 4)
actualWeights(Index, 5) = allActualWeights(Index, 5)
End If
Next Index
Range("G6:K29") = actualWeights
But I'm not getting the results I hoped for.
What am I doing wrong, or is there a better approach?
Here's one approach:
Sub Tester()
Dim allActualWeights, actualweights(), i As Long, n As Long, c As Long
Dim rngSource As Range
Set rngSource = ActiveSheet.Range("A6:E29")
With rngSource
allActualWeights = .Value
'size the output array # of rows to count of values in ColB
ReDim actualweights(1 To Application.CountA(.Columns(1)), _
1 To .Columns.Count)
End With
n = 1
For i = LBound(allActualWeights, 1) To UBound(allActualWeights, 1)
If Len(allActualWeights(i, 2)) > 0 Then
For c = LBound(allActualWeights, 2) To UBound(allActualWeights, 2)
actualweights(n, c) = allActualWeights(i, c)
Next c
n = n + 1 'next output row
End If
Next i
'put the array on the sheet
Range("G6").Resize(UBound(actualweights, 1), UBound(actualweights, 2)) = actualweights
End Sub
This should do it and is easily maintainable...
Sub ActualWeights()
Dim c&, i&, j&, n&, a, b
With [a6:e29] '<-- allActualWeights
a = .Value2
n = UBound(a) - Application.CountBlank(.Offset(, 1).Resize(, 1))
ReDim b(1 To n, 1 To UBound(a, 2))
For i = 1 To UBound(a)
If a(i, 2) Then
c = c + 1
For j = 1 To UBound(a, 2)
b(c, j) = a(i, j)
Next
End If
Next
.Offset(, 6).Resize(n) = b
End With
End Sub

Excel vba - joining two arrays

I need to join two arrays vd and vd1 into vdu.
ReDim vdu(1 To (UBound(vd, 1) + UBound(vd1, 1)), 1 To 1)
For i = 1 To UBound(vd, 1)
vdu(i, 1) = vd(i, 1)
Next i
For j = i To UBound(vdu, 1)
vdu(j, 1) = vd1(j - i + 1, 1)
Next j
First, I get and "out of range" error.
Second, in the end I will have 18 arrays that I'll need to join, so I don't know if this is the best idea for joining them.
try with
ReDim vdu(LBound(vd) To UBound(vd) + UBound(vd1), 1 To 1)
For i = LBound(vdu) To UBound(vdu)
If i <= UBound(vd) Then
vdu(i, 1) = vd(i, 1)
Else
vdu(i, 1) = vd1(i - UBound(vd), 1)
End If
Next i
Update for second part of question
I'd convert your merge code into a function
Public Function MergeArrays(arr1 As Variant, arr2 As Variant) As Variant
Dim i As Long
Dim arr As Variant
ReDim arr(LBound(arr1, 1) To UBound(arr1, 1) + UBound(arr2, 1), 1 To 1)
For i = LBound(arr, 1) To UBound(arr, 1)
If i <= UBound(arr1, 1) Then
arr(i, 1) = arr1(i, 1)
Else
arr(i, 1) = arr2(i - UBound(arr1, 1), 1)
End If
Next i
MergeArrays = arr
End Function
And then pass each array to it 1 at a time e.g.
arr = MergeArrays(vd1, vd2)
arr = MergeArrays(arr, vd3)
arr = MergeArrays(arr, vdx)
You could loop through this by storing your arrays in an array or dictionary and looping through that instead as well
Other option
Public Function MergeArrays(ParamArray arrays() As Variant) As Variant
Dim i As Long, j As Long, cnter As Long, UBoundArr As Long, OldUBoundArray As Long
Dim arr() As Variant
For j = LBound(arrays) To UBound(arrays)
UBoundArr = UBoundArr + UBound(arrays(j), 1)
Next j
ReDim arr(1 To UBoundArr, 1 To 1)
For j = LBound(arrays) To UBound(arrays)
For i = LBound(arrays(j)) To UBound(arrays(j))
arr(i + OldUBoundArray, 1) = arrays(j)(i, 1)
Next i
OldUBoundArray = OldUBoundArray + UBound(arrays(j), 1)
Next j
MergeArrays = arr
End Function
This method uses a ParamArray. If you're not sure what that is look it up but effectively you're able to pass an unspecified amount of arguments to the function. Therefore with this function you can combine any amount of arrays (of the same shape and same base i.e. x to x, 1 to 1) and it will combine them. Call like
arr = MergeArrays(vd, vd1, vd2,....,vd18)
getMasterArray will return an array that combines up to 60 different 2d arrays into one. getMasterArray also give you the option of returning a 0 or based array.
Sub TestgetMasterArray()
Dim data
data = getMasterArray(False, Range("List1").Value, Range("List2").Value, Range("List3").Value, Range("List4").Value)
Worksheets("Result").Range("A1").Resize(UBound(data), UBound(data, 2)).Value = data
End Sub
Function getMasterArray(Base0 As Boolean, ParamArray Arrays() As Variant)
Dim result As Variant, v As Variant
Dim Count As Long, Count2 As Long, lowBound As Integer, lOffset As Integer, x As Long, x1 As Long, y As Long
For Each v In Arrays
Count = Count + UBound(v) + IIf(LBound(v) = 0, 1, 0)
y = UBound(v, 2) + IIf(LBound(v, 2) = 0, 1, 0)
If y > Count2 Then Count2 = y
Next
lowBound = IIf(Base0, 0, 1)
ReDim result(lowBound To Count, lowBound To Count2)
For Each v In Arrays
If LBound(v, 2) > LBound(result, 2) Then
lOffset = -1
ElseIf LBound(v, 2) < LBound(result, 2) Then
lOffset = 1
End If
For x = LBound(v) To UBound(v)
For y = LBound(v, 2) To UBound(v, 2)
result(lowBound, y + lOffset) = v(x, y)
Next
lowBound = lowBound + 1
Next
Next
getMasterArray = result
End Function
Sample data generated by ockaroo.com

Resources