【问题标题】:How create range map in Java when the keys are strings当键是字符串时如何在Java中创建范围映射
【发布时间】:2018-05-31 04:46:58
【问题描述】:

我想创建一个大范围的地图,将根据他们的数字映射到存储桶的键,例如:

            NavigableMap<String, String> map = new TreeMap<>();

            map.put("var2#" + 0L,   "out0");      // "var2#0..100        => out0
            map.put("var2#" + 100L, "out1");    // "var2#100..200"     => out1
            map.put("var2#" + 200L, "out2");    // "var2#200..300"     => out2
            map.put("var2#" + 300L, "out3");    // "var2#300..+"       => out3

这意味着如果一个新键将到达,值 res 应该是"var2#150" ==&gt; "out1"

我试图做的是使用排序地图,一切都在地图内的数字范围内工作

类似:

String out1 = map.floorEntry("var2#" + 150L).getValue(); //out1 , works!

,但是如果发送 var2#2000 ,而不是获取 res "out3" ,我得到了 "out2" ,依此类推...

String res = map.floorEntry("var2#" + 2000L).getValue(); 
Syso(res)  ==> out2 , BUT I expected result => "out3"
// because it is bigger that the range.

PS:

It is very large map with prefix of some "string" and comes after typed
 long number . Eg. "var1#100, var1#200 , ...bla1#1000 , bla5#2000....

另一个问题 - 当我在不同的键上具有相同的长值时,我希望在字符串上进行第一次匹配,然后在数字上进行 ...

    map.put("var1#" + 200L, "out0");
    map.put("var2#" + 200L, "out1");
    map.put("var3#" + 200L, "out2");
    map.put("var4#" + 200L, "out3");

    String out1 = map.floorEntry("var2#" + 150L).getValue();
    System.out.println("====> " + out1); //expected  out1 , because only match of "var2
    String out3 = map.floorEntry("var2#" + 250L).getValue(); //expected  out1 , because only match of "var2
    System.out.println("====> " + out3);" ....

请有任何建议,也许是一些算法?

【问题讨论】:

    标签: java java-8 treemap


    【解决方案1】:

    比较前缀字符串,然后比较后缀数字的一种方法是:

    public static int compareParts(String a, String b) {
        String[] aa = a.split("#", 2), ba = b.split("#", 2);
        int c = aa[0].compareTo(ba[0]);
        return c != 0? c: Integer.compare(Integer.parseInt(aa[1]), Integer.parseInt(ba[1]));
    }
    

    但由于比较方法可能会被非常频繁地调用,即使是一次查找也可能涉及多次比较,因此值得研究一些时间来提高性能,即使代码看起来会更复杂:

    public static int compareParts(String a, String b) {
        final int aLen = a.length(), bLen = b.length(), l = Math.min(aLen, bLen);
        int ix = 0;
        stringPart: {
            for(; ix < l; ix++) {
                char aCh = a.charAt(ix), bCh = b.charAt(ix);
                int cmp = Character.compare(aCh, bCh);
                if(cmp != 0)
                    return aCh == '#'? -1: bCh == '#'? +1: cmp;
                if(aCh == '#') break stringPart;
            }
            return 0;
        }
        // number part
        int aIx = ix+1, bIx = aIx;
        while(aIx < aLen && a.charAt(aIx)=='0') aIx++;
        while(bIx < bLen && b.charAt(bIx)=='0') bIx++;
        int cmp = Integer.compare(aLen-aIx, bLen-bIx);
        for(; cmp == 0 && aIx < aLen; aIx++, bIx++) {
            cmp = Character.compare(a.charAt(aIx), b.charAt(bIx));
        }
        return cmp;
    }
    

    这只会对字符串进行一次传递。首先,它像String.compareTo 那样遍历字符串的第一个字符,在第一个不匹配字符或'#' 字符处停止。如果只有一个字符串遇到'#',另一个字符串有更长的前缀,我们必须考虑结果。

    只有当两个字符串具有相同的前缀时,'#' 之后的数字部分才会被处理。如果有的话,我们跳过所有前导零,而不是进行完整的整数解析。然后,如果剩余有效部分的长度不同,它已经表明哪个数字更大。只有重要部分的长度相同时,我们才需要迭代它们。但是在这种情况下,我们可以逐字比较数字而不需要将它们解释为数字,因为迭代顺序已经是从最高有效位到最低有效位。

    任何一种方法都可以使用

    NavigableMap<String, String> map = new TreeMap<>(MyClass::compareParts);
    
    map.put("var2#" + 0L,   "out0");
    map.put("var2#" + 100L, "out1");
    map.put("var2#" + 200L, "out2");
    map.put("var2#" + 300L, "out3");
    
    String out1 = map.floorEntry("var2#" + 150L).getValue();
    System.out.println("out1 = "+out1);
    String out3 = map.floorEntry("var2#" + 2000L).getValue();
    System.out.println("res = "+out3);
    

    【讨论】:

    • 嗨@Holger,我有一些问题是你的建议不起作用,如果你能看看就太好了,谢谢:)
    • @VitalyT 当您使用floorEntry 时,您请求的条目等于或更小,因此floorEntry("var2#"+150L) 永远不会在"var2#"+200L 的条目处结束,因为200L 既不等于也不小于比150L。由于没有带有"var2#" 前缀的较小键,因此最接近的键是"var1#"+200L。前缀 具有 优先级。但是,比较器仍然会强制执行总顺序,floorEntry 将始终根据该顺序返回具有相等或更小的键的条目。您不能指望它会根据附加条件突然返回具有更大键的条目。
    • 谢谢,我明白你在说什么,但如果我需要先通过“var2#”搜索,然后映射到 backet 范围,你将如何更改代码,因为它可以完美运行,直到我遇到了与描述的场景类似的错误:(
    • 您可以使用String out1 = map.subMap("var2#0", true, "var3#0", false).get("var2#150"); 强制前缀,然后,结果将是null,因为没有更小的键。或者,如果不存在具有该前缀的较小密钥,则使用更高的密钥,例如Map.Entry&lt;String, String&gt; e = map.subMap("var2#0", true, "var2#150", true).lastEntry(); if(e == null) e = map.subMap("var2#0", true, "var3#0", false).firstEntry(); String out1 = e != null? e.getValue(): null;。如果不存在具有该前缀的键,它仍然可能导致 null
    • 我的意思是在你的建议代码 sn-p , 'int compareParts(String a, String b) ' ...
    【解决方案2】:

    问题是TreeMap 使用字符串进行比较。所以它按字母顺序排序,var2#2000 介于var2#200var2#300 之间。您应该使用specify a custom comparator,或者使用LongInteger 作为TreeMap 的键。所以,这应该工作:

    NavigableMap<Long, String> map = new TreeMap<>();
    map.put(0L,   "out0");      // "var2#0..100        => out0
    map.put(100L, "out1");    // "var2#100..200"     => out1
    map.put(200L, "out2");    // "var2#200..300"     => out2
    map.put(300L, "out3");    // "var2#300..+"       => out3
    

    【讨论】:

    • 赞成您的答案,因为将 only 有意义的部分保留为关键是有意义的。它更具可读性,并且还可以避免将来出现错误。
    • 如果"var2#" 在这些键中是常量,我们绝对可以使用数值。但是如果这个想法是为var1var2、...varN...提供一个范围,这将需要类似Map&lt;String, Map&lt;Long, String&gt;&gt;
    • @ AxelH , var2 不是 const ,它是一个非常大的映射,带有一些“字符串”前缀,并且在输入 long number 之后出现。例如。 "var1#100, var1#200, ...bla1#1000, bla5#2000...."
    • 请在您的任务中添加此信息@VitalyT,这很重要。
    【解决方案3】:

    您可以提取密钥的第二部分并将其用作可导航地图的比较器:

    Comparator.comparingLong(key -> Long.parseLong(key.split("#")[1]))
    

    所以:

    NavigableMap<String, String> map =
        new TreeMap<>(Comparator.comparingLong(key -> Long.parseLong(key.split("#")[1])));
    
    map.put("var2#" + 0L,   "out0");    // "var2#0..100        => out0
    map.put("var2#" + 100L, "out1");    // "var2#100..200"     => out1
    map.put("var2#" + 200L, "out2");    // "var2#200..300"     => out2
    map.put("var2#" + 300L, "out3");    // "var2#300..+"       => out3
    
    assertThat(map.floorEntry("var2#" + 150L).getValue()).isEqualTo("out1");
    assertThat(map.floorEntry("var2#" + 2000L).getValue()).isEqualTo("out3");
    

    【讨论】:

    • 这对于更大的Map 来说会很痛苦!
    • @AxelH 是的,这个比较器效率不高。
    【解决方案4】:

    我会拆分密钥以获得每个变量的Map 范围:

    Map<String, Ranges> map;
    

    我们在哪里实现Ranges,因为我们需要映射值和结果,就像Hari Menon 提出的那样。

    class Ranges {
    
        NavigableMap<Long, String> map = new TreeMap<>();
    
        public String setFloor(long l, String s){
            return map.put(l, s);
        }
    
        public String getFloor(long l){
            return map.floorEntry(l).getValue();
        }
    }
    

    这将很容易填充:

    Map<String, Ranges> map = new HashMap<>();
    
    Ranges r = new Ranges();
    r.setFloor(0L, "out1");
    r.setFloor(100L, "out2");   
    map.put("var1", r);
    
    r = new Ranges();
    r.setFloor(0L, "out3");
    r.setFloor(100L, "out4");
    map.put("var2", r);
    
    System.out.println(map.get("var1").getFloor(50L));
    System.out.println(map.get("var2").getFloor(150L));
    

    输出1
    输出4

    我们可以使用NavigableMap 代替HashMap,但我没有看到这里的重点。

    请注意,这不是 NPE 安全的,这不是保证解决方案简短易读的安全措施。

    【讨论】:

      猜你喜欢
      • 2018-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多