Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial search in HashMap

I need to create phone book kind of thing. It contains name & number. Now when I type letters matching list should be returned. For the example given below, when I type H, a list containing Harmer, Harris, Hawken, Hosler should be returned. When type Ha then list containing only Harmer, Harris, Hawken should be returned.

  Map<String, String> nameNum = new HashMap<String, String>();

  nameNum.put("Brown", "+1236389023");
  nameNum.put("Bob", "+1236389023");
  nameNum.put("Harmer", "+1236389023");
  nameNum.put("Harris", "+1236389023");
  nameNum.put("Hawken", "+1236389023");
  nameNum.put("Hosler", "+1236389023");

Any idea how achieve it? Thanks in advance.

like image 220
Partha Avatar asked Jul 15 '11 21:07

Partha


3 Answers

Yeah, a HashMap is not the right data structure for this. As Bozho said, a Trie would be the right one.

With Java's on-board tools, a TreeMap (or any SortedMap, actually) could be used:

public <V> SortedMap<String, V> filterPrefix(SortedMap<String,V> baseMap, String prefix) {
    if(prefix.length() > 0) {
        char nextLetter = prefix.charAt(prefix.length() -1) + 1;
        String end = prefix.substring(0, prefix.length()-1) + nextLetter;
        return baseMap.subMap(prefix, end);
    }
    return baseMap;
}

The output would even be sorted by key.

Here an usage example:

SortedMap<String, String> nameNum = new TreeMap<String, String>();
// put your phone numbers

String prefix = ...;
for(Map.Entry<String,String> entry : filterPrefix(nameNum, prefix).entrySet()) {
    System.out.println(entry);
}

If you want your prefix filter to not be depending on case differences, use a suitable Comparator for your map (like a Collator with a suitable strength setting, or String.CASE_INSENSITIVE_ORDER).

like image 57
Paŭlo Ebermann Avatar answered Nov 05 '22 11:11

Paŭlo Ebermann


This calls for a Trie data structure. See this question for java implementations. I used this one.

like image 24
Bozho Avatar answered Nov 05 '22 11:11

Bozho


Remove all values which doesn't contain key part:

yourMap.keySet().removeIf(key -> !key.contains(keyPart));

Or regex:

yourMap.keySet().removeIf(key -> !key.matches(".*keyPart.*"));

Or filter stream and collect to a new map:

yourMap.entrySet().stream().filter(e -> e.getKey().contains(keyPart)).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
like image 2
Justinas Jakavonis Avatar answered Nov 05 '22 11:11

Justinas Jakavonis