当键是字符串时,如何在Java中创建范围映射

当键是字符串时,如何在Java中创建范围映射,java,java-8,treemap,Java,Java 8,Treemap,我想创建一个大范围的地图,将按键及其编号映射到桶,例如: NavigableMap<String, String> map = new TreeMap<>(); map.put("var2#" + 0L, "out0"); // "var2#0..100 => out0 map.put("var2#" + 100L, "out1"); // "var2#100

我想创建一个大范围的地图,将按键及其编号映射到桶,例如:

            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
,但如果发送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.
附言:

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

    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);" ....

有什么建议吗,也许是一些算法

问题在于
TreeMap
使用字符串进行比较。因此它按字母顺序排序,
var2#2000
介于
var2#200
var2#300
之间。您应该或者使用
Long
Integer
作为
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
NavigableMap=newtreemap();
映射输出(0升,“输出0”);//“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

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

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

导航地图=
新的树映射(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
资产(map.floorEntry(“var2#“+150L).getValue()).isEqualTo(“out1”);
资产(map.floorEntry(“var2#“+2000L).getValue()).isEqualTo(“out3”);

我将拆分键以获得每个变量范围的
映射:

Map<String, Ranges> map;
这将很容易填充:

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));
Map Map=newhashmap();
范围r=新范围();
r、 设置地板(0升,“室外1”);
r、 设置地板(100L,“out2”);
地图放置(“var1”,r);
r=新范围();
r、 设置地板(0升,“out3”);
r、 设置地板(100L,“out4”);
地图放置(“var2”,r);
System.out.println(map.get(“var1”).getFloor(50L));
系统输出打印LN(映射获取(“var2”).getFloor(150L));
out1
输出4

我们可以使用
NavigableMap
而不是
HashMap
,但我没有看到这里的要点


请注意,这不是NPE安全的,这并不能保证解决方案的简短和可读性。

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

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;
}

提高你的答案,因为只保留有意义的部分作为关键是明智的。它更具可读性,也可以避免将来出现错误。如果这些键中的
“var2#”
是常量,我们肯定可以使用数值。但是如果想法是为
var1
var2
提供一个范围<代码>变量
。。。这需要一个类似于
Map
@AxelH的东西,var2不是const,它是一个非常大的Map,前缀是一些“string”,位于键入的长数字之后。例如,“var1#100,var1#200,…bla1#1000,bla5#2000…”请将此信息添加到您的questin@VitalyT中,这很重要。这对于更大的
地图来说将是痛苦的@AxelH是的,这个比较器不是很有效。嗨@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 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
。我的意思是在您的建议代码段中,“int compareParts(String a,String b)”。。。
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));
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;
}
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);