Warm tip: This article is reproduced from serverfault.com, please click

VBA function runs as a macro but gives error when called with function

发布于 2020-11-28 14:03:42

I have an excel table called AnimeList, where I have listed all the anime I have finished watching along with their info. The table has the following headers:

Name, Main Genre, Genre2, Genre3, Rating, Seasons, Episodes, Mins/Episode, Status.

I have written some VBA code that can count the distinct genres from the 3 columns as well as the number of them present.

Function CountAndSortGenre()

Dim size As Integer: size = Range("AnimeList[Main Genre]").Rows.Count

ReDim genreExtract((size * 3) - 1) As String

Dim i As Integer: i = 0
Dim cell As Range
For Each cell In Range("AnimeList[Main Genre]")
    genreExtract(i) = cell.Value
    i = i + 1
Next
For Each cell In Range("AnimeList[Genre - 2]")
    genreExtract(i) = cell.Value
    i = i + 1
Next
For Each cell In Range("AnimeList[Genre - 3]")
    genreExtract(i) = cell.Value
    i = i + 1
Next

Dim distinctGenres As New Dictionary
Dim genre As Variant
For Each genre In genreExtract
    If distinctGenres.exists(genre) Then
        distinctGenres(genre) = distinctGenres(genre) + 1
    Else
        distinctGenres.Add genre, 1
    End If
Next

size = distinctGenres.Count
Erase genreExtract

ReDim sortedGenres(size - 1, 1) As Variant
For i = 0 To distinctGenres.Count - 1
    sortedGenres(i, 0) = distinctGenres.Keys(i)
    sortedGenres(i, 1) = distinctGenres.Items(i)
Next i

distinctGenres.RemoveAll
QuickSort sortedGenres, 0, size - 1 'This is done in a separate function

End Function

At the end I have what I need, i.e. the sorted genre counts in my sortedGenre array. But I need to output it to the excel sheet now which is proving to be rather difficult task.

I tried calling the function after adding return type "As Variant" in the declaration and adding the statement CountAndSortGenre = sortedGenres at the end like so:

=CountAndSortGenre()

but the array which is returned is not spilled across multiple cells. Instead only the first element of the array is displayed on the cell where I input the formula.

I tried using Ctrl+Shift+Enter which changed the formula to:

{=CountAndSortGenre()}

but that did not change the output. It was still the first element of the array

I tried putting it in the index formula like so:

INDEX(CountAndSortGenre(), 1, 2)

trying to at least get something other than the first value of the array but that still kept returning the first value only.

Afterwards I tried using a manual approach to push the values into the cells by removing the As Variant return type and the return value in the end and adding the following code:

For i = 0 To size - 1
    Application.ActiveCell.Offset(i + 1, 1) = sortedGenres(i, 0)
    Application.ActiveCell.Offset(i + 1, 2) = sortedGenres(i, 1)
Next i

This approach worked when I ran the code but when I tried using the function like:

= CountAndSortGenre()

Excel gave me circular reference warning and thus it did not work.

The reason I dont want to use the macro and want to use it as a function is that I want these values to get updated as I update my source table. I am not sure that using a function will be dynamic, but it is the best bet. But right now I just want this function to start working.

Questioner
Firebender
Viewed
0
Ron Rosenfeld 2020-11-29 06:50:39

I used an Array List because I'm too lazy to go look for my QuickSort routine; and I only created a single dimension output for horizontal output.

I used the range as an argument for the function so it would update dynamically when a cell in the called range is changed.

If your range may change in size, I'd suggest using either a dynamic named range, or using a Table with structured references, either of which can auto adjust the size.

If you require a vertical output, you can either Transpose before setting the output of the function; or loop into a 2D array.

Option Explicit
Option Compare Text
Function CountAndSortGenre(rg As Range) As Variant()
    Dim v As Variant, w As Variant
    Dim distinctGenres As Object
    
v = rg

Set distinctGenres = CreateObject("System.Collections.ArrayList")
With distinctGenres
    For Each w In v
        If w <> "" Then
            If Not .contains(w) Then .Add w
        End If
    Next w
    .Sort
    CountAndSortGenre = .toarray
End With
        
End Function

enter image description here