文本相似度分析(Excel)

3
我有一份清单,我想确定其中的条目与此清单中其他条目的相似性。
我的期望输出应该是这样的:enter image description here 相似度列中显示的百分比仅为说明性质。我认为相似性测试可能是以下内容之一:
并排字母数量 / 匹配项中字母总数
但我也很乐意听到其他人的意见。
在Excel上做这个事情是可行的吗?这是一个小数据集(140kb),只包含字母数字值。
我也愿意尝试其他方法来处理此事情,因为我以前没有处理过类似的问题!
附:我已经学了几个月的Python,如果使用Python提供建议也是好的!

只需使用instr() - https://msdn.microsoft.com/zh-cn/library/8460tsh1(v=vs.90).aspx - Vityata
谢谢你的回复,@Vityata。但是我对VBA不是很熟悉,不确定如何实现? - Maverick
3个回答

2

以下是使用VBA UDF的解决方案:

编辑: 添加了一个名为arg_lMinConsecutive的新可选参数,用于确定必须匹配的最小连续字符数。请注意下面公式中的额外参数2,表示至少必须有2个连续字符匹配。

Public Function FuzzyMatch(ByVal arg_sText As String, _
                           ByVal arg_vList As Variant, _
                           ByVal arg_lOutput As Long, _
                           Optional ByVal arg_lMinConsecutive As Long = 1, _
                           Optional ByVal arg_bMatchCase As Boolean = True, _
                           Optional ByVal arg_bExactCount As Boolean = True) _
                As Variant

    Dim dExactCounts As Object
    Dim aResults() As Variant
    Dim vList As Variant
    Dim vListItem As Variant
    Dim sLetter As String
    Dim dMaxMatch As Double
    Dim lMaxIndex As Long
    Dim lResultIndex As Long
    Dim lLastMatch As Long
    Dim i As Long
    Dim bMatch As Boolean

    If arg_lMinConsecutive <= 0 Then
        FuzzyMatch = CVErr(xlErrNum)
        Exit Function
    End If

    If arg_bExactCount = True Then Set dExactCounts = CreateObject("Scripting.Dictionary")

    If TypeName(arg_vList) = "Collection" Or TypeName(arg_vList) = "Range" Then
        ReDim aResults(1 To arg_vList.Count, 1 To 3)
        Set vList = arg_vList
    ElseIf IsArray(arg_vList) Then
        ReDim aResults(1 To UBound(arg_vList) - LBound(arg_vList) + 1, 1 To 3)
        vList = arg_vList
    Else
        ReDim vList(1 To 1)
        vList(1) = arg_vList
        ReDim aResults(1 To 1, 1 To 3)
    End If

    dMaxMatch = 0#
    lMaxIndex = 0
    lResultIndex = 0

    For Each vListItem In vList
        If vListItem <> arg_sText Then
            lLastMatch = -arg_lMinConsecutive
            lResultIndex = lResultIndex + 1
            aResults(lResultIndex, 3) = vListItem
            If arg_bExactCount Then dExactCounts.RemoveAll
            For i = 1 To Len(arg_sText) - arg_lMinConsecutive + 1
                bMatch = False
                sLetter = Mid(arg_sText, i, arg_lMinConsecutive)
                If Not arg_bMatchCase Then sLetter = LCase(sLetter)
                If arg_bExactCount Then dExactCounts(sLetter) = dExactCounts(sLetter) + 1

                Select Case Abs(arg_bMatchCase) + Abs(arg_bExactCount) * 2
                    Case 0
                        'MatchCase is false and ExactCount is false
                        If InStr(1, vListItem, sLetter, vbTextCompare) > 0 Then bMatch = True

                    Case 1
                        'MatchCase is true and ExactCount is false
                        If InStr(1, vListItem, sLetter) > 0 Then bMatch = True

                    Case 2
                        'MatchCase is false and ExactCount is true
                        If Len(vListItem) - Len(Replace(vListItem, sLetter, vbNullString, Compare:=vbTextCompare)) >= dExactCounts(sLetter) Then bMatch = True

                    Case 3
                        'MatchCase is true and ExactCount is true
                        If Len(vListItem) - Len(Replace(vListItem, sLetter, vbNullString)) >= dExactCounts(sLetter) Then bMatch = True

                End Select

                If bMatch Then
                    aResults(lResultIndex, 1) = aResults(lResultIndex, 1) + WorksheetFunction.Min(arg_lMinConsecutive, i - lLastMatch)
                    lLastMatch = i
                End If
            Next i
            If Len(vListItem) > 0 Then
                aResults(lResultIndex, 2) = aResults(lResultIndex, 1) / Len(vListItem)
                If aResults(lResultIndex, 2) > dMaxMatch Then
                    dMaxMatch = aResults(lResultIndex, 2)
                    lMaxIndex = lResultIndex
                End If
            Else
                aResults(lResultIndex, 2) = 0
            End If
        End If
    Next vListItem

    If dMaxMatch = 0# Then
        Select Case arg_lOutput
            Case 1:     FuzzyMatch = 0
            Case 2:     FuzzyMatch = vbNullString
            Case Else:  FuzzyMatch = CVErr(xlErrNum)
        End Select
    Else
        Select Case arg_lOutput
            Case 1:     FuzzyMatch = Application.Min(1, aResults(lMaxIndex, 2))
            Case 2:     FuzzyMatch = aResults(lMaxIndex, 3)
            Case Else:  FuzzyMatch = CVErr(xlErrNum)
        End Select
    End If

End Function

只使用列A和B中的原始数据,您可以使用此UDF在列C和D中获得所需的结果:

enter image description here

在单元格C2并向下复制的是以下公式:

=FuzzyMatch($B2,$B$2:$B$6,COLUMN(A2),2)

在单元格D2中并复制下来的是这个公式:

=IFERROR(INDEX(A:A,MATCH(FuzzyMatch($B2,$B$2:$B$6,COLUMN(B2),2),B:B,0)),"-")

请注意,它们都使用FuzzyMatch UDF。

谢谢你,非常感激!这对我正在进行的另一个项目非常合适。然而,对于这个工作来说并不完全是我想要做的。我正在尝试匹配并发字母,而不仅仅是匹配出现次数。因此,在上面的例子中,Lemon 应该等于 0%。这段代码能够适应这种情况吗? - Maverick
1
@Maverick 即使是并发的字母,“e”和“n”也会至少匹配一个。你的意思是至少要匹配两个连续的字母吗? - tigeravatar
是的,我也这么想。我会说至少2个,可能不超过5-10个,但如果可能的话,能够进行调整就更好了。 - Maverick
@Maverick 我在UDF中添加了一个新的可选参数:arg_lMinConsecutive并已更新答案。请注意公式中额外的参数,结尾处为2,它表示必须匹配至少两个连续字符。您可以将此定制为任何正整数,只要它是正整数即可。如果省略此参数,该公式将假定MinConsecutive为1(原始行为)。 - tigeravatar
UDF进行了轻微更新,使用新的bMatch变量来减少重复代码(最终结果没有变化,这只是为了代码优化)。 - tigeravatar
谢谢,非常完美!我也看了你的RPG,两个都很不错! - Maverick

1

谢谢,我会研究一下! - Maverick

1

我真的没有完全理解整个逻辑,但如果你需要100%的逻辑在这里:

Option Explicit

Sub TestMe()

    Dim rngCell         As Range
    Dim rngCell2        As Range
    Dim lngTotal        As Long
    Dim lngTotal2       As Long
    Dim lngCount        As Long

    For Each rngCell In Sheets(1).Range("A1:A5")
        For Each rngCell2 In Sheets(1).Range("A1:A5")
            If rngCell.Address <> rngCell2.Address Then
                If InStr(1, rngCell, rngCell2) Then
                    rngCell.Offset(0, 1) = 1
                Else
                    If InStr(1, rngCell2, rngCell) Then
                        rngCell.Offset(0, 2) = Round(CDbl(Len(rngCell) / Len(rngCell2)), 2)
                    End If
                End If
            End If
        Next rngCell2
    Next rngCell

End Sub

这是英文,意思是“这里有一张图片:”,其中保留了HTML标记。

enter image description here


谢谢你,非常感谢你的帮助!我正在尝试匹配具有并发字母的单词。因此,如果我在3个单独的行中有Lemon,Lemons和Yellow Lemons,我想快速确定哪些包含单词Lemon。因此,在该示例中,每个都将匹配100%,然后我将快速将它们全部转换为Lemon,以便删除相同但以不同方式输入的重复项。这有意义吗? - Maverick
谢谢 @Vityata,真的很感激!只是确认一下,第一列返回100%匹配,第二列返回部分匹配。对吗? - Maverick
我在D列中有要测试的文本,而我的参考文献在A列中,如果这对您有影响的话? - Maverick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接