Fuzzy Vlookup для отображения всех совпадающих результатов в одной строке

Я знаю, что vlookup возвращает только один результат, но я ищу способ поиска по 2 столбцам и возврата всех результатов, соответствующих этому запросу:

SUBSTITUTE("*"&C2&"*"," ","*")

Таким образом, он также возвращает похожие совпадения. Я могу вернуть первое совпадение (с помощью vlookup), но мне нужно вернуть все совпадения и отобразить их в строке.

Если бы он создал массив, я мог бы отобразить первое совпадение в строке с первым элементом в массиве, отобразить второе совпадение со вторым элементом... и так далее.

VBA до сих пор:

Function Occur(text, occurence, column_to_check)
  newarray = Split(text, " ")

  Dim temp As New Collection
  Dim intX As Integer

   For i = 1 To 90000
   intX = 1
        For j = 0 To Len(newarray)
             If Not InStr(Range(column_to_check + i).Value, newarray(j)) Then
                intX = 0
             End If
        Next j
        Exit For
        If intX = 1 Then
            temp.Add (Cells(i, column_to_check))
        End If
    Next i

End Function

Спасибо!


person Tom Wall    schedule 13.09.2013    source источник
comment
Вы можете просто построить массив в цикле For...Next и проверить, соответствует ли значение ячейки Like Range(C2).Value?   -  person David Zemens    schedule 13.09.2013
comment
@DavidZemens У меня нет проблем с распечаткой массива. Меня больше интересует, как создать массив.   -  person Tom Wall    schedule 13.09.2013
comment
Кроме того, если вы имеете в виду, как заполнить массив...   -  person David Zemens    schedule 13.09.2013
comment
не уверен, что вам конкретно нужен способ VBA, но вы можете сделать это и с формулами массива (хотя он работает медленно, если у вас много тысяч строк)   -  person Cor_Blimey    schedule 13.09.2013
comment
@Cor_Blimey Нужно проверить 90 000 строк и 2 столбца. Я пытался использовать формулы массива перед использованием ИНДЕКС и МАЛЕНЬКИЙ, но замена вместе с двумя столбцами всегда вызывает ошибку. Как бы вы предложили сделать это таким образом?   -  person Tom Wall    schedule 13.09.2013
comment
С 90 000 строк? я бы не стал! Расчет занял бы 30 секунд или больше...! VBA — это то, что нужно, поскольку, в отличие от формул массива, вы можете запрограммировать его, зная, что данные понадобятся в будущем, тогда как формулы массива как бы «начинают заново» для каждой итерации ВПР/ПОИСКПОЗ, и вы получите O( n+(n-1)+(n-2)..0) задача.   -  person Cor_Blimey    schedule 13.09.2013
comment
@Cor_Blimey Какой-нибудь рекомендуемый алгоритм поиска vba (который будет заполнять массив каждым совпадением) для реализации в любом количестве столбцов / строк для этого нечеткого совпадения?   -  person Tom Wall    schedule 13.09.2013
comment
@TomWall, чтобы я правильно понял основной вопрос, у вас есть одно значение, для которого вы хотите выполнить «нечеткое» совпадение по ячейкам в двух столбцах и вернуть массив с каждой соответствующей ячейкой?   -  person Cor_Blimey    schedule 13.09.2013
comment
ПОСТАВЬТЕ СВОЙ КОД В ВОПРОСЕ, ЧИТАТЬ ЕГО В КОММЕНТАРИЯХ НЕВОЗМОЖНО.   -  person David Zemens    schedule 13.09.2013
comment
@Cor_Blimey Точно. И нечеткое совпадение сработало бы там, где, если бы я набрал энгри бердс, массив содержал бы все ячейки, содержащие энгри и птичек где-то в них. (например, будет возвращен ipad_app_angry_birds)   -  person Tom Wall    schedule 13.09.2013
comment
Я также немного не понимаю, что вы пытаетесь сопоставить. Вы ищете соответствие всему, что содержит значение подстроки из C2? И потом, почему вы заменяете пробелы/звездочки?   -  person David Zemens    schedule 13.09.2013
comment
@TomWall о, так что, если в ячейке есть любое слово, которое вы ищете, в любой ее части, которую вы хотите, чтобы она соответствовала? Итак, ища внутри нет ложки, Ничто не пройдет! будет ли совпадение?   -  person Cor_Blimey    schedule 13.09.2013
comment
@DavidZemens Извините за это. Опубликовано. Замена выполняется потому, что результаты должны содержать все строки в ячейке C2 где-то в ячейке. Пример: если C2 содержит злых птиц, то вот несколько ячеек, которые могут быть возвращены: ipad_angry_birds Angry Birds Game Birds_rovio_angry_   -  person Tom Wall    schedule 14.09.2013
comment
@Cor_Blimey Нет, потому что все отдельные слова должны содержаться в ячейке. См. выше комментарий к Дэвиду для другого примера. Люблю ваш пример, хотя.   -  person Tom Wall    schedule 14.09.2013
comment
а ладно, выложу что-нибудь через пару минут   -  person Cor_Blimey    schedule 14.09.2013
comment
Что, если там будет что-то вроде «Птицы злятся»? Это тоже должно совпадать?   -  person David Zemens    schedule 14.09.2013
comment
@DavidZemens Да, так и должно быть.   -  person Tom Wall    schedule 14.09.2013
comment
@Cor_Blimey Спасибо.   -  person Tom Wall    schedule 14.09.2013


Ответы (2)


Попробуй это. Вы можете использовать его как формулы массива, выбирая разумное количество ячеек для отображения результата, или использовать его в коде и выгружать на рабочий лист любым удобным для вас способом.

Он принимает одну строку для поиска (которую он разбивает и проверяет каждое слово в одной строке), затем массив Param из строк, диапазонов или массивов для поиска. Он возвращает массив совпадений, поэтому вы можете использовать его как формулу массива или использовать в коде как любой другой массив.

Примеры использования:

  • =GetAllMatches("two three",A1:A5) пример с одним непрерывным диапазоном
  • =GetAllMatches("two three",A1,A3:A20,B5:B8,D1) 'пример с несмежными ячейками
  • =GetAllMatches("two three",{"one two","three two","one two three"}) пример с массивом
  • =GetAllMatches("two three","one two","one","three two","one two three") пример со строками
  • For each match in GetAllMatches(blah,blahblah):Debug.Print match:Next match пример использования в коде вместо формулы

Возможно, вам придется настроить по вкусу, но я прокомментировал, что он делает в коде.

Пример кода:

Public Function GetAllMatches(searchFor As String, ParamArray searchWithin()) As Variant

    'I use a ParamArray to handle the case of wanting to pass in non-contiguous ranges to search other
    'e.g. Blah(A1,A2,A3,C4:C10,E5)
    'nice little feature of Excel formulae :)

    Dim searchRange, arr, ele, searchComponents
    Dim i As Long
    Dim results As Collection
    Dim area As Range
    Set results = New Collection

    'generate words to test
    searchComponents = Split(searchFor, " ")

    For Each searchRange In searchWithin
        If TypeOf searchRange Is Range Then 'range (we test to handle user passing in arrays)
            For Each area In searchRange.Areas 'we enumerate to handle multi-area ranges
                arr = area.Value
                If VarType(arr) < vbArray Then 'we test to handle single cell areas
                    If isMatch(arr, searchComponents) Then results.Add arr 'is a match so add to results
                Else 'is an array, so enumerate
                    For Each ele In arr
                        If isMatch(ele, searchComponents) Then results.Add ele  'is a match so add to results
                    Next ele
                End If
            Next area
        Else
            Select Case VarType(searchRange)
                Case Is > vbArray 'user passed in an array not a range
                    For Each ele In searchRange 'enumerate, not iterate, to handle multiple dimensions etc
                        If isMatch(ele, searchComponents) Then results.Add ele  'is a match so add to results
                    Next ele
                Case vbString
                    If isMatch(searchRange, searchComponents) Then results.Add searchRange  'is a match so add to results
                Case Else 'no idea - return an error then fail fast (suppressed if called by an excel formula so ok)
                    GetAllMatches = CVErr(XlCVError.xlErrRef)
                    Err.Raise 1, "GetAllMatches", "Invalid Argument"
            End Select
        End If
    Next searchRange

    'Process Results
    If results.Count = 0 Then 'no matches
        GetAllMatches = CVErr(XlCVError.xlErrNA) 'return #N/A
    Else
        'process results into an array
        ReDim arr(0 To results.Count - 1)
        For i = 0 To UBound(arr)
            arr(i) = results(i + 1)
        Next i
        GetAllMatches = arr 'Return the array of matches
    End If
End Function
Private Function isMatch(ByRef searchIn, ByRef searchComponents) As Boolean
    Dim ele
    For Each ele In searchComponents
        If Not (InStr(1, searchIn, ele, vbTextCompare) > 0) Then
            Exit Function
        End If
    Next ele
    isMatch = True
End Function

Пример электронной таблицы:

one                  
one two         
one two three           
one three two           
four three one two  

результаты: one two three one three two four three one two

person Cor_Blimey    schedule 13.09.2013
comment
Большая часть кода является «пухлой», чтобы позволить ему справиться с множеством вещей, которые нужно просмотреть. Я часто сам повторно использую части этого пуха, поэтому я просто скопировал части. Это позволяет пользовательским функциям обрабатывать большинство вещей, которые пользователи ожидают как «стандартные» от многих встроенных формул Excel, таких как оператор объединения и поддержка формул массива. Специальные биты для этого — такие как строка Split() и основное «соответствующее» тестирование в функции «isMatch» — очень похожи на подход Дэвида Земенса, который наверняка заслуживает +1 за хороший рекомендуемый принцип/подход. Рад, что это помогает. - person Cor_Blimey; 14.09.2013
comment
Эй, у меня есть еще один вопрос. Я использую это некоторое время, и это работает отлично, но я хотел бы внести изменения. Я бы хотел, чтобы он всегда возвращал что-то из определенного столбца. Поэтому, если массив включает все столбцы A и B и находит совпадение в B50, я бы хотел, чтобы он возвращал B50. Но если он найдет совпадение на A50, я бы хотел, чтобы он возвращал B50 вместо A50. Таким образом, он всегда возвращает «B» и номер строки, в которой он найден. Может быть, у вас есть какие-то сведения о том, как изменить ваш код, чтобы обеспечить это? - person Tom Wall; 10.10.2013

Используйте словарь сценариев и некоторые манипуляции с массивами/диапазонами. Я проверил это примерно на 30 000 строк, и он вернул примерно 10 000 совпадений быстрее, чем я мог моргнуть.

Sub TestWithoutRE()
    Dim dict As Object
    Dim srchStrings() As String
    Dim s As Variant
    Dim colsToSearch As Range
    Dim cl As Range
    Dim allMatch As Boolean
    Dim matchArray As Variant

    'Define the strings you're looking for
    srchStrings = Split([C2], " ")

    'Define the ranges to search:
    Set colsToSearch = Range("F1:G33215")

    'Build a dictionary of the column data
    Set dict = CreateObject("Scripting.Dictionary")
    For Each cl In colsToSearch.Cells
        allMatch = True 'this will be set to false on the first non-matching value, no worries
        'Make sure each word is in the cell's value:
        For Each s In srchStrings
            If InStr(1, LCase(cl), LCase(s)) = 0 Then
                allMatch = allMatch + 1
                Exit For  'exit this if ANY substring is not found
            End If
        Next
        If allMatch Then
            '## As long as all strings were found, add this item to the dictionary
            dict.Add cl.Address, cl.Value
        End If
    Next

    '## Here is your array of matching values:
    matchArray = dict.Items


End Sub

В основном я разделил ваш параметр поиска (C2) на массив. Затем я перебираю каждую ячейку в этих столбцах, проверяя каждый элемент разделенного массива из C2. Если какое-либо из слов из C2 не найдено, я игнорирую его как частичное совпадение, вы ищете только оба слова, совпадающие в произвольном порядке.

Если оба слова совпадают, добавьте значение в объект словаря.

Затем вы можете получить доступ ко всем совпадающим значениям, обратившись к dictionary.Items, который возвращает массив.

person David Zemens    schedule 13.09.2013