使整数列表更加人性化

时间:2011-09-03 23:42:05

标签: algorithm compression data-compression lossless-compression

这是我为解决工作中的无法修复问题而采取的一个侧面项目。我们的系统输出一个代码来表示另一件事情的组合。一些示例代码是:

  

9-9-0-4-4-5-4-0-2-0-0-0-2-0-0-0-0-0-2-1-2-1-2-2 -2-4

     

9-5-0-7-4-3-5-7-4-0-5-1-4-2-1-5-5-4-6-3-7-9-72

     

9-15-0-9-1-6-2-1-2-0-0-1-6-0-7

到目前为止,我见过的其中一个插槽的最大数量大约为150,但它们可能会更高。

在设计系统时,不需要这个代码的样子。但现在客户希望能够从一张纸上手工输入,上面的代码不适合。我们已经说过我们不会对此采取任何措施,但这似乎是一个有趣的挑战。

我的问题是,开始无损压缩此代码的好地方在哪里?使用较短的密钥存储此代码等明显的解决方案不是一种选择;我们的数据库是只读的。我需要构建一种双向方法,使这段代码更加人性化。

4 个答案:

答案 0 :(得分:1)

1)我同意你肯定需要一个校验和 - 数据输入错误很常见,除非你有真正训练有素的工作人员和使用自动交叉检查的独立重复键控。

2)我建议http://en.wikipedia.org/wiki/Huffman_coding将您的数字列表转换为比特流。为了获得所需的概率,您需要一个相当大的实际数据样本,因此您可以进行计数,将Ni设置为数字i出现在数据中的次数。然后我建议设置Pi =(Ni + 1)/(Sum_i(Ni + 1)) - 这使概率平滑一点。此外,使用此方法,如果您看到例如数字0-150你可以通过输入数字151-255并将它们设置为Ni = 0来增加一些松弛。另一种绕过罕见大数字的方法是添加某种转义序列。

3)找到一种让人们输入比特序列的方法实际上是一个应用心理学问题,但这里有一些想法的建议。

3a)软件许可证 - 在一些64个字符的字母表中,每个字符只编码6位,但组合字符的方式使人们更容易保留位置,例如BC017-06777-14871-160C4

3b)英国汽车牌照。使用字母更改向人们展示如何对字符进行分组,例如ABCD0123EFGH4567IJKL ...

3c)一个非常大的字母表 - 为一些体面大小的n获取一个2 ^ n个字的列表,并将n个比特编码为一个字,例如GREEN ENCHANTED LOGICIAN ... -

答案 1 :(得分:0)

这个问题有一些非常巧妙的答案,可能会帮助你:

How to convert an integer to the shortest url-safe string in Python?

答案 2 :(得分:0)

我曾经担心过这个问题。事实证明,你不能比base64做得更好 - 尝试每个字符多挤几个位并不值得努力(一旦你进入“奇怪”的位数编码和解码变得更加复杂)。但与此同时,你最终得到的东西在输入时可能会出错(将0与O等混淆)。一个选项是选择一组经过修改的字符和字母(所以它仍然是64位,但是,你用“>”替换为“0”。另一种选择是添加校验和。为了简化实现,我觉得校验和方法更好。

不幸的是我再也没有了 - 事情改变了方向 - 所以我不能提供代码或特定的校验和选择。

ps我意识到有一个缺失的步骤我没有解释:我打算在编码之前将文本压缩成某种二进制形式(使用一些标准的压缩算法)。总结一下:压缩,添加校验和,base64编码; base 64解码,检查校验和,解压缩。

答案 3 :(得分:0)

这与我过去使用的相似。有一些更好的方法可以做到这一点,但我使用这种方法是因为它很容易在Transact-SQL中镜像,这在当时是一个要求。如果你的id的分布是非随机的,你当然可以修改它以合并霍夫曼编码,但它可能是不必要的。

您没有指定语言,因此这是在c#中,但转换到任何语言应该非常容易。在查找中,您将看到通常混淆的字符被省略。这应该加快进入。我也要求有一个固定的长度,但你很容易修改它。

static public class CodeGenerator
{
    static Dictionary<int, char> _lookupTable = new Dictionary<int, char>();

    static CodeGenerator()
    {
        PrepLookupTable();
    }

    private static void PrepLookupTable()
    {
        _lookupTable.Add(0,'3');
        _lookupTable.Add(1,'2');
        _lookupTable.Add(2,'5');
        _lookupTable.Add(3,'4');
        _lookupTable.Add(4,'7');
        _lookupTable.Add(5,'6');
        _lookupTable.Add(6,'9');
        _lookupTable.Add(7,'8');
        _lookupTable.Add(8,'W');
        _lookupTable.Add(9,'Q');
        _lookupTable.Add(10,'E');
        _lookupTable.Add(11,'T');
        _lookupTable.Add(12,'R');
        _lookupTable.Add(13,'Y');
        _lookupTable.Add(14,'U');
        _lookupTable.Add(15,'A');
        _lookupTable.Add(16,'P');
        _lookupTable.Add(17,'D');
        _lookupTable.Add(18,'S');
        _lookupTable.Add(19,'G');
        _lookupTable.Add(20,'F');
        _lookupTable.Add(21,'J');
        _lookupTable.Add(22,'H');
        _lookupTable.Add(23,'K');
        _lookupTable.Add(24,'L');
        _lookupTable.Add(25,'Z');
        _lookupTable.Add(26,'X');
        _lookupTable.Add(27,'V');
        _lookupTable.Add(28,'C');
        _lookupTable.Add(29,'N');
        _lookupTable.Add(30,'B');          
    }


    public static bool TryPCodeDecrypt(string iPCode, out Int64 oDecryptedInt)
    {
        //Prep the result so we can exit without having to fiddle with it if we hit an error.
        oDecryptedInt = 0;

        if (iPCode.Length > 3)
        {
            Char[] Bits = iPCode.ToCharArray(0,iPCode.Length-2);

            int CheckInt7 = 0; 
            int CheckInt3 = 0;
            if (!int.TryParse(iPCode[iPCode.Length-1].ToString(),out CheckInt7) ||
                !int.TryParse(iPCode[iPCode.Length-2].ToString(),out CheckInt3))
            {
                //Unsuccessful -- the last check ints are not integers.
                return false;
            }
            //Adjust the CheckInts to the right values.
            CheckInt3 -= 2;
            CheckInt7 -= 2;

            int COffset = iPCode.LastIndexOf('M')+1;


            Int64 tempResult = 0;
            int cBPos = 0;
            while ((cBPos + COffset) < Bits.Length)
            {
                //Calculate the current position.
                int cNum = 0;
                foreach (int cKey in _lookupTable.Keys)
                {
                    if (_lookupTable[cKey] == Bits[cBPos + COffset])
                    {
                        cNum = cKey;
                    }
                }
                tempResult += cNum * (Int64)Math.Pow((double)31, (double)(Bits.Length - (cBPos + COffset + 1)));
                cBPos += 1;
            }

            if (tempResult % 7 == CheckInt7 && tempResult % 3 == CheckInt3)
            {
                 oDecryptedInt =  tempResult;
                return true;    
            }


            return false;

        }
        else
        {
            //Unsuccessful -- too short.
            return false;
        }
    }
    public static string PCodeEncrypt(int iIntToEncrypt, int iMinLength)
    {
        int Check7 = (iIntToEncrypt % 7) + 2;
        int Check3 = (iIntToEncrypt % 3) + 2;

        StringBuilder result = new StringBuilder();
        result.Insert(0, Check7);
        result.Insert(0, Check3);

        int workingNum = iIntToEncrypt;

        while (workingNum > 0)
        {
            result.Insert(0, _lookupTable[workingNum % 31]);
            workingNum /= 31;
        }

        if (result.Length < iMinLength)
        {
            for (int i = result.Length + 1; i <= iMinLength; i++)
            {
                result.Insert(0, 'M');
            }
        }

        return result.ToString();
    }

}