Алгоритм группировки объектов

у меня есть следующие классы:

class Sport {
    private String sportsName;
    private List<People> peopleWhoPlayThisSport;
    //...
}
class People {
    private String name;
    private long uniqueId;
   // ...
}

мой вход-это список спортивных объектов, для простоты рассмотрим следующие примеры:

sport1 - Football,   <Sam, Dylan>
sport2 - Basketball, <Tyler, John>
sport3 - Baseball,   <Carter, Dylan>
sport4 - Hockey,     <Kane, Michael>
sport5 - Soccer,     <Carter, Frank>

Я должен создать List<List<>>, так что внутренний список-это все виды спорта, у которых есть хотя бы 1 общий игрок (здесь применяется транзитивное свойство). В приведенном выше примере вывод должен быть,

<<sport1,sport3,sport5> , <sport2> , <sport4>>

любые предложения по решению этого и / или псевдо-кода?

4 ответов


звучит как проблема с графом для меня. Я бы сделал следующее:

  1. создайте граф (неориентированный), где люди являются узлами, пока без ребер
  2. Я бы пошел через спорт, и для каждого вида спорта я бы сделал край между людьми, если они играют в один и тот же вид спорта (например, при обработке спорта 1 он создаст край между Сэмом и Диланом, при обработке спорта 3, он будет делать край между Диланом и Картером)
  3. в качестве последнего шага я бы взял компонентов из конечного графика (в вашем примере Сэм-Дилан-Картер-Фрэнк, Кейн-Майкл, Тайлер-Джон) и "применить к ним спорт" - это означает, что для каждого мальчика/девочки в компоненте я бы добавил Все виды спорта, которые он/она делает, во "внутренний" список (я бы предпочел установить, чтобы каждый вид спорта был там один раз).

таким образом, график будет расти таким образом:

  1. Переработка Футбол: Сэм-Дилан
  2. Обработка Баскетбола: Сэм-Дилан, Тайлер-Джон
  3. Обработка Бейсбола: Сэм-Дилан -Картер Тайлер-Джон
  4. Обработка Хоккея: Сэм-Дилан-Картер, Тайлер-Джон,Кейн-Майкл
  5. Обработка Футбол: Сэм-Дилан-Картер -Фрэнк Тайлер-Джон, Кейн-Майкл

и "применение спорт" :

  1. Сэм (Футбол), Дилан (Футбол, Бейсбол), Картер (Бейсбол, Футбол), Фрэнк (Футбол) = > (Футбол, Бейсбол, Футбол)
  2. Тайлер (Баскетбол), Джон (Баскетбол) => (Баскетбол)
  3. Кейн (Хоккей), Майкл (Хоккей) = > (Хоккей)

==> (Футбол, Бейсбол, Футбол), (Баскетбол), (Хоккей)

Edit: При желании можно оптимизировать алгоритм, чтобы для каждого компонента вы запомнили, какие виды спорта к нему привязаны. Другими словами, при создании edge вы добавите спорт в коллекцию спорта компонента. Затем " применить спорт" шаг больше не будет нужен. Одно простое правило: когда два компонента соединяются, вы объединяете спортивные коллекции перед добавлением нового вида спорта. Алгоритм тогда будет идти:

  1. Переработка Футбол: Сэм-Дилан(Футбол)
  2. Обработка Баскетбола: Сэм-Дилан (Футбол), Тайлер-Джон(Баскетбол)
  3. Обработка Бейсбола: Сэм-Дилан -Картер(футбол, Бейсбол), Тайлер-Джон (Баскетбол)
  4. Хоккей: Сэм-Дилан-Картер(Футбол,Бейсбол), Тайлер-Джон (Баскетбол), Кейн-Майкл (Хоккей)
  5. Обработка Футбол: Сэм-Дилан-Картер -Фрэнк(Футбол,Бейсбол,футбол), Тайлер-Джон(Баскетбол), Кейн Майкл(Хоккей)

обратите внимание, что использование графика не является необходимым. Вы все еще можете уйти с простыми коллекциями, но график казался самым чистым способом и алгоритм-оптимальный способ сделать это. Это также позволяет дальнейшую расширяемость, потому что он моделирует данные естественным образом - например, вы можете узнать, почему Сэм находится в группе с Картером (потому что их общий друг Дилан играет в другой вид спорта с ними обоими).


Create HashMap<People, List<Sport>> pList
for each Sport s in sportList
   for each People p in peopleWhoPlayThisSport 
      if p present in pList,
          pList.get(p).add(s)      
      else,
          pList.put(p,s)

Iterate on pList 
    If list size of Sport Objects for a People > 1
        Add to Set of Sport Objects which have at least 1 common 


Create another Set from first sportList
Do a Set minus to get Sports without any common player        

Я сделал работу с аналогичным подходом, как заявил @Somabrata.

Map<People, Set<Sport>> mapping = new HashMap<>();
for (Sport s : sports) {
    for (People p : s.getPeopleWhoPlayThisSport()) {
        Set<Sport> sportByPeople = mapping.get(p);
        if (sportByPeople == null) {
            sportByPeople = new HashSet<>();
            mapping.put(p, sportByPeople);
        }
        sportByPeople.add(s);
    }
}

List<Set<Sport>> groupings = new ArrayList<>();
outer: for (Set<Sport> sportSet : mapping.values()) {
    for (Set<Sport> group : groupings) {
        for (Sport s : sportSet) {
            if (group.contains(s)) {
                group.addAll(sportSet);
                continue outer;
            }
        }
    }
    groupings.add(sportSet);
}
System.out.println(groupings);

я реализовал код для вас. Если вы видите метод "группа", вы бы поняли. Так что в псевдокоде не будет необходимости. Выход будет:

[[Футбол, Бейсбол, Футбол], [Баскетбол], [Хоккей]]

Я также добавил новую запись :

sport6 - Гандбол,

чтобы проверить алгоритм для более чем одного общего списка. Выход будет:

[[футбол, Бейсбол, Футбол], [Баскетбол, Гандбол], [Хоккей]

вот код:

public class GroupObjects {
int uniqueIdCounter = 1;

People p1 = new People("Sam",uniqueIdCounter++);
People p2 = new People("Dylan",uniqueIdCounter++);
People p3 = new People("Tyler",uniqueIdCounter++);
People p4 = new People("John",uniqueIdCounter++);
People p5 = new People("Carter",uniqueIdCounter++);
People p6 = new People("Kane",uniqueIdCounter++);
People p7 = new People("Michael",uniqueIdCounter++);
People p8 = new People("Frank",uniqueIdCounter++);
People p9 = new People("Reza",uniqueIdCounter++);


Sport s1 = new Sport("Football", Arrays.asList(p1,p2));
Sport s2 = new Sport("Basketball", Arrays.asList(p3,p4));
Sport s3 = new Sport("Baseball", Arrays.asList(p5,p2));
Sport s4 = new Sport("Hockey", Arrays.asList(p6,p7));
Sport s5 = new Sport("Soccer", Arrays.asList(p5,p8));
Sport s6 = new Sport("Handball", Arrays.asList(p3,p9));

List<Sport> sports = Arrays.asList(s1,s2,s3,s4,s5,s6);

public List<List<Sport>> group(List<Sport> sports){
    List<List<Sport>> answerList = new ArrayList<>();
    while (!sports.isEmpty()) {
        List<Sport> common = new ArrayList<>();
        List<Sport> toBeRemoved = new ArrayList<>();
        List<People> people = new ArrayList<>();
        people.addAll(sports.get(0).getPeopleWhoPlayThisSport());
        common.add(sports.get(0));
        toBeRemoved.add(sports.get(0));
        for (int i = 1; i < sports.size(); i++) {
            for (People p : sports.get(i).getPeopleWhoPlayThisSport()) {
                if (people.contains(p)) {
                    people.addAll(sports.get(i).getPeopleWhoPlayThisSport());
                    common.add(sports.get(i));
                    toBeRemoved.add(sports.get(i));
                    break;
                }
            }
        }
        sports = sports.stream().filter(sp->!toBeRemoved.contains(sp)).collect(Collectors.toList());
        answerList.add(common);
    }
    return answerList;
}
public static void main(String[] args) {
    GroupObjects groupObjects = new GroupObjects();
    List<List<Sport>> answer = groupObjects.group(groupObjects.sports);
    System.out.println(answer);
}

class Sport {
...

@Override
public String toString() {
    return sportsName;
}

также обратите внимание, что я использовал API потоков Java-8 в своем коде. Поэтому, если вы не используете Java-8, измените эту строку.

Удачи!