【问题标题】:Obtaining the equivalent to printf or String.Format in Excel在 Excel 中获取 printf 或 String.Format 的等价物
【发布时间】:2013-06-21 11:05:24
【问题描述】:

我似乎在电子表格上花费了大量时间来处理以下公式:

="some text '" & A1 & "', more text: '" & A2 &" etc."

使用类似的 printf 或 String.Format 字符串会快得多

=String.Format ("Some text '{0}', more text: '{1}'",A1,A2)

Excel 中是否有类似的内容,或者我可以调用 CLR 吗?

【问题讨论】:

  • 有一个插件:seotoolsforexcel.com/format
  • 对于从 Google 中找到此内容的其他人:@PMBottas 删除的答案链接到 this VB sprintf() (Wayback direct code link),这对我有用。我将包含的printf.bas 导入到Word VBA 项目中,并且能够毫无问题地使用SPrintF 函数。我也可以在 Excel 中使用它,例如 =sprintf("Hello, %s!","World")

标签: excel


【解决方案1】:

不,但您可以通过将以下内容添加到 VBA 模块来简单地创建一个简单的:

Public Function printf(ByVal mask As String, ParamArray tokens()) As String
    Dim i As Long
    For i = 0 To ubound(tokens)
        mask = replace$(mask, "{" & i & "}", tokens(i))
    Next
    printf = mask
End Function

...

=printf("Some text '{0}', more text: '{1}'", A1, A2)

【讨论】:

  • 请记住,如果您的一个替换令牌可能包含另一个占位符,这是不安全的。 (例如,A1 等于 'test {1}'
  • Marius 是对的,为了使其防弹,您可以在替换之前在 tokens 数组中转义 {},然后将它们转义(例如,将 { 替换为 @ 987654329@ 和 } 通过*},然后运行for 循环,然后在返回字符串之前将{**} 替换回{}
  • 另一件事 - 因为这与 .NET 中的 String.Format 有更多相似之处,为什么不将其命名为 StrFormat?原因是,下面我们可以找到另一个答案,其中 C 样式 printf 是从您的代码中实现的......
  • 您可能希望 mask 按值传递,例如ByVal mask As String
  • @Marius & Matt - 实现了“Bullet Proof”版本,以及省略索引的能力:stackoverflow.com/a/67858844/6609896
【解决方案2】:

不是真的 - 有 CONCATENATE 函数:

=CONCATENATE("some text '", A1, "', more text: '", A2, " etc.")

但在我看来,这并不比使用& 更好。

【讨论】:

  • 谢谢。这稍微好一点,因为您可以在键入时看到您在哪个参数中,我认为它会更好地处理错误。
  • 它还可以防止您不得不以丑陋的方式处理字符串中的引号。
  • 公式还有一个字符限制,这让这种方法令人沮丧......我最终创建了一组包含所有文本的列......然后将所有文本单元格与数据单元格连接起来来解决这个问题。
  • 这是 imo 最优雅的解决方案,无需 VBA 代码/宏
【解决方案3】:

我已经更新了 Alex 的代码,因此您可以在每次插入时使用 %s

代替:

=printf("Some text '{0}', more text: '{1}'", A1, A2)

你可以使用:

=printf("Some text '%s', more text: '%s'", A1, A2) 

就像原来的sprintf一样。

更新后的代码:

Public Function Printf(ByVal mask As String, ParamArray tokens()) As String
   Dim i As Long

   For i = 0 To UBound(tokens)          
      mask = Replace$(mask, "%s", tokens(i), , 1)
   Next

   Printf = mask

End Function

【讨论】:

  • 是的,看起来确实像原始 printf。但是 Marius 在原始答案中提到的内容在这里也是正确的:对于 tokens 数组包含另一个占位符的实例是不安全的 - 例如"test %s"。在上面的 cmets 中,我提出了如何解决这个问题的建议。
【解决方案4】:

你可以使用TEXT函数-

您可以像我一样将格式字符串存储在单元格中的某个位置。

我的 D1 单元格中有 "BUY "#" CREDITS" 的值。 在我的 A5 单元格中,我的值为 5000。 当我想显示格式化字符串时,我使用=TEXT(A5, $D$1)

它将单元格的值设置为 BUY 5000 CREDITS。

【讨论】:

  • 请注意,此方法仅适用于一个令牌,即您不能使用"I bought "#" Apples and "#" Oranges."。我希望像String.Format("I bought {0} apples and {1} oranges.", <Cell1>, <Cell2>) 这样的东西,但这种方法不能做到这一点。 (我试图避免使用 VBA)
【解决方案5】:

@AlexK 提供的“防弹”版本,允许:

  • 转义令牌printf("{0}{1}", "test {1}", 2) -> "test {1}2" (NOT "test 22")
  • 位置语法printf("{}{}", "a", "b") -> "ab"
Public Function printf(ByVal mask As String, ParamArray tokens() As Variant) As String
    Dim i As Long
    For i = 0 To UBound(tokens)
        Dim escapedToken As String
        escapedToken = Replace$(tokens(i), "}", "\}") 'only need to replace closing bracket since {i\} is already invalid
        If InStr(1, mask, "{}") <> 0 Then
            'use positional mode {}
            mask = Replace$(mask, "{}", escapedToken, Count:=1)
         
        Else
            'use indexed mode {i}
            mask = Replace$(mask, "{" & i & "}", escapedToken)
        
        End If
    Next
    mask = Replace$(mask, "\}", "}")
    printf = mask
End Function

用法和以前一样:

=printf("Some text '{0}', more text: '{1}'", A1, A2)

OR 位置(从左到右)

=printf("Some text '{}', more text: '{}'", A1, A2)

注意,正如@CristianBuse 在 cmets 中指出的那样,此实现仍会被 {0\}{1} 之类的掩码绊倒,这会导致 {0}{foo} 而不是 {0\}{foo},解决方法是不要使用 \}在你的面具里。


*使用混合语法的行为 `=printf("Some text '{}', more text: '{1}'", A1, A2)` 是未定义的,但替换从左到右所以 `"{}{2}{ 1}"` 等价于`"{0}{2}{1}"`。 `"{}{1}{}"` 不会做你想做的事

【讨论】:

  • 也许您应该在循环之前进行初始替换以将 "\} 替换为其他内容,然后在返回结果之前恢复,以避免掩码已按特定顺序包含这些字符的情况。例如"{0\}{1}" 之类的掩码应按原样返回{0\} 部分,而不是像您的方法那样返回{0}。 +1
  • @CristianBuse 是的,我之前确实在考虑这个问题,除非我用 \} 替换的东西本身在掩码中,那么当你恢复它时,你会得到两个 \} 回来。可以用像 GUID 这样很长的东西来代替以避免冲突,或者密切监视 Len() 发生的情况并将字符插入到特定位置的字符串中,但这对于正则表达式来说确实是一个更好的工作,或者只使用 Matt's version 所以我保留很简单,这足以满足我的目的。但是很好,包含\} 的掩码确实是一个有问题的边缘案例
  • 你提到它很有趣,因为我也在想同样的事情。如果“临时”替换实际上是掩码的一部分,那么您添加另一个“临时”,它可以是令牌的一部分,然后可以成为掩码的一部分。它永远不会真正结束。我还想遍历/解析整个掩码,然后通过连接在特定位置“替换”(不调用Replace)。感谢您的链接,我会看看虽然正则表达式在 VBA 中非常慢,即使对于简单的模式(可能会慢数千倍)。
【解决方案6】:

好的,我给了VBA solution,但这是一个使用相对较新的 LAMBDA 函数的纯 Excel 版本,没有 VBA


请注意,我已经在 Code Review 上发布了一个 alternate version 和逗号分隔的 args PRINTF(mask, arg1, arg2, ...),但是我会尽量保持这个规范和最新,因为有新的更好的选项可用


为了实现这一点,我创建了一个带有签名printf(mask, tokensArray) 的命名函数,其中:

  • mask 是您要格式化的字符串,包含位置 {} 或索引 {i} 插值位置。
  • tokensArray 是要替换的一组值,以一维范围(行或列)、数组(硬编码或从函数返回)或单个值的形式提供。

... 并返回一个格式化的字符串。从像这样的单元格调用:

=printf("Some text '{1}', more text: '{2}'", A1:A2) //continuous 1D row/col
=printf("Some text '{1}', more text: '{2}'", {"foo","bar"}) //hardcoded array
=printf("Single Value {1}", "foo")

或使用位置参数(从左到右)

=printf("Some text '{}', more text: '{}'", A1:A2)

通过在名称管理器中输入这两个函数来定义它们(有关详细说明,请参阅LAMBDA function MSDN 文档,尽管我确定这个链接会失效...):

Param Value
Name ReplaceRecursive
Scope Workbook
Comment Recursively substitutes {} or {i} with tokens from the tokens list, which it escapes one by one leaving } in the result string
Refers To =LAMBDA(mask,tokens,i,tokenCount, IF(i &gt;tokenCount, mask, LET(token, INDEX(tokens,i),escapedToken,SUBSTITUTE(token,"}", "\}"),inIndexedMode,ISERROR(FIND("{}",mask,1)),substituted, IF(inIndexedMode, SUBSTITUTE(mask,"{"&amp;i&amp;"}", escapedToken),SUBSTITUTE(mask, "{}", escapedToken,1) ),ReplaceRecursive(substituted,tokens,i+1,tokenCount))))
=LAMBDA(
    mask,
    tokens,
    i,
    tokenCount,
    IF(
        i > tokenCount,
        mask,
        LET(
            token,
            INDEX(
                tokens,
                i
            ),
            escapedToken,
            SUBSTITUTE(
                token,
                "}",
                "\}"
            ),
            inIndexedMode,
            ISERROR(
                FIND(
                    "{}",
                    mask,
                    1
                )
            ),
            substituted,
            IF(
                inIndexedMode,
                SUBSTITUTE(
                    mask,
                    "{" & i & "}",
                    escapedToken
                ),
                SUBSTITUTE(
                    mask,
                    "{}",
                    escapedToken,
                    1
                )
            ),
            ReplaceRecursive(
                substituted,
                tokens,
                i + 1,
                tokenCount
            )
        )
    )
)
Param Value
Name printf
Scope Workbook
Comment printf(mask: str, tokensArray: {array,} | range | str ) -> str | mask: string to substitute tokens into e.g. "Hello {}, {}" or "Hello {2}, {1}" (1-indexed) | tokensArray: 1D range or array of tokens, e.g. "world" or {"foo","bar"} or A1:A5
Refers To =LAMBDA(mask,tokensArray,LET(r,ROWS(tokensArray), c, COLUMNS(tokensArray), length, MAX(r,c), IF(AND(r&gt;1, c&gt;1), "tokensArray must be 1 dimensional", SUBSTITUTE(ReplaceRecursive(mask, tokensArray, 1, length), "\}","}"))))
=LAMBDA(
    mask,
    tokensArray,
    LET(
        r,
        ROWS(
            tokensArray
        ),
        c,
        COLUMNS(
            tokensArray
        ),
        length,
        MAX(
            r,
            c
        ),
        IF(
            AND(
                r > 1,
                c > 1
            ),
            "tokensArray must be 1 dimensional",
            SUBSTITUTE(
                ReplaceRecursive(
                    mask,
                    tokensArray,
                    1,
                    length
                ),
                "\}",
                "}"
            )
        )
    )
)

我敢肯定,Excel 的 LAMBDA 函数会有所改进,使编写起来更容易,但我认为这种递归方法现在很好。

【讨论】:

    【解决方案7】:

    有趣的问题...我也这么想...如何构建一个字符串而不必在数字之间的某些单个部分中截断长句。

    因为我不想创建 VBA 函数(这会更智能),所以这是我的解决方案...

    SUBSTITUTE(P253;O252;"A1A";1)
    

    在哪里

    • P253 是我的长字符串,#1 标签为“A1A”
    • O252 是 #1 标签的值

    等等......(即使不是我需要)如果其他值......

    =SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(P253;O251;"A1A";1);O252;"A2A";1);O253;"A2A";1)
    

    好吧,我想,即使在#C 中,sprintf(....%s, %s, %d..) 也应该有多重替换原语函数。

    【讨论】:

      猜你喜欢
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 2014-12-30
      • 2014-11-23
      • 2020-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多