需求是这样的,有一段字符串,是把一个 hash 经过简单编码产生的:
~
会被替换成~~
~
连接起来代码形式;
{id: 'P111', nm: '~nm xxx ~~~~ ~id~br~', qt: 10}.map{
|k,v| [k, v.to_s.gsub("~", "~~")].join
}.join("~")
# idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10
问题是如何把idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10
decode 成 {id: 'P111', nm: '~nm xxx ~~~~ ~id~br~', qt: 10}
俺试试勒
s = "idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10"
KEY = %w(id nm qt pr)
groups = s.chars.chunk_while do |before, after|
(before == '~' && after == '~') or (before != '~' && after != '~')
end.map(&:join)
p groups #=》["idP111", "~", "nm", "~~", "nm xxx ", "~~~~~~~~", " ", "~~", "id", "~~", "br", "~~~", "qt10"]
kv = groups.slice_after { |e| e[0] == '~' and e.size.odd? }.map do |arr|
arr.map do |str|
if str[0] == '~'
'~' * (str.size/2)
else
str
end
end.join
end
p kv #=》["idP111", "nm~nm xxx ~~~~ ~id~br~", "qt10"]
rst = kv.each_with_object({}) do |e, obj|
if key = KEY.find { |k| key = e.match?(/^(#{k})/) }
obj[key.to_sym] = e[key.size..-1]
else
raise 'Wrong code'
end
end
p rst #=》{:id=>"P111", :nm=>"~nm xxx ~~~~ ~id~br~", :qt=>"10"}
# rebuild-hash.rb
str = 'idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10'
KEYS = %w[id nm qt pr]
# 'idP111' => ['id', 'P111']
def unjoin(pair)
key = KEYS.detect{ |key| pair[/^#{key}/] }
value = pair[/(?<=#{key}).*/].gsub('^^', '~')
[key, value]
end
puts str.gsub(/~{2}/, '^^').split('~').map{ |pair| unjoin(pair) }.to_h
# => {"id"=>"P111", "nm"=>"~nm xxx ~~~~ ~id~br~", "qt"=>"10"}
decode 的难点在 N 个~
,把 ~~
替换成 ^^
,让正则去做复杂的事情
这个处理没考虑 corner case,比如有两个 key: nm
和 nmok
的场景,pair[/^#{key}/]
就不准确了
补充一下:
把 ~~
替换成 ^^
可行是因为规则 2 使得 encode 后 value 里的 ~
必然是双数,规则 3 的连接 ~
必然是 N 个双数 ~
的最后一个,除非 id 前会有 ~
。
^^
可以换成任何小概率出现在 value 的字符串,比如 ^^^^^^^^^
补充一下:
把 ~~
替换成 ^^
可行是因为规则 2 使得 encode 后 value 里的 ~
必然是双数,规则 3 的连接 ~
必然是 N 个双数 ~
的最后一个,除非 id 前会有 ~
。
^^
可以换成任何小概率出现在 value 的字符串,比如 ^^^^^^^^^
正则方案
require 'strscan'
str = 'idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10'
scanner = StringScanner.new(str)
KEYS = %w[id nm qt br]
KEYS_REG = KEYS.join("|")
result = {}
KEYS.size.times do
segment = scanner.scan(/(#{KEYS_REG})([^~]|(?:~~))*(?:$|~(?=#{KEYS_REG}))/)
next unless segment
matched = segment.match(/^(#{KEYS_REG})(.*?)~?$/)
next if matched[1].nil? || matched[2].nil?
result[matched[1]] = matched[2].gsub("~~", "~")
end
p result
受 scan 启发,调整了一下,似乎也可以
str = 'idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10'
keys = %w[id nm qt pr]
pairs = str.scan(/(#{keys.join('|')})(.+?[^~](?:~~)*(?=(?:~[^~])|$))/)
pairs.each_with_object({}) { |(k, v), hash| hash[k] = v.gsub('~~', '~') }
one line style
'idP111~nm~~nm xxx ~~~~~~~~ ~~id~~br~~~qt10'.scan(/(id|nm|qt|pr)(.+?[^~](?:~~)*(?=(?:~[^~])|$))/).to_h.transform_values{|s| s.gsub('~~', '~')}