ebmgh.com

Testing for String Membership in Excel VBA »« California Urban Drought Guidebook

Fixing Excel’s SMALL and LARGE Functions

Excel has a pair of useful built-in functions called SMALL and LARGE. Suppose you want to know the third-largest number in a column. Or you want to find the fourth-smallest.

The function LARGE returns the kth largest value in a dataset. Entering

=LARGE(A1:A9,3) 

returns the third largest value in the range A1 to A9. The function is smart enough to ignore blank cells and text. However, if you have an error in a cells, the result of the formula will be an error.

Here are custom functions to replace the built-in ones, called MyLarge and MySmall. Their use is identical to the Excel version: just enter

=MYLARGE(A1:A9,3).

If you don’t know how to use these, check out the following: How do I… Create a user-defined function in Microsoft Excel at Tech Republic. Or you can download an example workbook with the functions.

Note that these functions call a subroutine called QuickSort, which I copied from John Walkenbach‘s book Power Programming with Excel VBA years ago. (Highly recommended if you use Excel a lot and want to learn to automate repetitive tasks or do more advanced stuff.)


Function MyLarge(x, k)
'Replicates Excel's LARGE function, but will not balk at errors
'Returns the k-th largest value in a data set.
'Usage: =MYLARGE(A1:A15,3), returns the third largest value in the range A1 to A15

Dim arr() As Double 'An array to hold the cell values
Dim i As Long  'counter for looping through the array
Dim n As Long  'n is the number of elements in array
Dim nrev As Long  'revised count of number of VALID elements in array x

nrev = -1
n = Int(x.Count)
ReDim arr(n) As Double

'Note that Excel ranges always start with an index = 1
For i = 1 To n
  If Not IsEmpty(x(i)) And Not IsError(x(i)) And IsNumeric(x(i)) Then
    nrev = nrev + 1
    arr(nrev) = x(i)
  End If
Next i

ReDim Preserve arr(nrev)
Call Quicksort(arr, 0, nrev)

On Error GoTo IndexError
  MyLarge = arr(nrev - k + 1)
  Exit Function

IndexError:
  MyLarge = CVErr(xlErrNum)

End Function

Function MySmall(x, k)
'Replicates Excel's SMALL function, but will not balk at errors
'Returns the k-th smallest value in a data set.
'Usage: =MYSMALL(A1:A15,3), returns the third smallest value in the range A1 to A15

Dim arr() As Double 'An array to hold the cell values
Dim i As Long  'counter for looping through the array
Dim n As Long  'n is the number of elements in array
Dim nrev As Long  'revised count of number of VALID elements in array x

nrev = -1
n = Int(x.Count)
ReDim arr(n) As Double

'Note that Excel ranges always start with an index = 1
For i = 1 To n
  If Not IsEmpty(x(i)) And Not IsError(x(i)) And IsNumeric(x(i)) Then
    nrev = nrev + 1
    arr(nrev) = x(i)
  End If
Next i

ReDim Preserve arr(nrev)
Call Quicksort(arr, 0, nrev)

On Error GoTo IndexError
  MySmall = arr(k - 1)
  Exit Function

IndexError:
  MySmall = CVErr(xlErrNum)

End Function

Sub Quicksort(values() As Double, ByVal min As Long, ByVal max As Long)

'A fairly efficient sort algorithm for sorting 1-dimensional arrays of numbers
'Source: Walkenbach, J., 1999, Power Programming for Excel/VBA

'values() is the 1-dimensional array to be sorted

'min is the array index for the beginning of slice of the array where the sort is to begin
'To sort the entire array, min = 0 or 1 (depending on what Base you are working in)

'max is the array index for the end of the part of the array to be sorted.
'To sort the entire array, use max = len(values)

'Note that this is an example of a *recursive algorithm*
'To understand recursive algorithms, first you have to understand recursive algorithms :) 

Dim med_value As String
Dim Hi As Long
Dim lo As Long
Dim i As Long

  ' If the list has only 1 item, it's sorted.
  If min >= max Then Exit Sub

  ' Pick a dividing item randomly.
  i = min + Int(Rnd(max - min + 1))
  med_value = values(i)

  ' Swap the dividing item to the front of the list.
  values(i) = values(min)

  ' Separate the list into sublists.
  lo = min
  Hi = max
  Do
    ' Look down from hi for a value < med_value.     Do While values(Hi) >= med_value
      Hi = Hi - 1
      If Hi <= lo Then Exit Do
    Loop

    If Hi <= lo Then       ' The list is separated.       values(lo) = med_value       Exit Do     End If     ' Swap the lo and hi values.     values(lo) = values(Hi)     ' Look up from lo for a value >= med_value.
    lo = lo + 1
    Do While values(lo) < med_value       lo = lo + 1       If lo >= Hi Then Exit Do
    Loop

    If lo >= Hi Then
      ' The list is separated.
      lo = Hi
      values(Hi) = med_value
      Exit Do
    End If

    ' Swap the lo and hi values.
    values(Hi) = values(lo)
  Loop ' Loop until the list is separated.

  ' Recursively sort the sublists.
  Quicksort values, min, lo - 1
  Quicksort values, lo + 1, max

End Sub
January 14, 2010 at 3:04 pm
Commenting is closed