Генерация возможных комбинаций - PullRequest
3 голосов
/ 26 сентября 2011

У меня есть несколько хеш-карт, мне нужно сгенерировать комбинации:

A: [x->1, y->2,...]
B: [x->1, a->4,...]
C: [x->1, b->5,...]
...

некоторые возможные комбинации:

A+B; A; A+C; A+B+C...

Для каждой комбинации мне нужно создать объединенную хэш-карту и выполнитьработа пар ключ-значение с одним и тем же ключом в обеих хеш-картах.

Все, что я мог придумать, это использовать двоичный счетчик и сопоставить цифры с соответствующей хеш-картой:

001 -> A
101 -> A,C
...

Хотя это решение работает, операции по модулю отнимают много времени, когда у меня более 100 хэш-карт.Я новичок в Scala, но я считаю, что должен быть лучший способ добиться этого?

Ответы [ 3 ]

14 голосов
/ 27 сентября 2011

Последовательности Scala имеют функцию combinations.Это дает вам комбинации для выбора определенного числа из общего числа.От вашего вопроса похоже, что вы хотите выбрать все разные числа, поэтому ваш теоретический код может выглядеть примерно так:

val elements = List('a, 'b, 'c, 'd)
(1 to elements.size).flatMap(elements.combinations).toList

/* List[List[Symbol]] = List(List('a), List('b), List('c), List('d), List('a, 'b), 
   List('a, 'c), List('a, 'd), List('b, 'c), List('b, 'd), List('c, 'd), 
   List('a, 'b, 'c), List('a, 'b, 'd), List('a, 'c, 'd), List('b, 'c, 'd), 
   List('a, 'b, 'c, 'd)) */

Но, как указывалось, всех комбинаций будет слишком много.При 100 элементах выбор 2 из 100 даст вам 4950 комбинаций, 3 - 161700, 4 - 3921225, а 5 - ошибку переполнения.Так что, если вы просто оставите аргумент для combinations равным 2 или 3, все будет в порядке.

12 голосов
/ 26 сентября 2011

Хорошо, подумайте, сколько комбинаций у ваших карт: предположим, у вас есть N карт.

(the maps individually) + (pairs of maps) + (triples of maps) + ... + (all the maps)

Что, конечно,

(N choose 1) + (N choose 2) + ... + (N choose N-1)

Где определено N choose Mas:

N! / (M! * (N-M)!)

Для N=100 и M=50, N choose M - это более 100 000 000 000 000 000 000 000 000 000, поэтому «отнимающие много времени» действительно не решают проблему!

О, и это предполагает, что порядок не имеет значения - то есть, что A + B равно B + A.Если это предположение неверно, вы сталкиваетесь со значительно большим числом перестановок, чем имеется в видимой вселенной частиц

Почему scala может помочь с этой проблемой: ее структура параллельных коллекций!

0 голосов
/ 27 сентября 2011

В продолжение вашей идеи использовать целые числа для представления битовых наборов. Вы используете фактический оператор по модулю? Вы также можете использовать битовые маски, чтобы проверить, есть ли какое-то число в битах. (Обратите внимание, что на JVM они оба являются одной инструкцией, так что кто знает, что там происходит?)

Другое потенциальное значительное улучшение заключается в том, что, поскольку ваша работа с диапазоном карт является ассоциативной, вы можете сохранять вычисления, повторно используя предыдущие. Например, если вы объединяете A,B,C, но уже скомбинировали, скажем, A,C в AC, например, вы можете просто объединить B с AC.

Следующий код реализует обе идеи:

type MapT = Map[String,Int] // for conciseness later

@scala.annotation.tailrec
def pow2(i : Int, acc : Int = 1) : Int = {
  // for reasonably sized ints...
  if(i <= 0) acc else pow2(i - 1, 2 * acc)
}

// initial set of maps
val maps = List(
  Map("x" -> 1, "y" -> 2),
  Map("x" -> 1, "a" -> 4),
  Map("x" -> 1, "b" -> 5)
)
val num = maps.size

// any 'op' that's commutative will do
def combine(m1 : MapT, m2 : MapT)(op : (Int,Int)=>Int) : MapT = 
  ((m1.keySet intersect m2.keySet).map(k => (k -> op(m1(k), m2(k))))).toMap

val numCombs = pow2(num)

// precomputes all required powers of two
val masks : Array[Int] = (0 until num).map(pow2(_)).toArray

// this array will be filled, à la Dynamic Algorithm
val results : Array[MapT] = Array.fill(numCombs)(Map.empty)
// fill in the results for "combinations" of one map
for((m,i) <- maps.zipWithIndex) { results(masks(i)) = m }

val zeroUntilNum = (0 until num).toList
for(n <- 2 to num; (x :: xs) <- zeroUntilNum.combinations(n)) {
  // The trick here is that we already know the result of combining the maps
  // indexed by xs, we just need to compute the corresponding bitmask and get
  // the result from the array later.
  val known = xs.foldLeft(0)((a,i) => a | masks(i))
  val xm = masks(x)
  results(known | xm) = combine(results(known), results(xm))(_ + _)
}

Если вы напечатаете полученный массив, вы получите:

0  ->  Map()
1  ->  Map(x -> 1, y -> 2)
2  ->  Map(x -> 1, a -> 4)
3  ->  Map(x -> 2)
4  ->  Map(x -> 1, b -> 5)
5  ->  Map(x -> 2)
6  ->  Map(x -> 2)
7  ->  Map(x -> 3)

Конечно, как и все остальные, он со временем взорвется, когда количество входных карт увеличится.

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