Эффективно выбирая комбинации целых чисел - PullRequest
8 голосов
/ 12 февраля 2020

Допустим, у нас есть матрица 5x5, заполненная нулями.

myMatrix <- matrix(rep(0, 25), ncol = 5)

Теперь давайте выберем триплет целых чисел от 1 до 5.

triplet <- c(1,2,3)

Для всех комбинаций В этот триплет мы теперь добавляем 1 в матрицу с помощью этой функции:

addCombinationsToMatrix <- function(.matrix, .triplet){
    indexesToChange <- as.matrix(expand.grid(.triplet, .triplet))
    .matrix[indexesToChange] <- .matrix[indexesToChange] + 1
    .matrix
}

Используя функцию, мы go из

myMatrix

     [,1] [,2] [,3] [,4] [,5]
[1,]    0    0    0    0    0
[2,]    0    0    0    0    0
[3,]    0    0    0    0    0
[4,]    0    0    0    0    0
[5,]    0    0    0    0    0

до

myMatrix <- addCombinationsToMatrix(myMatrix, triplet)
myMatrix

     [,1] [,2] [,3] [,4] [,5]
[1,]    1    1    1    0    0
[2,]    1    1    1    0    0
[3,]    1    1    1    0    0
[4,]    0    0    0    0    0
[5,]    0    0    0    0    0

Если мы выбираем другой триплет, мы переходим к

nextTriplet <- 2:4
myMatrix <- addCombinationsToMatrix(myMatrix, nextTriplet)
myMatrix

     [,1] [,2] [,3] [,4] [,5]
[1,]    1    1    1    0    0
[2,]    1    2    2    1    0
[3,]    1    2    2    1    0
[4,]    0    1    1    1    0
[5,]    0    0    0    0    0

Таким образом, комбинации строк и столбцов показывают, как часто два целых числа были показаны вместе в триплете: 3 и 4 были показаны вместе один раз, 2 и 3 были показаны вместе дважды.

Вопрос : Как можно выбрать триплеты, чтобы каждая комбинация (1-2, 1-3, 1-4 ...) была выбрана как минимум один раз и количество триплетов минимизируется.

Я ищу алгоритм, который выбирает следующий триплет.

В идеале его можно расширить до

  • произвольно большие матрицы (10x10, 100x100 ...)
  • произвольно большие векторы (четверки, квинтуплеты, n-туплеты)
  • произвольное количество раз, когда комбинация должна быть выбрана не менее

Пример:

myMatrix
myMatrix <- addCombinationsToMatrix(myMatrix, 1:3)
myMatrix
myMatrix <- addCombinationsToMatrix(myMatrix, 3:5)
myMatrix
myMatrix <- addCombinationsToMatrix(myMatrix, c(1,4,5))
myMatrix
myMatrix <- addCombinationsToMatrix(myMatrix, c(2,4,5))
myMatrix

РЕДАКТИРОВАТЬ : просто будьте уверены: ответом не должен быть R код. Это может быть и другой язык или даже псевдокод.

РЕДАКТИРОВАТЬ 2 : Мне пришло в голову, что существуют разные способы измерения эффективности. Я действительно имел в виду, что алгоритм должен принимать как можно меньше итераций. Быстрый алгоритм тоже очень крутой, но здесь не главная цель.

Ответы [ 3 ]

6 голосов
/ 14 февраля 2020

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

Это называется дизайн покрытия и является вариантом классической c задачи покрытия набора . Как вы можете прочитать в превосходном посте Математический стек на topi c, люди используют нотацию C (v, k, t), указывающую минимальное количество подмножеств k-элементов, которое вам нужно нарисовать (k = 3 в вашем случае) из набора v-элементов (v = 5 в вашем случае), так что каждое подмножество t-элементов во всем наборе (t = 2 в вашем случае) содержится в одном из выбранных вами подмножеств. Люди оценивали эту функцию для множества различных (v, k, t) кортежей; см., например, https://ljcr.dmgordon.org/cover/table.html. Из этой таблицы мы можем прочитать, что C (5, 3, 2) = 4 со следующим возможным вариантом:

  1  2  3
  1  4  5
  2  3  4
  2  3  5

Во-первых, эта проблема является NP-трудной, поэтому все известные точные алгоритмы будут экспоненциально масштабироваться на входах v, k и t. Поэтому, несмотря на то, что вы можете решать мелкие экземпляры точно с помощью перечисления или более точного точного метода (например, целочисленного программирования), вам, вероятно, придется прибегнуть к методам heuristi c, поскольку размер проблемы становится очень большим.

Одной из возможностей в этом направлении является лексикографическое c покрытие, как предложено в https://arxiv.org/pdf/math/9502238.pdf (вы заметите, что многие решения на сайте, ссылки на которые приведены выше, перечисляют «покрытие lex» как метод построения) , По сути, вы перечисляете все возможные k-кортежи в лексикографическом порядке c:

123
124
125
134
135
145
234
235
245
345

Затем вы жадно добавляете k-кортеж, который покрывает наиболее ранее обнаруженные t-кортежи, разрывая связи с помощью лексикографа c ordering.

Вот как работает алгоритм в нашем случае:

  1. В начале каждый 3-кортеж охватывает 3 разных 2-кортежа, поэтому мы добавляем 123 поскольку он является лексикографически ранним не распространяется. Ряд из 3 кортежей охватывает еще 3 кортежа, например, 145 и 245. Мы выбираем 145, так как это лексикографически первый, охватывающий 14, 45 и 15.

  2. Теперь у нас есть 4 оставшихся непокрытых 2-кортежа - 24, 25, 34 и 35. Нет 3-х обложек 3 из них, но несколько обложек 2, например 234 и 345. Мы выбираем 234 как лексикографически раннее.

  3. У нас есть два оставшихся непокрытых 2-кортежа - 25 и 35. Мы выбираем 235 в качестве единственного 3-го кортежа, который охватывает оба.

В итоге мы получаем точное решение, показанное выше. Важно, что это всего лишь метод heuristi c - он не дает никакой гарантии, что 4 - это наименьшее количество из 3-х кортежей, необходимое для покрытия всех пар в наборе из 5 элементов. В этом случае нижняя граница Шёнхейма (ссылка приведена в связанной статье выше) убеждает нас в том, что на самом деле C (5, 3, 2) не может быть меньше 4. Мы заключаем, что решение из лексикографии c покрытие на самом деле оптимально.

Вам понадобится настройка, чтобы покрыть каждый t-корте определенное количество раз r. Один очевидный из них - просто повторить каждый кортеж, который будет покрыт «r» раз, и затем запустить lex cover как обычно (например, на первом шаге выше каждый 3-кортеж будет охватывать 9 2-кортежа с r = 3). Конечно, это остается проблемой c для вашей общей проблемы из-за использования покрытия lex.

2 голосов
/ 16 февраля 2020

Так как этот вопрос требует алгоритмических c подходов к покрытию проектов, я предоставлю один, который дает точные ответы (или наилучший возможный дизайн), используя целочисленное программирование на R. Для каждого рассматриваемого вами k-кортежа ( k = 3 для вас, так как вы выбираете триплеты), определите переменную решения, которая принимает значение 1, если вы включаете его в свой дизайн, и 0, если нет. Таким образом, в вашем случае вы должны определить x_123, чтобы указать, выбран ли tuple (1,2,3), x_345 для (3,4,5) и т. Д.

Задача модели оптимизации состоит в том, чтобы чтобы свести к минимуму количество выбранных кортежей, то есть сумму всех ваших переменных решения. Однако для каждого t-кортежа (t = 2 в вашем случае) вам необходимо включить переменную решения, которая содержит этот t-кортеж. Это дает ограничение для каждого t-кортежа. Например, у нас x_123+x_124+x_125 >= 1 будет ограничение, которое требует, чтобы пара 12 находилась в некотором выбранном кортеже.

Это дает следующую модель оптимизации:

min  x_123+x_124+...+x_345
s.t. x_123+x_124+x_125 >= 1  # constraint for 12
     x_123+x_134+x_135 >= 1  # constraint for 13
     ...
     x_145+x_245+x_345 >= 1  # constraint for 45
     x_ijk binary for all i, j, k

Вы могли бы расширить это, чтобы потребовать r повторений каждого t-кортежа, изменив правую часть каждого неравенства на «r» и требуя, чтобы все переменные были целыми числами, а не двоичными.

Это легко решить с пакетом типа lpSolve в R:

library(lpSolve)
C <- function(v, k, tt, r) {
  k.tuples <- combn(v, k)
  t.tuples <- combn(v, tt)
  mod <- lp(direction="min",
            objective.in=rep(1, ncol(k.tuples)),
            const.mat=t(apply(t.tuples, 2, function(x) {
              apply(k.tuples, 2, function(y) as.numeric(sum(x %in% y) == tt))
            })),
            const.dir=rep(">=", ncol(t.tuples)),
            const.rhs=rep(r, ncol(t.tuples)),
            all.int=TRUE)
  k.tuples[,rep(seq_len(ncol(k.tuples)), round(mod$solution))]
}
C(5, 3, 2, 1)
#      [,1] [,2] [,3] [,4]
# [1,]    1    1    1    3
# [2,]    2    2    2    4
# [3,]    3    4    5    5
C(5, 3, 2, 3)
#      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
# [1,]    1    1    1    1    1    1    2    2    2     3
# [2,]    2    2    2    3    3    4    3    3    4     4
# [3,]    3    4    5    4    5    5    4    5    5     5

Хотя это точно решит вашу проблему, она не будет хорошо масштабироваться до больших размеров проблем. Это потому, что проблема NP-сложная - ни один известный точный алгоритм не будет хорошо масштабироваться. Если вам нужно решить большие проблемы, то эвристика, рекомендованная в других ответах, - ваш лучший выбор Или вы можете решить с помощью целочисленного программирования (как мы делаем здесь) и установить время ожидания; тогда вы будете работать с наилучшим решением, найденным по вашему тайм-ауту, которое представляет собой heuristi c решение проблемы в целом.

2 голосов
/ 13 февраля 2020

Вот опция, использующая data.table для отслеживания количества матриц и RcppAlgos для генерации комбинаций:

library(RcppAlgos)
library(data.table)

M <- 100 #5 #10 #100
sz <- 5 #3 #4 5 
minpick <- 3 #1 #2
d <- integer(M)

system.time({
    universe <- as.data.table(comboGeneral(M, 2L, nThreads=4L))[, count := 0L]
    ntuples <- 0
    while (universe[, any(count < minpick)]) {
        v <- universe[order(count), head(unique(c(V1[1L:2L], V2[1L:2L])), sz)]
        universe[as.data.table(comboGeneral(v, 2L, nThreads=4L)), on=.NATURAL, count := count + 1L]
        ntuples = ntuples + 1L
    }
    ntuples
})
#   user  system elapsed 
#  26.82    9.81   28.75 

m <- matrix(0L, nrow=M, ncol=M)
m[as.matrix(universe[, V1:V2])] <- universe$count
m + t(m) + diag(d)

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

...