2010-05-13 8 views
35

GUID के मानक स्ट्रिंग प्रस्तुति में लगभग 36 वर्ण होते हैं। जो बहुत अच्छा है, लेकिन वास्तव में भी अपमानजनक है। मैं सोच रहा हूं, 33-127 की सीमा में सभी ASCII वर्णों का उपयोग करके इसे सबसे कम संभव तरीके से कैसे एन्कोड करना है। अनुभवहीन कार्यान्वयन 22 वर्ण, बस क्योंकि 128 बिट/6 बिट पैदावार 22.पठनीय ASCII (33-127) में मनमाने ढंग से GUID को एन्कोड करने का सबसे प्रभावी तरीका क्या है?

Huffman एन्कोडिंग मेरी दूसरी सबसे अच्छा है, केवल सवाल यह है कि कोड का चयन करने के .... है

है पैदा करता है एन्कोडिंग निश्चित रूप से लापरवाह होना चाहिए।

+3

मुझे इस बिंदु को जानना अच्छा लगेगा। क्या आपको वास्तव में अरबों GUID स्टोर करने की आवश्यकता है? क्योंकि अरबों से कम कुछ भी और आधा में स्ट्रिंग लंबाई काटने का महत्व लगभग एल्गोरिदमिक परेशानी के लायक नहीं है। – jmucchiello

+5

हफमैन कोडिंग निश्चित रूप से काम नहीं करेगा - एक यादृच्छिक GUID में सभी प्रतीकों की समान संभावना है। –

+0

@ निक जॉन्सन निश्चित नहीं हैं, क्योंकि GUID के पास अजीब पीढ़ी के नियम हैं, जिनमें से एक पीढ़ी की तारीख शामिल है, जो कहने के लिए, 5 साल की अवधि के बाद, इन 5 वर्षों के दौरान हफमैन कोडिंग में काफी कमी हो सकती है। बेशक मैं कह सकता हूं "मई" क्योंकि मुझे नहीं पता कि तारीख "हैश" कैसे है। अगर बुरी तरह से धोया जाता है तो हफमैन संपीड़न बना सकता है। –

उत्तर

19

आधार 85 का उपयोग करें। अनुभाग 4.1 देखें। क्यों 85? A Compact Representation of IPv6 Addresses

एक IPv6 पता की, एक GUID की तरह आठ 16-बिट टुकड़े से बना है।

+1

अच्छा लगता है, लेकिन वर्ण '%' और '?' डेटाबेस क्वेरी में एन्कोडिंग का उपयोग करने के लिए उपयुक्त नहीं हैं। हम उन्हें ':' और '।' से बदलते हैं, क्योंकि हमें आईपीवी 6 स्वरूपण के मुद्दों की परवाह नहीं है। – mark

+0

@ मार्क मैं देख सकता था कि यह कैसे एक समस्या हो सकती है, लेकिन कई मामलों में यदि आप इसे कॉलम में संग्रहीत कर रहे थे और 'tableName.Guid85' '% blah%' की तरह एन्कोड किया गया था, तो यह कोई समस्या नहीं होगी क्योंकि% और? जैसे ऑपरेटर के बाईं ओर हैं। – AaronLS

+1

या बेस 91: http://base91.sourceforge.net/ – CMCDragonkai

5

बस Base64 पर जाएं।

+1

मैं इसे पसंद करता हूं क्योंकि बेस 85 की तुलना में बेस 64 एन्कोडिंग के कार्यान्वयन को ढूंढना बहुत आसान है, और बेस 85 केवल आपको 2 और वर्ण सहेजे गए हैं। यदि आपका सिस्टम बेस 85 में इन ग्रिड को स्टोर करता है तो कभी भी एक विरासत प्रणाली बन जाती है और किसी को इन चीजों को किसी अन्य प्रोग्रामिंग भाषा के साथ पोर्ट/निर्यात करना होगा और GUID को डीकोड करना होगा, फिर बेस 85 एन्कोडिंग एक बाधा हो सकती है जो कार्यान्वयन व्यापक रूप से उपलब्ध नहीं है । – AaronLS

+0

वास्तव में 66 अनारक्षित यूआरएल वर्ण हैं (http://tools.ietf.org/html/rfc3986#section-2.3 देखें) ताकि आप एक कस्टम बेस -66 एन्कोडर लिख सकें जो बेवकूफ बेस 64 से थोड़ा बेहतर हो। – slacy

+0

बेस -66 एन्कोडर यहां उपलब्ध है: http://stackoverflow.com/a/18001426/76900 –

0

कि आपके GUIDs के सभी एक ही एल्गोरिथ्म द्वारा उत्पन्न किया जा रहा है मान लिया जाये कि, आप एल्गोरिथ्म निबल एन्कोडिंग नहीं, किसी भी अन्य एन्कोडिंग लागू करने से पहले से 4 बिट्स बचा सकते हैं: - |

3

127 से पूर्ण सीमा का उपयोग (127 में गलत क्या जगह है, आकस्मिक रूप से?) 127 आपको 95 संभव वर्ण देता है। 2^128 को व्यक्त करना बेस 95 में ग्रिड के संभावित मान 20 वर्णों का उपयोग करेंगे। यह (मॉड्यूलो चीजें जैसे कि निबल्स को छोड़ना) जो आप कर सकते हैं सबसे अच्छा है। अपने आप को परेशानी बचाएं - आधार 64 का उपयोग करें।

+1

बेस 95 में 128 बिट्स अधिक सटीक होने के लिए 1 9 .5 अंक हैं। यदि आप लम्बाई मुक्त मानते हैं, तो यदि आप सबसे महत्वपूर्ण शून्य छोड़ देते हैं तो आपको औसतन यह मिल जाएगा। –

0

एक मनमाना GUID? "बेवकूफ" एल्गोरिदम इष्टतम परिणाम देगा। एक GUID को संपीड़ित करने का एकमात्र तरीका है आपकी "मनमानी" बाधा से बाहर डेटा में पैटर्न का उपयोग करना।

+0

क्या आपने प्रश्न पढ़ा था? वह पूछ रहा है कि इसे कैसे अधिक कुशलतापूर्वक एन्कोड करना है, इसे कैसे संपीड़ित नहीं किया जाए। –

+1

क्या आप समझते हैं कि 22 बिट-बिट वर्णों की तुलना में 128 बिट्स को अधिक कुशलता से एन्कोडिंग * के लिए * संपीड़न के कुछ रूप और इनपुट में कुछ पैटर्न की आवश्यकता है? – jemfinch

13

आपके पास 95 वर्ण उपलब्ध हैं - इसलिए, 6 बिट्स से अधिक, लेकिन 7 (वास्तव में लगभग 6.57) नहीं हैं। 20 अक्षरों में एन्कोड करने के लिए आप 128/log2 (95) = लगभग 19.48 वर्णों का उपयोग कर सकते हैं। तो इनकोडिंग रूप में 2 वर्ण बचत के लायक पठनीयता की आप के लिए की तरह (स्यूडोकोड) हानि, कुछ है:

char encoded[21]; 
long long guid; // 128 bits number 

for(int i=0; i<20; ++i) { 
    encoded[i] = chr(guid % 95 + 33); 
    guid /= 95; 
} 
encoded[20] = chr(0); 

जो मूल रूप से सामान्य है कोड "कुछ आधार में एक नंबर सांकेतिक शब्दों में बदलना", कोई आवश्यकता नहीं है सिवाय इसके कि किसी भी तरह से आदेश के मनमाने ढंग से "अंक" को उलट करने के लिए (और थोड़ा-एंडियन अधिक प्रत्यक्ष और प्राकृतिक है)। एक बहुत ही समान तरीके से GUID वापस पाने के लिए से इनकोडिंग स्ट्रिंग है, आधार में बहुपद गणना 95 (बेशक प्रत्येक अंक से 33 घटाने के बाद):

guid = 0; 

for(int i=0; i<20; ++i) { 
    guid *= 95; 
    guid += ord(encoded[i]) - 33; 
} 

अनिवार्य रूप से बहुपद मूल्यांकन करने के लिए होर्नर के दृष्टिकोण का उपयोग कर।

+0

आपके डिकोडिंग स्निपेट में एक बग है, इसे वास्तव में रिवर्स में स्ट्रिंग पर फिर से चलाने की आवश्यकता होती है। (int i = 20; i> 0; --i) { guid * = 95; guid + = ord (एन्कोडेड [i-1]) - 33; } – paxos1977

29

यह एक पुराना सवाल है, लेकिन मुझे इसे उस प्रणाली के लिए हल करना पड़ा जिस पर मैं पिछड़ा संगत होने के लिए काम कर रहा था।

सटीक आवश्यकता क्लाइंट द्वारा जेनरेट किए गए पहचानकर्ता के लिए थी जो डेटाबेस में लिखी जाएगी और 20-वर्ण वाले अद्वितीय कॉलम में संग्रहीत की जाएगी। यह उपयोगकर्ता को कभी नहीं दिखाया गया है और किसी भी तरह से अनुक्रमित नहीं किया गया था।

चूंकि मैं आवश्यकता को खत्म नहीं कर सका, मैं वास्तव में एक ग्रिड (जो statistically unique है) का उपयोग करना चाहता था और यदि मैं इसे 20 अक्षरों में लापरवाही से एन्कोड कर सकता हूं, तो यह बाधाओं के बाद एक अच्छा समाधान होगा।

असीसी -85 आपको असीसी डेटा के 5 बाइट्स में बाइनरी डेटा के 4 बाइट एन्कोड करने की अनुमति देता है। तो एक 16 बाइट गाइड सिर्फ इस एन्कोडिंग योजना का उपयोग कर 20 असीसी पात्रों में फिट होगा। एक ग्रिड में 3.1 9 62657931507848761677563491821e + 38 अलग-अलग मूल्य हो सकते हैं जबकि असीसी -85 के 20 वर्णों में 3.8759531084514355873123178482056e + 38 अलग-अलग मान हो सकते हैं।

डेटाबेस में लिखते समय मुझे छंटनी के बारे में कुछ चिंताएं थीं इसलिए एन्कोडिंग में कोई व्हाइटस्पेस वर्ण शामिल नहीं थे। मुझे collation के साथ भी समस्याएं थीं, जिन्हें मैंने एन्कोडिंग से लोअरकेस वर्णों को छोड़कर संबोधित किया था। साथ ही, यह कभी भी paramaterized command के माध्यम से पारित किया जाएगा, इसलिए कोई भी विशेष SQL वर्ण स्वचालित रूप से बच जाएगा।

मैंने सीसी कोड को असीसी -85 एन्कोडिंग और डिकोडिंग करने के लिए शामिल किया है, यदि यह किसी को भी वहां मदद करता है। यहाँ कर रहे हैं

/// <summary> 
/// This code implements an encoding scheme that uses 85 printable ascii characters 
/// to encode the same volume of information as contained in a Guid. 
/// 
/// Ascii-85 can represent 4 binary bytes as 5 Ascii bytes. So a 16 byte Guid can be 
/// represented in 20 Ascii bytes. A Guid can have 
/// 3.1962657931507848761677563491821e+38 discrete values whereas 20 characters of 
/// Ascii-85 can have 3.8759531084514355873123178482056e+38 discrete values. 
/// 
/// Lower-case characters are not included in this encoding to avoid collation 
/// issues. 
/// This is a departure from standard Ascii-85 which does include lower case 
/// characters. 
/// In addition, no whitespace characters are included as these may be truncated in 
/// the database depending on the storage mechanism - ie VARCHAR vs CHAR. 
/// </summary> 
internal static class Ascii85 
{ 
    /// <summary> 
    /// 85 printable ascii characters with no lower case ones, so database 
    /// collation can't bite us. No ' ' character either so database can't 
    /// truncate it! 
    /// Unfortunately, these limitation mean resorting to some strange 
    /// characters like 'Æ' but we won't ever have to type these, so it's ok. 
    /// </summary> 
    private static readonly char[] kEncodeMap = new[] 
    { 
     '0','1','2','3','4','5','6','7','8','9', // 10 
     'A','B','C','D','E','F','G','H','I','J', // 20 
     'K','L','M','N','O','P','Q','R','S','T', // 30 
     'U','V','W','X','Y','Z','|','}','~','{', // 40 
     '!','"','#','$','%','&','\'','(',')','`', // 50 
     '*','+',',','-','.','/','[','\\',']','^', // 60 
     ':',';','<','=','>','?','@','_','¼','½', // 70 
     '¾','ß','Ç','Ð','€','«','»','¿','•','Ø', // 80 
     '£','†','‡','§','¥'      // 85 
    }; 

    /// <summary> 
    /// A reverse mapping of the <see cref="kEncodeMap"/> array for decoding 
    /// purposes. 
    /// </summary> 
    private static readonly IDictionary<char, byte> kDecodeMap; 

    /// <summary> 
    /// Initialises the <see cref="kDecodeMap"/>. 
    /// </summary> 
    static Ascii85() 
    { 
     kDecodeMap = new Dictionary<char, byte>(); 

     for (byte i = 0; i < kEncodeMap.Length; i++) 
     { 
      kDecodeMap.Add(kEncodeMap[i], i); 
     } 
    } 

    /// <summary> 
    /// Decodes an Ascii-85 encoded Guid. 
    /// </summary> 
    /// <param name="ascii85Encoding">The Guid encoded using Ascii-85.</param> 
    /// <returns>A Guid decoded from the parameter.</returns> 
    public static Guid Decode(string ascii85Encoding) 
    { 
     // Ascii-85 can encode 4 bytes of binary data into 5 bytes of Ascii. 
     // Since a Guid is 16 bytes long, the Ascii-85 encoding should be 20 
     // characters long. 
     if(ascii85Encoding.Length != 20) 
     { 
      throw new ArgumentException(
       "An encoded Guid should be 20 characters long.", 
       "ascii85Encoding"); 
     } 

     // We only support upper case characters. 
     ascii85Encoding = ascii85Encoding.ToUpper(); 

     // Split the string in half and decode each substring separately. 
     var higher = ascii85Encoding.Substring(0, 10).AsciiDecode(); 
     var lower = ascii85Encoding.Substring(10, 10).AsciiDecode(); 

     // Convert the decoded substrings into an array of 16-bytes. 
     var byteArray = new[] 
     { 
      (byte)((higher & 0xFF00000000000000) >> 56),   
      (byte)((higher & 0x00FF000000000000) >> 48),   
      (byte)((higher & 0x0000FF0000000000) >> 40),   
      (byte)((higher & 0x000000FF00000000) >> 32),   
      (byte)((higher & 0x00000000FF000000) >> 24),   
      (byte)((higher & 0x0000000000FF0000) >> 16),   
      (byte)((higher & 0x000000000000FF00) >> 8),   
      (byte)((higher & 0x00000000000000FF)), 
      (byte)((lower & 0xFF00000000000000) >> 56),   
      (byte)((lower & 0x00FF000000000000) >> 48),   
      (byte)((lower & 0x0000FF0000000000) >> 40),   
      (byte)((lower & 0x000000FF00000000) >> 32),   
      (byte)((lower & 0x00000000FF000000) >> 24),   
      (byte)((lower & 0x0000000000FF0000) >> 16),   
      (byte)((lower & 0x000000000000FF00) >> 8),   
      (byte)((lower & 0x00000000000000FF)), 
     }; 

     return new Guid(byteArray); 
    } 

    /// <summary> 
    /// Encodes binary data into a plaintext Ascii-85 format string. 
    /// </summary> 
    /// <param name="guid">The Guid to encode.</param> 
    /// <returns>Ascii-85 encoded string</returns> 
    public static string Encode(Guid guid) 
    { 
     // Convert the 128-bit Guid into two 64-bit parts. 
     var byteArray = guid.ToByteArray(); 
     var higher = 
      ((UInt64)byteArray[0] << 56) | ((UInt64)byteArray[1] << 48) | 
      ((UInt64)byteArray[2] << 40) | ((UInt64)byteArray[3] << 32) | 
      ((UInt64)byteArray[4] << 24) | ((UInt64)byteArray[5] << 16) | 
      ((UInt64)byteArray[6] << 8) | byteArray[7]; 

     var lower = 
      ((UInt64)byteArray[ 8] << 56) | ((UInt64)byteArray[ 9] << 48) | 
      ((UInt64)byteArray[10] << 40) | ((UInt64)byteArray[11] << 32) | 
      ((UInt64)byteArray[12] << 24) | ((UInt64)byteArray[13] << 16) | 
      ((UInt64)byteArray[14] << 8) | byteArray[15]; 

     var encodedStringBuilder = new StringBuilder(); 

     // Encode each part into an ascii-85 encoded string. 
     encodedStringBuilder.AsciiEncode(higher); 
     encodedStringBuilder.AsciiEncode(lower); 

     return encodedStringBuilder.ToString(); 
    } 

    /// <summary> 
    /// Encodes the given integer using Ascii-85. 
    /// </summary> 
    /// <param name="encodedStringBuilder">The <see cref="StringBuilder"/> to 
    /// append the results to.</param> 
    /// <param name="part">The integer to encode.</param> 
    private static void AsciiEncode(
     this StringBuilder encodedStringBuilder, UInt64 part) 
    { 
     // Nb, the most significant digits in our encoded character will 
     // be the right-most characters. 
     var charCount = (UInt32)kEncodeMap.Length; 

     // Ascii-85 can encode 4 bytes of binary data into 5 bytes of Ascii. 
     // Since a UInt64 is 8 bytes long, the Ascii-85 encoding should be 
     // 10 characters long. 
     for (var i = 0; i < 10; i++) 
     { 
      // Get the remainder when dividing by the base. 
      var remainder = part % charCount; 

      // Divide by the base. 
      part /= charCount; 

      // Add the appropriate character for the current value (0-84). 
      encodedStringBuilder.Append(kEncodeMap[remainder]); 
     } 
    } 

    /// <summary> 
    /// Decodes the given string from Ascii-85 to an integer. 
    /// </summary> 
    /// <param name="ascii85EncodedString">Decodes a 10 character Ascii-85 
    /// encoded string.</param> 
    /// <returns>The integer representation of the parameter.</returns> 
    private static UInt64 AsciiDecode(this string ascii85EncodedString) 
    { 
     if (ascii85EncodedString.Length != 10) 
     { 
      throw new ArgumentException(
       "An Ascii-85 encoded Uint64 should be 10 characters long.", 
       "ascii85EncodedString"); 
     } 

     // Nb, the most significant digits in our encoded character 
     // will be the right-most characters. 
     var charCount = (UInt32)kEncodeMap.Length; 
     UInt64 result = 0; 

     // Starting with the right-most (most-significant) character, 
     // iterate through the encoded string and decode. 
     for (var i = ascii85EncodedString.Length - 1; i >= 0; i--) 
     { 
      // Multiply the current decoded value by the base. 
      result *= charCount; 

      // Add the integer value for that encoded character. 
      result += kDecodeMap[ascii85EncodedString[i]]; 
     } 

     return result; 
    } 
} 

इसके अलावा, परन्तु इतना आसान हिस्सा है - जाहिर है, अपने उपयोग के आधार पर आप एक अलग चरित्र सेट के रूप में मेरी कमी मुझे 'ß' और 'ओ' जैसे कुछ असामान्य वर्ण का चयन किया जाता चयन करने के लिए आवश्यकता हो सकती है इकाई परीक्षण। वे के रूप में पूरी तरह से के रूप में मैं चाहूँगा नहीं हैं, और मैं कहाँ Guid.NewGuid() प्रयोग किया जाता है की गैर नियतिवाद पसंद नहीं है, लेकिन वे आपके लिए प्रारंभ करने चाहिए:

/// <summary> 
/// Tests to verify that the Ascii-85 encoding is functioning as expected. 
/// </summary> 
[TestClass] 
[UsedImplicitly] 
public class Ascii85Tests 
{ 
    [TestMethod] 
    [Description("Ensure that the Ascii-85 encoding is correct.")] 
    [UsedImplicitly] 
    public void CanEncodeAndDecodeAGuidUsingAscii85() 
    { 
     var guidStrings = new[] 
     { 
      "00000000-0000-0000-0000-000000000000", 
      "00000000-0000-0000-0000-0000000000FF", 
      "00000000-0000-0000-0000-00000000FF00", 
      "00000000-0000-0000-0000-000000FF0000", 
      "00000000-0000-0000-0000-0000FF000000", 
      "00000000-0000-0000-0000-00FF00000000", 
      "00000000-0000-0000-0000-FF0000000000", 
      "00000000-0000-0000-00FF-000000000000", 
      "00000000-0000-0000-FF00-000000000000", 
      "00000000-0000-00FF-0000-000000000000", 
      "00000000-0000-FF00-0000-000000000000", 
      "00000000-00FF-0000-0000-000000000000", 
      "00000000-FF00-0000-0000-000000000000", 
      "000000FF-0000-0000-0000-000000000000", 
      "0000FF00-0000-0000-0000-000000000000", 
      "00FF0000-0000-0000-0000-000000000000", 
      "FF000000-0000-0000-0000-000000000000", 
      "FF000000-0000-0000-0000-00000000FFFF", 
      "00000000-0000-0000-0000-0000FFFF0000", 
      "00000000-0000-0000-0000-FFFF00000000", 
      "00000000-0000-0000-FFFF-000000000000", 
      "00000000-0000-FFFF-0000-000000000000", 
      "00000000-FFFF-0000-0000-000000000000", 
      "0000FFFF-0000-0000-0000-000000000000", 
      "FFFF0000-0000-0000-0000-000000000000", 
      "00000000-0000-0000-0000-0000FFFFFFFF", 
      "00000000-0000-0000-FFFF-FFFF00000000", 
      "00000000-FFFF-FFFF-0000-000000000000", 
      "FFFFFFFF-0000-0000-0000-000000000000", 
      "00000000-0000-0000-FFFF-FFFFFFFFFFFF", 
      "FFFFFFFF-FFFF-FFFF-0000-000000000000", 
      "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", 
      "1000000F-100F-100F-100F-10000000000F" 
     }; 

     foreach (var guidString in guidStrings) 
     { 
      var guid = new Guid(guidString); 
      var encoded = Ascii85.Encode(guid); 

      Assert.AreEqual(
       20, 
       encoded.Length, 
       "A guid encoding should not exceed 20 characters."); 

      var decoded = Ascii85.Decode(encoded); 

      Assert.AreEqual(
       guid, 
       decoded, 
       "The guids are different after being encoded and decoded."); 
     } 
    } 

    [TestMethod] 
    [Description(
     "The Ascii-85 encoding is not susceptible to changes in character case.")] 
    [UsedImplicitly] 
    public void Ascii85IsCaseInsensitive() 
    { 
     const int kCount = 50; 

     for (var i = 0; i < kCount; i++) 
     { 
      var guid = Guid.NewGuid(); 

      // The encoding should be all upper case. A reliance 
      // on mixed case will make the generated string 
      // vulnerable to sql collation. 
      var encoded = Ascii85.Encode(guid); 

      Assert.AreEqual(
       encoded, 
       encoded.ToUpper(), 
       "The Ascii-85 encoding should produce only uppercase characters."); 
     } 
    } 
} 

मुझे आशा है कि यह किसी को कुछ परेशानी बचाता है।

इसके अलावा, अगर आपको कोई भी बग मिलती है तो मुझे बताएं ;-)

+2

आपकी दूसरी इकाई परीक्षण में "var guid = new guid();" "var guid = guid.NewGuid();" पढ़ना चाहिए – aboy021

+1

@ aboy021 - अच्छी तरह से देखा गया। मैंने कोड को सही किया है। इसके बिना लूपिंग पॉइंटलेस बनाया गया ;-) – sheikhjabootie

+0

सावधान रहें, कोड '¾' जैसे वर्ण लौटाता है जो 127 से ऊपर है, और कड़ाई से ASCII नहीं है। आपके एन्कोडिंग (उदा। यूटीएफ -8) के आधार पर, आपको बचने के दृश्य मिल सकते हैं। एक यूटीएफ -8 एन्कोडेड स्ट्रिंग में, 128-255 रेंज में प्रत्येक वर्ण कोड 2 बाइट्स का उपयोग करता है। –

संबंधित मुद्दे

 संबंधित मुद्दे