Приближается Рождество: пора определиться, кто кому сделает подарок. Ищу такой алгоритм.

Взяв, например, список (1 to 10), создайте случайные пары, гарантируя, что:

  • каждый элемент связан с другим элементом;
  • ни один из элементов не связан сам с собой;
  • каждый элемент связан один раз и только один раз.

Очевидно, что простого перемешивания недостаточно:

Random.shuffle(1 to 10)
  .toSeq
  .zipWithIndex

Например:

1 -> 2
2 -> 4
3 -> 1
4 -> 3

Но не (1 делает себе подарок):

1 -> 1
2 -> 3
3 -> 4
4 -> 2

Я думал об ограничениях в HList, но:

  • Я не смог выразить это
  • Это может быть немного перебором (даже если это смешно)
  • Могут существовать алгоритмы, гарантирующие, что "по конструкции"
1
Alban Dericbourg 26 Ноя 2016 в 17:24

4 ответа

Лучший ответ

Собственно, этот простой алгоритм должен помочь.

  1. Перемешать исходный список
  2. Свяжите индекс i с индексом i+1 (по модулю размера списка)

Это должно быть достаточно случайным образом для необходимости.

val people = Random.shuffle(Seq("a", "b", "c", "d"))

val associationIndices = (0 to people.length-1)
  .map(left => (left, (left + 1) % people.length))
  .foreach(assoc => println(s"${people(assoc._1)} -> ${people(assoc._2)}"))

Результат:

c -> d
d -> a
a -> b
b -> c

Он работает до тех пор, пока в списке не менее 2 элементов.

0
Alban Dericbourg 26 Ноя 2016 в 15:19

Надежное решение: случайное присвоение имен именам; выберите случайное простое число N (кроме количества людей, если это число само является простым) и примените ротацию к списку индексов N позиций (по модулю количества людей).

Код Java (любой код Java - это код Scala, верно?)

ArrayList<String> names=
    new ArrayList<>(Arrays.asList("Ann","Bob","Ed","Kim","Sue","Tom"));
SecureRandom rng=new SecureRandom(); // better seed it
String rndNames[]=new String[names.size()];
for(int i=0; names.size()>0; i++) {
  int removeAt=rng.nextInt(names.size());
  rndNames[i]=names.remove(removeAt);
}
int offset=1; // replace this with a random choice of a 
              // number coprime with rndNames.length, followed by
              // offset = (randomPrime % rndNames.length)
              // 1 will do just fine for the offset, it is a valid value anyway
for(int i=0; i<rndNames.length; i++) {
   System.out.println(rndNames[i] +"->"+rndNames[(i+offset) % rndNames.length]);
}    

Результат:

Ann->Sue
Sue->Bob
Bob->Ed
Ed->Tom
Tom->Kim
Kim->Ann
2
Adrian Colomitchi 26 Ноя 2016 в 22:37

Просто пример макета: есть несколько сценариев, на которые стоит обратить внимание:

import scala.collection.mutable.ListBuffer
import scala.util.Random

val n = 5
val rnd = new Random()
val result = ListBuffer.fill(n)( (0, 0) )

Я уверен, что это можно оптимизировать.

while( result.exists(x => x._1 == 0 && x._2 == 0) == true){
  val idx = result.zipWithIndex
  val p = idx.find(x => x._1._1 == 0 && x._1._2 == 0)
  p match {
    case None => Unit// ???
    case Some(x) => {
      val r = rnd.nextInt(n)
      if (result.exists(r => r._2 == r && x._2 != r) == false)
        result(x._2) = (x._2 + 1, r + 1)
    }
  }
}


result.foreach(x => println(x._1 + " : " + x._2 ))
1
Pavel 26 Ноя 2016 в 14:51

Использование Set гарантирует отсутствие дубликатов, и, поскольку Set не имеет определенного порядка, итерация по нему будет казаться случайной.

val names = Set("Ann","Bob","Ed","Kim","Sue","Tom") // alphabetical

Затем создайте круговые ассоциации.

val nameDraw = (names.sliding(2) ++ Iterator(Set(names.last,names.head)))
                  .map(x => x.head -> x.last).toMap
//nameDraw = Map(Sue -> Ann, Ann -> Tom, Tom -> Bob, Bob -> Ed, Ed -> Kim, Kim -> Sue)
1
jwvh 26 Ноя 2016 в 20:03