簡體   English   中英

如何將uuid縮短並擴展到15個或更少的字符

[英]How do I shorten and expand a uuid to a 15 or less characters

給定沒有破折號的uuid(v4),如何將其縮短為15個或少於15個字符的字符串? 我還應該能夠從15個字符的字符串回到原始的uuid。

我正在嘗試縮短它的發送時間,以平面文件的形式發送,並且文件格式將該字段指定為15個字符的字母數字字段。 鑒於縮短了的uuid,我應該能夠將其映射回原始的uuid。

這是我嘗試過的,但絕對不是我想要的。

export function shortenUUID(uuidToShorten: string, length: number) {
  const uuidWithoutDashes = uuidToShorten.replace(/-/g , '');
  const radix = uuidWithoutDashes.length;
  const randomId = [];

  for (let i = 0; i < length; i++) {
    randomId[i] = uuidWithoutDashes[ 0 | Math.random() * radix];
  }
  return randomId.join('');
}

正如AuxTaco指出的那樣,如果您實際上是說“字母數字”與“ / ^ [A-Za-z0-9] {0,15} /”相匹配(給出的位數為26 + 26 + 10 = 62) ,那真的是不可能的。 您不能在不損失任何東西的情況下將3加侖的水裝入一加侖的水桶中。 UUID是128位,因此要將其轉換為62個字符空間,您至少需要22個字符( log[base 62](2^128) ==〜22)。

如果您在字符集上更靈活,只需要15個unicode字符就可以放入文本文檔中,那么我的回答會有所幫助。


注意:此答案的第一部分,我認為長度為16,而不是15。更簡單的答案無效。 下面的更復雜的版本仍然會。


為此,您將使用某種雙向壓縮算法(類似於用於壓縮文件的算法)。

但是,嘗試壓縮類似UUID的問題是您可能會遇到很多沖突。

UUID v4的長度為32個字符(不帶破折號)。 它是十六進制的,所以它的字符空間是16個字符( 0123456789ABCDEF

這為您提供了16^32 ,大約3.4028237e+38340,282,370,000,000,000,000,000,000,000,000,000,000的許多可能組合。 為了使它在壓縮后可恢復,您必須確保沒有任何沖突(即,沒有2個UUID變成相同的值)。 這有很多可能的值(這就是為什么我們將這么多的值用於UUID,2個隨機UUID的機率只有這個大數中的1個)。

要將多種可能性縮減為16個字符,您必須至少具有盡可能多的值。 如果使用16個字符,則必須具有256個字符(該大數的根16,256 256^16 == 16 ^ 32`)。 假設您擁有永遠不會產生碰撞的算法。

確保從未發生沖突的一種方法是將其從16進制數轉換為256進制數。 這將為您提供一對一的關系,確保沒有碰撞並使其完全可逆。 通常,在JavaScript中切換基數很容易: parseInt(someStr, radix).toString(otherRadix) (例如parseInt('00FF', 16).toString(20) 。不幸的是,JavaScript的最大基數為36,因此我們將不得不自己進行轉換。

如此龐大的基數代表着這一趨勢。 您可以任意選擇256個不同的字符,將它們放入字符串中,然后將其用於手動轉換。 但是,即使您將大寫和小寫字母視為不同的字形,我也不認為標准的美國鍵盤上有256個不同的符號。

一個更簡單的解決方案是使用String.fromCharCode()0255任意字符代碼。

另一個小問題是,如果我們嘗試將所有內容都視為一個大數字,則會遇到問題,因為這是一個非常大的數字,而JavaScript無法正確地表示它。

取而代之的是,由於我們已經有了十六進制,我們可以將其拆分為成對的十進制數,將其轉換,然后將其吐出。 32個十六進制數字= 16對,因此(碰巧)是完美的。 (如果必須將其求解為任意大小,則必須做一些額外的數學運算,然后進行轉換以將數字分成幾部分,然后再重新組裝。)

 const uuid = '1234567890ABCDEF1234567890ABCDEF'; const letters = uuid.match(/.{2}/g).map(pair => String.fromCharCode(parseInt(pair, 16))); const str = letters.join(''); console.log(str); 

請注意,那里有一些隨機字符,因為不是每個字符代碼都映射到“正常”符號。 如果要發送的內容不能處理它們,則需要使用數組方法:找到它可以處理的256個字符,對它們進行數組處理,而不要使用String.fromCharCode(num) ,請使用charset[num]

要將其轉換回去,您只需做相反的事情:獲取char代碼,轉換為十六進制,然后將它們加在一起:

 const uuid = '1234567890ABCDEF1234567890ABCDEF'; const compress = uuid => uuid.match(/.{2}/g).map(pair => String.fromCharCode(parseInt(pair, 16))).join(''); const expand = str => str.split('').map(letter => ('0' + letter.charCodeAt(0).toString(16)).substr(-2)).join(''); const str = compress(uuid); const original = expand(str); console.log(str, original, original.toUpperCase() === uuid.toUpperCase()); 


有趣的是,這是您可以對任意輸入基礎和輸出基礎進行的操作。

這段代碼有點混亂,因為它確實進行了擴展以使其更加不言自明,但是它基本上完成了我上面所描述的。

由於JavaScript沒有無限的精確度,因此如果您最終轉換了一個非常大的數字(一個看起來像2.00000000e+10 ),那么該e之后未顯示的每個數字都會被截斷並替換為零。 為了解決這個問題,您必須以某種方式將其分解。

在下面的代碼中,有一種“簡單”的方法無法解決這個問題,因此僅適用於較小的字符串,然后才是將其分解的適當方法。 我選擇了一種簡單但效率不高的方法,即根據要轉換的位數將字符串分解。 這不是最好的方法(因為數學實際上不是那樣工作的),但是它可以解決問題(以需要較小的字符集為代價)。

如果確實需要將字符集大小保持為最小,則可以采用更智能的拆分機制。

 const smallStr = '1234'; const str = '1234567890ABCDEF1234567890ABCDEF'; const hexCharset = '0123456789ABCDEF'; // could also be an array const compressedLength = 16; const maxDigits = 16; // this may be a bit browser specific. You can make it smaller to be safer. const logBaseN = (num, n) => Math.log(num) / Math.log(n); const nthRoot = (num, n) => Math.pow(num, 1/n); const digitsInNumber = num => Math.log(num) * Math.LOG10E + 1 | 0; const partitionString = (str, numPartitions) => { const partsSize = Math.ceil(str.length / numPartitions); let partitions = []; for (let i = 0; i < numPartitions; i++) { partitions.push(str.substr(i * partsSize, partsSize)); } return partitions; } console.log('logBaseN test:', logBaseN(256, 16) === 2); console.log('nthRoot test:', nthRoot(256, 2) === 16); console.log('partitionString test:', partitionString('ABCDEFG', 3)); // charset.length should equal radix const toDecimalFromCharset = (str, charset) => str.split('') .reverse() .map((char, index) => charset.indexOf(char) * Math.pow(charset.length, index)) .reduce((sum, num) => (sum + num), 0); const fromDecimalToCharset = (dec, charset) => { const radix = charset.length; let str = ''; for (let i = Math.ceil(logBaseN(dec + 1, radix)) - 1; i >= 0; i--) { const part = Math.floor(dec / Math.pow(radix, i)); dec -= part * Math.pow(radix, i); str += charset[part]; } return str; }; console.log('toDecimalFromCharset test 1:', toDecimalFromCharset('01000101', '01') === 69); console.log('toDecimalFromCharset test 2:', toDecimalFromCharset('FF', hexCharset) === 255); console.log('fromDecimalToCharset test:', fromDecimalToCharset(255, hexCharset) === 'FF'); const arbitraryCharset = length => new Array(length).fill(1).map((a, i) => String.fromCharCode(i)); // the Math.pow() bit is the possible number of values in the original const simpleDetermineRadix = (strLength, originalCharsetSize, compressedLength) => nthRoot(Math.pow(originalCharsetSize, strLength), compressedLength); // the simple ones only work for values that in decimal are so big before lack of precision messes things up // compressedCharset.length must be >= compressedLength const simpleCompress = (str, originalCharset, compressedCharset, compressedLength) => fromDecimalToCharset(toDecimalFromCharset(str, originalCharset), compressedCharset); const simpleExpand = (compressedStr, originalCharset, compressedCharset) => fromDecimalToCharset(toDecimalFromCharset(compressedStr, compressedCharset), originalCharset); const simpleNeededRadix = simpleDetermineRadix(str.length, hexCharset.length, compressedLength); const simpleCompressedCharset = arbitraryCharset(simpleNeededRadix); const simpleCompressed = simpleCompress(str, hexCharset, simpleCompressedCharset, compressedLength); const simpleExpanded = simpleExpand(simpleCompressed, hexCharset, simpleCompressedCharset); // Notice, it gets a little confused because of a lack of precision in the really big number. console.log('Original string:', str, toDecimalFromCharset(str, hexCharset)); console.log('Simple Compressed:', simpleCompressed, toDecimalFromCharset(simpleCompressed, simpleCompressedCharset)); console.log('Simple Expanded:', simpleExpanded, toDecimalFromCharset(simpleExpanded, hexCharset)); console.log('Simple test:', simpleExpanded === str); // Notice it works fine for smaller strings and/or charsets const smallCompressed = simpleCompress(smallStr, hexCharset, simpleCompressedCharset, compressedLength); const smallExpanded = simpleExpand(smallCompressed, hexCharset, simpleCompressedCharset); console.log('Small string:', smallStr, toDecimalFromCharset(smallStr, hexCharset)); console.log('Small simple compressed:', smallCompressed, toDecimalFromCharset(smallCompressed, simpleCompressedCharset)); console.log('Small expaned:', smallExpanded, toDecimalFromCharset(smallExpanded, hexCharset)); console.log('Small test:', smallExpanded === smallStr); // these will break the decimal up into smaller numbers with a max length of maxDigits // it's a bit browser specific where the lack of precision is, so a smaller maxDigits // may make it safer // // note: charset may need to be a little bit bigger than what determineRadix decides, since we're // breaking the string up // also note: we're breaking the string into parts based on the number of digits in it as a decimal // this will actually make each individual parts decimal length smaller, because of how numbers work, // but that's okay. If you have a charset just barely big enough because of other constraints, you'll // need to make this even more complicated to make sure it's perfect. const partitionStringForCompress = (str, originalCharset) => { const numDigits = digitsInNumber(toDecimalFromCharset(str, originalCharset)); const numParts = Math.ceil(numDigits / maxDigits); return partitionString(str, numParts); } const partitionedPartSize = (str, originalCharset) => { const parts = partitionStringForCompress(str, originalCharset); return Math.floor((compressedLength - parts.length - 1) / parts.length) + 1; } const determineRadix = (str, originalCharset, compressedLength) => { const parts = partitionStringForCompress(str, originalCharset); return Math.ceil(nthRoot(Math.pow(originalCharset.length, parts[0].length), partitionedPartSize(str, originalCharset))); } const compress = (str, originalCharset, compressedCharset, compressedLength) => { const parts = partitionStringForCompress(str, originalCharset); const partSize = partitionedPartSize(str, originalCharset); return parts.map(part => simpleCompress(part, originalCharset, compressedCharset, partSize)).join(compressedCharset[compressedCharset.length-1]); } const expand = (compressedStr, originalCharset, compressedCharset) => compressedStr.split(compressedCharset[compressedCharset.length-1]) .map(part => simpleExpand(part, originalCharset, compressedCharset)) .join(''); const neededRadix = determineRadix(str, hexCharset, compressedLength); const compressedCharset = arbitraryCharset(neededRadix); const compressed = compress(str, hexCharset, compressedCharset, compressedLength); const expanded = expand(compressed, hexCharset, compressedCharset); console.log('String:', str, toDecimalFromCharset(str, hexCharset)); console.log('Neded radix size:', neededRadix); // bigger than normal because of how we're breaking it up... this could be improved if needed console.log('Compressed:', compressed); console.log('Expanded:', expanded); console.log('Final test:', expanded === str); 


要專門使用上述內容回答問題,可以使用:

const hexCharset = '0123456789ABCDEF';
const compressedCharset = arbitraryCharset(determineRadix(uuid, hexCharset));

// UUID to 15 characters
const compressed = compress(uuid, hexCharset, compressedCharset, 15);

// 15 characters to UUID
const expanded = expanded(compressed, hexCharset, compressedCharset);

如果任意字符中存在問題字符,則必須采取措施將其過濾掉,或對特定字符進行硬編碼。 只要確保所有功能都是確定性的(即每次相同的結果)即可。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM