Escolher com eficiência combinações de números inteiros

8

Digamos que temos uma matriz 5x5, preenchida com 0s.

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

Agora, vamos escolher um trio de números inteiros entre 1 e 5.

triplet <- c(1,2,3)

Para todas as combinações desse trio, agora adicionamos 1 na matriz, com esta função:

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

Usando a função, passamos de

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

para

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

Se escolhermos outro trigêmeo, passaremos para

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

Portanto, combinações de linhas e colunas representam a frequência com que dois números inteiros foram mostrados juntos em um trigêmeo: 3 e 4 foram mostrados juntos uma vez, 2 e 3 foram mostrados juntos duas vezes.

Pergunta : Como se pode escolher trigêmeos, de modo que todas as combinações (1-2, 1-3, 1-4 ...) foram escolhidas pelo menos uma vez e o número de trigêmeos é minimizado.

Estou procurando um algoritmo aqui que escolhe o próximo trigêmeo.

Idealmente, pode ser estendido para

  • matrizes arbitrariamente grandes (10x10, 100x100 ...)
  • vetores arbitrariamente grandes (quadrupletos, quintupletos, n-tupletos)
  • um número arbitrário de vezes que uma combinação deve ter sido escolhida pelo menos

Exemplo:

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

EDIT : Apenas para ter certeza: a resposta não precisa ser Rcódigo. Pode ser também algum outro idioma ou mesmo pseudo-código.

EDIÇÃO 2 : Ocorreu-me agora que existem diferentes maneiras de medir a eficiência. Na verdade, eu quis dizer que o algoritmo deve ter o mínimo de iterações possível. O algoritmo que é rápido também é muito legal, mas não é o objetivo principal aqui.

Georgery
fonte

Respostas:

6

Ótima pergunta! Isso ocorre no design da pesquisa, em que você deseja que algumas versões diferentes da pesquisa contenham apenas um subconjunto de perguntas, mas que todos os pares (ou t-tuplas) de perguntas tenham sido feitos pelo menos uma vez.

Isso é chamado de design de cobertura e é uma variante do problema clássico de cobertura de conjunto . Como você pode ler em uma excelente postagem do Mathematics Stack Exchange sobre o tópico, as pessoas usam a notação C (v, k, t) indicando o número mínimo de subconjuntos de elementos k que você precisa desenhar (k = 3 no seu caso) de um v conjunto de elementos (v = 5 no seu caso), de modo que todos os subconjuntos do elemento t em todo o conjunto (t = 2 no seu caso) estejam contidos em um dos subconjuntos selecionados. As pessoas avaliaram essa função para muitas tuplas diferentes (v, k, t); consulte, por exemplo, https://ljcr.dmgordon.org/cover/table.html . Podemos ler nessa tabela que C (5, 3, 2) = 4, com o seguinte como um design possível:

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

Em primeiro lugar, esse problema é difícil para NP, portanto, todos os algoritmos exatos conhecidos serão escalonados exponencialmente nas entradas v, ke et. Portanto, embora você possa resolver pequenas instâncias exatamente por enumeração ou algum método exato mais inteligente (por exemplo, programação inteira), provavelmente precisará recorrer a métodos heurísticos, pois o tamanho do problema fica muito grande.

Uma possibilidade nessa direção é a cobertura lexicográfica, conforme proposto em https://arxiv.org/pdf/math/9502238.pdf (você observará que muitas das soluções no site vinculado acima listam "cobertura lex" como o método de construção). Basicamente, você lista todas as tuplas k possíveis em ordem lexicográfica:

123
124
125
134
135
145
234
235
245
345

Em seguida, você adiciona avidamente a tupla k que cobre as tuplas t mais descobertas anteriormente, rompendo laços usando a ordem lexicográfica.

Veja como o algoritmo funciona no nosso caso:

  1. No início, todas as três tuplas cobrem três diferentes, portanto adicionamos 123uma vez que é lexicograficamente mais antiga.

  2. Depois de fazer isso, as 2-tuplas de 12, 13e 23foram cobertas, enquanto todas as 2-tuplas restantes não são cobertas. Um número de 3 tuplas cobre mais 3 tuplas, por exemplo, 145e 245. Nós escolhemos 145, pois é lexicographically primeiro, cobrindo 14, 45e 15.

  3. Agora temos 4 restantes descobertos 2-tuplas - 24, 25, 34, e 35. Nenhuma tupla de três abrange três delas, mas várias cobrem 2, por exemplo, 234e 345. Selecionamos 234como o primeiro lexicograficamente.

  4. Temos duas restantes tuplas descobertas - 25e 35. Selecionamos 235como a única tripla que abrange ambos.

Terminamos com a solução exata mostrada acima. É importante ressaltar que esse é apenas um método heurístico - não garante que 4 seja o menor número de 3 tuplas necessárias para cobrir todos os pares em um conjunto com 5 elementos. Nesse caso, um limite inferior de Schönheim (uma referência é fornecida no artigo vinculado acima) nos convence de que, de fato, C (5, 3, 2) não pode ser menor que 4. Concluímos que a solução da cobertura lexicográfica é de fato ideal.

Você precisaria de um ajuste para cobrir cada tupla t um certo número de vezes r. Uma óbvia seria apenas repetir cada tupla para ser coberta "r" vezes e, em seguida, executar a cobertura lex como de costume (por exemplo, no primeiro passo acima de cada 3 tuplas, 9 9-tuplas serão aplicadas com r = 3). Obviamente, isso continua sendo uma heurística para o seu problema geral devido ao uso da cobertura de lex.

josliber
fonte
2
Quem, esta é uma resposta incrivelmente boa. Muito obrigado. Basicamente, explica melhor a pergunta do que a própria pergunta. Isso é realmente esclarecedor.
Georgery 14/02
2

Aqui está uma opção usada data.tablepara acompanhar a contagem de matrizes e RcppAlgosgerar as combinações:

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)

É um algoritmo ganancioso, portanto, não tenho certeza se isso resultará em um número mínimo de tuplas.

chinsoon12
fonte
Hum, isso não funciona para mim. Eu recebo este erro: Error in eval(onsub, parent.frame(2L), parent.frame(2L)) : object '.NATURAL' not found
Georgery
você precisa da versão data.table> = 1.12.4, veja o item 10 dessa versão no github.com/Rdatatable/data.table/blob/master/NEWS.md
chinsoon12
2

Como essa pergunta solicita abordagens algorítmicas para cobrir projetos, fornecerei uma que forneça respostas exatas (também conhecidas como o melhor design possível) usando a programação inteira em R. desde que você esteja selecionando trigêmeos), defina uma variável de decisão que aceite o valor 1 se você a incluir em seu design e 0 se não. Portanto, no seu caso, você definiria x_123 para indicar se a tupla (1,2,3) está selecionada, x_345 para (3,4,5) e assim por diante.

O objetivo do modelo de otimização é minimizar o número de tuplas selecionadas, ou seja, a soma de todas as suas variáveis ​​de decisão. No entanto, para cada tupla t (t = 2 no seu caso), você precisa incluir uma variável de decisão que contenha essa tupla. Isso gera uma restrição para cada tupla t. Como exemplo, teríamos x_123+x_124+x_125 >= 1a restrição que exige que o par 12esteja em alguma tupla selecionada.

Isso produz o seguinte modelo de otimização:

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

Você pode estender isso para exigir repetições r de cada tupla t, alterando o lado direito de todas as desigualdades para "r" e exigindo que todas as variáveis ​​sejam inteiras em vez de binárias.

Isso é fácil de resolver com um pacote como lpSolveno 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

Embora isso resolva seu problema exatamente, ele não será bem dimensionado para tamanhos de problemas grandes. Isso ocorre porque o problema é difícil para o NP - nenhum algoritmo exato conhecido será dimensionado bem. Se você precisar solucionar grandes instâncias de problemas, as heurísticas recomendadas em outras respostas aqui são sua melhor aposta. Ou você pode resolver com programação inteira (como fazemos aqui) e definir um tempo limite; você estará trabalhando com a melhor solução encontrada pelo tempo limite, que é uma solução heurística para o problema em geral.

josliber
fonte