ruby - 在 Ruby 中随机生成一个有效的 unicode 字符
问题描述
如何在 Ruby 中生成由给定数量的 unicode 字符组成的随机 unicode 字符串?
以下工作,但包括控制字符(0x00-0x1F 等),例如:
20.times.map{ Random.rand(0xFFFF).chr('UTF-8')}.join
解决方案
该范围内的许多字符都是不可打印的(如您所述),或者它们是代理、自定义或其他无效字符。最好的方法(我能想到的)是生成一个字符序列,测试每个字符以确保它有效且可打印,然后取前 20 个字符。
一些笔记。在这种情况下,我们想做rand(0x10000)
not rand(0xFFFF)
,因为Random#rand
andKernel#rand
将返回一个小于其参数的数字,并且您希望在采样中包含 U+FFFF 。我们还应该给自己一些灵活性来处理一字节、二字节、三字节或四字节的 UTF-8。
让我们从一个基本的序列生成器开始,在 Ruby 中称为Enumerator 。这个对象每次产生一个值,并且可以表示一个有限或无限的序列。在这种情况下,我们要枚举一个无限的随机、三字节 UTF-8 字符序列,同时跳过无效字符。
random_utf8 = Enumerator.new do |yielder|
loop do
yielder << rand(0x10000).chr('UTF-8')
rescue RangeError
end
end
您可以从 Enumerator 中提取值#next
以查看它的实际作用:
irb(main):007:0> random_utf8.next
=> "\u9FEB"
irb(main):008:0> random_utf8.next
=> "槇"
irb(main):009:0> random_utf8.next
=> "엛"
(您会注意到其中一个没有“渲染”,因为它不是可打印的字符。这说明了为什么我们需要在选择其中的 20 个之前过滤这些值。)
现在我们可以从这个序列中取出字符并检查每个字符是否可以打印。唯一的问题是我们想懒惰地这样做,以避免在进入链中的下一步之前检查无限序列中的每个字符(这是不可能的)。最后,我们将获取前 20 个可打印字符并将它们连接成一个字符串。
random_utf8
.lazy
.grep(/[[:print:]]/) # or [[:alpha:]] or \p{L} or whatever test you want here
.first(20)
.join # => "醸긍ᅋꝇ꼏捁㨃농鳹䝛ㆅ⇂擒璝缀챼砶"
现在让我们将其抽象为一个方法,以便我们可以参数化一些东西。Ruby 为我们提供了一种简洁的方法来从一个方法中返回一个 Enumerator,该方法通过返回Object#enum_for
(aka Object#to_enum
) 以及方法符号和传递给函数的任何其他参数来产生值。
def random_utf8(mb=3)
return enum_for(__callee__, mb) unless block_given?
# determine the maximum codepoint based on the number of UTF-8 bytes
max = [0x80, 0x800, 0x10000, 0x110000][mb.pred]
loop do
yield rand(max).chr('UTF-8') # note the `yield` here
rescue RangeError
end
end
我们可以使用与上面使用 Enumerator 完全相同的方式使用此方法,可选择传入所需的 UTF-8 字节数。
这种方法还使我们可以选择使用块调用我们的方法,而不是从块中链接操作:
random_utf8(2) do |char|
next unless char.match?(/[[:print:]]/)
puts "Got >#{char}<!"
break # don't loop infinitely
end
诚然,这在这种特殊情况下并不是很有用。
关于此解决方案实现的一个附加说明:您可以轻松地将可打印检查移到方法主体中,或者将 RangeError 异常处理移出方法主体。您还可以让该方法默认返回一个惰性枚举器。围绕您的应用程序需求设计方法真的取决于您。
def lazy_printable_random_utf8(mb=3)
return enum_for(__callee__, mb).lazy unless block_given?
# determine the maximum codepoint based on the number of UTF-8 bytes
max = [0x80, 0x800, 0x10000, 0x110000][mb.pred]
loop do
char = rand(max).chr('UTF-8')
yield char if char.match?(/[[:print:]]/)
rescue RangeError
end
end
推荐阅读
- google-sheets - 如何查找谷歌电子表格重复项并仅从一列数据中删除
- typescript - 使用泛型时,Typescript 无法识别错误
- python - 异常类型错误:列表索引必须是整数或切片,而不是 str
- collections - 我想使用集合从理货 erp 中获取损益账户
- python - 当我使用 Scrapy 抓取 next_url 时,响应显示正确的 url 但响应正文显示另一个
- javascript - 在 javascript 中,为什么在我 console.log 时显示,但在我返回时不显示
- reactjs - 使用 useState 时状态过时
- r - 从字符串向量数据中提取一串单词
- wso2 - 删除 Grok 过滤器中的数字字符
- dart - 如何比较 Dart à la nodejs Buffer.compare 中的 Uint8List?