String.replace 函数返回非字符串输出
String.replace function returning non-string output
所以我有这个字符串,我想从中删除非字母数字字符:
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>>
包括 Unicode u
标志以获得 Unicode 支持。
例如
a = ~r/[\_\.,:;\?¿¡\!&@$%\^]/u
b = ~r/[[:punct:]]/u
可以在运行这里看到:
https://ideone.com/0nQKlq
虽然 Dean Taylor 描述了如何让它工作,但我将描述为什么输出是以前的样子。
首先,当计算开始时,我们需要有一些方法将字母转换为数字,以便有一些我们可以使用的统一标准,跳过很多历史,我们以 American Standard 结束信息交换代码 称为ASCII。 ASCII 标准是 7 位编码,这意味着在使用 ASCII 时,大多数机器上的最高位总是设置为 0
。 ASCII 的问题在于它非常以英语为中心并且仅包含 24 个基本拉丁字母并且不支持来自其他语言的任何变音符号。形成这种需要的想法是,只需使用最高位并允许使用另外 127 个代码。
所以现在我们有了一些解决方案,但很快又出现了其他问题 - 需要 很多 个字母。问题是如何适应它们。第一个也是最简单的解决方案是使用称为 "code pages" 的东西,这是 table 如何理解设置了最高位的代码。所以我们以世界不同地区的大量代码页结束。
到目前为止一切顺利。
除非没有。代码页有很大的缺陷 - 在一个文档中只能同时使用其中一个代码页,因此例如您不能同时使用丹麦语 (ISO-8859-1) 和俄语 (ISO-8859-2) 字母document 因为每组字符对不同的字符使用相同的代码,例如 Øи
是不可能的,因为它们在各自的代码页中都占用完全相同的代码。哎呀…
所以在那之后出现了 Unicode,它想要修复整个混乱。在 Unicode 中,每个字母都有分配的代码,但要小心,这个代码不是转储到文件中的字节,它就是这样。这些字节需要以某种方式进行编码。现在最流行的编码是:
- UTF-16,每个 "segment" 使用 16 位对字符进行编码 - 一开始这似乎是个好主意,因此它被 Java 和 Microsoft 选择为存储格式内部的东西;不幸的是,这非常浪费(ASCII 码而不是 8 位现在需要两倍,这意味着所有文本文件至少是原始大小的两倍,它需要 BOM 知道如何读取文件(字节顺序很重要),并且除此之外,很快就清楚了,16 位不足以存储所有字符,因此一些字符需要编码为 2 个 16 位数字(使文件膨胀更多)
- UTF-8 是一种可变长度编码,它使用 "plain old ASCII" 来表示可以编码为 ASCII 的字符,使用 special bit magic 来存储更高的字节
好的,现在我们知道如何对字符进行编码了。但还有一件事,为了简化转换(并且由于高度以西方为中心的委员会),Unicode 中使用的第一个代码页是 ISO-8859-1 代码页。
现在我们接近解开这个谜了。
Erlang(比 Unicode 至少早 5 年)是由爱立信在瑞典开发的,这意味着他们自然而然地选择了那里自然的代码页 - ISO-8859-1。此代码页还包含西班牙字符,如 ¿
,编码为 BF
(十六进制,191
十进制)。并且根据上面的规则,在UTF-8中,这个字符被编码为C2 BF
字节到二进制文件中。但是你的正则表达式没有声明它想要使用 unicode 字符组,所以 Erlang 假设你想要使用默认的 ISO-8859-1 代码页,其中 BF
字节是一个标点符号。这就是该字符从原始字符串中删除的原因。
为什么第一个版本有效。由于 Elixir 使用 UTF-8 二进制文件来存储字符串,因此您的正则表达式在 ¿
上不匹配,而是分别针对每个字节 C2
和 BF
因为它之前已转换为与~r/[\xC2\xBF]/
"internally",这是完全有效的正则表达式。这也是为什么字母 é
结束损坏,因为它被编码为 C3 A9
,其中给定代码页中的 A9
表示 ©
(也被视为标点符号)。这意味着您以 2 个不是有效 UTF-8 字符串的字符串结尾,Elixir inspect
不会尝试呈现它们。
如果您想删除非字母数字字符,您确实应该删除非字母数字字符(可能还有非空格)而不是[:punct:]
。
"¿Habla usted Inglés, por favor?"
|> String.replace(~r/[^[:alnum:]\s]+/u, "")
#⇒ "Habla usted Inglés por favor"
所以我有这个字符串,我想从中删除非字母数字字符:
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>>
包括 Unicode u
标志以获得 Unicode 支持。
例如
a = ~r/[\_\.,:;\?¿¡\!&@$%\^]/u
b = ~r/[[:punct:]]/u
可以在运行这里看到: https://ideone.com/0nQKlq
虽然 Dean Taylor 描述了如何让它工作,但我将描述为什么输出是以前的样子。
首先,当计算开始时,我们需要有一些方法将字母转换为数字,以便有一些我们可以使用的统一标准,跳过很多历史,我们以 American Standard 结束信息交换代码 称为ASCII。 ASCII 标准是 7 位编码,这意味着在使用 ASCII 时,大多数机器上的最高位总是设置为 0
。 ASCII 的问题在于它非常以英语为中心并且仅包含 24 个基本拉丁字母并且不支持来自其他语言的任何变音符号。形成这种需要的想法是,只需使用最高位并允许使用另外 127 个代码。
所以现在我们有了一些解决方案,但很快又出现了其他问题 - 需要 很多 个字母。问题是如何适应它们。第一个也是最简单的解决方案是使用称为 "code pages" 的东西,这是 table 如何理解设置了最高位的代码。所以我们以世界不同地区的大量代码页结束。
到目前为止一切顺利。
除非没有。代码页有很大的缺陷 - 在一个文档中只能同时使用其中一个代码页,因此例如您不能同时使用丹麦语 (ISO-8859-1) 和俄语 (ISO-8859-2) 字母document 因为每组字符对不同的字符使用相同的代码,例如 Øи
是不可能的,因为它们在各自的代码页中都占用完全相同的代码。哎呀…
所以在那之后出现了 Unicode,它想要修复整个混乱。在 Unicode 中,每个字母都有分配的代码,但要小心,这个代码不是转储到文件中的字节,它就是这样。这些字节需要以某种方式进行编码。现在最流行的编码是:
- UTF-16,每个 "segment" 使用 16 位对字符进行编码 - 一开始这似乎是个好主意,因此它被 Java 和 Microsoft 选择为存储格式内部的东西;不幸的是,这非常浪费(ASCII 码而不是 8 位现在需要两倍,这意味着所有文本文件至少是原始大小的两倍,它需要 BOM 知道如何读取文件(字节顺序很重要),并且除此之外,很快就清楚了,16 位不足以存储所有字符,因此一些字符需要编码为 2 个 16 位数字(使文件膨胀更多)
- UTF-8 是一种可变长度编码,它使用 "plain old ASCII" 来表示可以编码为 ASCII 的字符,使用 special bit magic 来存储更高的字节
好的,现在我们知道如何对字符进行编码了。但还有一件事,为了简化转换(并且由于高度以西方为中心的委员会),Unicode 中使用的第一个代码页是 ISO-8859-1 代码页。
现在我们接近解开这个谜了。
Erlang(比 Unicode 至少早 5 年)是由爱立信在瑞典开发的,这意味着他们自然而然地选择了那里自然的代码页 - ISO-8859-1。此代码页还包含西班牙字符,如 ¿
,编码为 BF
(十六进制,191
十进制)。并且根据上面的规则,在UTF-8中,这个字符被编码为C2 BF
字节到二进制文件中。但是你的正则表达式没有声明它想要使用 unicode 字符组,所以 Erlang 假设你想要使用默认的 ISO-8859-1 代码页,其中 BF
字节是一个标点符号。这就是该字符从原始字符串中删除的原因。
为什么第一个版本有效。由于 Elixir 使用 UTF-8 二进制文件来存储字符串,因此您的正则表达式在 ¿
上不匹配,而是分别针对每个字节 C2
和 BF
因为它之前已转换为与~r/[\xC2\xBF]/
"internally",这是完全有效的正则表达式。这也是为什么字母 é
结束损坏,因为它被编码为 C3 A9
,其中给定代码页中的 A9
表示 ©
(也被视为标点符号)。这意味着您以 2 个不是有效 UTF-8 字符串的字符串结尾,Elixir inspect
不会尝试呈现它们。
如果您想删除非字母数字字符,您确实应该删除非字母数字字符(可能还有非空格)而不是[:punct:]
。
"¿Habla usted Inglés, por favor?"
|> String.replace(~r/[^[:alnum:]\s]+/u, "")
#⇒ "Habla usted Inglés por favor"