sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo escribir la función combinatoria en postgres?

Después de dormirme, tuve una idea completamente nueva, más simple y más rápida:

CREATE OR REPLACE FUNCTION f_combos(_arr anyarray)
  RETURNS TABLE (combo anyarray) LANGUAGE plpgsql AS
$BODY$
BEGIN
    IF array_upper(_arr, 1) IS NULL THEN
        combo := _arr; RETURN NEXT; RETURN;
    END IF;

    CASE array_upper(_arr, 1)
--  WHEN 0 THEN -- does not exist
    WHEN 1 THEN
        RETURN QUERY VALUES ('{}'), (_arr);
    WHEN 2 THEN
        RETURN QUERY VALUES ('{}'), (_arr[1:1]), (_arr), (_arr[2:2]);
    ELSE
        RETURN QUERY
        WITH x AS (
            SELECT f.combo FROM f_combos(_arr[1:array_upper(_arr, 1)-1]) f
            )
        SELECT x.combo FROM x
        UNION ALL
        SELECT x.combo || _arr[array_upper(_arr, 1)] FROM x;
    END CASE;
END
$BODY$;

Llamar:

SELECT * FROM f_combos('{1,2,3,4,5,6,7,8,9}'::int[]) ORDER BY 1;

512 filas, tiempo de ejecución total:2,899 ms

Explicar

  • Tratar casos especiales con NULL y matriz vacía.
  • Construye combinaciones para una matriz primitiva de dos.
  • Cualquier matriz más larga se divide en:
    • las combinaciones para la misma matriz de longitud n-1
    • más todos aquellos combinados con el elemento n .. recursivamente .

Realmente simple, una vez que lo tienes.

  • Funciona para arreglos enteros unidimensionales que comienzan con subíndice 1 (ver más abajo).
  • 2 o 3 veces más rápido que la solución anterior, escala mejor.
  • Funciona para cualquier tipo de elemento nuevamente (usando tipos polimórficos).
  • Incluye la matriz vacía en el resultado como se muestra en la pregunta (y como @Craig me señaló en los comentarios).
  • Más corto, más elegante.

Esto supone subíndices de matriz a partir de 1 (Defecto). Si no está seguro acerca de sus valores, llame a la función de esta manera para normalizar:

SELECT * FROM  f_combos(_arr[array_lower(_arr, 1):array_upper(_arr, 1)]);

No estoy seguro de si hay una forma más elegante de normalizar los subíndices de matriz. Publiqué una pregunta sobre eso:
Normalice los subíndices de matriz para una matriz unidimensional para que comiencen con 1

Solución antigua (más lenta)

CREATE OR REPLACE FUNCTION f_combos2(_arr int[], _a int[] = '{}', _z int[] = '{}')
 RETURNS SETOF int[] LANGUAGE plpgsql AS
$BODY$
DECLARE
   i int;
   j int;
   _up int;
BEGIN
   IF array_length(_arr,1) > 0 THEN 
      _up := array_upper(_arr, 1);

      FOR i IN array_lower(_arr, 1) .. _up LOOP
         FOR j IN i .. _up  LOOP
            CASE j-i
            WHEN 0,1 THEN
               RETURN NEXT _a || _arr[i:j] || _z;
            WHEN 2 THEN
               RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
               RETURN NEXT _a || _arr[i:j] || _z;
            ELSE
               RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
               RETURN QUERY SELECT *
               FROM f_combos2(_arr[i+1:j-1], _a || _arr[i], _arr[j] || _z);
            END CASE;
         END LOOP;
      END LOOP;
   ELSE
      RETURN NEXT _arr;
   END IF;
END;
$BODY$;

Llamar:

SELECT * FROM f_combos2('{7,15,48}'::int[]) ORDER BY 1;

Funciona para matrices de enteros unidimensionales. Esto podría optimizarse aún más, pero ciertamente no es necesario para el alcance de esta pregunta.
ORDER BY para imponer el orden mostrado en la pregunta.

Proporcione NULL o matriz vacía, como NULL se menciona en los comentarios.

Probado con PostgreSQL 9.1, pero debería funcionar con cualquier versión medianamente moderna.array_lower() y array_upper() han existido por lo menos desde PostgreSQL 7.4. Solo los valores predeterminados de los parámetros son nuevos en la versión 8.4. Se puede reemplazar fácilmente.

El rendimiento es decente.

SELECT DISTINCT * FROM f_combos('{1,2,3,4,5,6,7,8,9}'::int[]) ORDER BY 1;

511 filas, tiempo de ejecución total:7,729 ms

Explicación

Se basa en este formulario simple que solo crea todas las combinaciones de elementos vecinos:

CREATE FUNCTION f_combos(_arr int[])
  RETURNS SETOF int[] LANGUAGE plpgsql AS
$BODY$
DECLARE
   i  int;
   j  int;
  _up int;
BEGIN
   _up := array_upper(_arr, 1);

   FOR i in array_lower(_arr, 1) .. _up LOOP
      FOR j in i .. _up LOOP
         RETURN NEXT _arr[i:j];
      END LOOP;
   END LOOP;
END;
$BODY$;

Pero esto fallará para sub-matrices con más de dos elementos. Entonces:

  • Para cualquier subarreglo con 3 elementos, se agrega un arreglo con solo los dos elementos externos. este es un atajo para este caso especial que mejora el rendimiento y no es estrictamente necesario .

  • Para cualquier subarreglo con más de 3 elementos, tomo los dos elementos externos y complete con todas las combinaciones de elementos internos construido por la misma función recursivamente .