如何在Ruby中生成由给定数目的unicode字符组成的随机unicode字符串?
以下内容有效,但是例如包含控制字符(0x00-0x1F等)
20.times.map{ Random.rand(0xFFFF).chr('UTF-8')}.join
答案 0 :(得分:3)
该范围内的许多字符不可打印(如前所述),或者它们是替代字符,自定义字符或其他无效字符。最好的方法(我能想到的)是生成一个字符序列,测试每个字符以确保其有效且可打印,然后获取其中的前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
将值从Enumerator中拉出以查看其作用:
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