【问题标题】:Compress a number as a string to fit in 256 char space将数字压缩为字符串以适应 256 个字符空间
【发布时间】:2018-10-03 13:39:50
【问题描述】:

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

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

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

【问题讨论】:

  • 它必须是基数 36 吗? Base 64、85 或“原始字节”当然都是更高的密度
  • 它不必是基数 36,这只是我目前的解决方案。
  • @Harold,注意n &lt;= 36to_s(n)

标签: ruby string memory compression bit-manipulation


【解决方案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,以避免每次尝试编码时都计算它一个数字。

【讨论】:

  • 谢谢,这个解决方案很有趣。假设我的n 代表我在编码之前可以存储为字符串的最大数字,这种方法将我的二进制插槽从 1318 增加到 2035 左右。这绝对是一个改进。换句话说,encode((0 | (1&lt;&lt;2035)), extended_alphabet).length 给了我 255。解码方法是什么样的?有没有办法让这个数字增加很多?
  • 我添加了解码功能。请注意,我还反转了编码以使解码更简单。您可以对包含在此方法中的 0 到 ((1
  • 另外,( 0 | ( 1
【解决方案2】:

数字是非负数

如果数字是非负数,我们可以将每个数字的每个 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 类中使用以下位操作方法:&amp;&gt;&gt;&lt;&lt;|

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    相关资源
    最近更新 更多