Алгоритм генерации всех возможных (неупорядоченных) назначений одного набора другому - PullRequest
1 голос
/ 07 марта 2020

Дано:

  1. Набор цветных карандашей уникального цвета (размера x).
  2. Набор дочерних элементов.
  3. Все мелки должны быть назначены children.
  4. У ребенка может быть от нуля до x мелков.
  5. У каждого мелка должен быть ровно 1 ребенок. Карандаш не может быть назначен двум или более детям.

Как мне go найти все возможные комбинации назначений?

Например:

class Crayon {
    String color;

    public Crayon(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

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


class Child {

    String name;
    Set<Crayon> crayons;

    public Child(String name) {
        this.name = name;
        crayons = new HashSet<Crayon>();
    }

    public void addCrayon(Crayon crayon) {
        crayons.add(crayon);
    }

    public Set<Crayon> getCrayons() {
        return crayons;
    }

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

public class DistributeCrayons {

    public static void main(String[] args) {

        Set<Crayon> crayons = new HashSet<>();
        crayons.add(new Crayon("red"));
        crayons.add(new Crayon("blue"));
        crayons.add(new Crayon("green"));
        crayons.add(new Crayon("orange"));
        crayons.add(new Crayon("brown"));
        crayons.add(new Crayon("yellow"));
        crayons.add(new Crayon("purple"));

        Child bob = new Child("bob");
        Child amy = new Child("amy");
        Child tom = new Child("tom");

        for(??) {
            for(??) {
                ??
                    System.out.println(bob +" "+ amy +" "+ tom);
                ??
            }
        }
    }
}

Это должно вывести все возможные комбинации назначений, например:

Child [имя = боб, мелки = [зеленый, синий]] Child [имя = Эми, мелки = [коричневый, красный]] Child [имя = Том, карандаши = [желтый, фиолетовый, оранжевый]]

Ребенок [имя = Боб, карандаши = []] Ребенок [имя = Эми, карандаши = [коричневый, красный, синий]] Ребенок [имя = Том , мелки = [желтый, фиолетовый, оранжевый, зеленый]]

ребенок [имя = боб, мелки = [красный]] ребенок [имя = ами, мелки = [коричневый, зеленый, фиолетовый, оранжевый]] ребенок [имя = том, мелки = [синий, желтый]]

и c.

ОБНОВЛЕНИЕ

Спасибо всем за ценные отзывы и גלעד ברקן за предоставление рабочего js решения. (извините, я не могу ответить или принять ответ из-за подсчета репутации).

Теперь я могу обернуть голову, вот моя версия решения:

I преобразовал мелки и детей в списки вместо наборов. Для списка из 7 мелков (красного, синего, зеленого, оранжевого, коричневого, желтого, фиолетового) я стремлюсь создать все задания для трех детей: боб (id = "b"), amy (id = "a ") и том (id =" t ") в форме 7-символьных слов. Например: слово типа" tbbbtat "означает, что Том получает красный, коричневый и фиолетовый карандаши, Боб получает синий, зеленый и оранжевые карандаши, а Эми получает желтый.

public class DistributeCrayons {

    public static void main(String[] args) {

        List<Crayon> crayons = new ArrayList<>();
        crayons.add(new Crayon("red"));
        crayons.add(new Crayon("blue"));
        crayons.add(new Crayon("green"));
        crayons.add(new Crayon("orange"));
        crayons.add(new Crayon("brown"));
        crayons.add(new Crayon("yellow"));
        crayons.add(new Crayon("purple"));

        List<Child> children = new ArrayList<>();
        children.add(new Child("b"));
        children.add(new Child("a"));
        children.add(new Child("t"));

        List<String> assignments = null;
        for(int i = 0; i < crayons.size(); i++)
            assignments = addCrayonCombos(assignments, children);

        System.out.println(assignments);

    }

    static List<String> addCrayonCombos(List<String> assignments, List<Child> children) {
        if(assignments == null) {
            assignments = new ArrayList<String>();
            for(Child c: children)
                assignments.add(c.getId());
            return assignments;
        } else {
            List<String> updatedAssignments = new ArrayList<String>();
            for(String assignment: assignments) {
                for(Child c: children)
                    //append next permutations for a new crayon to existing "words"
                    updatedAssignments.add(assignment+c.getId());
            }
            return updatedAssignments;
        }
    }
}

Это генерирует ожидаемый список слов назначения (точнее 2187 слов), так как у нас есть 3 возможности для каждого из 7 карандашей (т.е. 3 ^ 7 = 2187).

Ответы [ 3 ]

1 голос
/ 07 марта 2020

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

Я собираюсь даст вам постановку задачи и некоторый псевдокод, и, возможно, вы сможете реализовать его из этого.

У вас есть два набора:

  1. Набор карандашей. Мы назовем это C.
  2. Люди установили. Мы назовем это P.

В вашем примере

C = {"red", "orange", "yellow", "green", "blue", "purple", "brown"}
P = {"bob", "amy", "tom"}

Итак, вы захотите реализовать функцию DistributeCrayons(Set<People> P, Set<Crayons> C), которая находит все способы распространения мелки в C среди людей в P, где мы можем дать ноль или более мелков каждому человеку в P для любого данного распределения. И два распределения A и B различны, если хотя бы у одного человека в P есть карандаш в A, которого у него не было в B

Я дам рекурсивный реализация, так как об этом будет проще всего думать:

DistributeCrayons(Set<People> P, Set<Crayons> C):
    for subset C' of C:
        for person P' in P:
            assign C' to P'

            if (P - P') == {}:
                return assignments
            else:
                DistributeCrayons(C - C', P - P')

где:

for subset C' of C Перебирает все возможные подмножества C и назначает C' одному из них на заданная итерация.

for person P' in P: Просто проходит через P - получение одного человека на каждую итерацию.

assign C' to P' Назначает набор мелков C' человеку P' (имеется в виду, что человек P' получает все мелки в наборе C'.)

{} Пустой набор. Итак, оператор if: if (P - P') == {} просто проверяет, является ли P' единственным человеком из набора людей, P которого передали DistributeCrayons(...).

Примечание: я не на самом деле имел дело с заданиями и как их кодировать. Это будет сложнее, чем то, что я показал в псевдокоде, потому что при каждом вызове придется сбрасывать назначения на DistributeCrayons(...)

Я буду об этом думать чтобы посмотреть, смогу ли я придумать настоящий фрагмент кода, чтобы дать вам.

ОБНОВЛЕНИЕ: Похоже, вы решили это самостоятельно. Nice!

0 голосов
/ 07 марта 2020

Дети немного уловка. Если мы перебираем мелки и по очереди присваиваем их другому блоку, мы можем получить все способы назначить их. Затем мы можем пометить эти поля, как мы sh (или отдать их детям по порядку).

JavaScript код:

function f(cr, ch, x, comb=[], i=0){
  if (ch.length * x < cr.length)
    return [];
    
  if (i == cr.length)
    return [comb];
    
  let result = [];
  
  for (let j=0; j<ch.length; j++){
    let _comb = ch.map((_, idx) =>
      comb[idx] ? comb[idx].slice() : []);
    
    if (_comb[j].length == x)
      continue;

    _comb[j].push(cr[i]);
    
    result = result.concat((f(cr, ch, x, _comb, i + 1)));
  }

  return result;
}

var crayons = ["red", "blue", "green", "orange", "brown", "yellow"];

var children = ["bob", "amy", "tom"];

var str = "";

for (let comb of f(crayons, children, 3))
  str += JSON.stringify(comb) + "\n";

console.log(str);
0 голосов
/ 07 марта 2020

Интересная проблема. Решение, которое приходит на ум, объединяет как перестановки, так и разбиения. Сначала возьмем каждую перестановку из списка мелков. Существуют хорошо известные алгоритмы перестановок, и при поиске их должно быть кворум.

Для каждой перестановки списка разбейте список на три части и назначьте трем дочерним элементам мелки из каждого раздела. , Это должно генерировать все возможные способы назначения мелков детям.

Чтобы разделить список из семи мелков на три части, вы разделите его на две части. Первый будет где угодно от элемента 0 в списке до элемента 6 включительно. Второе деление будет находиться в любом месте с той же позиции, что и первое деление, вплоть до конца списка (после элемента 6), также включительно. Лучше всего думать, что эти разрывы происходят как между двумя элементами списка.

Разделение может быть выполнено с помощью вложенного l oop.

Имеется 5040 перестановок, и я полагаю, что в каждой перестановке 36 разделов, так что, если я не ошибаюсь, всего должно быть 181 440 способов распределения карандашей. Я не думаю будут какие-либо дубликаты с этим подходом, но я не доказал это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...