如何在ruby中生成由给定数量的unicode字符组成的随机unicode字符串?
以下操作有效,但包括控制字符(0x00-0x1f等):
20.times.map{ Random.rand(0xFFFF).chr('UTF-8')}.join
最佳答案
该范围内的许多字符是不可打印的(如您所注意到的),或者它们是代理字符、自定义字符或其他无效字符。最好的方法(我能想到)是生成一个字符序列,测试每个字符以确保它是有效的和可打印的,然后取前20个字符。
几点注意事项。在这种情况下,我们要做的是rand(0x10000)
,而不是rand(0xFFFF)
,因为Random#rand
和Kernel#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
您可以使用
#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为我们提供了一种简洁的方法来从一个方法返回一个枚举器,该方法通过返回带有方法符号和传递给函数的任何其他参数的
Object#enum_for
(又称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
我们可以使用与上面使用枚举器完全相同的方法,可以选择传入所需的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