Generar una matriz que contenga todas las combinaciones de elementos tomados de n vectores


Esta pregunta aparece muy a menudo de una forma u otra (ver por ejemplo aquí o aquí). Así que pensé en presentarlo en forma general, y proporcionar una respuesta que podría servir para referencia futura.

Dado un número arbitrario n de vectores de tamaños posiblemente diferentes, genere una matriz n-columna cuyas filas describan todas las combinaciones de elementos tomadas de esos vectores (producto cartesiano) .

Para ejemplo,

vectors = { [1 2], [3 6 9], [10 20] }

Debería dar

combs = [ 1     3    10
          1     3    20
          1     6    10
          1     6    20
          1     9    10
          1     9    20
          2     3    10
          2     3    20
          2     6    10
          2     6    20
          2     9    10
          2     9    20 ]
Author: Community, 2014-02-20

4 answers

El ndgrid la función casi da la respuesta, pero tiene una advertencia: n las variables de salida deben definirse explícitamente para llamarla. Dado que n es arbitrario, la mejor manera es usar una lista separada por comas (generada a partir de una matriz de celdas con celdas n) para servir como salida. Las matrices n resultantes se concatenan en la matriz de columnas n deseada:

vectors = { [1 2], [3 6 9], [10 20] }; %// input data: cell array of vectors

n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combs{end:-1:1}] = ndgrid(vectors{end:-1:1}); %// the reverse order in these two
%// comma-separated lists is needed to produce the rows of the result matrix in
%// lexicographical order 
combs = cat(n+1, combs{:}); %// concat the n n-dim arrays along dimension n+1
combs = reshape(combs,[],n); %// reshape to obtain desired matrix
 43
Author: Luis Mendo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-20 00:02:31

Un poco más simple ... si tiene la caja de herramientas de la Red neuronal, simplemente puede usar combvec:

vectors = {[1 2], [3 6 9], [10 20]};
combs = combvec(vectors{:}).' % Use cells as arguments

Que devuelve una matriz en un orden ligeramente diferente:

combs =

     1     3    10
     2     3    10
     1     6    10
     2     6    10
     1     9    10
     2     9    10
     1     3    20
     2     3    20
     1     6    20
     2     6    20
     1     9    20
     2     9    20

Si desea la matriz que está en la pregunta, puede usar sortrows:

combs = sortrows(combvec(vectors{:}).')
% Or equivalently as per @LuisMendo in the comments: 
% combs = fliplr(combvec(vectors{end:-1:1}).') 

Que da

combs =

     1     3    10
     1     3    20
     1     6    10
     1     6    20
     1     9    10
     1     9    20
     2     3    10
     2     3    20
     2     6    10
     2     6    20
     2     9    10
     2     9    20

Si miras el funcionamiento interno de combvec (escribe edit combvec en la ventana de comandos), verás que usa un código diferente al de la respuesta de @LuisMendo. No puedo decir cuál es más eficiente en general.

Si tiene una matriz cuyas filas son similares a la matriz de celdas anterior, puede usar:

vectors = [1 2;3 6;10 20];
vectors = num2cell(vectors,2);
combs = sortrows(combvec(vectors{:}).')
 26
Author: horchler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-20 01:15:59

He hecho algunos benchmarking sobre las dos soluciones propuestas. El código de benchmarking se basa en timeit función, y se incluye al final de este post.

Considero dos casos: tres vectores de tamaño n, y tres vectores de tamaños n/10, n y n*10 respectivamente (ambos casos dan el mismo número de combinaciones). n se varía hasta un máximo de 240 (Elijo este valor para evitar el uso de memoria virtual en mi computadora portátil).

Los resultados se dan en la figura siguiente. Se considera que la solución basada en ndgrid tarda menos tiempo que combvec. También es interesante notar que el tiempo que toma combvec varía un poco menos regularmente en el caso de diferentes tamaños.

introduzca la descripción de la imagen aquí


Código de evaluación comparativa

Función para la solución basada en ndgrid:

function combs = f1(vectors)
n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combs{end:-1:1}] = ndgrid(vectors{end:-1:1}); %// the reverse order in these two
%// comma-separated lists is needed to produce the rows of the result matrix in
%// lexicographical order
combs = cat(n+1, combs{:}); %// concat the n n-dim arrays along dimension n+1
combs = reshape(combs,[],n);

Función para combvec solución:

function combs = f2(vectors)
combs = combvec(vectors{:}).';

Script para medir el tiempo llamando a timeit en estas funciones:

nn = 20:20:240;
t1 = [];
t2 = [];
for n = nn;
    %//vectors = {1:n, 1:n, 1:n};
    vectors = {1:n/10, 1:n, 1:n*10};
    t = timeit(@() f1(vectors));
    t1 = [t1; t];
    t = timeit(@() f2(vectors));
    t2 = [t2; t];
end
 12
Author: Luis Mendo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-26 23:13:14

Aquí está un método del hágalo usted mismo que me hizo reír con placer, usando nchoosek, aunque es no mejor que la solución aceptada de @Luis Mendo.

Para el ejemplo dado, después de 1,000 corridas esta solución tomó mi máquina en promedio 0.00065935 s, versus la solución aceptada 0.00012877 s. Para vectores más grandes, siguiendo el post de benchmarking de @Luis Mendo, esta solución es consistentemente más lenta que la respuesta aceptada. Sin embargo, decidí publicarlo con la esperanza de que tal vez encontrarás algo útil al respecto:

Código:

tic;
v = {[1 2], [3 6 9], [10 20]};

L = [0 cumsum(cellfun(@length,v))];
V = cell2mat(v);

J = nchoosek(1:L(end),length(v));
J(any(J>repmat(L(2:end),[size(J,1) 1]),2) | ...
  any(J<=repmat(L(1:end-1),[size(J,1) 1]),2),:)  = [];

V(J)
toc

Da

ans =

 1     3    10
 1     3    20
 1     6    10
 1     6    20
 1     9    10
 1     9    20
 2     3    10
 2     3    20
 2     6    10
 2     6    20
 2     9    10
 2     9    20

Elapsed time is 0.018434 seconds.

Explicación:

L obtiene las longitudes de cada vector usando cellfun. Aunque cellfun es básicamente un bucle, es eficiente aquí teniendo en cuenta que su número de vectores tendrá que ser relativamente bajo para que este problema sea práctico.

V concatena todos los vectores para un fácil acceso más tarde (esto supone que ingresaste todos tus vectores como filas. v ' haría trabajar para vectores de columna.)

nchoosek obtiene todas las formas de elegir elementos n=length(v) del número total de elementos L(end). Habrá más combinaciones aquí de las que necesitamos.

J =

 1     2     3
 1     2     4
 1     2     5
 1     2     6
 1     2     7
 1     3     4
 1     3     5
 1     3     6
 1     3     7
 1     4     5
 1     4     6
 1     4     7
 1     5     6
 1     5     7
 1     6     7
 2     3     4
 2     3     5
 2     3     6
 2     3     7
 2     4     5
 2     4     6
 2     4     7
 2     5     6
 2     5     7
 2     6     7
 3     4     5
 3     4     6
 3     4     7
 3     5     6
 3     5     7
 3     6     7
 4     5     6
 4     5     7
 4     6     7
 5     6     7

Dado que solo hay dos elementos en v(1), necesitamos tirar cualquier fila donde J(:,1)>2. Del mismo modo, donde J(:,2)<3, J(:,2)>5, etc... Usando L y repmat podemos determinar si cada elemento de J está en su rango apropiado, y luego usar any para descartar filas que tengan mal elemento.

Finalmente, estos no son los valores reales de v, solo los índices. V(J) devolverá la matriz deseada.

 2
Author: Geoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-19 14:44:09