Use Arrays to do "count sort" in VBA-excel - excel

I'm trying to learn how to do count-sorting in excel-vba using arrays. I'm having some issue with the final step of actually doing the count sort (v1Sort and V2 Sort columns). This is the table so far
V1 V2 Bin V1Count PointerV1 V2Count PointerV2 V1Sort V2Sort
6 3 -1 2 2 1 1 ? ?
7 2 0 3 5 4 5
1 6 1 4 9 4 9
5 3 2 5 14 5 14
6 0 3 2 16 2 16
9 7 4 4 20 2 18
7 9 5 4 24 4 22
8 6 6 2 26 4 26
2 4 7 4 30 1 27
8 3 8 1 31 2 29
4 2 9 0 31 2 31
1 3 10
2 6
0 10
1 5
8 7
5 9
5 10
5 1
6 7
3 2
8 5
0 6
2 8
3 1
2 3
4 4
3 1
3 7
3 1
6 2
Here's my code so far without actually doing the final step of the count sort. I understand the concept fully but am finding it hard to use arrays to do the count sort.
Sub Count_Sorting()
Const iOffData As Integer = 1
Const iOffPoint As Integer = 2
Const iOffPoint_2 As Integer = 3
Dim iBin As Integer
Dim iData As Integer
Dim iPointerVector_1() As Integer
Dim iPointerVector_2() As Integer
Dim iPoint As Integer
Dim iRow As Integer
Dim iDataVector_1(32) As Integer
Dim iDataVector_2(32) As Integer
Dim iSortVector_1(1 To 50) As Integer
Dim iSortVector_2(1 To 50) As Integer
Application.ScreenUpdating = True
Sheets("Sheet1").Range(Cells(1, 3), Cells(50, 8)).Clear
ReDim iPointerVector_1(-1 To 11)
ReDim iPointerVector_2(-1 To 11)
Sheets("sheet1").Cells(1, 1).Value = "V1"
Sheets("sheet1").Cells(1, 2).Value = "V2"
Sheets("sheet1").Cells(1, 3).Value = "Bin"
Sheets("sheet1").Cells(1, 4).Value = "V1Count"
Sheets("sheet1").Cells(1, 5).Value = "PointerV1"
Sheets("sheet1").Cells(1, 6).Value = "V2Count"
Sheets("sheet1").Cells(1, 7).Value = "PointerV2"
'Write bin numbers.
For iRow = 2 To 13
Sheets("Sheet1").Cells(iRow, 3).Value = iRow - iOffPoint_2
Next iRow
'Read the values from Sheet1.
For iRow = 2 To 32
iDataVector_1(iRow) = Sheets("Sheet1").Cells(iRow, 1).Value
iDataVector_2(iRow) = Sheets("Sheet1").Cells(iRow, 2).Value
Next iRow
'************************************************************
'First we do the procedure for sorting Vector 1
'************************************************************
'Count the number of data points in each bin for Vector 1 .
For iRow = 2 To 32
iPointerVector_1(iDataVector_1(iRow)) = iPointerVector_1(iDataVector_1(iRow)) + 1
Next iRow
For iBin = 0 To 10
Sheets("Sheet1").Cells(iBin + iOffPoint, 4).Value = iPointerVector_1(iBin)
Next iBin
'Get cumulative counts, located in prior bins, in preparation for later
're-reading of the data and decrementing of the pointers for Vector 1.
For iBin = 0 To 10
iPointerVector_1(iBin) = iPointerVector_1(iBin - 1) + iPointerVector_1(iBin)
Next iBin
For iBin = 0 To 10
Sheets("SHeet1").Cells(iBin + iOffPoint, 5).Value = iPointerVector_1(iBin)
Next iBin
'
'************************************************************
'Now we do the procedure for sorting Vector 2
'************************************************************
'Count the number of data points in each bin for Vector 2.
For iRow = 2 To 32
iPointerVector_2(iDataVector_2(iRow)) = iPointerVector_2(iDataVector_2(iRow)) + 1
Next iRow
For iBin = 0 To 10
Sheets("SHeet1").Cells(iBin + iOffPoint, 6).Value = iPointerVector_2(iBin)
Next iBin
'Get cumulative counts, located in prior bins, in preparation for later
're-reading of the data and decrementing of the pointers for Vector 2.
For iBin = 0 To 10
iPointerVector_2(iBin) = iPointerVector_2(iBin - 1) + iPointerVector_2(iBin)
Next iBin
For iBin = 0 To 10
Sheets("SHeet1").Cells(iBin + iOffPoint, 7).Value = iPointerVector_2(iBin)
Next iBin
```End Sub

You indeed get the idea about count sorting. What makes your implementation difficult is the way you organize your code. Probably the first thing to do is to define a CountSort() function which takes as input an array of values, and returns an array of the same values sorted. Now, I see that you want to output the contents of intermediate arrays used in the count sort function to cells of Sheet1. So, you could pass to the CountSort() function the row and column indexes where to output intermediate arrays. So, your function could look like this:
' Returns array of the values sorted.
Public Function CountSort(values() As Integer, rowIndex As Integer, columnIndex As Integer) As Integer()
End Function
To output the contents of an array to Sheet1 you can define a routine like:
' Print contents of array values in column columnIndex, starting at rowIndex going downwards.
Sub PrintArray(values() As Integer, rowIndex As Integer, columnIndex As Integer)
Dim i As Integer
For i = 0 To UBound(values) - LBound(values)
Sheets("Sheet1").Cells(rowIndex + i, columnIndex).Value = values(LBound(values) + i)
Next
End Sub
And the CountSort() function would look like:
' Returns array of the values sorted.
Public Function CountSort(values() As Integer, rowIndex As Integer, columnIndex As Integer) As Integer()
' Assuming values are in range [0 10].
Dim bin(0 To 10) As Integer
Dim i As Integer
' Initialize bin to 0.
For i = LBound(bin) To UBound(bin)
bin(i) = 0
Next
PrintArray values, rowIndex, columnIndex
' Count number of occurrences of each value.
For i = LBound(values) To UBound(values)
bin(values(i)) = bin(values(i)) + 1
Next
PrintArray bin, rowIndex, columnIndex + 1
' Find cumulative frequency.
For i = LBound(bin) + 1 To UBound(bin)
bin(i) = bin(i) + bin(i - 1)
Next
PrintArray bin, rowIndex, columnIndex + 2
' Build sorted array.
Dim sorted() As Integer
ReDim sorted(LBound(values) To UBound(values)) As Integer
For i = UBound(values) To LBound(values) Step -1
sorted(bin(values(i))) = values(i)
bin(values(i)) = bin(values(i)) - 1
Next
PrintArray sorted, rowIndex, columnIndex + 3
' Return sorted array.
CountSort = sorted
End Function
In VBA, it is always a good idea to use the LBound() and UBound() functions as they make your code independent of how arrays have been declared index-wise.

Related

Using vba function to split range into even and odd

I'm tring to write an Excel (2013) function that would take a 1x2n range of cells and return 1xn vector of cells that are of even/odd index. So if I put some numbers in cells A1:F1 as this
A
B
C
D
E
F
1
43
23
67
12
6
1
And put this function in A2:C2, it should return
A
B
C
D
E
F
1
43
23
67
12
6
1
2
23
12
1
I wrote something like this, but it doesn't work (#Arg! error)
Public Function Even(X As Variant) As Variant
Dim N As Integer
N = UBound(X)
ReDim Y(N / 2)
For i = 1 To N
If i Mod 2 = 0 Then
Y(i / 2) = X(i)
End If
Next i
Even = Y
End Function
After #BigBen comments I've changed the code to
Public Function Even(X As Variant) As Variant
Dim N As Integer
N = Application.CountA(X.Value)
ReDim Y(N / 2)
For i = 1 To N
If i Mod 2 = 0 Then
Y(i / 2) = X(i)
End If
Next i
Even = Y
End Function
It now returns almost what I want, it returns:
A
B
C
D
E
F
1
43
23
67
12
6
1
2
0
23
12
1
where's 0 coming from
Here is a possibility. EVEN is a spreadsheet function, so a different name is preferable. EveryOther seems natural, but with a name like that, why not make it flexible enough to select the odds if need be? A good way to do that is to make an optional Boolean argument which controls if even or odd indices are chosen:
Function EveryOther(Rng As Range, Optional Evens As Boolean = True) As Variant
Dim i As Long, j As Long, n As Long
Dim cell As Range
Dim returnVals As Variant
n = Rng.Cells.count
ReDim returnVals(1 To n)
i = 0
j = 0
For Each cell In Rng.Cells
i = i + 1
If i Mod 2 = IIf(Evens, 0, 1) Then
j = j + 1
returnVals(j) = cell.Value
End If
Next cell
ReDim Preserve returnVals(1 To j)
EveryOther = returnVals
End Function

VBA excel. Loop through with condition

I am looking to loop through Column A.
- If the next number is greater than the previous number continue (A: 0,1,2,3..).
- Do this until the next number is equal or less than (A: 0,1,2,3,4,4..).
- If number is less than(A: 0,1,2,3,4,3..). or equal, take the highest # 4 subtract lowest #0, and put the results in columnB next to the highest number.
- If the next number is equal the previous number, subtract and put the answer 0 in columnB.
- If the next number is lower than the previous number continue. Do this until the next number is equal or less than.
- If number is less than or equal, take the highest # 4 subtract lowest #0...
I am not sure If I am clear but I am thinking a loop might work for this situation. Or perhaps any other idea would be greatly appreciated. Thanks in advance.
A B
1 0
2 1
3 2
4 3
5 4 4
6 4 0
7 3
8 2
9 1
10 0 4
11 1
12 2 2
13 2 0
14 3
15 4 2
... ...
You can use dictionary... adding the row number to the key value and check the positions...
Sub YourLoop()
Dim dic As Scripting.Dictionary
Set dic = New Scripting.Dictionary
Dim i As Integer
Dim n As Integer
For i = 1 To Rows.Count
''ColumnA values
dic.Add i, Cells(i, 1).Value
Next i
Dim k1 As Integer
Dim k2 As Integer
Dim k3 As Integer
Dim k4 As Integer
Dim v1 As Integer
Dim v2 As Integer
Dim v3 As Integer
Dim v4 As Integer
Dim v As Integer
Dim c As Integer
c = 1
For Each key In dic.Keys
v = dic(key)
If c = 1 Then
''do nothing
ElseIf c = 2 Then
k1 = key - 1
v1 = dic(k1)
If v <= v1 Then
End If
ElseIf c = 3 Then
k2 = key - 2
k1 = key - 1
v1 = dic(k1)
v2 = dic(k2)
ElseIf c >= 4 And c < dic.Count Then
k4 = key - 4
k3 = key - 3
k2 = key - 2
k1 = key - 1
v1 = dic(k1)
v2 = dic(k2)
v3 = dic(k3)
v4 = dic(k4)
ElseIf c = dic.Count Then
End If
c = c + 1
Next

Excel Function to repeat a set of cells for a certain number of times based on cell value

I am looking for a way to repeat a set of cells horizontally a certain number of times before moving on to the next set of cells. For example:
If I have this in 3 columns:
5 4 3
0 1 2
and I have 3 columns which dictate how many times I want the values iterated:
4 2 3
This function should give me this when dragged over a range:
5 5 5 5 4 4 3 3 3
0 0 0 0 1 1 2 2 2
Does anyone know the best manner to do this?
I have been using some convoluted reasoning to get through this with an array formula ( has the "{}" brackets around it and you have to use Shift+Enter). I am using SUMIF and COUNTIF functions to do some things, but it never really works out.
Here's a hacky VBA solution. This assumes the "repeat counts" are on the first row (4, 2, 3) and the "values to repeat" are on the second row (5, 4, 3, 0, 1, 2).
Sub outputRepeatedValues()
Dim xStart As Integer, yStart As Integer
Dim i As Integer, j As Integer
Dim valueToRepeat As Integer, numTimesRepeat As Integer
Dim xOffset As Integer, yOffset As Integer
xStart = ActiveCell.Column
yStart = ActiveCell.Row
i = 1
j = 1
xOffset = 0
yOffset = 0
While Cells(2, j) <> ""
If Cells(1, i) = "" Then
i = 1
yOffset = yOffset + 1
xOffset = 0
End If
numTimesRepeat = Cells(1, i)
valueToRepeat = Cells(2, j)
For k = 1 To numTimesRepeat
Cells(yStart + yOffset, xStart + xOffset) = valueToRepeat
xOffset = xOffset + 1
Next k
j = j + 1
i = i + 1
Wend
End Sub
Stick this in a new module. To use this code, select the cell representing the upper left corner of the output region. Then press Alt+F8 to bring up the Macro box, and then you can run the macro.

Using transpose function in Excel

I have a several text separated by ;
A
1;2;3;5;6;7;8;9;10
11;12;13;14;15;16;17
18;19;20;21
Column A has few text in each row 1,2,3 and so on separated by ';'
With the help of 'Text to Column' Option in 'Data' tab we are able to separate the same
Which will look something like this
A B C D E F G H I
1 2 3 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21
Now we have to manually copy all text and shift the same to same column
A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Numbers shown are already example, it can be any text string.
We are doing manually and it takes lot of time as there are more than 100's of entry.
Is there any way to automate the same with the help of macro? Have been googling and trying but not yet successful.
You can do this by some VBA source code:
Add a button on your sheet
Double click on it
Add the following code:
Private Sub CommandButton1_Click()
Dim Counter As Integer
Counter = 1
Dim curString As String
Dim whereComma As Integer
Dim i As Integer
For i = 1 To 10
curString = Cells(i, 1)
whereComma = InStr(1, curString, ";", vbTextCompare)
While InStr(whereComma, curString, ";", vbTextCompare) > 0
Cells(Counter, 4) = Left(curString, whereComma - 1)
Counter = Counter + 1
curString = Right(curString, Len(curString) - whereComma)
Wend
Cells(Counter, 4) = curString
Counter = Counter + 1
Next I
End Sub
End Sub
Click on button
You can see your result on column D
This VBA will do what you want and output to column B:
Sub TransposeColumnA()
Dim X As Long
For X = 1 To Range("A" & Rows.Count).End(xlUp).Row
Range("B" & Range("B" & Rows.Count).End(xlUp).Row + 1).Resize(UBound(Split(Range("A" & X).Text, ";")) + 1, 1).Formula = Application.Transpose(Split(Range("A" & X).Text, ";"))
Next
End Sub

Find every combination of cells in a column where the sum is a specific value

If given a column of data in Excel, for example:
2
5
8
10
3
6
Is it possible to check the column to find every all combinations where they meet a specific criteria? In this example when and the sum of any combination of those values is equal to 8.
The values it should find would be
2
5
8
3
6
As 2+6, 5+3, 8 all equal 8
It's my understanding that I am basically asking to check the following
if 2 = 8 or
if 5 = 8 or
if 8 = 8 or
if 10 = 8 or
if 3 = 8 or
if 6 = 8 or
if 2+5 = 8 or
if 2+8 = 8 or
if 2+10 = 8 or
if 2+3 = 8 or
if 2+6 = 8 or
etc
I have only used simple numbers to try and give an example. I'm fairly certain this is not possible.
Try the below code. You have to enter the values to match in column A and the results will show up in column B. I was able to achive a two way match and a three way match. if you wan Can do it till 5 way match after it will become more complex.
Sub Testing()
Dim RowNumber As Double
Dim Temp1 As Double
Dim Temp2 As Double
Dim Temp3 As Double
Dim Result As Double
Dim MatchCount As Double
'Value to be searched
Result = Application.InputBox("Please insert a Number", "Combi Calculator", "", , , , , 1)
'get the last row
RowNumber = Sheet1.Range("A1048576").End(xlUp).Row
'set matchcount to
MatchCount = 1
'Two way match
For Temp1 = 2 To RowNumber
For Temp2 = 3 To RowNumber
If Cells(Temp1, 2) = "" And Cells(Temp2, 2) = "" Then
If Cells(Temp1, 1) + Cells(Temp2, 1) = Result Then
Cells(Temp1, 2) = MatchCount
Cells(Temp2, 2) = MatchCount
MatchCount = MatchCount + 1
End If
End If
Next
Next
'Three way match
For Temp1 = 2 To RowNumber
For Temp2 = 3 To RowNumber
For Temp3 = 4 To RowNumber
If Cells(Temp1, 2) = "" And Cells(Temp2, 2) = "" And Cells(Temp3, 2) = "" And Temp1 <> Temp2 And Temp2 <> Temp3 And Temp1 <> Temp3 Then
If Cells(Temp1, 1) + Cells(Temp2, 1) + Cells(Temp3, 1) = Result Then
Cells(Temp1, 2) = MatchCount
Cells(Temp2, 2) = MatchCount
Cells(Temp3, 2) = MatchCount
MatchCount = MatchCount + 1
End If
End If
Next
Next
Next
End Sub
I am not sure how to send a excel file as an attachement as I am new to this page.
meanwhile let me reseach time topic a bit more. This was always area I wanted to work in (i.e., logic).
A two-way match is very simple using COUNTIF. If your number range is in column A and you want to find two numbers in the range that sum to 72, enter this formula in B1 and copy it down to B20:
=COUNTIF($A$1:$A$20,72-A1)

Resources