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

时间:2018-05-31 04:46:59

标签: 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..200"     => out1
            map.put("var2#" + 200L, "out2");    // "var2#200..300"     => out2
            map.put("var2#" + 300L, "out3");    // "var2#300..+"       => out3

这意味着如果新密钥以值res到达,则应为"var2#150" ==> "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.

P.S:

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

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

4 个答案:

答案 0 :(得分:2)

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

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); 之后的数字部分。如果有一些,我们不会执行完整的整数解析,而是跳过所有前导零。然后,如果剩余重要部分的长度不同,则它已经指示哪个数字更大。只有当重要部分具有相同的长度时,我们才需要迭代它们。但是我们可以逐字地比较数字,而不需要在这种情况下将它们解释为数字,因为迭代顺序已经从最高有效数字到最低有效数字。

任何一种方法都可以像

一样使用
import numpy as np
import matplotlib.pyplot as plt


# Read your file properly
with open('00001.txt', 'r') as f
    # Retrieve the data without '\n' code (it was your problem)
    data = f.read().splitlines()
    # Load it in numpy
    a = np.array(data)
    # Do what you want with it
    yvec1 = a.astype(int)

答案 1 :(得分:1)

问题是TreeMap正在使用String进行比较。因此按字母顺序排序,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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

我会拆分密钥以获得每个变量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));
  

OUT1
  OUT4

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

请注意,这不是NPE安全的,但这并不能确保解决方案简短易读。