如何将字符串转换为在 Ruby 中保留空格的数组?

How do I convert a string to an array with spaces preserved in Ruby?

如何将字符串:'Hello world!' 转换为数组:['Hello', ' ', 'world!'] 并保留所有空格?

我尝试使用不同参数的split方法转换字符串,但没有找到正确的解决方案。

而且我在文档(Class: String (Ruby 3.1.0))中也没有找到适合解决这个问题的任何其他方法。

我突然想到,您可以使用 scan。假设您的字符串存储在变量 s 中,并且您希望将 space 区域和 non-space 区域分开,您可以执行

s.scan(/[ ]+|[^ ]+/)

在你的情况下会产生

["Hello", "   ", "world!"]

使用 String#scan 代替 String#split

您不想使用 String#split because that won't preserve your spaces. You want to use String#scan or String#partition instead. Using Unicode character properties,您可以扫描匹配项:

'Hello   world!'.scan /[\p{Alnum}\p{Punct}]+|\p{Space}+/
#=> ["Hello", "   ", "world!"]

如果愿意,您也可以使用 POSIX 字符 类(在 Ruby 中发音为“括号表达式”)来做同样的事情。例如:

'Hello   world!'.scan /[[:alnum:][:punct:]]+|[[:space:]]+/
#=> ["Hello", "   ", "world!"]

这些选项中的任何一个都会比依赖 ASCII-only 字符或文字空白原子的解决方案更健壮,但如果您知道您的字符串不会包含其他类型的字符或编码,那么这些解决方案将起作用也是。

为简洁起见,谨慎使用元字符

如果您正在寻找正则表达式的简洁性,并且您确定不需要关心 Unicode 字符或明确区分 non-whitespace 字符和标点符号,您还可以使用\s\S metacharacters。例如:

'Hello   world!'.scan /\s+|\S+/
#=> ["Hello", "   ", "world!"]

这通常不如上面的字符属性或括号表达式稳健,但仍然明确、简短且易于阅读。它适合你的例子,所以值得一提,但是 \S 元字符可以匹配控制字符和其他意想不到的东西,所以除非你真的了解你的数据,否则你需要谨慎使用它。例如,您的字符串可能包含一个不可见的 NUL 或一个控制字符,如 CTRL-D,在这种情况下 \S 会捕获它,而 return 一个 Unicode-escaped 字符:

"\x00".scan /\S+/
#=> ["\u0000"]

?\C-D.scan /\S+/
#=> ["\u0004"]

这可能不是您所期望的,但是对于更大的数据集,这种情况不可避免地会发生。您越明确,生产数据出现的问题就越少。

使用字符串#partition

对于原始示例中非常简单的用例,您只有两个单词,用空格分隔。这意味着您还可以使用 String#partition 对顺序空白进行分区。这会将字符串拆分为三个元素,保留分隔单词的空格。例如:

'Hello   world!'.partition /\s+/
#=> ["Hello", "   ", "world!"]

虽然更简单,但分区方法不适用于较长的字符串,例如:

'Goodbye   cruel world!'.partition /\s+/
#=> ["Goodbye", "   ", "cruel world!"]

所以 String#scan 将成为一般用例的更好、更灵活的方法。但是,任何时候您想要将一个字符串拆分为三个元素,或者要保留分区元素本身,#partition 都非常方便。

您可以继续使用 split 并仍然保留 spaces,方法是使用带有 capture group 的简单 regex:

"Hello   World  ! ".split(/( +)/)
#=>  ["Hello", "   ", "World", "  ", "!", " "]

我知道的唯一问题是,以 space 开头的字符串将生成一个以空字符串开头的数组:

"  Hello   World  ! ".split(/( +)/)
#=>  ["", "  ", "Hello", "   ", "World", "  ", "!", " "]

如果这是一个问题,您可以添加类似 drop_while 的内容:

"  Hello   World  ! ".split(/( +)/).drop_while(&:empty?)
#=>  ["  ", "Hello", "   ", "World", "  ", "!", " "]