Using vba function to split range into even and odd - excel

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

Related

pick random names from a list

I am trying to use a formula that it will allow me to pick 183 names randomly from a list of 355 names. My excel sheet will look something like this:
Names Random.Names
Paty
Oscar
John
Anna
Jane
Carlos
Maria
Jennifer
Susan
Kayla
On my actual sheet I have more names but this is just an example. I used the following formula but I have a few cells that show #REF after it randomizes.
=IF(ROWS($1:1)>$E$2,"",INDEX($A$8:$A$355,RANDBETWEEN(1,354)))
Please let me know if you have a better formula or if you know what I am doing wrong.
That is because INDEX is relative, so row 8 is 1 and row 355 is 355-8+1 = 348. Change the RANDBETWEEN to 1,348
Anything greater than the number of cells referenced will produce the error.
=IF(ROWS($1:1)>$E$2,"",INDEX($A$8:$A$355,RANDBETWEEN(1,348)))
Or you can reference the whole column and use 8,355:
=IF(ROWS($1:1)>$E$2,"",INDEX($A:$A,RANDBETWEEN(8,355)))
You do not have 355 names between A8 and A355 only 355-8+1.
So fix the RANDBETWEEN()
Following the logic of my previous anwser
You only have to open your VBA editor an paste the following code:
'By Julio Jesus Luna Moreno
'jlqmoreno#gmail.com
Option Base 1
Public Function UNIQRAND(a As Variant, b As Variant) As Variant
Application.Volatile
Dim k%, p As Double, flag As Boolean, x() As Variant
k = 1
flag = False
ReDim x(1)
x(1) = Application.RandBetween(a, b)
Do Until k = b - a + 1
Do While flag = False
Randomize
p = Application.RandBetween(a, b)
'Debug.Assert p = 2
resultado = Application.Match(p, x, False)
If IsError(resultado) Then
k = k + 1
ReDim Preserve x(k)
x(k) = p
flag = True
Else
flag = False
End If
Loop
flag = False
Loop
UNIQRAND = x
End Function
This function will do the trick
Public Function RANDNAMES(Rango As Range, HowMany As Integer) As Variant
Dim n, p(), x(), i As Variant
n = Rango.Rows.Count
If n < HowMany Then
MsgBox "Number of pairs must be less than number of total elements"
Exit Function
End If
ReDim x(HowMany)
ReDim p(n)
p = UNIQRAND(1, n)
For i = 1 To HowMany Step 1
x(i) = Application.Index(Rango, p(i))
Next i
Debug.Print HowMany
RANDNAMES = Application.Transpose(x)
End Function

Excel find 17 Cells with highest value, 5 of which are the highest in a specific row

I'm struggling with a complex excel problem, and I would be amazed by any solution.
I have a table with 4 columns and the following values
The highest |13|12|12|12|
The two highest|11|12|11|11|
The two highest|12|12|12|12|
|12|11|11|11|
|12|11|11|11|
|12|11|11|11|
My problem requires from the first three rows to select the highest respectively the two highest values. Over the complete matrix there should be a sum of 12 values.
The required 5 plus whatever are the remaining 7 highest values. My current approach is to do a sum of the required rows and add the rest together, but that is obviouly not working.
|13|12|12|12|[MAX(B10:E10)]13|
|11|12|11|11|[LARGE(B11:E11;1)+LARGE(B11:E11;2)23|
|12|12|12|12|[LARGE(B12:E12;1)+LARGE(B12:E12;2)24|
|12|11|11|11|
|12|11|11|11|
|12|11|11|11|
Any ideas or suggestions are highly appreciated. Also a more understandable title for references would be great. Thanks!
Explanation:
It's sloppy VBA, but this works and the structure is generally expandable if you need it to be. You can just paste this in a VBA module, run Sum57(), and the result will be in the debug window (Ctl + G). To modify this for other array sizes, change the following :
the size of the used array in line 1
the values of arrR and arrC in lines 5 and 6 which define the start of the array
the pattern of the function calls in the body of Sum57()
The base pattern is:
For i = 1 To N
x = x + LargeOfRange([rStart], [rEnd], [cStart], [cEnd])
Next
where N is top N largest numbers from the range.
VBA:
Public used(5, 3) As Boolean
Public arrR, arrC As Integer
Public Sub Sum57()
arrR = 10
arrC = 2
For a = LBound(used, 1) To UBound(used, 1)
For b = LBound(used, 2) To UBound(used, 2)
used(a, b) = False
Next
Next
Dim x As Integer
x = x + LargeOfRange(10, 10, 2, 5)
For i = 1 To 2
x = x + LargeOfRange(11, 11, 2, 5)
Next
For i = 1 To 2
x = x + LargeOfRange(12, 12, 2, 5)
Next
For i = 1 To 7
x = x + LargeOfRange(10, 15, 2, 5)
Next
Debug.Print x
End Sub
Public Function LargeOfRange(rStart As Integer, rEnd As Integer, _
cStart As Integer, cEnd As Integer) As Integer
On Error GoTo SkipVal
Dim l, x, xR, xC As Integer
x = 0
For r = rStart To rEnd
For c = cStart To cEnd
If x < Cells(r, c).Value And used(r - arrR, c - arrC) = False Then
xR = r
xC = c
x = Cells(r, c).Value
End If
Next
Next
used(xR - arrR, xC - arrC) = True
LargeOfRange = x
Exit Function
SkipVal:
LargeOfRange = 0
End Function
Why not just extend the range and add more elements to the Large() calc?
=LARGE(B13:E15,1)+LARGE(B13:E15,2)+LARGE(B13:E15,3)+LARGE(B13:E15,4)+
LARGE(B13:E15,5)+LARGE(B13:E15,6)+LARGE(B13:E15,7)
This returns 80

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

Dynamically adding nested loops

I have an 'X' amount of variables (likely to range between 3 to 20 options), which will be combined to calculate all possible combinations to meet a criteria. For every extra variable an extra loop is introduced, however I do not know if it possible to make the creation of loops dynamic (in excel VBA, the code doesn't have to be very fast).
To demonstrate:
I have var. A with h = 2, var. B with h = 3.
I would like to know all combinations which are equal to 10 or the best combination of the 2 variables.
In this case: option 1 = 5*A = 10, 3*B = 9,2*A and 2*B = 10, 3*A and 1*B = 9.
The code looks like this:
For A = 0 to 5
h = 0 'Reset previous h if solution is found
For B = 0 to 5
h_test = A * height(A) + B * heigth(B)
if h_test > 10
if h = 0 then
exit for
else
write h
exit for
end if
h = h_test
Next B
Next A
If another parameter is introduced (for example C = 4), the code is:
For A = 0 to 5
h = 0 'Reset previous h if solution is found
For B = 0 to 5
h = 0 'Reset previous h if solution is found
For C = 0 to 5
h_test = A * height(A) + B * heigth(B) + C * heigth(C)
if h_test > 10
if h = 0 then
exit for
else
write h
exit for
end if
h = h_test
Next C
Next B
Next A
In other words, I would like to know if it is possible to translate the pseudocode to real code:
For #parameter. = X
For loop1 = 1 to 5
h = 0
For loop2 = 1 to 5
h = 0
....
For loopX = 1 to 5
h_test = loop1 *parameter1 + loop2 * parameter 2 ...
+ loopX * parameter X
If h_test > 10
Somecode
exit for
End if
Next X
...
Next loop2
Next loop1
There are two distinct problems here. You didn't mention the first, and that is you also need to calculate a value with an indeterminate number of arguments. For that, you can use a ParamArray.
For example:
Public Function Sum(ParamArray args() As Variant) As Long
Dim i As Long
Dim operand As Integer
Dim result As Long
For i = LBound(args) To UBound(args)
result = args(i) + result
Next i
Sum = result
End Function
Which can be used and tested like this:
Public Sub Test()
Debug.Print Sum(1,2) '=> 3
Debug.Print Sum(1,2,3) '=> 6
End Sub
So, that takes care of that problem. Now, as for the problem you asked about, we'll take a similar approach. The key is to loop once for each argument you've received.
Public Sub Run()
NestedLoop 1, 2, 3
End Sub
Public Sub NestedLoop(ParamArray args() As Variant)
Dim result As Long
Dim a As Variant
a = args
Dim h_test As Long
Dim i As Long, j As Long
For i = LBound(args) To UBound(args)
For j = 1 To 5
result = 0
h_test = Sum(a)
If h_test > 10 Then
If result = 0 Then
Exit For
Else
Debug.Print result
Exit For
End If
End If
result = h_test
Next j
Next i
End Sub
Public Function Sum(args As Variant) As Long
Dim i As Long
Dim operand As Integer
Dim result As Long
For i = LBound(args) To UBound(args)
result = args(i) + result
Next i
Sum = result
End Function

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