Distinct Values Function
This page describes a VBA Function that will return an array of the distinct values in a range or array of
input values.
Excel has some manual methods, such as Advanced Filter, for getting a list of distinct items from an input range. The drawback of
using such methods is that you must manually refresh the results when the input data changes. Moreover, these methods work only with
ranges, not arrays of values, and, not being functions, cannot be called from worksheet cells or incorporated into array formulas.
This page describes a VBA function called DistinctValues that accepts as input either a range or
an array of data and returns as its result an array containing the distinct items from the input list. That is, the elements
with all duplicates removed. The order of the input elements is preserved. The order of the elements in the output array is the
same as the order in the input values. The function can be called from an array entered range on a worksheet (see
this page for information about array formulas), or from in an array formula in a single
worksheet cell, or from another VB function.
The function declaration is shown below:
Function DistinctValues(InputValues As Variant, _
Optional IgnoreCase As Boolean = False) As Variant
You can download an
example workbook or just the
bas
module file with the complete code.
The parameter
InputValues is either a range on a worksheet or an array of values. If it is a worksheet range,
the range must have exactly one column or one row. Two-dimensional ranges are not supported. If
InputValues
is an array, it must be a single dimensional array. Two-dimensional arrays are not supported. The parameter
IgnoreCase indicates whether the comparisons should be case-sensitive or case-insensitive. If this value is
True, case is ignored and
abc is considered equal to
ABC. If this value is
False, case is taken into account and
abc is consider different from
ABC.
If the function is array entered into a range on a worksheet, the size of the returned array is equal to the size of the range into
which the function was entered, regardless of the number of distinct elements found, and unused entries at the end of the resulting
array are set to vbNullStrings. This prevents #N/A errors from appearing. Note that this differs from the
default behavior of Excel's own array formulas. If the function is entered in a single cell array formula, the size of the result
array is equal to the number of distinct elements from the input list. Similarly, if the function is called from another VB function,
not from a worksheet cell, the result array contains only the distinct elements.
Empty elements, those with a value of vbNullString or Empty are not counted as distinct
elements -- they are ignored. Thus, the array {"a","b","","","c"} has three distinct elements,
a, b, and c. The empty string is ignored by the function.
Spaces and zero values, however, are considered when creating the list of distinct elements.
If an array, not a range, is passed into DistinctValues, that array must not contain any Object type variables
(other than Excel.Range objects) and must not contain any Null values.
The most common usage is to array enter the DistinctValues function into a range of cells and pass it
another range of cells as the input list. For example, select cells B1:B10 type
=DistinctValues(A1:A10,FALSE)
and press CTRL SHIFT ENTER. This list of distinct values from cells A1:A10 will
be returned to cells B1:B10. Unpopulated cells in B1:B10 will be filled
with empty strings.
You can also use DistinctValues in an array formula. For example,
=MATCH("chip",DistinctValues(A1:A10,TRUE),0)
will return the position of the string chip in the list of distinct values from cells A1:A10.
To count the number of distinct values in a range, just pass the results of DistinctValue to the
COUNT or COUNTA function:
=COUNTA(DistinctValues(A1:A10,TRUE))
In addition, the DistinctValues function may be called from other VB code, passing either a Range or an
Array as the input parameter. For example,
Sub Test()
Dim InputRange As Range
Dim ResultArray As Variant
Dim Ndx As Long
Set InputRange = Range("InputValues")
ResultArray = DistinctValues(InputValues:=InputRange, IgnoreCase:=True)
If IsArray(ResultArray) = True Then
For Ndx = LBound(ResultArray) To UBound(ResultArray)
Debug.Print ResultArray(Ndx)
Next Ndx
Else
If IsError(ResultArray) = True Then
Debug.Print "ERROR: " & CStr(ResultArray)
Else
Debug.Print "UNEXPECTED RESULT: " & CStr(ResultArray)
End If
End If
End Sub
In addition to a range, the
InputValues can be an array literal. For example,
=DistinctValues({"a","b","a","b","c"},TRUE)
The code for the DistinctValues function is shown below. It requires the
NumberOfArrayDimensions, TransposeArray, and
Transpose1DArray functions, all of which are listed below following the listing for
DisinctValues.
You can download an example workbook or just the bas
module file with the complete code.
Option Explicit
Function DistinctValues(InputValues As Variant, _
Optional IgnoreCase As Boolean = False) As Variant
Dim ResultArray() As Variant
Dim UB As Long
Dim TransposeAtEnd As Boolean
Dim N As Long
Dim ResultIndex As Long
Dim M As Long
Dim ElementFoundInResults As Boolean
Dim NumCells As Long
Dim ReturnSize As Long
Dim Comp As VbCompareMethod
Dim V As Variant
If IgnoreCase = True Then
Comp = vbTextCompare
Else
Comp = vbBinaryCompare
End If
If IsObject(Application.Caller) = True Then
If Application.Caller.Rows.Count > 1 And Application.Caller.Columns.Count > 1 Then
DistinctValues = CVErr(xlErrRef)
Exit Function
End If
If Application.Caller.Rows.Count > 1 Then
TransposeAtEnd = True
ReturnSize = Application.Caller.Rows.Count
Else
TransposeAtEnd = False
ReturnSize = Application.Caller.Columns.Count
End If
End If
If IsObject(InputValues) = True Then
If TypeOf InputValues Is Excel.Range Then
If InputValues.Rows.Count > 1 And InputValues.Columns.Count > 1 Then
DistinctValues = CVErr(xlErrRef)
Exit Function
End If
If InputValues.Rows.Count > 1 Then
NumCells = InputValues.Rows.Count
Else
NumCells = InputValues.Columns.Count
End If
UB = NumCells
Else
DistinctValues = CVErr(xlErrRef)
Exit Function
End If
Else
If IsArray(InputValues) = True Then
Select Case NumberOfArrayDimensions(InputValues)
Case 0
ReDim ResultArray(1 To 1)
ResultArray(1) = InputValues
DistinctValues = ResultArray
Exit Function
Case 1
UB = UBound(InputValues) - LBound(InputValues) + 1
If IsObject(InputValues) = False Then
NumCells = UB
End If
Case Else
DistinctValues = CVErr(xlErrValue)
Exit Function
End Select
Else
ReDim ResultArray(1 To 1)
ResultArray(1) = InputValues
DistinctValues = ResultArray
Exit Function
End If
End If
For Each V In InputValues
If IsNull(V) = True Then
DistinctValues = CVErr(xlErrNull)
Exit Function
End If
If IsObject(V) = True Then
If Not TypeOf V Is Excel.Range Then
DistinctValues = CVErr(xlErrValue)
Exit Function
End If
End If
Next V
ReDim ResultArray(1 To UB)
For N = LBound(ResultArray) To UBound(ResultArray)
If IsObject(Application.Caller) = True Then
ResultArray(N) = vbNullString
Else
ResultArray(N) = Empty
End If
Next N
ResultIndex = 1
ResultArray(1) = InputValues(1)
For N = 2 To UB
ElementFoundInResults = False
For M = 1 To N
If StrComp(CStr(ResultArray(M)), CStr(InputValues(N)), Comp) = 0 Then
ElementFoundInResults = True
Exit For
End If
Next M
If ElementFoundInResults = False Then
ResultIndex = ResultIndex + 1
ResultArray(ResultIndex) = InputValues(N)
End If
Next N
If ReturnSize <> 0 Then
If ResultIndex < NumCells Then
If ResultIndex < ReturnSize Then
ResultIndex = ReturnSize
End If
End If
End If
ReDim Preserve ResultArray(1 To ResultIndex)
If UBound(ResultArray) > NumCells Then
For N = NumCells + 1 To ReturnSize
ResultArray(N) = vbNullString
Next N
End If
If TransposeAtEnd = True Then
DistinctValues = Transpose1DArray(Arr:=ResultArray, ToRow:=False)
Else
DistinctValues = ResultArray
End If
End Function
Function TransposeArray(Arr As Variant) As Variant
Dim R1 As Long
Dim R2 As Long
Dim C1 As Long
Dim C2 As Long
Dim LB1 As Long
Dim LB2 As Long
Dim UB1 As Long
Dim UB2 As Long
Dim Res() As Variant
Dim NumDims As Long
If IsArray(Arr) = False Then
TransposeArray = Arr
Exit Function
End If
NumDims = NumberOfArrayDimensions(Arr)
Select Case NumDims
Case 0
If IsObject(Arr) = True Then
Set TransposeArray = Arr
Else
TransposeArray = Arr
End If
Case 1
TransposeArray = Arr
Case 2
LB1 = LBound(Arr, 1)
UB1 = UBound(Arr, 1)
LB2 = LBound(Arr, 2)
UB2 = UBound(Arr, 2)
R2 = LB1
C2 = LB2
ReDim Res(LB2 To UB2, LB1 To UB1)
For R1 = LB1 To UB1
For C1 = LB2 To UB2
Res(C1, R1) = Arr(R1, C1)
C2 = C2 + 1
Next C1
R2 = R2 + 1
Next R1
TransposeArray = Res
Case Else
TransposeArray = CVErr(9)
End Select
End Function
Function NumberOfArrayDimensions(Arr As Variant) As Long
Dim LB As Long
Dim N As Long
On Error Resume Next
N = 1
Do Until Err.Number <> 0
LB = LBound(Arr, N)
N = N + 1
Loop
NumberOfArrayDimensions = N - 2
End Function
Function Transpose1DArray(Arr As Variant, ToRow As Boolean) As Variant
Dim Res As Variant
Dim N As Long
If IsArray(Arr) = False Then
Transpose1DArray = CVErr(xlErrValue)
Exit Function
End If
If NumberOfArrayDimensions(Arr) <> 1 Then
Transpose1DArray = CVErr(xlErrValue)
Exit Function
End If
If ToRow = True Then
ReDim Res(LBound(Arr) To LBound(Arr), LBound(Arr) To UBound(Arr))
For N = LBound(Res, 2) To UBound(Res, 2)
Res(LBound(Res), N) = Arr(N)
Next N
Else
ReDim Res(LBound(Arr) To UBound(Arr), LBound(Arr) To LBound(Arr))
For N = LBound(Res, 1) To UBound(Res, 1)
Res(N, LBound(Res)) = Arr(N)
Next N
End If
Transpose1DArray = Res
End Function
This page last updated: 5-November-2007