Использование Ruby для замены числовых данных с использованием простого hashmap

Я пытаюсь найти простой способ использования Ruby для скремблирования (или маскирования) некоторых числовых данных, чтобы создать фиктивный набор данных из живых данных. Я хочу сохранить данные как можно ближе к исходному формату (то есть сохранить все нечисловые символы). Числа в данных соответствуют индивидуальным идентификационным номерам, которые (иногда) являются ключами, используемыми в реляционной базе данных. Таким образом, если числовая строка встречается более одного раза, я хочу последовательно сопоставить ее с одним и тем же (в идеале уникальным) значением. После того, как данные зашифрованы, мне не нужно иметь возможность изменить скремблирование.

Я создал функцию скремблирования, которая берет строку и генерирует простой хеш для отображения чисел на новые значения (функция отображает только числовые цифры и оставляет все остальное как есть). Для дополнительной безопасности каждый раз, когда вызывается функция, ключ обновляется. Таким образом, одна и та же фраза будет давать два разных результата каждый раз, когда вызывается функция.

module HashModule
  def self.scramble(str)
    numHash ={}
    0.upto(9) do |i|
      numHash[i.to_s]=rand(10).to_s
    end

    output= String.new(str)
    output.gsub!(/\d/) do|d|
      d.replace numHash[d]
    end

    puts "Input: " + str
    puts "Hash Key: " + numHash.to_s
    puts "Output: " + output
  end
end

HashModule.scramble("56609-8 NO PCT 001")
HashModule.scramble("56609-8 NO PCT 001")

Это дает следующий вывод:

Input: 56609-8 NO PCT 001
Hash Key: {"0"=>"9", "1"=>"4", "2"=>"8", 
           "3"=>"9", "4"=>"4", "5"=>"8", 
           "6"=>"4", "7"=>"0", "8"=>"2", 
           "9"=>"1"}
Output: 84491-2 NO PCT 994

Input: 56609-8 NO PCT 001
Hash Key: {"0"=>"2", "1"=>"0", "2"=>"9", 
           "3"=>"8", "4"=>"4", "5"=>"5", 
           "6"=>"7", "7"=>"4", "8"=>"2", 
           "9"=>"0"}
Output: 57720-2 NO PCT 220

Учитывая набор данных:

PTO NO PC
R5632893423 IP
R566788882-001
NO PCT AMB PTO
NO AMB/CALL IP
A566788882
1655543AACHM IP
56664320000000
00566333-1

Сначала я извлекаю все числа в массив. Затем я использую созданную мной функцию шифрования, чтобы создать заменяющую хэш-карту, например

 {"5632893423"=>"5467106076", "566788882"=>"888299995", 
  "001"=>"225", "1655543"=>"2466605", 
  "56664320000000"=>"70007629999999", 
  "00566333"=>"00699999", "1"=>"3"}

[Между прочим, в моем примере я не нашел способа настаивать на том, что все значения хеш-функции являются уникальными, что актуально в том случае, если отображаемая строка соответствует уникальному идентификатору в базе данных отношений, как описано выше.]

Я использую gsub в моей исходной строке и заменяю ключи хеш-функции на зашифрованное значение. У меня работает код, но мне любопытно узнать, как я могу сделать его более кратким. Понимая, что при каждом вызове функции заново генерирую ключ, я создаю дополнительную работу. (В противном случае я мог бы просто создать один ключ, чтобы заменить все цифры).

У кого-нибудь есть предложения о том, как я могу сделать это по-другому? (Я новичок в Ruby, поэтому предложения по улучшению моего кода также получили много).

input = <<EOS
PTO NO PC
R5632893423 IP
R566788882-001
NO PCT AMB PTO
NO AMB/CALL IP
A566788882
1655543AACHM IP
56664320000000
00566333-1
EOS

module HashModule
  def self.scramble(str)
    numHash ={}
    0.upto(9) do |i|
      numHash[i.to_s]=rand(10).to_s
    end

    output= String.new(str)
    output.gsub!(/\d/) do|d|
      d.replace numHash[d]
    end
    return output
  end
end

# Extract unique non-null numbers from the input file
numbers = input.split(/[^\d]/).uniq.reject{ |e| e.empty? }

# Create a hash that maps each number to a scrambled value
# Using the function defined above

mapper ={}
numbers.map(&:to_s).each {|x| mapper[x]=HashModule.scramble(x)}

# Create a regexp to find all numbers in input file
re = Regexp.new(mapper.keys.map { |x| Regexp.escape(x) }.join('|'))

# Replace numbers with scrambled values
puts input.gsub(re, mapper)

Приведенный выше код производит следующий вывод:

PTO NO PC
R7834913043 IP
R799922223-772
NO PCT AMB PTO
NO AMB/CALL IP
A799922223
6955509AACHM IP
13330271111111
66166777-6

2 ответа

Решение

Может быть, что-то вроде этого:

module HashModule
  ScrambleKey = Hash[(0..9).map(&:to_s).zip((0..9).to_a.shuffle)]
  def self.scramble(str); str.gsub(/\d/){ScrambleKey[$&]} end
end

puts HashModule.scramble(input)

который дает:

PTO NO PC
R6907580170 IP
R699455557-223
NO PCT AMB PTO
NO AMB/CALL IP
A699455557
3966610AACHM IP
69991072222222
22699000-3

В дополнение к блестящему ответу @sawa, я бы предложил вам "внедрить" этот метод схватки непосредственно в String класс (изготовление str.scramble доступный для всего проекта без каких-либо дополнительных реверансов):

class String
  @@ScrambleKey = Hash[(0..9).map(&:to_s).zip((0..9).to_a.shuffle)]
  def scramble ; self.gsub(/\d/) { @@ScrambleKey [$&] } end
end

Эта реализация вводит переменную класса, а не экземплярную. Если вам нужно ScrambleKey отличаясь от String на String, используйте вместо этого переменную экземпляра.

Уступая:

input = <<EOS
PTO NO PC
R5632893423 IP
R566788882-001
NO PCT AMB PTO
NO AMB/CALL IP
A566788882
1655543AACHM IP
56664320000000
00566333-1
EOS

puts input.scramble

дает:

PTO NO PC
R1548024784 IP
R155600008-339
NO PCT AMB PTO
NO AMB/CALL IP
A155600008
9511174AACHM IP
15557483333333
33155444-9
Другие вопросы по тегам