Effiziente Auswahl von Kombinationen von Ganzzahlen

8

Nehmen wir an, wir haben eine 5x5-Matrix, die mit Nullen gefüllt ist.

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

Lassen Sie uns nun ein Triplett von ganzen Zahlen zwischen 1 und 5 auswählen.

triplet <- c(1,2,3)

Für alle Kombinationen dieses Tripletts fügen wir nun mit dieser Funktion 1 in die Matrix ein:

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

Mit der Funktion gehen wir von

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

zu

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

Wenn wir ein anderes Triplett auswählen, gehen wir weiter zu

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

Zeilen-Spalten-Kombinationen geben also an, wie oft zwei Ganzzahlen in einem Triplett zusammen gezeigt wurden: 3 und 4 wurden einmal zusammen gezeigt, 2 und 3 wurden zweimal zusammen gezeigt.

Frage : Wie kann man Drillinge auswählen, so dass jede Kombination (1-2, 1-3, 1-4 ...) mindestens einmal ausgewählt wurde und die Anzahl der Drillinge minimiert wird.

Ich suche hier nach einem Algorithmus, der das nächste Triplett auswählt.

Idealerweise kann es erweitert werden auf

  • beliebig große Matrizen (10x10, 100x100 ...)
  • beliebig große Vektoren (Quadruplets, Quintuplets, n-Tuplets)
  • Es muss mindestens beliebig oft eine Kombination ausgewählt worden sein

Beispiel:

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 : Nur um sicher zu gehen: Die Antwort muss kein RCode sein. Es kann auch eine andere Sprache oder sogar Pseudocode sein.

EDIT 2 : Mir ist jetzt aufgefallen, dass es verschiedene Möglichkeiten gibt, die Effizienz zu messen. Ich meinte eigentlich, der Algorithmus sollte so wenig Iterationen wie möglich dauern. Der schnelle Algorithmus ist auch sehr cool, aber nicht das Hauptziel hier.

Georgery
quelle

Antworten:

6

Gute Frage! Dies tritt beim Umfragedesign auf, bei dem Sie einige verschiedene Versionen der Umfrage wünschen, die jeweils nur eine Teilmenge der Fragen enthalten, aber jedes Paar (oder T-Tupel) von Fragen mindestens einmal gestellt werden soll.

Dies wird als Abdeckungsdesign bezeichnet und ist eine Variante des klassischen Set-Cover-Problems . Wie Sie in einem ausgezeichneten Mathematics Stack Exchange-Beitrag zu diesem Thema lesen können , verwenden die Leute die Notation C (v, k, t), die die Mindestanzahl von k-Element-Teilmengen angibt, die Sie aus einem v ziehen müssen (in Ihrem Fall k = 3) -Elementmenge (in Ihrem Fall v = 5), sodass jede t-Element-Teilmenge in der gesamten Menge (in Ihrem Fall t = 2) in einer Ihrer ausgewählten Teilmengen enthalten ist. Leute haben diese Funktion für viele verschiedene (v, k, t) Tupel bewertet; Siehe zum Beispiel https://ljcr.dmgordon.org/cover/table.html . Aus dieser Tabelle können wir lesen, dass C (5, 3, 2) = 4 ist, wobei Folgendes als ein mögliches Design gilt:

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

In erster Linie ist dieses Problem NP-schwer, so dass alle bekannten exakten Algorithmen in den Eingängen v, k und t exponentiell skalieren. Während Sie möglicherweise in der Lage sind, kleine Instanzen genau durch Aufzählung oder eine cleverere exakte Methode (z. B. Ganzzahlprogrammierung) zu lösen, müssen Sie wahrscheinlich auf heuristische Methoden zurückgreifen, da die Problemgröße sehr groß wird.

Eine Möglichkeit in dieser Richtung ist die lexikografische Abdeckung, wie in https://arxiv.org/pdf/math/9502238.pdf vorgeschlagen (Sie werden feststellen, dass viele der Lösungen auf der oben verlinkten Website "Lex-Abdeckung" als Methode von Konstruktion). Grundsätzlich listen Sie alle möglichen k-Tupel in lexikografischer Reihenfolge auf:

123
124
125
134
135
145
234
235
245
345

Dann fügen Sie gierig das k-Tupel hinzu, das die zuvor nicht abgedeckten T-Tupel abdeckt, und brechen die Bindungen mithilfe der lexikografischen Reihenfolge.

So funktioniert der Algorithmus in unserem Fall:

  1. Zu Beginn deckt jedes 3-Tupel 3 verschiedene 2-Tupel ab, also fügen wir hinzu, 123da es lexikographisch am frühesten ist.

  2. Nachdem Sie das getan, die 2-Tupel 12, 13und 23bedeckt worden sind, während alle restlichen 2-Tupel nicht abgedeckt sind. Eine Anzahl von 3-Tupeln deckt 3 weitere 2-Tupel ab, z . B. 145und 245. Wir wählen 145, da es lexikographisch zuerst 14ist 45, und 15.

  3. Jetzt haben wir vier verbleibenden aufgedeckt 2-Tupel - 24, 25, 34, und 35. Kein 3-Tupel deckt 3 davon ab, sondern mehrere decken 2 ab, z . B. 234und 345. Wir wählen 234als lexikographisch früheste.

  4. Wir haben zwei verbleibende unbedeckte 2-Tupel - 25und 35. Wir wählen 235als einziges 3-Tupel, das beide abdeckt.

Wir erhalten die exakte Lösung, die oben gezeigt wurde. Wichtig ist, dass dies nur eine heuristische Methode ist - sie gibt keine Garantie dafür, dass 4 die kleinste Anzahl von 3 Tupeln ist, die benötigt werden, um alle Paare in einem Satz mit 5 Elementen abzudecken. In diesem Fall überzeugt uns eine Untergrenze von Schönheim (eine Referenz finden Sie im oben verlinkten Artikel), dass C (5, 3, 2) tatsächlich nicht kleiner als 4 sein kann. Wir schließen daraus, dass die Lösung aus der lexikografischen Abdeckung ist in der Tat optimal.

Sie würden eine Optimierung benötigen, um jedes T-Tupel eine bestimmte Anzahl von Male r abzudecken. Eine naheliegende Möglichkeit wäre, jedes zu behandelnde Tupel "r" mal zu wiederholen und dann die Lex-Abdeckung wie gewohnt auszuführen (so würde beispielsweise im ersten Schritt oben jedes 3-Tupel 9 2-Tupel mit r = 3 abdecken). Natürlich bleibt dies aufgrund der Verwendung der Lex-Abdeckung eine Heuristik für Ihr Gesamtproblem.

josliber
quelle
2
Wer, das ist eine erstaunlich gute Antwort. Ich danke dir sehr. Grundsätzlich erklärt es die Frage besser als die Frage selbst. Das ist wirklich aufschlussreich.
Georgery
2

Hier ist eine Option, mit data.tableder Sie die Matrixanzahl verfolgen und RcppAlgosdie Kombinationen generieren können:

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)

Es ist ein gieriger Algorithmus, daher bin ich mir nicht sicher, ob dies zu einer minimalen Anzahl von Tupeln führt.

chinsoon12
quelle
Hm, das funktioniert bei mir nicht. Ich erhalte diesen Fehler: Error in eval(onsub, parent.frame(2L), parent.frame(2L)) : object '.NATURAL' not found
Georgery
Sie benötigen data.table version> = 1.12.4, siehe Punkt 10 unter dieser Version auf github.com/Rdatatable/data.table/blob/master/NEWS.md
chinsoon12
2

Da diese Frage nach algorithmischen Ansätzen zur Abdeckung von Entwürfen fragt, werde ich einen bereitstellen, der genaue Antworten (auch bekannt als das bestmögliche Design) unter Verwendung der Ganzzahlprogrammierung in R gibt. Für jedes einzelne k-Tupel, das Sie in Betracht ziehen (k = 3 für Sie, Da Sie Triplets auswählen, definieren Sie eine Entscheidungsvariable, die den Wert 1 annimmt, wenn Sie sie in Ihr Design aufnehmen, und 0, wenn nicht. In Ihrem Fall würden Sie also x_123 definieren, um anzugeben, ob Tupel (1,2,3) ausgewählt ist, x_345 für (3,4,5) und so weiter.

Das Ziel des Optimierungsmodells besteht darin, die Anzahl der ausgewählten Tupel, auch bekannt als die Summe aller Ihrer Entscheidungsvariablen, zu minimieren. Für jedes t-Tupel (in Ihrem Fall t = 2) müssen Sie jedoch eine Entscheidungsvariable einfügen, die dieses t-Tupel enthält. Dies ergibt eine Einschränkung für jedes t-Tupel. Als Beispiel hätten wir x_123+x_124+x_125 >= 1die Einschränkung, die erfordert, dass sich das Paar 12in einem ausgewählten Tupel befindet.

Dies ergibt das folgende Optimierungsmodell:

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

Sie können dies dahingehend erweitern, dass r Wiederholungen jedes t-Tupels erforderlich sind, indem Sie die rechte Seite jeder Ungleichung in "r" ändern und alle Variablen ganzzahlig statt binär sein müssen.

Dies ist einfach mit einem Paket wie lpSolvein R zu lösen :

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

Dies löst Ihr Problem zwar genau, lässt sich jedoch nicht gut auf große Problemgrößen skalieren. Dies liegt daran, dass das Problem NP-schwer ist - kein bekannter exakter Algorithmus lässt sich gut skalieren. Wenn Sie große Problemfälle lösen müssen, sind die in anderen Antworten hier empfohlenen Heuristiken die beste Wahl. Oder Sie können mit ganzzahliger Programmierung (wie hier) lösen und eine Zeitüberschreitung festlegen. Dann arbeiten Sie mit der besten Lösung, die Sie durch Ihr Timeout gefunden haben. Dies ist eine heuristische Lösung für das Problem insgesamt.

josliber
quelle