Удаление дублированных значений из Map

Нижеприведенный код, должен удалить из изначального списка МАП mapOfSoNameName повторяющиеся по именам элементы т.е по value. Я писал код и застрял... Каким путем пошел:

  1. С помощью foreach беру элемент основого мапа mapOfSoNameName
  2. Создаю копию изначального мапа (все это позже в функцию оберну) в mapOfSoNameNameClone тут же удаляю из него элемент который взят из оснвоного мапа
  3. Далее прохожусь в каждом цикле foreach с помощью цикла while каждый элемент mapOfSoNameNameClone и сравниваю с каждым элементом mapOfSoNameName

И вот засада кроется в том что при условии if(!value.equals(entry.getValue())) копирует все значения, а по моей идее не должно и если я меняю условие if(value.equals(entry.getValue())) то копирует только те которые СОВПАДАЮТ. А в моей логике сортированый мап должен быть БЕЗ совпадающих....

public static void main(String[] args)
    {
        Map<String,String> mapOfSoNameName = new HashMap<>();
        Map<String,String> mapOfSoNameNameSorted = new HashMap<>();
        Map<String,String> mapOfSoNameNameTemp = new HashMap<>();
        mapOfSoNameName.put("Петров","Иван");
        mapOfSoNameName.put("Иванов","Иван");
        mapOfSoNameName.put("Анисимов","Николай");
        mapOfSoNameName.put("Баширов","Азамат");
        mapOfSoNameName.put("Викулов","Сергей");
        mapOfSoNameName.put("Мешников","Степан");
        mapOfSoNameName.put("Мясников","Борис");
        mapOfSoNameName.put("Каспаров","Гари");
        mapOfSoNameName.put("Златин","Гари");
        mapOfSoNameName.put("Южаков","Ярослав");
        int i = 1;// для проверки
        for(Map.Entry<String,String> pairString : mapOfSoNameName.entrySet())
        {
            String key = pairString.getKey();
            String value = pairString.getValue();
            Map<String,String> mapOfSoNameNameClone = new HashMap<>();
            mapOfSoNameNameClone.putAll(mapOfSoNameName);
            mapOfSoNameNameClone.remove(key, value);
            Iterator <Map.Entry<String,String>> itr = mapOfSoNameNameClone.entrySet().iterator();
            while(itr.hasNext())
            {
                Map.Entry<String,String> entry = itr.next();
                if(!value.equals(entry.getValue()))
                {
                    mapOfSoNameNameSorted.put(entry.getKey(), entry.getValue());
                }
            }
            System.out.println(i + " Изначальный список -  " + mapOfSoNameName);        //Для проверки
            System.out.println(i + " Клон  Изначального -  " + mapOfSoNameNameClone);   //Для проверки
            System.out.println(i + " Отсортированный   -   " + mapOfSoNameNameSorted);  // Для проверки
            i++; // для проверки
            mapOfSoNameNameClone.clear();
        }
    }

Ответы (1 шт):

Автор решения: default locale

Сейчас по коду получается, что включаются все люди, для которых есть хоть один другой человек с несовпадающим именем. Т.е. если в списке есть люди с разными именами, то попадут все, а если у всех имя одинаковое, то никто.

Чтобы исправить код нужно для каждого человека проверять, что нет тезок, и только потом добавлять. Простое исправление может выглядеть так:

//есть ли тезка
boolean hasNamesake = false;
Iterator<Map.Entry<String, String>> itr = mapOfSoNameNameClone.entrySet().iterator();
while (itr.hasNext()) {
    Map.Entry<String, String> entry = itr.next();
    if (value.equals(entry.getValue())) {
        //нашли тезку
        hasNamesake = true;
        break;
    }
}
if (!hasNamesake) {
    //нету тезки, добавляем
    mapOfSoNameNameSorted.put(key, value);
}

Но проще обойтись вообще без цикла с помощью метода containsValue:

Map<String, String> mapOfSoNameNameClone = new HashMap<>();
mapOfSoNameNameClone.putAll(mapOfSoNameName);
mapOfSoNameNameClone.remove(key, value);
if (!mapOfSoNameNameClone.containsValue(value)) {
    mapOfSoNameNameSorted.put(key, value);
}

Еще лучше будет не клонировать мап на каждом шагу, а проверять дубликаты с помощью множеств.

//имена
Set<String> names = new HashSet<>();
//дубликаты
Set<String> duplicatedNames = new HashSet<>();
for (String name : mapOfSoNameName.values()) {
    if (names.contains(name)) {
        duplicatedNames.add(name);
    }
    names.add(name);
}

for (Map.Entry<String, String> pairString : mapOfSoNameName.entrySet()) {
    String key = pairString.getKey();
    String value = pairString.getValue();
    if (!duplicatedNames.contains(value)) {
        mapOfSoNameNameSorted.put(key, value);
    }
    System.out.println(i + " Изначальный список -  " + mapOfSoNameName);        //Для проверки
    System.out.println(i + " Отсортированный   -   " + mapOfSoNameNameSorted);  // Для проверки
    i++; // для проверки
}

Можно написать короче, хоть и не так эффективно, через Stream API:

Collection<String> names = mapOfSoNameName.values();
Set<String> duplicatedNames = names.stream().filter(n -> Collections.frequency(names, n) > 1).collect(Collectors.toSet());
Map<String, String> mapOfSoNameNameSorted = mapOfSoNameName
    .entrySet()
    .stream()
    .filter(a -> !duplicatedNames.contains(a.getValue()))
    .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
System.out.println(i + " Отсортированный   -   " + mapOfSoNameNameSorted);

Или еще короче:

Map<String, String> mapOfSoNameNameSorted = mapOfSoNameName.entrySet()
    .stream()
    .filter(a -> Collections.frequency(mapOfSoNameName.values(), a.getValue()) == 1)
    .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));

P.S. Если это учебная задача, то все ОК. Но если практическая, то так хранить имена-фамилии очень странно, т.к. Map не поддерживает однофамильцев.
P.P.S. HashMap не обеспечивает что элементы будут идти по порядку. Если нужно сортировать фамилии по алфавиту, используйте TreeMap, если нужно хранить в порядке вставки, то LinkedHashMap

→ Ссылка