I'm currently working on this vba programming question.
Here's my code:
Option Explicit
Option Base 1
Function ShiftVector(rng As Range, n As Integer) As Variant
Dim A() As Variant, B() As Variant
Dim nr As Integer, i As Integer
nr = rng.Rows.Count
ReDim A(nr, 1) As Variant
ReDim B(nr, 1) As Variant
A = rng
For i = 1 To nr - n
If i <= (nr - n) Then
B(i, 1) = A(i + n, 1)
End If
Next i
For i = (nr - n + 1) To nr
If i <= nr Then
B(i, 1) = A(i - nr + n, 1)
End If
Next i
ShiftVector = B
End Function
When n=3, the top 3 items cannot be wrapped down to the bottom. Instead, 0s are showing in the bottom three cells.
Here is the flowchart to the problem.
Any assistance will be much appreciated, thanks!
Running your code, it works nicely for values between 0 to the number of rows of rng (including n=3. However, it fails for values of n > nr and also n < 0 (negative), which is important if the user of your function needs to shift the vector down by n positions.
I also have to admit that I struggled to understand the logic of your algorithm and why you need 2 loops to perform the task (entirely a failure on my part).
Edit:
I completely undertand the logic of your algorithm now with the aid of your nice flow-chart. I don't see why it wouldn't work for you, it works well on my computer for 0 <= n <= nr.
End of Edit
In situations like this using Mod seems like the most natural option to me. It accounts for all values of n nicely (positive, negative or zero)
Here is a working code. If you want to build your own algorithm, then look away now :)
Please note the use of Long wherever one might be tempted to use Integer, but obviously you should change it if it is a requirement of the question.
Function ShiftVector(rng As Range, n As Long) As Variant
Application.Volatile
Dim aResult() As Variant
Dim vRng As Variant
Dim lRows As Long
Dim i As Long
Dim i_new As Long
vRng = rng.Value ' Process vector more efficiently
lRows = rng.Rows.Count ' Don't use Ubound(vRng) here
ReDim aResult(1 To lRows, 1 To 1)
If lRows = 1 Then
' vRng is simply the value of the cell (not array of values)
' You'll get an error in you try something like vRng(i, 1)
aResult(1, 1) = vRng
Else
For i = 1 To lRows
' This is the transformation rule.
' Using Mod is the most natural way in this case
' and works for all values of n (+, - and 0).
' iRows * Abs(n) ensures (i - n + iRows * Abs(n)) > 0
i_new = (i - n + lRows * Abs(n)) Mod lRows
' If n is a multiple of iRows then i_new=0
If i_new = 0 Then i_new = lRows
' Perform transformation
aResult(i_new, 1) = vRng(i, 1)
Next i
End If
ShiftVector = aResult
End Function
Something like this:
Function ShiftVector(rng As Range, n As Integer) As Variant
Dim A As Variant, B As Variant
Dim nr As Long, i As Long, pos As Long
A = rng.Value
nr = UBound(A, 1)
ReDim B(1 To nr, 1 To 1)
For i = 1 To nr
pos = IIf(i > n, i - n, (nr - n) + i)
B(pos, 1) = A(i, 1)
Next i
ShiftVector = B
End Function
FYI I would avoid Option Base 1 in VBA - the default base is zero and it's best to leave it at that. And Option Base 1 has no impact on the base of an array obtained using someRange.Value (always 1)
Related
I've limited knowledge in maths so pardon the terms if i'm mistaken.
I need to create all possible combinations from multiple sets with at least 1 item included from that set.
- SetA: [1, 2, 3, 4, 5, 6, 7]
- SetB: [a, b, c, d]
- SetC: [!, #, #, $, %]
Example Output:
- [1,a,!]
- [1,2,a,c,#]
- [1,2,3,4,5,6,7,a,b,c,d,!,#,#,$,%]
Is there a specific combination formula for this since i can only come up with nested loops and i'm not sure if it is even correct.
#barrycarter had good idea of obtaining the power set. However, we don't need to reject anything as we are not getting the power set of the union of sets (this would end up being inefficient as there will be many rejects as the number of sets grows). We simply get the power set of each set, then get all combinations of those power sets. The subroutine below, works for an arbitrary number sets of arbitrary length.
Sub CreateAllCombs()
Dim ArrayOfPowSets() As Variant, mySet() As Variant, ArrCounter() As Long, myPS As Variant
Dim myCombs() As Variant, nextComb() As Variant, ParentComb() As Variant, ArrMax() As Long
Dim i As Long, j As Long, k As Long, count1 As Long, count2 As Long, CombExist As Boolean
Dim tempCol As Long, myMax As Long, maxRow As Long, totalCombs As Long
With ActiveSheet
maxRow = .Cells(.Rows.count, "A").End(xlUp).Row
End With
ReDim ArrayOfSets(1 To maxRow, 1 To 1)
ReDim ArrCounter(1 To maxRow)
ReDim ArrMax(1 To maxRow)
myMax = 0
For i = 1 To maxRow
With ActiveSheet
tempCol = .Cells(i, .Columns.count).End(xlToLeft).Column
End With
ReDim mySet(1 To tempCol)
For j = 1 To tempCol: mySet(j) = Cells(i, j): Next j
myPS = PowerSet(mySet)
ArrMax(i) = UBound(myPS)
If ArrMax(i) > myMax Then
myMax = ArrMax(i)
ReDim Preserve ArrayOfPowSets(1 To maxRow, 1 To ArrMax(i))
End If
For j = 1 To ArrMax(i)
ArrayOfPowSets(i, j) = myPS(j)
Next j
ArrCounter(i) = 1
Next i
CombExist = True
totalCombs = 0
Do While CombExist
count1 = 1
ReDim ParentComb(1 To 1)
For i = 1 To maxRow - 1
For j = 1 To UBound(ArrayOfPowSets(i, ArrCounter(i)))
ReDim Preserve ParentComb(1 To count1)
ParentComb(count1) = ArrayOfPowSets(i, ArrCounter(i))(j)
count1 = count1 + 1
Next j
Next i
For i = 1 To ArrMax(maxRow)
count2 = count1
nextComb = ParentComb
For j = 1 To UBound(ArrayOfPowSets(maxRow, i))
ReDim Preserve nextComb(1 To count2)
nextComb(count2) = ArrayOfPowSets(maxRow, i)(j)
count2 = count2 + 1
Next j
totalCombs = totalCombs + 1
ReDim Preserve myCombs(1 To totalCombs)
myCombs(totalCombs) = nextComb
Next i
k = maxRow - 1
Do While (ArrCounter(k) >= ArrMax(k))
ArrCounter(k) = 1
k = k - 1
If k = 0 Then Exit Do
Loop
If k > 0 Then ArrCounter(k) = ArrCounter(k) + 1 Else CombExist = False
Loop
Sheets("Sheet2").Select
For i = 1 To totalCombs
For j = 1 To UBound(myCombs(i))
Cells(i, j) = myCombs(i)(j)
Next j
Next i
End Sub
I used a slightly modified version of the power set function written by John Coleman found here
Function PowerSet(Items As Variant) As Variant
Dim PS As Variant
Dim i As Long, j As Long, k As Long, n As Long
Dim subset() As Variant
n = UBound(Items)
ReDim PS(1 To 1 + 2 ^ n - 2)
For i = 1 To 2 ^ n - 1
ReDim subset(1 To n)
k = 0
For j = 0 To n - 1
If i And 2 ^ j Then
k = k + 1
subset(k) = Items(j + 1)
End If
Next j
ReDim Preserve subset(1 To k)
PS(i) = subset
Next i
PowerSet = PS
End Function
This assumes that SetA is on row 1, SetB is on row 2, etc. Observe:
Additionally, the reader should be warned that this may take a while as there is over 14 million possible combinations.
(2^3 - 1) * (2^5 - 1) * (2^16 - 1) = 7 * 31 * 65535 = 14221095
Also, all combinations are written out generically to Sheet2.
I think i found my solution please verify.
First, for each sets, i created all possible combinations and checked the length using the sum of pascal triangle without the null or this formula:
n!/(r!(n-r)!) - 1
e.g.
SetB: [a, b, c, d] -> [a,b,c,d,ab,ac,ad,bc,bd,cd,abc,abd,acd,bcd,abcd]
After creating all possible combinations for each sets, I just used product rule
[SetA] X [SetB] X [SetC]
Which resulted for all possible combinations for:
multiple items
multiple sets
no repeat
no order
Reference: https://www.mathsisfun.com/combinatorics/combinations-permutations-calculator.html
EDIT1: checking for amount of combinations per set can also be (2^n)-1 where n= length of set
Have you tried using nested for loops.
Sub Hello()
MsgBox ("Hello, world!")
Dim arr1
arr1 = Array("1", "2", "3")
Dim arr2
arr2 = Array("a", "b", "c")
Dim arr3
arr3 = Array("!", "#", "$")
For i = 0 To UBound(arr1)
For j = 0 To UBound(arr2)
For k = 0 To UBound(arr3)
MsgBox (arr1(i) & arr2(j) & arr3(k))
Next
Next
Next
End Sub
I have a workseeht with about 20.000 cells of data. This data belong to a different sample every 200 rows. I would like to store this data into a 3 dimensional array to be able to access the data afterwards, I'd like to do something like this:
lastCol = 15
n = 1
For i = 1 To 200 Step 199
sample(n) = sheets(1).range(Cells(i, lastCol), Cells(i + 199, lastCol)).Resize(200, lastCol)
n = n + 1
Next
*I know that the way I'm assigning the values is incorrect, but I haven't managed to find a clear explanation on how to do this correctly.
*To clarify this again: Im trying to store and then access a matrix that would have the following size: 10x200x15, 10 samples of 200 rows times 15 columns
A 1D array containing 200 (2D) range variables might be a better way to go about it:
Sub Test()
Dim i As Long
Dim v As Variant
ReDim v(1 To 1)
For i = 1 To 20000 Step 200
If VarType(v(1)) <> vbVariant Then ReDim Preserve v(UBound(v) + 1)
Set v(UBound(v)) = ThisWorkbook.Worksheets("Sheet1").Range("A" & i).Resize(200, 15)
Debug.Print v(UBound(v)).Address
Next i
End Sub
The Debug.Print command produce the output below, confirming that the correct range variables are in the variant array:
$A$1:$O$200
$A$201:$O$400
$A$401:$O$600
...
...
$A$19401:$O$19600
$A$19601:$O$19800
$A$19801:$O$20000
Another option (if you want exactly the structure you mentioned) would be to read the range in an array and then loop through it and put it in the desired structure, like in the following code:
Sub Arrays()
Dim InArr As Variant
Dim OutArr() As String
Dim R As Long, rowcount As Long, C As Integer, colcount As Integer, Samp As Long
InArr = ThisWorkbook.Sheets(1).Range("A1:B2000").Value2
rowcount = UBound(InArr, 1)
colcount = UBound(InArr, 2)
ReDim OutArr(1 To Int((rowcount - 1) / 200) + 1, 1 To 200, 1 To colcount)
For R = 1 To rowcount
Samp = Int((R - 1) / 200) + 1
For C = 1 To colcount
OutArr(Samp, (R - 1) Mod 200 + 1, C) = InArr(R, C)
Next C
Next R
End Sub
Really new and self taught so would appreciate any help
Have allocated a range from a spreadsheet to an array but it starts the data in row 1 of the array.
Dim arr As Variant
arr = Range("DATA")
Is there any way of starting in row 0?
Thanks in advance
Range operations are slow, the best is to use a temporary array , and then make a copy to another array while changing the start position.
Indeed array -> array = very fast
Dim arr, arr2 As Variant
arr2 = Range("DATA").value2
redim arr(0 to ubound(arr2)-1, 0 to ubound(arr2,2)-1)
for i= 0 to ubound(arr2)-1
for j= 0 to ubound(arr2,2)-1
arr(i,j)= arr2(i+1,j+1)
next
next
Well, if you are going to clean-up range-derived VBA arrays, why not make them 1-dimensional instead of falsely 2-dimensional in the case that you are dealing with a column range or a row range? The following does that as a default and also uses 0-based as a default, but provides ways to override both defaults:
Function RangeToArray(R As Range, Optional b As Long = 0, Optional collapse As Boolean = True) As Variant
'returns a b-based array of the values in R
'if the Range is 1-dimensional and collapse is true, the array returned is 1-dimensional
Dim i As Long, j As Long, m As Long, n As Long, k As Long
Dim vals As Variant, arr As Variant
m = R.Rows.Count
n = R.Columns.Count
If m = 1 And n = 1 Then 'to catch an edge-case
ReDim vals(1 To 1, 1 To 1)
vals(1, 1) = R.Value
Else
vals = R.Value
End If
If collapse And (m = 1 Or n = 1) Then
k = Application.WorksheetFunction.Max(m, n)
ReDim arr(b To b + k - 1)
For i = 1 To k
If m = 1 Then
arr(b + i - 1) = vals(1, i)
Else
arr(b + i - 1) = vals(i, 1)
End If
Next i
Else
ReDim arr(b To b + m - 1, b To b + n - 1)
For i = 1 To m
For j = 1 To n
arr(b + i - 1, b + j - 1) = vals(i, j)
Next j
Next i
End If
RangeToArray = arr
End Function
It would be used like A = RangeToArray(Range("A1:B3"))
I tried to write a simple bubble sort function in VBA but it does not work. The code is following
Public Function BubbSort(arr As Variant) As Variant
Dim arrReturn As Variant
arrReturn = Array()
ReDim arrReturn(UBound(arr))
arrReturn = arr.Value
For i = 1 To UBound(arr) - 1
For j = 1 To UBound(arr) - 1
If arrReturn(j) > arrReturn(j + 1) Then
temp = arrReturn(j)
arrReturn(j) = arrReturn(j + 1)
arrReturn(j + 1) = temp
End If
Next j
Next i
arr = arrReturn
End Function
In Excel I tried to select 4x1 range of cells and insert formula $=BubbSort(A1:A4)$ and press Ctrl+Shift+Enter so it works as array function, but it said "Function has an error". Any help please?
If arr is a range then UBound(arr) will throw an error. The way around this is to use the line
arrReturn = arr.Value
instead of just
arrReturn = arr
and then to use Ubound(arrReturn)
Also -- arrReturn would be a 2-d array rather than a 1-d array. All references to arrReturn should look like e.g. arrReturn(j,1)
Finally -- you aren't returning anything (which is assigning to the function name in VBA).
The following code seems to work (if Bubble sort can ever be described as "working"):
Public Function BubbSort(arr As Variant) As Variant
Dim arrReturn As Variant
Dim i As Long, j As Long, temp As Variant
arrReturn = arr.Value
For i = 1 To UBound(arrReturn) - 1
For j = 1 To UBound(arrReturn) - 1
If arrReturn(j, 1) > arrReturn(j + 1, 1) Then
temp = arrReturn(j, 1)
arrReturn(j, 1) = arrReturn(j + 1, 1)
arrReturn(j + 1, 1) = temp
End If
Next j
Next i
BubbSort = arrReturn
End Function
The above will only work when passed a range contained in a column. It is possible to make it more flexible to be able to handle either a column range or a row range or a VBA array.
While John Coleman's code will technically work it is fundamentally flawed. You will notice that the outer loop over i is invariant - the loop value is never used.
Bubblesort is indeed inefficient (at least for long sequences) but not as much as the code enforces.
At the end of the inner loop the biggest element will have propagated to the very end of the sequence. So, there is no need to compare it with any previous elements during the next repetitions of the inner loop. If you shorten the inner loop every time by 1 (in total, by the number of outer loop completions so far, i) you reduce the number of loops by 50%:
Public Function Bubble1(arr As Variant) As Variant
Dim arrReturn As Variant
Dim i As Long, j As Long, temp As Variant
arrReturn = arr.Value
For i = UBound(arrReturn, 1) To 2 Step -1
For j = 1 To i - 1
If arrReturn(j, 1) > arrReturn(j + 1, 1) Then
temp = arrReturn(j, 1)
arrReturn(j, 1) = arrReturn(j + 1, 1)
arrReturn(j + 1, 1) = temp
End If
Next j
Next i
Bubble1 = arrReturn
End Function
While we're at it we can further reduce the effort by observing that if all elements are sorted (that is, no exchanges have happened in the inner loop) we are finished - further loops are redundant. We can implement this with a flag:
Public Function Bubble2(arr As Variant) As Variant
Dim arrReturn As Variant
Dim i As Long, j As Long, temp As Variant
Dim sorted As Boolean
arrReturn = arr.Value
For i = UBound(arrReturn, 1) To 2 Step -1
sorted = True
For j = 1 To i - 1
If arrReturn(j, 1) > arrReturn(j + 1, 1) Then
temp = arrReturn(j, 1)
arrReturn(j, 1) = arrReturn(j + 1, 1)
arrReturn(j + 1, 1) = temp
sorted = False
End If
Next j
If sorted Then Exit For
Next i
Bubble2 = arrReturn
End Function
I'm using VBA to calculate pairwise slopes, store them in an array, then using Chip Pearson's technique of transposing the array on a workheet to sort them. My code fails when the number of slopes exceeds 65K, which would make sense in Excel 2003, due to number of rows. I thought it would work in Excel 2010, but I seem to have the same issue. Does anyone know if there's limitations to the Resize property or Transpose method?
Thanks
Sub pairwise()
Dim endrow As Long, i As Long, j As Long, s As Long
Dim num As Double, denom As Double, sij As Double
Dim r As Range
Dim slopes()
endrow = Range("A1").End(xlDown).Row
n = endrow - 1
nrd = endrow * n / 2
ReDim slopes(nrd)
Debug.Print LBound(slopes); UBound(slopes)
For i = 1 To n
For j = (i + 1) To endrow
num = Cells(i, 2).Value - Cells(j, 2).Value
denom = Cells(i, 1).Value - Cells(j, 1).Value
If denom <> 0 Then
sij = num / denom
slopes(s) = sij
s = s + 1
End If
Next j
Next i
Set r = Range("C1").Resize(UBound(slopes) - LBound(slopes) + 1, 1)
r = Application.Transpose(slopes)
' sort the range
r.Sort key1:=r, order1:=xlAscending, MatchCase:=False
End Sub
I found the same limitation on the INDEX function. http://dailydoseofexcel.com/archives/2013/10/11/worksheetfunction-index-limitations/
Here's how you can make the output array a two dimensional array and read in all the values at once rather than inside a loop.
Sub pairwise()
Dim lEndRow As Long
Dim vaValues As Variant
Dim aSlopes() As Variant
Dim lCnt As Long
Dim rOutput As Range
Dim i As Long, j As Long
'A 2d array here can easily be written to a sheet
lEndRow = Sheet3.Range("a1").End(xlDown).Row
ReDim aSlopes(1 To lEndRow * (lEndRow - 1), 1 To 1)
'Create a two-d array of all the values
vaValues = Sheet3.Range("A1").Resize(lEndRow, 2).Value
'Loop through the array rather than the cells
For i = LBound(vaValues, 1) To UBound(vaValues, 1) - 1
For j = 1 + 1 To UBound(vaValues, 1)
If vaValues(i, 1) <> vaValues(j, 1) Then
lCnt = lCnt + 1
aSlopes(lCnt, 1) = (vaValues(i, 2) - vaValues(j, 2)) / (vaValues(i, 1) - vaValues(j, 1))
End If
Next j
Next i
'Output the array to a range, and sort
Set rOutput = Sheet3.Range("C1").Resize(UBound(aSlopes, 1), UBound(aSlopes, 2))
rOutput.Value = aSlopes
rOutput.Sort rOutput.Cells(1), xlAscending, , , , , , , , False
End Sub
It a limitation of the Transpose method.
My suggestion would be to declare your array as 2D from the start
Redim Slopes(1 To nrd, 1 To 1)
Also, your should use the Variant Array approach instead of looping over cells in your For loop