我的单元格包含一个!,后跟长度不一的字母数字字符串。

我想将 ! 更改为 NOT(,这相对容易,我遇到困难的部分是字母数字字符串后的右括号

IE

!AlphaNumericString 变为NOT(AlphaNumericString )

更改前的单元格示例,

AND(
!BI ,OR(Z1,Z2,Z102,Z103,Z104,Z306))

!L1M23或 (NP001 和!CA );

Z200 和
!VP100 ;
!N001L010D132

我希望这些单元格变成

AND(
NOT(BI),OR(Z1,Z2,Z102,Z103,Z104,Z306))

非 (L1M23)或 (NP001 且非 (CA) );

Z200 且
非 (VP100) ;
非 (N001L010D132)

Exclamation 后面总是跟着 1 个字符,然后它将是一系列字母数字字符,并且 Exclamation 字符串将始终以下列字符之一结尾

空格

)


;




或者以单元格中的最后一个字符结尾,

根据上述例子

希望这是有意义的。

我更喜欢宏,这样它可以从 EXCEL 365 向后兼容到 2010,但如果有简单的公式,那就太棒了。

谢谢 mbart67

1

  • 感谢这个有趣的任务!受 Tom 的回答启发,添加了一个递归版本来处理可能的嵌套。如果您有更大的输入,我想知道它是如何处理的。


    – 


6 个回答
6

您可以尝试这个 UDF(用户定义函数),并在工作表上使用它作为单元格存放原始字符串的=myReplace(A1)位置。A1

该功能是;

Function myReplace(myRange As Range) As String
    Dim regExp As Object
    
    Set regExp = CreateObject("VBScript.RegExp")
    
    regExp.IgnoreCase = True
    regExp.Global = True
    regExp.Pattern = "!([A-Z0-9]*)"
    
    myReplace = regExp.Replace(myRange.Text, "NOT($1)")

    Set regExp = Nothing
End Function

4

  • 3
    并且它按照 OP 的标签在 Excel 2010 中运行。


    – 

  • 虽然这可能并不重要,但 VBScript.RegExp 在 Mac 上不可用。


    – 

  • 1
    @nkalvi,然后他可以使用跨浏览器和操作系统的 Google Sheets……=REGEXREPLACE(A1,"!([A-Z0-9]+)","NOT($1)")


    – 


  • 你说得对。我想提一下,因为它在我的 Mac 上不起作用。多亏了你的回答,我现在知道如何在旧版本的 Excel 中(在 Windows 下)获取正则表达式功能。


    – 

非正则表达式解决方案:

Function exclamToNot(s As String) As String

    Dim found As Boolean
    Dim i As Integer
    Dim c As String
    Dim t As String
    
    Const terminators As String = ");, "
    
    
    For i = 1 To Len(s)
    
    c = Mid(s, i, 1)
    
 ' Check for exclamation mark
 
    If c = "!" Then
        found = True
        t = t & "not("
        
    ' Check for end of string controlled by exclamation mark
    
    Else
        If found And InStr(terminators, c) > 0 Then
            found = False
            t = t & ")" & c
            
        ' Normal character
        
        Else
            t = t & c
        End If
    End If
            
    Next i
    
    ' Add final bracket if we have reached end of string after exclamation mark
    
    If found Then t = t & ")"
    
    exclamToNot = t

End Function

我想知道您是否可以在工作表公式中使用完全相同的逻辑。这似乎可以正常工作:

=LET(
    c, MID(s, i, 1),
    terminators, ";,) ",
    IF(
        i > LEN(s),
        IF(found, ")", ""),
        IF(
            c = "!",
            "NOT(" & Repex(s, TRUE, i + 1),
            IF(
                AND(found, ISNUMBER(FIND(c, terminators))),
                ")" & c & Repex(s, FALSE, i + 1),
                c & Repex(s, found, i + 1)
            )
        )
    )
)

称为

=repex(A1,false,1)

并保存为 lambda,因此在名称管理器中看起来像这样:

6

  • 递归版本非常好!几乎可以处理!(OR(A-B, !(C-D)), E)=>NOT((OR(A-B), NOT((C-D))), E)


    – 

  • 谢谢!我现在要拼命努力让嵌套案例发挥作用了。


    – 

  • Tom,谢谢你的帮助,你提供的函数 (exclamToNot) 非常好用。我还找人用正则表达式做了这件事,如果你知道它是如何工作的,那么就很简单了,下面提供了解决方案。


    – 


  • 1
    感谢@TomSharpe 的启发,尽管我的尝试很粗糙,但我还是忍不住尝试 🙂


    – 


  • LAMBDA(s,found,i, ) 对于 LET 函数的运行至关重要,但之后它就起作用了。我喜欢这种方法。


    – 

但如果有简单的公式,那就太棒了

事实并非如此,但在 Office 365 中,我们可以组合公式来获得想要的结果:

=MAP(A1:A2, 
 LAMBDA(a,
     REDUCE(a,SEQUENCE(LEN(a)-LEN(SUBSTITUTE(a,"!",))),
     LAMBDA(r,_,
LET(x,MID(r,FIND("!",r),LEN(r)),
    y,TOROW(HSTACK(FIND({" ","(",";",","},x),LEN(x)+1),2),
IFERROR(
        SUBSTITUTE(
                   REPLACE(r,
                           MIN(y+FIND("!",r)-1),
                           ,
                           ")"),
                   "!",
                   "NOT(",
                   1),
        r))))))

基本上,它会检查在初始文本字符串中找到的次数,!以设置 Reduce 的迭代次数,我将其声明为,因为_它用于迭代,但在函数中不再调用。初始文本字符串本身声明为r

迭代!我们在字符串中
搜索第一个找到的字符
r,并找到最接近的 );,字符或第一个找到的字符之后的字符串结尾!(以先到者为准),在)其后面添加 ,并将第一个计数替换!NOT(。如果上述错误,则保留r,否则,被替换/替代的字符r将成为新的r,并重复迭代_次数。

我曾经MAP一次将公式循环到多个单元格,但如果需要,您可以替换 -partMAP/LAMBDALET(a,A1,REDUCE(..使其在单个单元格上起作用。

如果您使用的是 Beta 频道,则可以使用:

=REGEXREPLACE(A1:A2,"!(\w+)","NOT($1)")

4

  • 可能有使用 REGEXREPLACE 的更简单的方法,但这仍处于测试阶段。


    – 

  • 谢谢,是的,这有效,我永远也不会得到这个,因为我仍然不理解所有这些公式,但我可以看到它试图做什么。是否可以将其写成宏函数,这样我就不必在需要时输入所有内容?谢谢 Heaps,效果很好 🙂


    – 


  • 记录为宏并作为值写掉?


    – 

  • 我还添加了 REGEXREPLACE 选项,以防您可以访问它(或者它可能在不久的将来可用)。


    – 

尝试使用递归,尝试处理嵌套:

在名称管理器中定义exclaim_not

=LAMBDA(str, IF(ISNUMBER(FIND("!", str)), LET(delims, {"("," ",",",";",")"}, left_, LEFT(str, FIND("!", str) - 1), right_, exclaim_not(RIGHT(str, LEN(str) - LEN(left_) - 1)), symbol_len, MIN(IFERROR(SEARCH(delims, right_), LEN(right_) + 1)) - 1, symbol, LEFT(right_, symbol_len), after_symbol, RIGHT(right_, LEN(right_) - LEN(symbol)), left_ & "NOT" & IF(AND(LEFT(after_symbol) = "(", RIGHT(after_symbol) = ")"), IF(LEN(symbol), "(" & symbol & after_symbol & ")", after_symbol), "(" & symbol & ")" & after_symbol)), str))

格式化:

=LAMBDA(str,
    IF(
        ISNUMBER(FIND("!", str)),
        LET(
            delims, {"(", " ", ",", ";",")"},
            left_, LEFT(str, FIND("!", str) - 1),
            right_, exclaim_not(RIGHT(str, LEN(str) - LEN(left_) - 1)),
            symbol_len, MIN(IFERROR(SEARCH(delims, right_), LEN(right_) + 1)) - 1,
            symbol, LEFT(right_, symbol_len),
            after_symbol, RIGHT(right_, LEN(right_) - LEN(symbol)),
            left_ & "NOT" &
                IF(
                    AND(LEFT(after_symbol) = "(", RIGHT(after_symbol) = ")"),
                    IF(LEN(symbol), "(" & symbol & after_symbol & ")", after_symbol),
                    "(" & symbol & ")" & after_symbol
                )
        ),
        str
    )
)

2

  • 这真的很棒,应该引起更多关注。也许我会尝试对它或类似的东西进行逆向工程,将其放入 VBA 中(也许)。它还展示了 stackoverflow 应该如何工作。


    – 

  • 您的反馈非常宝贵 – 谢谢!我几十年前就用过 VB,很少用 VBA。现在更喜欢用 JS 和 Swift 🙂


    – 

Like使用否定模式替代正则表达式

这种方法不需要逐个检查并将每个字符添加到整个字符串中,

  • 通过相应的分隔符将每行代码拆分"!"成标记,
  • varStr通过辅助函数识别每个标记的起始字母数字变量字符串getVarStr()
  • Enclose()通过函数执行简单的替换
  • 将修改后的标记加入到最终结果中。

示例调用

Sub EncloseExclam()
'Purp: Replace "!" and following variable alpha-num string part with surrounding elements
    With Sheet1.Range("A2:A5")    ' << redefine as needed
        Dim data: data = .Value2
        Dim i As Long
        For i = LBound(data) To UBound(data)
             data(i, 1) = enclose(data(i, 1)) ' << Function enclose()
        Next i
        .Offset(, 1) = data
    End With
End Sub

函数enclose()

Function enclose(ByVal s As String) As String
'Purp.: Execute "!"-replacement by given prefix+"(" ENCL and closing bracket ENCL2
'0) Define constants
Const REPLACECHAR = "!", ENCL = "NOT(", ENCL2 = ")"
'1) Split code into tokens
    Dim tokens:  tokens = Split(s, REPLACECHAR)
'2) Loop through all "!"-split tokens
    Dim i As Long
    For i = 1 To UBound(tokens)  ' i=1, as very first index i=0 can't contain "!"
        'Identify variable alphanum string
        Dim varStr As String: varStr = getVarStr(tokens(i))
        tokens(i) = Replace(tokens(i), varStr, ENCL & varStr & ENCL2)
    Next i
'3) Join tokens as function result
    enclose = Join(tokens, "")
End Function

辅助函数 getVarStr()

通过负面 模式使用负面字符检查,例如(注意之前的"[!A-Z0-9]"感叹号,即识别第一个非字母数字字符)。!A-Z0-9

另一个好处是,可以传递一个可选参数,UCaseOnly:=False以允许变量字符串内使用小写字符。

Function getVarStr(ByVal s As String, Optional UCaseOnly As Boolean = True) As String
'Purp:  get starting alphanumeric string part
    Dim Pattern As String
    Pattern = IIf(UCaseOnly, "[!A-Z0-9]", "[!A-Za-z0-9]")
    Dim i As Long
    For i = 1 To Len(s)
        If Mid(s, i, 1) Like Pattern Then
            s = Left(s, i - 1): Exit For
        End If
    Next i
    getVarStr = s
End Function

Function ConvertString(s As String) As String
    With CreateObject(Class:="VBScript.RegExp")
        .Pattern = "!([A-Z0-9]{1,})"
        .Global = True
        ConvertString = .Replace(s, "NOT($1)")
    End With
End Function

3

  • 感谢您为 Stack Overflow 社区做出贡献。这可能是一个正确答案,但提供代码的额外解释以便开发人员了解您的推理确实很有用。这对于不太熟悉语法或难以理解概念的新开发人员尤其有用。您能否您的答案以包含更多详细信息以造福社区?


    – 

  • 代码如果附有解释会更有帮助。Stack Overflow 旨在提供学习,而不是提供代码片段供盲目复制粘贴。请您的答案并解释它如何回答所提出的具体问题。请参阅


    – 

  • 好的,但非常接近@Haluk 的解决方案:-;


    –