将数字压缩为字符串以适合256个字符的空间

时间:2018-10-03 13:39:51

标签: ruby string memory compression bit-manipulation

我正在尝试使用位掩码来提供尽可能多的二进制值,以便最终值将存储在有限的字符串分配内存中。我当前的方法是找到一个最大数字,并将其转换为以base-36开头的字符串。

value = (0 | (1<<1318)).to_s(36)

结果是255个压缩数字的字符,我可以从中提取我的原始数字1318。缺点是我限于1,318个二进制值,并且我想扩展该数字。 Ruby中是否有其他替代策略可以进一步压缩该数字?

2 个答案:

答案 0 :(得分:1)

您始终可以将数字编码为基数s,然后将其表示为带有所需字母的字符串。

def encode(n, alphabet)
  s = alphabet.size
  res = []
  while (n > 0)
    res << n % s
    n = n / s
  end
  res.reverse.map { |i| alphabet[i] }.join
end

然后您的方法等效于encode(n, alphabet),其中alphabet被定义为

alphabet = ((0..9).to_a + ("a".."z").to_a).join
# => "0123456789abcdefghijklmnopqrstuvwxyz"

但是您最好使用所有可能的字符,而不是只使用36个字符:

extended_alphabet = (0..255).map { |i| i.chr }.join

这总共提供(256 ** 255)种可能性,即高达(2 ** 2040),这比实际的(2 ** 1318)好得多。

这种编码恰好是最佳的,因为字符串中的每个字符最多可以有256个不同的值,并且所有这些都在这里使用。


然后可以按以下方式执行解码:

def decode(encoded, alphabet)
  s = alphabet.size
  n = 0
  decode_dict = {}; i = -1
  alphabet.each_char { |c| decode_dict[c] = (i += 1) }
  encoded.each_char do |c|
    n = n * s + decode_dict[c]
  end
  n
end

如果您要对所有编码使用固定的字母,我建议您在函数外部计算解码字典并将其作为参数而不是alphabet,以避免每次尝试时都进行计算编码数字。

答案 1 :(得分:1)

数字为非负数

如果数字为非负数,我们可以将每个数字的每个8位编码为字符串的一部分,然后将每个字符转换为数字的8位以对字符串进行解码。

def encode(n)
  str = ''
  until n.zero?
    str << (n & 255).chr
    n = n >> 8
  end
  str.reverse
end

def decode(str)     
  str.each_char.reduce(0) { |n,c| (n << 8) | c.ord }
end

这在类Integer中使用以下位操作方法:&>><<|

def test(n)
  encoded = encode(n)
  puts "#{n} => #{encoded} => #{decode(encoded)}"
end

test      1  #      1 => ["\u0001"]            =>      1
test     63  #     63 => ["?"]                 =>     63
test     64  #     64 => ["@"]                 =>     64
test    255  #    255 => ["\xFF"]              =>    255
test    256  #    256 => ["\u0001", "\u0000"]  =>    256
test 123456  # 123456 => ["\x01", "\xE2", "@"] => 123456

例如,

n = 123456
n.to_s(2)
  #=> "11110001001000000"

如此

n = 0b11110001001000000
  #=> 123456

此数字的字节可以显示为:

00000001 11100010 01000000

我们看到了

a = [0b00000001, 0b11100010, 0b01000000]
a.map(&:chr)
  #=> ["\x01", "\xE2", "@"]

数字可以为负

如果要编码的数字可以是负数,则我们需要先将其转换为绝对值,然后在编码的字符串中添加一些信息,指示它们是非负数还是负数。这将至少需要一个额外的字节,因此我们可能在非负数中包含"+",在负数中包含"-"

def encode(n)
  sign = "+"
  if n < 0
    sign = "-"
    n = -n
  end
  str = ''
  until n.zero?
    str << (n & 255).chr
    n = n >> 8
  end
  (str << sign).reverse
end

def decode(str)
  n = str[1..-1].each_char.reduce(0) { |n,c| (n << 8) | c.ord }
  str[0] == '+' ? n : -n
end

test    -255  # -255    => ["-", "\xFF"]              => -255
test    -256  # -256    => ["-", "\u0001", "\u0000"]  => -256
test -123456  # -123456 => ["-", "\x01", "\xE2", "@"] => -123456
test  123456  #  123456 => ["+", "\x01", "\xE2", "@"] =>  123456