Случайное генерирование действительного символа Юникода в Ruby - PullRequest
0 голосов
/ 04 октября 2018

Как мне сгенерировать случайную строку Юникода, состоящую из заданного количества символов Юникода в Ruby?

Следующее работает, но включает управляющие символы (0x00-0x1F и т. Д.), Например:

20.times.map{ Random.rand(0xFFFF).chr('UTF-8')}.join

1 Ответ

0 голосов
/ 05 октября 2018

Многие символы в этом диапазоне не могут быть напечатаны (как вы уже заметили) или являются суррогатными, пользовательскими или иным недопустимыми символами.Лучший подход (который я могу придумать) состоит в том, чтобы сгенерировать последовательность символов, проверить каждый из них, чтобы убедиться, что он действителен и пригоден для печати, а затем взять первые 20 из них.

Несколько заметок.Мы хотим сделать rand(0x10000) в этом случае, а не rand(0xFFFF), потому что Random#rand и Kernel#rand вернут число, меньшее, чем его аргумент, и вы хотите включить U + FFFF в вашу выборку.Мы также должны дать себе некоторую гибкость для выполнения однобайтового, двухбайтового, трехбайтового или четырехбайтового UTF-8.

Давайте начнем с базового генератора последовательностей, называемого Enumerator в Ruby.Этот объект выдает значения по одному и может представлять конечную или бесконечную последовательность.В этом случае мы хотим перечислить бесконечную последовательность случайных трехбайтовых символов 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 (он же 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 из тела метода.Вы также можете заставить метод возвращать ленивый Enumerator по умолчанию.Это действительно зависит от вас, чтобы разработать метод с учетом требований вашего приложения.

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
...