Passing rows or columns from a sheet to a VBA function - excel

I have written a VBA code that solves a set of algebraic equations of the form
A(i)X(i-1)+B(i)X(i)+C(i)X(i+1)=R(i)
A portion of the function is given below. Currently, the coefficients A, B, C, and R have to be stored in columns in the main worksheet to be passed to the function. Is there a way to provide the flexibility of having the coefficients either in rows or columns?
Function TRIDI(ByVal Ac As Range, ByVal Bc As Range, ByVal Cc As Range, _
ByVal Rc As Range) As Variant
Dim BN As Single
Dim i As Integer
Dim II As Integer
Dim A() As Single, B() As Single, C() As Single, R() As Single, X() As Single
N = Ac.Rows.Count
ReDim A(N), B(N), C(N), R(N), X(N)
For i = 1 To N
A(i) = Ac.Parent.Cells(Ac.Row + i - 1, Ac.Column)
B(i) = Bc.Parent.Cells(Bc.Row + i - 1, Bc.Column)
C(i) = Cc.Parent.Cells(Cc.Row + i - 1, Cc.Column)
R(i) = Rc.Parent.Cells(Rc.Row + i - 1, Rc.Column)
Next i

maybe you can add an optional variable to the function to indicate a column function.
Se example: (Edited)
Function TRIDI(ByVal Ac As Range, ByVal Bc As Range, ByVal Cc As Range, ByVal Rc As Range) As Variant
Dim BN As Single
Dim i As Integer
Dim II As Integer
Dim ColumnN As Boolean
Dim A() As Single, B() As Single, C() As Single, R() As Single, X() As Single
If Ac.Rows.Count = 1 Then
N = Ac.Columns.Count
ColumnN = True
Else If Ac.Columns.Count = 1 Then
N = Ac.Rows.Count
Else
Exit Function
End If
ReDim A(N), B(N), C(N), R(N), X(N)
If ColumnN = True Then
For i = 1 To N
A(i) = Ac.Parent.Cells(Ac.Row, Ac.Column + i - 1)
B(i) = Bc.Parent.Cells(Bc.Row, Bc.Column + i - 1)
C(i) = Cc.Parent.Cells(Cc.Row, Cc.Column + i - 1)
R(i) = Rc.Parent.Cells(Rc.Row, Rc.Column + i - 1)
Next i
Else
For i = 1 To N
A(i) = Ac.Parent.Cells(Ac.Row + i - 1, Ac.Column)
B(i) = Bc.Parent.Cells(Bc.Row + i - 1, Bc.Column)
C(i) = Cc.Parent.Cells(Cc.Row + i - 1, Cc.Column)
R(i) = Rc.Parent.Cells(Rc.Row + i - 1, Rc.Column)
Next i
End If
End Function
I might have missed some of the functionality of your function in the example, but i think you get the point. If this does not work give me feedback and ill try another solution. :)
Edit: You can also make the function above a function that receives input form two other functions named CTRIDI and RTRIDI. These two functions just pas ether true or false to the column variable.

Related

Select random item from 1d array no repeat

I am trying to select random item from 1d array using this code
Sub Select_Random_Item_From_1D_Array()
Dim arr(), x As Long
arr = Array("Good", "Very Good", "Excellent")
Randomize
x = Int((UBound(arr) + 1) * Rnd + 1)
Debug.Print arr(x - 1)
End Sub
How can I be able to prevent a repetition? I mean I need to select all the items randomly with no repetition. And if all the items are selected then to reset the process. Simply I need to select all the items randomly
This is a simple way to return a random permutation of an array that takes exactly n steps, where n is the number of entries in the array.
Dim arr(), x As Long, r As Long
arr = Array("Good", "Very Good", "Excellent")
x = UBound(arr)
While x >= 0
r = Int(Rnd * x)
Debug.Print arr(r)
arr(r) = arr(x)
x = x - 1
Wend
Pick r at random from (0,..,x) and print out arr(r). Then replace the entry at r with the entry at x, and choose again, but this time from (0,..,x-1), and repeat until x=0.
A fuller version that lets you read one entry at a time is here:
Place this in a module:
Public rarr(), ctr As Integer, arr()
Sub init()
With Cells
.Clear
.ColumnWidth = 10
End With
Dim x As Long, r As Long
arr = Array("Very Poor", "Poor", "Average", "Good", "Very Good", "Excellent")
x = UBound(arr)
ReDim rarr(0 To x)
Randomize
While x >= 0
r = Int(Rnd * x)
rarr(x) = arr(r)
arr(r) = arr(x)
x = x - 1
Wend
[a1:f1] = rarr
ctr = 0
End Sub
Sub Button1_Click()
Cells(ctr + 3, 1) = rarr(ctr)
ctr = ctr + 1
If ctr > UBound(rarr) Then init
End Sub
and add two buttons to the worksheet. Point one at init and the other at Button1_Click. Click init first, and then pressing Button1 displays a random and unique entry one at a time.
You could create a second array of Booleans, having the same length.
This array is initialized with only False. If a value from your array is selected by the rand, then set the boolean array matching value to True. And if the rand value next time is on a value which has already been selected (with a True in the boolean array), do the rand again
Try this little example step by step, you'll see the logic:
Sub Select_Random_Item_From_1D_Array()
Dim arr(), x As Long, cpt As Long
Dim mBool(2) As Boolean
cpt = 0
arr = Array("Good", "Very Good", "Excellent")
Do While cpt < 3 '3 being the number of items in your array + 1 (from 0 to 2)
Randomize
x = Int((UBound(arr) + 1) * Rnd + 1)
If mBool(x - 1) = False Then
mBool(x - 1) = True
Debug.Print arr(x - 1)
cpt = cpt + 1
End If
Loop
End Sub
It will print a random item from your array, and every time it does so it changes the matching value of the 2nd array from False to True. Then it does it again and if it has already been printed (if the matching value on the boolean array is True) it tries again.
I added a variable named cpt, which goes from 0 to the number of items in your array, it makes the algorithm stop when it has printed all the items one time.
This is probably not he best way to do what you want, but it works and it's not that complicated
Select Random Item Series
Option Explicit
Sub resetRandomItem()
getRandomItem
End Sub
Sub selectRandomItem()
Dim arr As Variant
arr = Array("Bad", "Better", "Good", "Very Good", "Excellent")
Debug.Print getRandomItem(arr)
End Sub
' If x elements in 1D array, it returns a series of x different values.
Function getRandomItem(Optional Data1D As Variant) As Variant
Static arr As Variant
' Reset 'arr': use 'getRandomItem' without 'Data1D' parameter.
If IsMissing(Data1D) Then arr = Empty: Exit Function
If IsEmpty(arr) Then arr = Data1D
Dim lb As Long: lb = LBound(arr)
Dim ub As Long: ub = UBound(arr)
If lb = ub Then
getRandomItem = arr(lb)
arr = Empty
Else
Randomize
Dim x As Long: x = Int((ub - lb + 1) * Rnd + 1)
Dim y As Long: y = x + lb - 1
getRandomItem = arr(y)
arr(y) = Empty
Dim i As Long, k As Long
For i = lb To ub - 1
If arr(i) = Empty Then
For k = i + 1 To ub
arr(k - 1) = arr(k)
Next k
Exit For
End If
Next i
ReDim Preserve arr(ub - 1)
End If
End Function

VBA - Curly bracket {} equivalent operation

In VBA for excel, I can simply use a formula as such as =SUM(X*{Y,Z}) to sum the multiplied results, however transferring this to VBA Is not a 1:1 as it does not support curly brackets for array purposes. Using a matching setup such as Result = Application.Worksheetfunction.Sum(X * Array(Y, Z)) results in a type mismatch error.
I do know I can simple use the evaluate function, or individually multiply and add after, very specifically seeing if there is a way to accomplish the above in a 1:1 matching method.
Approach using the MMULT() function
If you you want to multiply e.g. {10,1,30} with a single factor you'd have to repeat the factor in the 2nd argument within another array:
Dim a = Array(10, 1, 30)
b = WorksheetFunction.MMult(a, Application.Transpose(Array(1, 1, 1)))
Debug.Print b(1) ' 41
It would also be possible to define a separate array with corresponding individual factors for each base item:
Dim a = Array(10, 1, 30)
Dim b: b = WorksheetFunction.MMult(a, Application.Transpose(Array(1, 1.5, 1)))
Debug.Print b(1) ' 41.5
A flexibilized function comprising both versions (for any zero or 1-based arrays, filling up missing factors in version 2. with zero values by default) might be:
Function SumPro(a, factor, Optional missingFactor As Long = 0)
'Purp: sum products of array items multiplied by a) a single value or b) a set of factors in a 1-dim array
'Note: assumes "flat" 1-dim array(s)
' fills up missing factors with zero values by default (missingFactor = 0)
Dim n As Long: n = UBound(a) - LBound(a) + 1 ' items count independant from base start
Dim b: ReDim b(1 To n, 1 To 1) ' provide for sufficient 1-based factor items
Dim i As Long ' counter
If Not IsArray(factor) Then ' a) := single value
For i = 1 To UBound(b): b(i, 1) = factor: Next ' insert as many factors as array items
Else ' b) := already a factor array
For i = LBound(factor) To UBound(factor)
b(i - LBound(factor) + 1, 1) = factor(i) ' collect existing factors
Next
For i = i - LBound(factor) + 1 To UBound(b)
b(i, 1) = missingFactor ' complete missing factor items
Next
End If
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' calculate SumProduct (without Evaluate)
' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dim tmp: tmp = Application.MMult(a, b) ' execute MMULT() function
' return function result
SumPro = tmp(1) ' return result
End Function
Example call
Dim a: a = Array(10, 1, 30) ' base values
Dim b: b = Array(1, 1.15) ' zero-based factors, third factor left free ~> changes to zero by default
debug.print SumPro(a, b) ' ~> 11.15 as it multiplies only first two items with 10*1 + 1*1.15 + 30*0 = 11.15
For a single factor it suffices to pass a number as 2nd argument:
Debug.Print SumPro(a, 2)
WorksheetFunction.SumProduct can be used, requiring no additional function.
Debug.Print Application.WorksheetFunction.SumProduct(Array(y, z), Array(x, x))
Note that you just have to express your single factor as an array with the same shape as the target array.
Alternatively to using .Product() or .SumProduct() you an write your own functions to do algebra on arrays.
Put the following in a module
Public Function FactorArray(ByVal factor As Double, ByRef a() As Variant) As Variant()
Dim i_1 As Long, i_2 As Long, i As Long
i_1 = LBound(a, 1)
i_2 = UBound(a, 2)
Dim result() As Variant
ReDim result(i_1 To i_2)
For i = i_1 To i_2
result(i) = factor * a(i)
Next i
FactorArray = result
End Function
Public Function AddArrays(ByRef a() As Variant, ByRef b() As Variant) As Variant()
Dim i_1 As Long, i_2 As Long, i As Long
Dim j_1 As Long, j_2 As Long, n As Long
i_1 = LBound(a, 1)
i_2 = UBound(a, 2)
j_1 = LBound(b, 1)
j_2 = UBound(b, 2)
n = WorksheetFunction.Min(i_2 - i_1 + 1, j_2 - j_1 + 1)
Dim result() As Variant
ReDim result(0 To n-1)
For i = 0 To n-1
result(i) = a(i + i_1) + b(i + i_2)
Next i
AddArrays = result
End Function
Public Function SubArrays(ByRef a() As Variant, ByRef b() As Variant) As Variant()
SubArrays = AddArrays(a, FactorArray(-1, b))
End Function
Public Function DivArray(ByRef a() As Variant, ByVal divisor As Double) As Variant()
DivArray = FactorArray(1 / divisor, a)
End Function
And then use it as
Dim arr as Variant, res as Variant
arr = Array(1,2,3,4)
res = FactorArray(X, arr)
WorksheetFunction.Sum(res)
Of course for your specific example you could factor out X and just do a scalar multiplication.
Dim arr as Variant, res as Variant
arr = Array(1,2,3,4)
res = X*WorksheetFunction.Sum(arr)

Find minimum Levenshtein Distance between one word and an array of thousands

So my users wrote their addresses in a registration form, but a lot of them have typos. I have another list retrieved from the city records with the correct spelling of those addresses. So let's say I have "Brooklny" typed by them and I have the list of correct names: Brooklyn, Manhattan, Bronx, Staten Island, Queens (this is an example, the actual addresses are in Spanish and refer to neighborhoodS in Mexico City).
I want to find the edit distance between Brooklyn and each of the borough names and then find the word to whick Brooklyn has the minimum edit distance.
So edit distance between: Brooklny-Brooklyn is 2, Brooklny-Bronx is 4 and so on. The minimum of course is 2 with Brooklyn.
Imagine that I have Brooklny in cell A1 and Brooklyn, Manhattan, Bronx, Staten Island, Queens each in a cell from B1:B6
Im doing this in VBA for a user defined function in Excel and so far I have this code but it doesnt work.
Function Minl(ByVal string1 As String, ByVal correctos As Range) As Variant
Dim distancias(3) As Integer
Dim i, minimo As Integer
i = 0
For Each c In correctos.Cells
distancias(i) = Levenshtein(string1, c.Value)
i = i + 1
Next c
Minl = Minrange(distancias)
End Function
Function Levenshtein(ByVal string1 As String, ByVal string2 As String) As Long
Dim i As Long, j As Long
Dim string1_length As Long
Dim string2_length As Long
Dim distance() As Long
string1_length = Len(string1)
string2_length = Len(string2)
ReDim distance(string1_length, string2_length)
For i = 0 To string1_length
distance(i, 0) = i
Next
For j = 0 To string2_length
distance(0, j) = j
Next
For i = 1 To string1_length
For j = 1 To string2_length
If Asc(Mid$(string1, i, 1)) = Asc(Mid$(string2, j, 1)) Then
distance(i, j) = distance(i - 1, j - 1)
Else
distance(i, j) = Application.WorksheetFunction.Min _
(distance(i - 1, j) + 1, _
distance(i, j - 1) + 1, _
distance(i - 1, j - 1) + 1)
End If
Next
Next
Levenshtein = distance(string1_length, string2_length)
End Function
Function Minrange(ParamArray values() As Variant) As Variant
Dim minValue, Value As Variant
minValue = values(0)
For Each Value In values
If Value < minValue Then minValue = Value
Next
Minrange = minValue
End Function
I think the algorithm is right but I think I might be having trouble with the syntax. The levenshtein function works but im not sure about the other two.
To get the closest output you could use this:
Function get_match(ByVal str As String, rng As Range) As String
Dim itm As Variant, outp(0 To 2) As Variant
outp(1) = 0: outp(2) = ""
For Each itm In rng.Text
outp(0) = Levenshtein(itm, str)
If outp(0) = 0 Then
get_match = itm
Exit Function
ElseIf outp(1) = 0 Or outp(0) < outp(1) Then
outp(1) = outp(0)
outp(2) = itm
End If
Next
get_match = outp(1)
End Function
to get the distance later, you simply could run an Levenshtein(string,get_match(string,range))
Still... I'm not exactly sure what you are looking for :/

How to find FFT in Excel without using Fourier Analysis function?

I am trying to write an FFT application in Excel that claculates frequencies, amplitude and phase. I know how to use the in-built function but the data I am trying to analyse has 32,795 points, more than the maximum 4096 for the in-buit function.
Does anyone know how I can either (1) Increase the maximum number of data inputs? (2) Write my own macro to avoid using the in-built function (if this allows me to analyse all the points)? or (3) Start over in Matlab or a with programming language that allows me to analyse all the points and get all the data I need?
You can easily use the matlab built in function and it doesnt have the limitation like Excel and then import the results to excel
Yes, Excel FFT has the limit of data point 4096 and slow.
I programmed FFT using only Excel VBA code and there is no limit of the data point.
Below is the performance for the data point count.
There was a part where I could speed it up a bit, but I didn't b/c it makes the code less readable. However, even now, it may be the fastest FFT code in the Excel.
[data point] [FFT execution time]
4 kB 62ms
16 kB 235ms
64 kB 984ms
Computer Specification: 11th Gen Intel(R) Core(TM) i7-1165G7 # 2.80GHz
I implemented Cooley-Tukey algorithm, and use several techniques to speed-up the code running time in the Excel environment.
You can find the code and download the excel file in here. (https://infograph.tistory.com/351)
Otherwise you can review main logic as below:
'Module: fftProgram
'Author: HJ Park
'Date : 2019.5.18(v1.0), 2022.8.1(v2.0)
Option Explicit
Public Const myPI As Double = 3.14159265358979
Public Function Log2(X As Long) As Double
Log2 = Log(X) / Log(2)
End Function
Public Function Ceiling(ByVal X As Double, Optional ByVal Factor As Double = 1) As Double
' X is the value you want to round
' Factor is the multiple to which you want to round
Ceiling = (Int(X / Factor) - (X / Factor - Int(X / Factor) > 0)) * Factor
End Function
Public Function Floor(ByVal X As Double, Optional ByVal Factor As Double = 1) As Double
' X is the value you want to round
' Factor is the multiple to which you want to round
Floor = Int(X / Factor) * Factor
End Function
' return 0 if N is 2^n value,
' return (2^n - N) if N is not 2^n value. 2^n is Ceiling value.
' return -1, if error
Public Function IsPowerOfTwo(N As Long) As Long
If N = 0 Then GoTo EXIT_FUNCTION
Dim c As Long, F As Double
c = Ceiling(Log2(N)) 'Factor=0, therefore C is an integer number
F = Floor(Log2(N))
If c = F Then
IsPowerOfTwo = 0
Else
IsPowerOfTwo = (2 ^ c - N)
End If
Exit Function
EXIT_FUNCTION:
IsPowerOfTwo = -1
End Function
''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''
Function MakePowerOfTwoSize(ByRef r As Range, ByVal fillCount As Long) As Boolean
Dim arr() As Integer
On Error GoTo ERROR_HANDLE
'1)make a array with zero
ReDim arr(0 To fillCount - 1) As Integer
'2)set a range to be filled with zero
Dim fillRowStart As Long
Dim fillRange As Range
fillRowStart = r.Row + r.Rows.Count
Set fillRange = Range(Cells(fillRowStart, r.Column), Cells(fillRowStart + fillCount - 1, r.Column))
'3)fill as zero
fillRange = arr
'4)update range area to be extended
Set r = Union(r, fillRange)
MakePowerOfTwoSize = True
Exit Function
ERROR_HANDLE:
MakePowerOfTwoSize = False
End Function
' read the range and return it as complex value array
Function Range2Array(r As Range) As Complex()
Dim i As Long, size As Long
Dim arr() As Complex
size = r.Rows.Count
ReDim arr(0 To size - 1) As Complex
Dim re As Double, im As Double
On Error GoTo ERROR_HANDLE
For i = 1 To size
arr(i - 1) = String2Complex(r.Rows(i).Value)
Next i
Range2Array = arr
Exit Function
ERROR_HANDLE:
MsgBox "Error: " & i
End Function
Function ArrangedNum(num As Long, numOfBits As Integer) As Long
Dim arr() As Byte
Dim i As Integer, j As Integer
Dim k As Long
If (2 ^ numOfBits) <= num Then GoTo EXIT_FUNCTION
'1) Decimal number -> Reversed Binary array : (13,4) -> {1,1,0,1} -> {1,0,1,1}
ReDim arr(0 To numOfBits - 1) As Byte
For i = 0 To numOfBits - 1
j = (numOfBits - 1) - i
k = Int((num / (2 ^ j)))
arr(j) = (k And 1)
Next i
'2) Reversed Binary -> Decimal: {1,0,1,1} -> 1*2^3 + 0*2^2 + 1*2&1 + 1 = 11
Dim d As Long
For i = 0 To numOfBits - 1
d = d + (arr(i) * 2 ^ (numOfBits - 1 - i))
Next i
ArrangedNum = d
Exit Function
EXIT_FUNCTION:
ArrangedNum = 0
End Function
' rangeArr[1 to n, 1]
Function arrangeToFFTArray(arr() As Complex, size As Long, numOfBits As Integer) As Complex()
Dim i As Long, j As Long
Dim arrangedArr() As Complex
ReDim arrangedArr(0 To size - 1) As Complex
For i = 0 To size - 1
j = ArrangedNum(i, numOfBits) '{000,001,010, 011, 100, 101, 110, 111} -> {0, 4, 2, 6, 1, 5, 3, 7}
arrangedArr(j) = arr(i)
Next i
arrangeToFFTArray = arrangedArr
End Function
' calculate convolution ring W
' W[k] = cos(theta) - isin(theta)
' theta = (2pi*k/N)
Function CalculateW(cnt As Long, isInverse As Boolean) As Complex()
Dim arr() As Complex
Dim i As Long
Dim T As Double, theta As Double
Dim N As Long, N2 As Long
N = cnt
N2 = N / 2
ReDim arr(0 To N2 - 1) As Complex 'enough to calculate 0 to (N/2 -1)
T = 2 * myPI / CDbl(N)
If isInverse Then
For i = 0 To N2 - 1
theta = -(T * i)
arr(i) = Cplx(Cos(theta), -Sin(theta))
Next i
Else
For i = 0 To N2 - 1
theta = T * i
arr(i) = Cplx(Cos(theta), -Sin(theta))
Next i
End If
CalculateW = arr
End Function
' X({0,1}, [0,n-1]): 2d array. (0, n) <--> (1,n)
' src: src index of the array. 0 or 1
' tgt: tgt index of the array. 1 or 0
' s : starting index of the data in the array
' size: region size to be calculated
' kJump : k's jumping value
' W(0 ~ n-1) : Convolution ring
Sub RegionFFT(X() As Complex, src As Integer, tgt As Integer, _
s As Long, size As Long, kJump As Long, W() As Complex)
Dim i As Long, e As Long
Dim half As Long
Dim k As Long
Dim T As Complex
' Xm+1[i] = Xm[i] + Xm[i+half]W[k]
' Xm+1[i+half] = Xm[i] - Xm[i+half]W[k]
k = 0
e = s + (size / 2) - 1
half = size / 2
For i = s To e
T = CMult(X(src, i + half), W(k))
X(tgt, i) = CAdd(X(src, i), T)
X(tgt, i + half) = CSub(X(src, i), T)
k = k + kJump
Next i
End Sub
Sub WriteToTarget(tgtRange As Range, X() As Complex, tgtIdx As Integer, N As Long, roundDigit As Integer)
Dim i As Long
Dim arr() As Variant
ReDim arr(0 To N - 1) As Variant
For i = 0 To N - 1
If X(tgtIdx, i).im < 0 Then
arr(i) = Round(X(tgtIdx, i).re, roundDigit) & Round(X(tgtIdx, i).im, roundDigit) & "i"
Else
arr(i) = Round(X(tgtIdx, i).re, roundDigit) & "+" & Round(X(tgtIdx, i).im, roundDigit) & "i"
End If
Next i
tgtRange.Rows = Application.Transpose(arr)
End Sub
' xRange: input data
' tgtRange: output range
' isInverse: FFT or IFFT
Public Function FFT_Forward(xRange As Range, tgtRangeStart As Range, roundDigit As Integer, isInverse As Boolean) As Complex()
Dim i As Long, N As Long
Dim totalLoop As Integer, curLoop As Integer 'enough as Integer b/c it is used for loop varoable
Dim xArr() As Complex, xSortedArr() As Complex
Dim W() As Complex 'convolution ring
Dim X() As Complex 'output result
Dim errMsg As String
errMsg = "Uncatched error"
'1) check whether 2^r count data, if not pad to zero
Dim fillCount As Long
N = xRange.Rows.Count
fillCount = IsPowerOfTwo(N)
If fillCount = -1 Then
errMsg = "No input data. Choose input data"
GoTo ERROR_HANDLE
End If
If fillCount <> 0 Then
If MakePowerOfTwoSize(xRange, fillCount) = False Then 'xRange's size will be chnaged
errMsg = "Error while zero padding"
GoTo ERROR_HANDLE
End If
End If
'2) calculate loop count for FFT: 2->1 4->2 8->3 ...
N = xRange.Rows.Count 'xRange's size can be changed so read one more...
totalLoop = Log2(N)
'3) sort x for 2's FFT : convert to reversed binary and then convert to decimal
xArr = Range2Array(xRange) 'xArr[0,n-1]
xSortedArr = arrangeToFFTArray(xArr, N, totalLoop) 'xSortedArr[0,n-1]
'4) calculate W
W = CalculateW(N, isInverse)
'5) use 2-dimensional array to save memory space. X[0, ] <-> X[1, ]
ReDim X(0 To 1, 0 To N - 1) As Complex
For i = 0 To N - 1
X(0, i) = xSortedArr(i)
Next i
'6) Do 2's FFT with sorted x
Dim srcIdx As Integer, tgtIdx As Integer
Dim kJump As Long, regionSize As Long
tgtIdx = 0
For curLoop = 0 To totalLoop - 1
tgtIdx = (tgtIdx + 1) Mod 2
srcIdx = (tgtIdx + 1) Mod 2
regionSize = 2 ^ (curLoop + 1) ' if N=8: 2 -> 4 -> 8
kJump = 2 ^ (totalLoop - curLoop - 1) ' if N=8: 4 -> 2 -> 1
i = 0
Do While i < N
Call RegionFFT(X, srcIdx, tgtIdx, i, regionSize, kJump, W)
i = i + regionSize
Loop
Next curLoop
'7)return the value
Dim resultIdx As Integer
If (totalLoop Mod 2) = 0 Then resultIdx = 0 Else resultIdx = 1
Dim result() As Complex
ReDim result(0 To N - 1) As Complex
If isInverse = True Then
For i = 0 To N - 1
result(i) = CDivR(X(resultIdx, i), N)
Next i
Else
For i = 0 To N - 1
result(i) = X(resultIdx, i)
Next i
End If
FFT_Forward = result
Exit Function
ERROR_HANDLE:
Err.Raise Number:=vbObjectError, Description:=("FFT calculation error: " & errMsg)
End Function
Public Sub FFT(xRange As Range, tgtRangeStart As Range, roundDigit As Integer)
Dim X() As Complex
Dim tgtRange As Range
'1. calculate FFT_forward value
On Error GoTo ERROR_HANDLE
X = FFT_Forward(xRange, tgtRangeStart, roundDigit, False)
'2. write to the worksheet
Dim N As Long
N = UBound(X) - LBound(X) + 1
Dim i As Long
Dim arr() As Variant
ReDim arr(0 To N - 1) As Variant
For i = 0 To N - 1
If X(i).im < 0 Then
arr(i) = Round(X(i).re, roundDigit) & Round(X(i).im, roundDigit) & "i"
Else
arr(i) = Round(X(i).re, roundDigit) & "+" & Round(X(i).im, roundDigit) & "i"
End If
Next i
Set tgtRange = Range(Cells(tgtRangeStart.Row, tgtRangeStart.Column), Cells(tgtRangeStart.Row + N - 1, tgtRangeStart.Column))
tgtRange.Rows = Application.Transpose(arr)
Exit Sub
ERROR_HANDLE:
End Sub
Public Sub IFFT(xRange As Range, tgtRangeStart As Range, roundDigit As Integer)
Dim X() As Complex
Dim tgtRange As Range
'1. calculate FFT_forward value
On Error GoTo ERROR_HANDLE
X = FFT_Forward(xRange, tgtRangeStart, roundDigit, True)
'2.write to the worksheet
Dim N As Long
N = UBound(X) - LBound(X) + 1
Dim arr() As Variant
ReDim arr(0 To N - 1) As Variant
Dim i As Long
For i = 0 To N - 1
arr(i) = Round(X(i).re, roundDigit)
Next i
Set tgtRange = Range(Cells(tgtRangeStart.Row, tgtRangeStart.Column), Cells(tgtRangeStart.Row + N - 1, tgtRangeStart.Column))
tgtRange.Rows = Application.Transpose(arr)
Exit Sub
ERROR_HANDLE:
End Sub
Sub LoadFFTForm()
FFT_Form.Show
In alternative to the VBA solution from HeeJin, with LAMBDA functions in recent versions of Excel it is possible to implement the FFT as a pure formula (i.e. without VBA).
One such implementation is https://github.com/altomani/XL-FFT.
For power of two length it uses a recursive radix-2 Cooley-Tukey algorithm
and for other length a version of Bluestein's algorithm that reduces the calculation to a power of two case.

Simple Histogram in VBA?

I have data stored in some column (Say, Column A). The length of Column A is not fixed (depends on previous steps in the code).
I need a histogram for the values in Column A, and have it in the same sheet. I need to take the values in column A, and automatically compute M Bins, then give the plot.
I looked online for a "simple" code, but all codes are really fancy, with tons of details that I don't need, to the extent that I am not even able to use it. (I am a VBA beginner.)
I found the following code that seems to do the job, but I am having trouble even calling the function. Besides, it only does computations but does not make the plot.
Sub Hist(M As Long, arr() As Single)
Dim i As Long, j As Long
Dim Length As Single
ReDim breaks(M) As Single
ReDim freq(M) As Single
For i = 1 To M
freq(i) = 0
Next i
Length = (arr(UBound(arr)) - arr(1)) / M
For i = 1 To M
breaks(i) = arr(1) + Length * i
Next i
For i = 1 To UBound(arr)
If (arr(i) <= breaks(1)) Then freq(1) = freq(1) + 1
If (arr(i) >= breaks(M - 1)) Then freq(M) = freq(M) + 1
For j = 2 To M - 1
If (arr(i) > breaks(j - 1) And arr(i) <= breaks(j)) Then freq(j) = freq(j) + 1
Next j
Next i
For i = 1 To M
Cells(i, 1) = breaks(i)
Cells(i, 2) = freq(i)
Next i
End Sub
And then I try to call it simply by:
Sub TestTrial()
Dim arr() As Variant
Dim M As Double
Dim N As Range
arr = Range("A1:A10").Value
M = 10
Hist(M, arr) ' This does not work. Gives me Error (= Expected)
End Sub
A little late but still I want to share my solution. I created a Histogram function which might be used as array formula in the excel spread sheet. Note: you must press
CTRL+SHIFT+ENTER to enter the formula into your workbook. Input is the range of values and the number M of bins for the histogram. The output range must have M rows and two columns. One column for the bin value and one column for the bin frequency.
Option Explicit
Option Base 1
Public Function Histogram(arr As Range, M As Long) As Variant
On Error GoTo ErrHandler
Dim val() As Variant
val = arr.Value
Dim i As Long, j As Integer
Dim Length As Single
ReDim breaks(M) As Single
ReDim freq(M) As Integer
Dim min As Single
min = WorksheetFunction.min(val)
Dim max As Single
max = WorksheetFunction.max(val)
Length = (max - min) / M
For i = 1 To M
breaks(i) = min + Length * i
freq(i) = 0
Next i
For i = 1 To UBound(val)
If IsNumeric(val(i, 1)) And Not IsEmpty(val(i, 1)) Then
If val(i, 1) > breaks(M) Then
freq(M) = freq(M) + 1
Else
j = Int((val(i, 1) - min) / Length) + 1
freq(j) = freq(j) + 1
End If
End If
Next i
Dim res() As Variant
ReDim res(M, 2)
For i = 1 To M
res(i, 1) = breaks(i)
res(i, 2) = freq(i)
Next i
Histogram = res
ErrHandler:
'Debug.Print Err.Description
End Function
Not 100% sure as to the efficacy of that approach but;
Remove the parens as your calling a sub; Hist M, arr
M is declared as double but received by the function as a long; this won't work so declare it in the calling routine as long
You will need to recieve arr() As Variant
Range -> Array produces a 2 dimensional array so the elements are arr(1, 1) .. arr(n, 1)

Resources