2010-02-10 16 views
4

我目前正在尋找一個簡單的編程問題,可能會對優化起到有趣的作用 - 至少對於任何相信編程是藝術的人來說:)所以這裏是:如何在保持自然順序的情況下將Java long的字符串轉換爲字符串

如何在保持自然秩序的同時最大限度地將long作爲字符串表示?

此外,字符串表示應匹配^[A-Za-z0-9]+$。 (我不是太嚴格這裏,但避免使用控制字符或任何可能引起頭痛與編碼,是在XML非法的,已換行,或類似的文字,一定會導致問題)

這裏有一個JUnit測試用例:

@Test 
public void longConversion() { 
    final long[] longs = { Long.MIN_VALUE, Long.MAX_VALUE, -5664572164553633853L, 
      -8089688774612278460L, 7275969614015446693L, 6698053890185294393L, 
      734107703014507538L, -350843201400906614L, -4760869192643699168L, 
      -2113787362183747885L, -5933876587372268970L, -7214749093842310327L, }; 

    // keep it reproducible 
    //Collections.shuffle(Arrays.asList(longs)); 

    final String[] strings = new String[longs.length]; 
    for (int i = 0; i < longs.length; i++) { 
     strings[i] = Converter.convertLong(longs[i]); 
    } 

    // Note: Comparator is not an option 
    Arrays.sort(longs); 
    Arrays.sort(strings); 

    final Pattern allowed = Pattern.compile("^[A-Za-z0-9]+$"); 
    for (int i = 0; i < longs.length; i++) { 
     assertTrue("string: " + strings[i], allowed.matcher(strings[i]).matches()); 
     assertEquals("string: " + strings[i], longs[i], Converter.parseLong(strings[i])); 
    } 
} 

,這裏是我要找的

public static class Converter { 
    public static String convertLong(final long value) { 
     // TODO 
    } 

    public static long parseLong(final String value) { 
     // TODO 
    } 
} 

我已經在如何處理這個問題的一些想法的方法。儘管如此,我仍然可以從社區獲得一些不錯的(創造性)建議。

此外,這將是很好,如果這種轉換會

  • 儘可能短
  • 容易在其他語言
  • 實施

編輯:我很高興地看到,兩位非常有信譽的程序員遇到同樣的問題,因爲我使用' - '作爲負數不能正常工作,因爲' - '不反轉排序順序:

  1. -0001
  2. -0002
  3. 技術

回答

13

好吧,採取兩種:

class Converter { 
    public static String convertLong(final long value) { 
    return String.format("%016x", value - Long.MIN_VALUE); 
    } 

    public static long parseLong(final String value) { 
    String first = value.substring(0, 8); 
    String second = value.substring(8); 
    long temp = (Long.parseLong(first, 16) << 32) | Long.parseLong(second, 16); 
    return temp + Long.MIN_VALUE; 
    } 
} 

這一個需要一點解釋。首先,讓我證明它是可逆的,所得的轉換應該表現出排序:

for (long aLong : longs) { 
    String out = Converter.convertLong(aLong); 
    System.out.printf("%20d %16s %20d\n", aLong, out, Converter.parseLong(out)); 
} 

輸出:

-9223372036854775808 0000000000000000 -9223372036854775808 
9223372036854775807 ffffffffffffffff 9223372036854775807 
-5664572164553633853 316365a0e7370fc3 -5664572164553633853 
-8089688774612278460 0fbba6eba5c52344 -8089688774612278460 
7275969614015446693 e4f96fd06fed3ea5 7275969614015446693 
6698053890185294393 dcf444867aeaf239 6698053890185294393 
    734107703014507538 8a301311010ec412 734107703014507538 
-350843201400906614 7b218df798a35c8a -350843201400906614 
-4760869192643699168 3dedfeb1865f1e20 -4760869192643699168 
-2113787362183747885 62aa5197ea53e6d3 -2113787362183747885 
-5933876587372268970 2da6a2aeccab3256 -5933876587372268970 
-7214749093842310327 1be00fecadf52b49 -7214749093842310327 

正如你可以看到Long.MIN_VALUELong.MAX_VALUE(前兩行)是正確的,其他值基本符合。

這是什麼做的?

假設符號字節的值,在有:

  • -128 => 0x80的
  • -1 => 0xFF的
  • 0 => 0×00
  • 1 => 0×01
  • 127 = > 0x7F

現在,如果您將0x80添加到這些值,您會得到:

  • -128 => 0×00
  • -1 => 0x7F的
  • 0 => 0x80的
  • 1 => 0×81
  • 127 => 0xFF的

這是正確的(有溢出)。

基本上以上做,與64米帶符號的長材而不是8個帶符號的字節。

轉換回是多一點彎路。你可能會認爲你可以使用:

return Long.parseLong(value, 16); 

,但你不能。將16 f傳遞給該函數(-1),它會拋出異常。它似乎被視爲一個無符號的十六進制值,其中long無法容納。因此,我把它分成兩半,分解每一部分,將它們結合在一起,將前半部分左移32位。

+0

我會使用'String.format' ...哦,並缺少線程(聯合國)安全免責聲明 – 2010-02-10 11:45:00

+0

@Int:你可以使用'String.format',但你需要一些東西來解析它轉換反正另一種方式所以你可以用一種方法來做。 – cletus 2010-02-10 11:56:59

+0

簡單,呃?不起作用 - 請參閱我的編輯;) – sfussenegger 2010-02-10 12:26:30

2

編輯:好的,所以才增加了對負數不工作負號......但你可以將該值轉換爲有效的「無符號」長度,以使Long.MIN_VALUE映射爲「0000000000000000」,Long.MAX_VALUE映射爲「FFFFFFFFFFFFFFFF」。難以閱讀,但會得到正確的結果。

基本上你只需要把它變成六角前2^63增加值 - 但可能是輕微的疼痛在Java中做,由於它沒有無符號多頭...這可能是最簡單的使用做BigInteger

private static final BigInteger OFFSET = BigInteger.valueOf(Long.MIN_VALUE) 
                .negate(); 

public static String convertLong(long value) { 
    BigInteger afterOffset = BigInteger.valueOf(value).add(OFFSET); 
    return String.format("%016x", afterOffset); 
} 

public static long parseLong(String text) { 
    BigInteger beforeOffset = new BigInteger(text, 16); 
    return beforeOffset.subtract(OFFSET).longValue(); 
} 

這不會是非常有效的,無可否認的,但它與所有測試情況下工作。

+0

不行的 - 看到我的EDIT(?這是褻瀆,是不是) – sfussenegger 2010-02-10 12:28:31

+0

@sfussenegger:編輯......看看。 – 2010-02-10 13:08:12

+0

是的,那也是我所追求的道路。聽起來很有希望,我正在等待一些代碼:)順便說一句:我已經改變-1到+1,雖然我想要0,但擊中了錯誤的箭頭,現在不會讓我改變它(「投票太舊了被改變,除非這個答案被編輯「)......怪異的。 – sfussenegger 2010-02-10 13:17:58

0

如果不需要打印字符串,您可以編碼長四個字符你移Long.MIN_VALUE值後(-0x80000000)來模擬一個unsigned long:

public static String convertLong(long value) { 
    value += Long.MIN_VALUE; 
    return "" + 
     (char)(value>>48) + (char)(value>>32) + 
     (char)(value>>16) + (char)value; 
} 

public static long parseLong(String value) { 
    return (
     (((long)value.charAt(0))<<48) + 
     (((long)value.charAt(1))<<32) + 
     (((long)value.charAt(2))<<16) + 
     (long)value.charAt(3)) + Long.MIN_VALUE; 
} 

替代對使用是沒有問題的,因爲字符串的自然順序由UTF-16值在其字符,而不是由UCS-2編碼點值定義。

+0

對不起,但這雖然工作,它使用不可打印的字符這是不是一個選項我已編輯我的問題,包括此要求 – sfussenegger 2010-02-10 12:30:52

+0

所以在我花時間思考另一種解決方案之前,您是否介意與我們分享「可打印」的定義?是否所有字符都具有有效的Unicode代碼點,ISO-8859-1或可能只是ASCII?是可打印或不可打印的空格字符? – jarnbjo 2010-02-10 12:47:19

+0

我很想看到一些不會令可能出現編碼問題的麻煩,適合一行,並能很好地與XML或CSV中的表示方式搭配使用。[A-Za-Z0-9]應該是一個理智的選擇,但如果你需要的話可以多加一些,我對這個要求不太嚴格,例如,使用0x00作爲字符肯定會引起頭疼,必須避免。 – sfussenegger 2010-02-10 13:08:37

0

有一個在RFC2550的技術 - 的4月1日的笑話RFC有關Y10K問題4位數的日期 - 這可能適用於這一目的。本質上,每個時間的整數的字符串表示增長到需要另一個位,另一個字母或其他(可打印的)字符被預置到保留所需的排序次序。負面的規則更加神祕,產生的字符串一目瞭然難以閱讀......但仍然足夠容易應用於代碼中。

很好,對於正數,它們仍然可讀。

參見:

http://www.faqs.org/rfcs/rfc2550.html

相關問題