【问题标题】:String.replace function returning non-string outputString.replace 函数返回非字符串输出
【发布时间】:2019-12-04 20:09:57
【问题描述】:

所以我有这个字符串,我想从中删除非字母数字字符:

my_string = "¿Habla usted Inglés, por favor?"

在这种情况下,基本上我想去掉 ?、¿ 和 ,。然后我将这些单词分成一个列表,并对每个单词做各种有趣的事情。

我正在使用

String.replace(my_string, my_regex, "")
String.split(" ")

做这项工作。我尝试使用两个不同的正则表达式字符串:

my_regex = ~r/[\_\.,:;\?¿¡\!&@$%\^]/
my_regex = ~r/[[:punct:]]/

第一个就像一个魅力。我最终得到:

["habla", "usted", "inglés"]

第二个删除了正确的字符,但我最终得到:

[<<194, 104, 97, 98, 108, 97>>, "usted", <<105, 110, 103, 108, 195, 115>>]

起初我认为奇怪的输出只是因为非 ascii alpha 被转储到控制台。但是,当我尝试匹配预期的字符串列表时,它会失败。

无论如何,我只是不明白为什么两个不同的正则表达式会导致列表中的字符串不同的输出。

这是可以在 iex 中运行的代码,以简洁地重现我的问题:

a = ~r/[\_\.,:;\?¿¡\!&@$%\^]/
b = ~r/[[:punct:]]/
y = "¿Habla usted Inglés, por favor?"
String.replace(y, a, "")  
    # ->  "Habla usted Inglés por favor"
String.replace(y, b, "")
    # -> <<194, 72, 97, 98, 108, 97, 32, 117, 115, 116, 101, 100, 32, 73, 110, 103, 108, 195, 115, 32, 112, 111, 114, 32, 102, 97, 118, 111, 114>>

【问题讨论】:

    标签: regex unicode elixir unicode-string


    【解决方案1】:

    包含 Unicode u 标志以获得 Unicode 支持。

    例如

    a = ~r/[\_\.,:;\?¿¡\!&@$%\^]/u
    b = ~r/[[:punct:]]/u
    

    可以看到这里运行: https://ideone.com/0nQKlq

    【讨论】:

    • 完美!谢谢 :) 对于顶级正则表达式没有相同的问题,我仍然有点困惑——正则表达式中有 unicode 字符。但是,修复非常好,对我来说完全有意义。
    • 坦率地说,如果处理 Unicode 字符输入,我只包含 Unicode 标志。
    【解决方案2】:

    虽然 Dean Taylor 描述了如何使其工作,但我将描述为什么输出是以前的样子。

    首先,当计算开始时,我们需要有一些方法将字母转换为数字,以便有一些我们可以使用的统一标准,跳过很多历史,我们以美国信息交换标准代码结束 称为 ASCII。 ASCII 标准是 7 位编码,这意味着大多数机器上的最高位在使用 ASCII 时总是设置为0。 ASCII 的问题在于它非常以英语为中心,仅包含 24 个基本拉丁字母,并且不支持来自其他语言的任何变音符号。形成这种需求的想法是,只需使用最高位并允许使用另外 127 个代码。

    所以现在我们有了一些解决方案,但很快就提出了其他问题——需要更多更多的字母。问题是如何适应它们。第一个也是当时最简单的解决方案是使用称为“代码页”的东西,它是如何理解设置了最高位的代码的表格。因此,我们以世界不同地区的大量代码页结束。

    到目前为止一切顺利。

    除非没有。代码页有很大的缺陷 - 在一个文档中只能同时使用一个代码页,例如,您不能同时使用丹麦语 (ISO-8859-1) 和俄语 (ISO-8859-2) 字母文档,因为每组字符对不同的字符使用相同的代码,例如Øи 是不可能的,因为它们在各自的代码页中都占用完全相同的代码。哎呀……

    所以之后出现了 Unicode,它想要解决整个混乱。在 Unicode 中,每个字母都有指定的代码,但要小心,这个代码不是转储到文件中的字节,就是这样。这些字节需要以某种方式编码。现在最流行的编码是:

    • UTF-16,它对每个“段”使用 16 位的字符进行编码 - 起初这似乎是一个好主意,因此它被 Java 和 Microsoft 选为在内部存储内容的格式;不幸的是,它非常浪费(ASCII 码而不是 8 位现在需要两倍多,这意味着所有文本文件至少是原始大小的两倍O,它需要 BOM 知道如何读取文件(字节序很重要),并且除此之外,很快就清楚了,16 位不足以存储所有字符,因此一些字符需要编码为 2 个 16 位数字(更多地膨胀文件)
    • UTF-8 是一种可变长度编码,它使用“普通旧 ASCII”来编码可以编码为 ASCII 的字符,special bit magic 来存储更高的字节

    好的,现在我们知道如何编码字符了。但还有一件事,为了简化转换(并且由于高度以西方为中心的委员会),Unicode 中使用的第一个代码页是 ISO-8859-1 代码页。

    现在我们接近解开谜团了。

    Erlang(比 Unicode 至少早 5 年)是由 Ericsson 在瑞典开发的,这意味着他们自然而然地选择了那里自然的代码页 - ISO-8859-1。此代码页还包含像 ¿ 这样的西班牙字符,它被编码为 BF(十六进制,191 dec)。并且根据上述规则,在 UTF-8 中,这个字符被编码为C2 BF 字节到二进制文件中。但是您的正则表达式并未声明它要使用 unicode 字符组,因此 Erlang 假定您要使用默认的 ISO-8859-1 代码页,其中 BF 字节是标点符号。这就是从原始字符串中删除该字符的原因。


    为什么第一个版本有效。由于 Elixir 使用 UTF-8 二进制文件存储字符串,因此您的正则表达式在 ¿ 上不匹配,而是分别针对 C2BF 的每个字节,因为它之前已转换为与 ~r/[\xC2\xBF]/“内部”相同,这是完全有效的正则表达式。这也是为什么字母é 结尾被破坏的原因,因为它被编码为C3 A9,其中给定代码页中的A9 表示©(也被视为标点符号)。这意味着您以 2 个不是有效 UTF-8 字符串的字符串结尾,并且 Elixir inspect 不会尝试显示它们。

    【讨论】:

    【解决方案3】:

    如果您想删除非字母数字字符,您确实应该删除非字母数字字符(可能还有非空格),而不是[:punct:]

    "¿Habla usted Inglés, por favor?"
    |> String.replace(~r/[^[:alnum:]\s]+/u, "")
    #⇒ "Habla usted Inglés por favor"
    

    【讨论】:

    • + 不是必需的。
    • 当然是为了效率。
    • 我没有考虑性能。我对它们进行了基准测试,没有什么可担心的,尽管没有加号的版本效率更高。它也更简单。 :-) pastebin.com/zy1DmEas
    猜你喜欢
    • 2017-09-28
    • 2015-11-10
    • 1970-01-01
    • 2015-02-03
    • 2016-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多