Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How create range map in Java when the keys are strings

I want to create a large range map that will map the keys accoriding their numbers to buckets , for instance :

            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

That means if a new key will arrive with value res should be "var2#150" ==> "out1"

What I tried to do , is to use sorted map , everything is working with range of the numbers inside the map

something like :

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

, but if send var2#2000 , instead to get res "out3" , I got "out2" , and so on...

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....

Another issue - when I have same long value on different keys I expect to do 1st match on the string and then on the number ...

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

Any suggestions please , maybe some algorithm ?

like image 225
VitalyT Avatar asked Jun 19 '26 20:06

VitalyT


1 Answers

One way to compare the prefix string-wise, followed by comparing the suffix numerically, would be:

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

But since comparison methods might get called very often, even a single lookup may involve multiple comparisons, it is worth investigating some time to improve the performance, even if the code will look more complicated:

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

This does only a single pass over the string. First, it iterates over the first characters of the string like String.compareTo would do, stopping at the first mismatching character or the '#' character. If only one string encountered the '#' the other has a longer prefix and we have to consider that for the result.

Only when both strings have the same prefix, the numerical part after the '#' gets processed. Instead of a doing a full integer parsing, we skip all leading zeros, if there are some. Then, if the length of the remaining significant part differs, it already indicates which number is larger. Only if the significant parts have the same length, we need to iterate them. But we can compare the digits literally without needing to interpret them as numbers in that case, as the iteration order is already from most significant digit to lowest significant digit.

Either method can be used like

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);
like image 77
Holger Avatar answered Jun 21 '26 11:06

Holger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!