【问题标题】:Excel - String Remove DuplicatesExcel - 字符串删除重复项
【发布时间】:2016-06-25 01:09:09
【问题描述】:

我正在处理一些英国地址数据,这些数据在 Excel 单元格中用逗号分成其组成部分。

我有一些从网络上获取的 VBA,它已经删除了一些完全重复的条目,但我留下了大量的数据,其中有一些按顺序重复的片段,一些是非按顺序的重复片段。

附上一张图片,突出显示我正在努力实现的目标,我迄今为止使用的代码不是我的,它包含在内,以向您展示我一直在寻找的方向。有人对如何实现这一目标有任何进一步的想法吗?

Function stringOfUniques(inputString As String, delimiter As String)
Dim xVal As Variant
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")

For Each xVal In Split(inputString, delimiter)
dict(xVal) = xVal
Next xVal

stringOfUniques = Join(dict.Keys(), ",")
End Function

这确实设法摆脱了其中一些,但我正在研究大量人口,因此自动化这将是不可思议的。

【问题讨论】:

  • 带有反向引用的正则表达式是另一种可能的选择

标签: regex string excel vba duplicates


【解决方案1】:

可能不是最优雅的答案,但这可以解决问题。 这里我使用 Split 命令在每个逗号处拆分字符串。 从这里返回的结果是

bat ball banana

代码:

Option Explicit
Private Sub test()
 Dim Mystring As String
 Dim StrResult As String

 Mystring = "bat,ball,bat,ball,banana"
 StrResult = shed_duplicates(Mystring)
End Sub
Private Function shed_duplicates(ByRef Mystring As String) As String
 Dim MySplitz() As String
 Dim J As Integer
 Dim K As Integer
 Dim BooMatch As Boolean
 Dim StrTemp(10) As String ' assumes no more than 10 possible splits!
 Dim StrResult As String


 MySplitz = Split(Mystring, ",")
  For J = 0 To UBound(MySplitz)
     BooMatch = False
     For K = 0 To UBound(StrTemp)
         If MySplitz(J) = StrTemp(K) Then
            BooMatch = True
            Exit For
         End If
     Next K
    If Not BooMatch Then
       StrTemp(J) = MySplitz(J)
    End If
Next
For J = 0 To UBound(StrTemp)
   If Len(StrTemp(J)) > 0 Then ' ignore blank entries
      StrResult = StrResult + StrTemp(J) + " "
   End If
Next J
Debug.Print StrResult
End Function

【讨论】:

    【解决方案2】:

    您可能真的会使用正则表达式替换:

    ^(\d*\s*([^,]*),.*)\2(,|$)
    

    替换模式是

    $1$3
    

    请参阅regex demo模式解释

    • ^ - 字符串的开头(如果.MultiLine = True,则为一行)
    • (\d*\s*([^,]*),.*) - 第 1 组(后来用替换模式中的 $1 反向引用引用)匹配:
      • \d* - 0+ 位后跟
      • \s* - 0+ 个空白字符
      • ([^,]*) - 第 2 组(稍后我们可以使用 \2 模式内反向引用来引用使用此子模式捕获的值)匹配 0+ 个字符而不是逗号
      • ,.* - 逗号后跟 0+ 个字符,换行符除外
    • \2 - Group 2 捕获的文本
    • (,|$) - 第 3 组(稍后用替换模式中的 $3 引用 - 以恢复逗号)匹配逗号或字符串结尾(如果 .MultiLine = True 则为行)。

    注意:如果您只检查包含一个地址的单个单元格,则不需要.MultiLine = True

    下面是一个示例 VBA Sub,展示了如何在 VBA 中使用它:

    Sub test()
      Dim regEx As Object
      Set regEx = CreateObject("VBScript.RegExp")
      With regEx
          .pattern = "^(\d*\s*([^,]*),.*)\2(,|$)"
          .Global = True
          .MultiLine = True ' Remove if individual addresses are matched
      End With
      s = "66 LAUSANNE ROAD,LAUSANNE ROAD,HORNSEY" & vbCrLf & _
          "9 CARNELL LANE,CARNELL LANE,FERNWOOD" & vbCrLf & _
          "35 FLAT ANDERSON HEIGHTS,1001 LONDON ROAD,FLAT ANDERSON HEIGHTS" & vbCrLf & _
          "27 RUSSELL BANK ROAD,RUSSEL BANK,SUTTON COLDFIELD"
      MsgBox regEx.Replace(s, "$1$3")
    End Sub
    

    【讨论】:

    • 干得好!请注意,在正则表达式替换后删除双倍逗号的小调整是值得的。
    • 我从未见过正则表达式函数,示例性解决方案!
    【解决方案3】:

    第一个解决方案是使用字典来获取唯一段的列表。 然后就像在分割段之前跳过第一个地址号一样简单:

    Function RemoveDuplicates1(text As String) As String
      Static dict As Object
      If dict Is Nothing Then
        Set dict = CreateObject("Scripting.Dictionary")
        dict.CompareMode = 1  ' set the case sensitivity to All
      Else
        dict.RemoveAll
      End If
    
      ' Get the position just after the address number
      Dim c&, istart&, segment
      For istart = 1 To Len(text)
        c = Asc(Mid$(text, istart, 1))
        If (c < 48 Or c > 57) And c <> 32 Then Exit For  ' if not [0-9 ]
      Next
    
      ' Split the segments and add each one of them to the dictionary. No need to keep 
      ' a reference to each segment since the keys are returned by order of insertion.
      For Each segment In Split(Mid$(text, istart), ",")
        If Len(segment) Then dict(segment) = Empty
      Next
    
      ' Return the address number and the segments by joining the keys
      RemoveDuplicates1 = Mid$(text, 1, istart - 1) & Join(dict.keys(), ",")
    End Function
    

    第二种解决方案是提取所有片段,然后搜索它们中的每一个是否存在于先前的位置:

    Function RemoveDuplicates2(text As String) As String
      Dim c&, segments$, segment$, length&, ifirst&, istart&, iend&
    
      ' Get the position just after the address number
      For ifirst = 1 To Len(text)
        c = Asc(Mid$(text, ifirst, 1))
        If (c < 48 Or c > 57) And c <> 32 Then Exit For  ' if not [0-9 ]
      Next
    
      ' Get the segments without the address number and add a leading/trailing comma
      segments = "," & Mid$(text, ifirst) & ","
      istart = 1
    
      ' iterate each segment
      Do While istart < Len(segments)
    
        ' Get the next segment position
        iend = InStr(istart + 1, segments, ",") - 1 And &HFFFFFF
        If iend - istart Then
    
          ' Get the segment
          segment = Mid$(segments, istart, iend - istart + 2)
    
          ' Rewrite the segment if not present at a previous position
          If InStr(1, segments, segment, vbTextCompare) = istart Then
            Mid$(segments, length + 1) = segment
            length = length + Len(segment) - 1
          End If
        End If
    
        istart = iend + 1
      Loop
    
      ' Return the address number and the segments
      RemoveDuplicates2 = Mid$(text, 1, ifirst - 1) & Mid$(segments, 2, length - 1)
    
    End Function
    

    第三种解决方案是使用正则表达式删除所有重复的段:

    Function RemoveDuplicates3(ByVal text As String) As String
    
      Static re As Object
      If re Is Nothing Then
        Set re = CreateObject("VBScript.RegExp")
        re.Global = True
        re.IgnoreCase = True
        ' Match any duplicated segment separated by a comma.
        ' The first segment is compared without the first digits.
        re.Pattern = "((^\d* *|,)([^,]+)(?=,).*),\3?(?=,|$)"
      End If
    
      ' Remove each matching segment
      Do While re.test(text)
        text = re.Replace(text, "$1")
      Loop
    
      RemoveDuplicates3 = text
    End Function
    

    这些是 10000 次迭代的执行时间(越低越好):

    input text  : "123 abc,,1 abc,abc 2,ABC,abc,a,c"
    output text : "123 abc,1 abc,abc 2,a,c"
    
    RemoveDuplicates1 (dictionary)  : 718 ms
    RemoveDuplicates2 (text search) : 219 ms
    RemoveDuplicates3 (regex)       : 1469 ms
    

    【讨论】:

    • 再次感谢您提供的众多优雅解决方案,每一个似乎都符合我的要求。赞赏!
    猜你喜欢
    • 1970-01-01
    • 2014-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多