so I have an assignment where I need to create a VBA array function called ShiftVector(rng,n) with arguments for a range (rng) and n that will shift the elements of an (m x 1) vector up by n rows. The first n rows of the range rng will “wrap around” and appear at the bottom of the resulting vector.
It's working on the locals window but its not working properly on excel, can some one help me?
Option Explicit
Option Base 1
Function ShiftVector(rng As Range, n As Integer)
Dim i As Integer, nr As Integer, B() As Variant
nr = rng.Rows.Count
ReDim B(nr, 1) As Variant
For i = 1 To nr - n
B(i, 1) = rng.Cells(i + n, 1)
Next i
For i = nr - n + 1 To nr
B(i, 1) = rng.Cells(i - nr + n, 1)
Next i
ShiftVector = Application.WorksheetFunction.Transpose(B)
End Function
Shift Vector
To increase efficiency, it is better to write the values from the range rng to a 2D one-column one-based array D.
Then loop the array instead of the range and directly write to a 1D array B, so there will be no need for Transpose.
The result is a 1D one-based array.
The Code
Option Explicit
Function ShiftVector(rng As Range, n As Long) As Variant
If rng Is Nothing Then
GoTo ProcExit
End If
If n < 1 Or n > rng.Rows.Count Then
GoTo ProcExit
End If
Dim D As Variant
If rng.Rows.Count > 1 Then
D = rng.Columns(1).Value
Else
ReDim D(1 To 1, 1 To 1)
D(1, 1) = rng.Columns(1).Value
End If
Dim nr As Long
nr = UBound(D, 1)
Dim B As Variant
ReDim B(1 To nr)
Dim i As Long
For i = 1 To nr - n
B(i) = D(i + n, 1)
Next i
For i = nr - n + 1 To nr
B(i) = D(i - nr + n, 1)
Next i
ShiftVector = B
ProcExit:
End Function
Sub testShiftVector()
Debug.Print Join(ShiftVector(Range("A1:A10"), 3), vbLf)
End Sub
If you want a 2D one-based one-column array as the result use the following:
The Code
Function ShiftVector2D(rng As Range, n As Long) As Variant
If rng Is Nothing Then
GoTo ProcExit
End If
If n < 1 Or n > rng.Rows.Count Then
GoTo ProcExit
End If
Dim D As Variant
If rng.Rows.Count > 1 Then
D = rng.Columns(1).Value
Else
ReDim D(1 To 1, 1 To 1)
D(1, 1) = rng.Columns(1).Value
End If
Dim nr As Long
nr = UBound(D, 1)
Dim B() As Variant
ReDim B(1 To nr, 1 To 1) As Variant
Dim i As Long
For i = 1 To nr - n
B(i, 1) = D(i + n, 1)
Next i
For i = nr - n + 1 To nr
B(i, 1) = D(i - nr + n, 1)
Next i
ShiftVector2D = B
ProcExit:
End Function
Sub testShiftVector2D()
Dim Data As Variant
Data = ShiftVector2D(Range("A1:A10"), 3)
Dim i As Long
For i = 1 To UBound(Data)
Debug.Print Data(i, 1)
Next i
' Or:
'Range("B1").Resize(UBound(Data, 1)).Value = Data
End Sub
Related
I'm completely new to VBA and have decided to try recreate excels built in functions. I'm currently trying to create a function that finds the median. for example, it first identifies whether the array is column vector or row vector. i used bubble sort to sort my array in ascending order and then apply a code to find the median value of the sorted array.
However i seem to get a error during the sort, it exists when it tries to swap two values. i get #VALUE error.
Function mymedian(x As Range) As Double
' order array of values asc
' use bubblesort
Dim nr As Integer
Dim nc As Integer
Dim i As Integer
Dim j As Integer
Dim temp As Double
Dim n As Integer
nr = x.Rows.count
nc = x.Columns.count
' col vector
If nc = 1 Then
For i = 2 To nr
For j = 2 To nr
If x.Cells(j - 1, 1).Value > x.Cells(j, 1).Value Then
temp = x.Cells(j, 1)
x.Cells(j, 1).Value = x.Cells(j - 1, 1).Value ' code exists here
x.Cells(j - 1, 1) = temp
n = n + 1
End If
Next j
Next i
Else
' row vector
If nc > 1 Then
For i = 2 To nc
For j = 2 To nc
If x.Cells(1, j - 1).Value > x.Cells(1, j).Value Then
temp = x.Cells(1, j)
x.Cells(1, j) = x.Cells(1, j - 1).Value
x.Cells(1, j - 1) = temp
n = n + 1
End If
Next j
Next i
End If
End If
As a sub this works fine, does this imply bubble sorts only work as sub routines? i also tried to call the sub within a function, however this wasn't working.
Sub bubblesort()
Dim x As Range
Set x = Selection
Dim nr As Integer
Dim temp As Double
Dim i As Integer
Dim j As Integer
nr = x.Rows.count
For i = 2 To nr
For j = 2 To nr
If x.Cells(j - 1, 1).Value > x.Cells(j, 1).Value Then
temp = x.Cells(j, 1)
x.Cells(j, 1) = x.Cells(j - 1, 1)
x.Cells(j - 1, 1) = temp
End If
Next j
Next i
End Sub
Function middle(x As Range)
Dim n As Integer
Dim mid As Double
Call bubblesort(x)
n = x.Rows.count
mid = x.Cells(n / 2, 1).Value
middle = mid
End Function
Reinventing the Wheel: VBA Median UDF
Reinventing the wheel
Median
MEDIAN
VarType
Cell error values
Function MyMedian(ByVal SourceRange As Range) As Variant
Const ProcName As String = "MyMedian"
On Error GoTo ClearError
' Calculate the source range number of cells ('dnCount').
Dim srCount As Long: srCount = SourceRange.Rows.Count
Dim scCount As Long: scCount = SourceRange.Columns.Count
Dim dnCount As Long: dnCount = srCount * scCount
Dim sData() As Variant
' Write the values from the source range to the source array ('sData'),
' a 2D one-based array.
If dnCount = 1 Then ' one cell
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = SourceRange.Value
Else ' multiple cells
sData = SourceRange.Value
End If
' Define the destination array('dArr'), a 1D one-based array.
Dim dArr() As Double: ReDim dArr(1 To dnCount)
Dim sValue As Variant
Dim sr As Long, sc As Long
Dim sNumber As Double
Dim dn As Long, n As Long, cn As Long
Dim dNumber As Double
' Bubble sort the numbers in the destination array
' while reading from the source array.
For sr = 1 To srCount
For sc = 1 To scCount
sValue = sData(sr, sc)
If VarType(sValue) = vbDouble Then ' the source value is a number
sNumber = CDbl(sValue)
dn = dn + 1
' Locate a greater number in the destination array.
For n = 1 To dn - 1
dNumber = dArr(n)
If dNumber > sNumber Then Exit For
Next n
' Shift the greater destination numbers to the right.
If n < dn Then
For cn = dn To n + 1 Step -1
dArr(cn) = dArr(cn - 1)
Next cn
'Else ' the source number is the greatest number; do nothing
End If
' Write the current source number to the destination array.
dArr(n) = sNumber
'Else ' the source value is not a number; do nothing
End If
Next sc
Next sr
' Mimicking the Excel 'MEDIAN' function to return '#NUM!'
' when there is no number in the source range.
If dn = 0 Then MyMedian = CVErr(xlErrNum): Exit Function
' Return the median using the middle destination array value(s).
If dn Mod 2 = 0 Then ' even
MyMedian = (dArr(dn / 2) + dArr(dn / 2 + 1)) / 2
Else ' odd
MyMedian = dArr(Int(dn / 2) + 1)
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
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
In Google Sheets, I can use the ={range1;range2;...} notation to append multiple ranges into one as part of a formula. In Excel, it seems this functionality isn't available as a formula. I would like to create a user defined function to allow me to append ranges in Excel.
Ideally, the function would have a similar simplicity to the Google Sheets version.
I've attached a link to a public Google Sheets document with a basic use case, in case it's helpful.
I've tried Tom's answer but it gives an error (see example photo).
Thanks!
Here is a general UDF to stack ranges of the same number of columns:
Function vStack(ParamArray rng() As Variant) As Variant
If TypeName(rng(1)) <> "Range" Then Exit Function
Dim otarr() As Variant
ReDim otarr(1 To 100000, 1 To rng(1).Columns.Count)
Dim z As Long
z = 1
Dim i As Long
For i = LBound(rng) To UBound(rng)
If TypeName(rng(i)) <> "Range" Then Exit Function
If i > LBound(rng) Then
If rng(i).Columns.Count <> rng(i - 1).Columns.Count Then Exit Function
End If
Dim rngarr As Variant
rngarr = Intersect(rng(i), rng(i).Parent.UsedRange)
Dim j As Long
For j = LBound(rngarr, 1) To UBound(rngarr, 1)
Dim k As Long
For k = LBound(rngarr, 2) To UBound(rngarr, 2)
otarr(z, k) = rngarr(j, k)
Next k
z = z + 1
Next j
Next i
Dim nArray() As Variant
ReDim nArray(1 To z - 1, 1 To UBound(otarr, 2))
For i = 1 To z - 1
For j = 1 To UBound(otarr, 2)
nArray(i, j) = otarr(i, j)
Next j
Next i
vStack = nArray
End Function
One note, I limit the initial array to 100,000 rows. If this is not enough you can up that to what ever you want, but also think, "Am I treating Excel as a database?". If the answer is yes, it is time to make the switch to an actual referential database.
Then one can use it in a formula:
=FILTER(vStack(A:C,F:H),vStack(A:A,F:F)="Apples")
Edit to include a version that works with arrays ie: =vstack({1;2;3},{4;5;6})
Function vStack(ParamArray rng() As Variant) As Variant
Dim otarr() As Variant
If TypeName(rng(1)) = "Range" Then
ReDim otarr(1 To 100000, 1 To rng(1).Columns.Count)
Else
ReDim otarr(1 To 100000, 1 To UBound(rng(1), 2))
End If
Dim z As Long
z = 1
Dim i As Long
For i = LBound(rng) To UBound(rng)
If i > LBound(rng) Then
If TypeName(rng(i)) = "Range" Then
If rng(i).Columns.Count <> UBound(otarr, 2) Then Exit Function
Else
If UBound(rng(i), 2) <> UBound(otarr, 2) Then Exit Function
End If
End If
Dim rngarr As Variant
If TypeName(rng(i)) = "Range" Then
rngarr = Intersect(rng(i), rng(i).Parent.UsedRange)
Else
rngarr = rng(i)
End If
Dim j As Long
For j = LBound(rngarr, 1) To UBound(rngarr, 1)
Dim k As Long
For k = LBound(rngarr, 2) To UBound(rngarr, 2)
otarr(z, k) = rngarr(j, k)
Next k
z = z + 1
Next j
Next i
Dim nArray() As Variant
ReDim nArray(1 To z - 1, 1 To UBound(otarr, 2))
For i = 1 To z - 1
For j = 1 To UBound(otarr, 2)
nArray(i, j) = otarr(i, j)
Next j
Next i
vStack = nArray
End Function
UPDATED With Dynamic Available Ranges
This function should create what you want. Note this does not take into account blanks or headers. See sample spreadsheet with working result.
Function combineRange(ParamArray theRanges() As Variant) As Variant()
Dim totalColumns As Long, z As Long, r As Long, g As Long, aCell As Range
ReDim zRanges(0) As Range
For r = LBound(theRanges) To UBound(theRanges)
ReDim Preserve zRanges(r)
Set zRanges(r) = theRanges(r)
Set zRanges(r) = Intersect(zRanges(r), zRanges(r).Worksheet.UsedRange)
totalColumns = Application.WorksheetFunction.Max(zRanges(r).Columns.Count, totalColumns)
Next r
r = 1
ReDim theRay(1 To totalColumns, 1 To r)
For z = LBound(zRanges) To UBound(zRanges)
For Each aCell In zRanges(z).Columns(1).Cells
ReDim Preserve theRay(1 To totalColumns, 1 To r)
For g = 1 To zRanges(z).Columns.Count
theRay(g, r) = aCell.Offset(0, g - 1).Value
Next g
r = r + 1
Next aCell
Next z
combineRange = Application.WorksheetFunction.Transpose(theRay)
End Function
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
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