如何在保持自然顺序的同时将Java long转换为字符串

时间:2010-02-10 11:26:38

标签: java algorithm sorting

我目前正在研究一个可能很有趣的简单编程问题 - 至少对于那些认为编程是艺术的人来说:)所以这就是:

如何在保持自然秩序的同时最好地代表字符串?

此外,字符串表示应与^[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. 0000
  4. 0001
  5. 0002

4 个答案:

答案 0 :(得分: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 =&gt; 0x80的
  • -1 =&gt; 0xFF的
  • 0 =&gt; 0×00
  • 1 =&gt; 0×01
  • 127 =&gt; 0x7F的

现在,如果您为这些值添加0x80:

  • -128 =&gt; 0×00
  • -1 =&gt; 0x7F的
  • 0 =&gt; 0x80的
  • 1 =&gt; 0×81
  • 127 =&gt; 0xFF的

这是正确的顺序(有溢出)。

基本上上面的做法是使用64位有符号长整数而不是8位有符号字节。

转换回来的情况稍微多了一点。您可能认为可以使用:

return Long.parseLong(value, 16);

但你不能。将16 f传递给该函数(-1),它将抛出异常。它似乎将其视为无符号十六进制值,long无法容纳。所以我将它分成两半并解析每一块,将它们组合在一起,将前半部分左移32位。

答案 1 :(得分:2)

编辑:好的,所以只添加负号的负号不起作用......但你可以将值转换为有效的“无符号”长,使Long.MIN_VALUE映射到“0000000000000000”和Long。 MAX_VALUE映射到“FFFFFFFFFFFFFFFF”。更难阅读,但会得到正确的结果。

基本上你只需要在将值转换为十六进制之前将2 ^ 63添加到该值 - 但由于没有无符号长整数,因此在Java中可能会有轻微的痛苦......使用{可能最容易做到{1}}:

BigInteger

诚然,这不会非常有效,但它适用于所有测试用例。

答案 2 :(得分:0)

如果您不需要可打印的字符串,则可以在将值移动Long.MIN_VALUE(-0x80000000)以模拟无符号长整数后,将长四个字符编码:

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代码点值定义。

答案 3 :(得分:0)

RFC2550中有一种技术 - 4月1日的笑话RFC,关于具有4位数日期的Y10K问题 - 可以应用于此目的。基本上,每次整数的字符串表示增长到需要另一个数字时,前面会添加另一个字母或其他(可打印)字符以保留所需的排序顺序。负面规则更加神秘,产生的字符串一目了然难以阅读......但仍然很容易在代码中应用。

很好,对于正数,它们仍然可读。

请参阅:

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