Частичный поиск в HashMap

Мне нужно создать что-то вроде телефонной книги. Он содержит имя и номер. Теперь, когда я набираю буквы, список соответствия должен быть возвращен. В приведенном ниже примере, когда я набираю H, должен быть возвращен список, содержащий Harmer, Harris, Hawken, Hosler. При вводе Ha должен быть возвращен список, содержащий только Harmer, Harris, Hawken.

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

Есть идеи, как этого добиться? Заранее спасибо.


person Partha    schedule 15.07.2011    source источник
comment
Вы уверены, что использование HashMap вообще является хорошей идеей для чего-то подобного? Я думаю, что другая структура данных может быть лучше.   -  person Tikhon Jelvis    schedule 16.07.2011
comment
Вы ищете только первую букву или удаляете список по мере ввода? Например, исключает ли ввод Ха Хослера?   -  person Nate Zaugg    schedule 16.07.2011


Ответы (5)


Да, HashMap не подходит для этого. Как сказал Божо, правильным будет Trie.

С помощью встроенных инструментов Java TreeMap (или любая SortedMap, на самом деле) можно было бы использовать:

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

Вывод будет даже отсортирован по ключу.

Вот пример использования:

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

Если вы хотите, чтобы ваш префиксный фильтр не зависел от различий в регистре, используйте подходящий компаратор для вашей карты (например, Collator с подходящей настройкой силы или String.CASE_INSENSITIVE_ORDER).

person Paŭlo Ebermann    schedule 15.07.2011
comment
@Paŭlo Ebermann: Почему Trie, как он экономит место {stackoverflow.com/questions/8265476/trie-saves-space-but-how} ? - person Rajat Gupta; 25.11.2011
comment
Вы также можете использовать префикс + ￿ в качестве конца. - person Tires; 27.11.2013
comment
@PaŭloEbermann У меня такой же сценарий. Но существующая карта является реализацией HashMap (для более 10 000 элементов), и ее нельзя изменить. Теперь, для достижения этого в соответствии с приведенным выше решением, если я собираюсь сбросить все, что содержится в Hashmap, в TreeMap, построение самого TreeMap будет очень дорогостоящим (поскольку он создает отсортированную структуру), остальное может быть легко и быстро . Любые предложения по переносу этого решения на мои требования? - person abksrv; 29.08.2014
comment
@abksrv Если это всего лишь один поиск, одна итерация по всем записям вашего HashMap должна быть самой быстрой. Если хотите делать это чаще, перенесите данные в более качественную структуру. (Кроме того, измерьте: возможно, для вашего набора данных и оборудования оптимизация даже не требуется.) - person Paŭlo Ebermann; 29.08.2014

Для этого требуется структура данных Trie. См. этот вопрос для Java-реализации. Я использовал это.

person Bozho    schedule 15.07.2011
comment
Спасибо Божо ваша ссылка пригодилась! Но прошло почти 3 года с тех пор, как на этот вопрос был дан ответ. Есть ли какие-либо лучшие решения сейчас, о которых вы можете знать? - person Rajat Gupta; 24.11.2011
comment
ссылка опять битая Божо - person mcvkr; 25.02.2019

Поместите все это в MultiMap (или просто сохраните список как значение в вашем HashMap). Для "Браун" хранить:

"B"->["Brown"]
"BR"->["Brown"]
"BRO"->["Brown"]

Если вы позже добавите «Брэдли»:

"B"->["Brown", "Bradley"]
"BR"->["Brown", "Bradley"]
"BRO"->["Brown"]
"BRA"->["Bradley"]

и т.д...

затем есть еще одна карта, чтобы сопоставить «Браун» или «Брэдли» с номером телефона.

person dgrant    schedule 15.07.2011
comment
Добавление и удаление элементов из этой структуры данных было бы очень дорогостоящим. - person Mark Elliot; 16.07.2011
comment
Я согласен. Но мы даже не знаем, насколько велика его телефонная книга. Я предпочитаю сначала сделать что-то простое, а потом оптимизировать. Это кажется самым простым. - person dgrant; 16.07.2011
comment
Доступ будет O (1), тогда как для деревьев это будет log (n). И разве это не более важно, если вы делаете что-то вроде автозаполнения? И как часто обновляется набор данных? Если get гораздо чаще, чем set, кого волнует, насколько медленно происходит добавление/удаление. И добавление и удаление здесь даже не так уж плохо, я не думаю. - person dgrant; 16.07.2011

Удалите все значения, которые не содержат ключевую часть:

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

Или регулярное выражение:

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

Или фильтровать поток и собирать на новую карту:

yourMap.entrySet().stream().filter(e -> e.getKey().contains(keyPart)).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
person Justinas Jakavonis    schedule 29.05.2019

Использование многокарты guava упростит ваше решение.

Ключ - первая буква имени, значение - Collection, содержащее всю пару имя-телефон, имя которой начинается с ключа (первой буквы).

Пример:

    public void test(){
      //firstLetter -> list of name-phone pair
      Multimap<String, Pair> mMap =  ArrayListMultimap.create();

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

      //Test
      System.out.println(mMap.get("H"));
   }

   void put(Multimap<String, Pair> mMap, String name, String phone){
      mMap.put(name.substring(0,1), new Pair(name, phone));
   }

   public static class Pair{
      String name;
      String phone;

      public Pair(String name, String phone) {
         this.name = name;
         this.phone = phone;
      }

      @Override
      public String toString() {
         return "Pair [name="+name+", phone="+phone+"]";
      }

}

person 卢声远 Shengyuan Lu    schedule 16.07.2011