cálculo de combinaciones

Colapsar
X
 
  • Tiempo
  • Mostrar
Limpiar Todo
nuevos mensajes
  • miko-lynn
    Junior Member
    • abr
    • 21

    #1

    cálculo de combinaciones

    Saludos
    Tengo un pollo montado y no se cómo salirme de él.
    Tengo una tabla con sólo dos campos, familia y característica y he de calcular todas las combinaciones posibles que se puedan dar.
    Por ejemplo:
    Familia Valor
    a 1
    a 2
    b 1
    b 2
    b 3
    c 1
    c 2
    y las combinaciones que puedo sacar de aquí son:
    a1+b1+c1
    a1+b1+c2
    a1+b2+c1
    a1+b2+c2
    a1+b3+c1
    a1+b3+c2
    a2+b1+c1
    a2+b1+c2
    a2+b2+c1
    a2+b2+c2
    a2+b3+c1
    a2+b3+c2
    Al ser n tanto las familias cómo las caracteristicas, supongo que lo he de realizar de alguna forma dinámica, pero no se me ocurre la manera...
    Por favor, por favor, por favor, ¿alguien me puede iluminar un poco?
  • ballan
    Senior Member
    • oct
    • 671

    #2
    Lo primero de todo hay algo que no queda del todo claro en tu post, hablas de combinaciones pero no te refieres a si el orden importa o no, es decir,
    a1+b1+c1 es lo mismo que b1+a1+c1

    Es un detalle importante que conviene aclarar, aunque por lo que comentas voy a dar por supuesto que te refieres a combinaciones sin orden, es decir a1+b1+c1 es lo mismo que b1+a1+c1

    Partiendo de este supuesto hay muchas maneras de hacer ese algoritmo, teniendo en cuenta las limitaciones de abap y que como has dicho es algo que tendras que hacer de manera dinamica puesto que el numero de familias y valores sera variable te digo de que manera lo haria yo

    Lo primero y fundamental es ordenar los datos de tal manera que luego el algoritmo para calcular todas las combinaciones sea sencillo

    Supongamos que de una manera magica obtenemos una serie de tablas con dos columnas (familia y valor) cada tabla contendra el valor de la familia y una linea por cada diferente valor, asi con los datos que has puesto quedaria

    TABLA 1

    FAMILIA VALOR
    A 1
    A 2

    TABLA 2

    FAMILIA VALOR
    B 1
    B 2
    B 3

    TABLA 3

    FAMILIA VALOR
    C 1
    C 2

    No nos preocupemos ni de como obtener dichas tablas ni generarlas ni el formato que tendran ni nada, despues hablaremos de ello, solo lo pongo de ejemplo para ver como seria el algoritmo

    Para hacerlo mas sencillo voy a suponer que solo hay 3 familias (A, B, C) y que tienen los valores que tu has puesto, despues hablaremos de como seria para hacerlo dinamicamente para el numero de familias que sea

    Si tu hicieras

    Código:
    LOOP AT TABLA1 INTO LINEA1.
    
      LOOP AT TABLA2 INTO LINEA2.
    
        LOOP AT TABLA3 INTO LINEA3.
    
        CONCATENATE LINEA1-FAMILIA LINEA1-VALOR
                                LINEA2-FAMILIA LINEA2-VALOR 
                                LINEA3-FAMILIA LINEA3-VALOR
        INTO LV_COMBINACION. " (aqui podriamos añadir separated by)
    
        ENDLOOP.
    
      ENDLOOP.
    
    ENDLOOP.
    Con esto irias obteniendo en lv_combinacion cada una de las diferentes combinaciones posibles que serian el resultado del producto cartesiano de todos los valores de todas las familias, podrias ir haciendo el append de lv_combinacion en otra tabla o algo asi para ir almacenando todas las combinaciones posibles y tu algoritmo estaria resuelto

    Ahora voy a hablar de lo que es lo mas complicado que es organizar la informacion de manera que podamos obtenerla como te he dicho

    Como el numero de familias no lo conocemos habria N tablas, tantas como familias existen pero todas las tablas tendrian la misma estructura que serian dos columnas, una para el nombre de la familia y otra para el valor

    Para crear una tabla en tiempo de ejecucion podemos utilizar muchos metodos, utilizar la clase cl_rs_struc, utilizar el metodo CREATE_DYNAMIC_TABLE de la clase CL_ALV_TABLE_CREATE, si buscas en el foro encontraras multiples posts explicando la creacion de tablas dinamicamente en tiempo de ejecucion (ojo porque creo que el metodo CREATE_DYNAMIC_TABLE por motivos tecnicos de sap solo puedes llamarlo 36 veces, es decir si hubiera 37 familias diferentes ya no te serviria porque te daria dump al intentar crear la tabla numero 37)

    Una vez que hubieramos creado las tablas y tuvieramos una tabla por cada familia la cuestion seria rellenarlas, para eso harias un loop a tu tabla maestra donde tienes todas las familias y todos los valores y con los AT NEW o ON CHANGE OF irias partiendo para ir creando cada una de las diferentes tablas donde tendras la familia y sus posibles valores

    Bueno me ha salido un ladrillo terrible, lo que quieres hacer es complicado y ABAP no es un lenguaje muy orientado a hacer cosas asi ve exponiendo las dudas que te vayan surgiendo

    Comentario

    • SidV
      Usuario Avanzado
      • oct
      • 1761

      #3
      @miko-lynn, por favor recuerda dar mejores detalles cuando abras los temas, así los usuarios que participamos de esta comunidad podemos ayudarte mejor.

      OFF TOPIC:
      Grosísimo el aporte de ballan.

      Se agradece!
      Sigue así!

      Comentario

      • miko-lynn
        Junior Member
        • abr
        • 21

        #4
        Bueno, ante todo, muchas gracias,

        No he respondido antes ya que he intentado la manera en que en PHP, hace mil años, yo había hecho algo parecido que es llamando un perform de manera recursiva pero con ABAP, no me aclaro.

        Bueno, el orden si es importante, pero no tanto ya que estas combinaciones las he de pasar después por un mf que realiza ciertos cálculos y si una de las combinaciones no la acepta, devuelve error, con lo cual, esa, la omito y continuo con la siguiente.

        Voy a intentar lo que comentas ya que me parece bastante más sencillo que lo que yo estoy intentando, a ver si pronto cuento como me va y si lo he conseguido...
        Gracias!

        Comentario

        • ballan
          Senior Member
          • oct
          • 671

          #5
          Este algoritmo tambien podria resolverse de manera recursiva aunque es algo que no te recomiendo puesto que ABAP no esta muy orientado a ello, ademas la recursividad es algo que nos enseñan mucho en la universidad pero si no es con lenguajes que esten preparados para ello al final en la vida real no es muy util porque a nivel de consumo de recursos y tiempos es bastante ineficiente

          El detalle de que importe o no el orden en las combinaciones no es algo trivial puesto que te modificaria el algoritmo

          Por ejemplo con el ejemplo que puse arriba serviria si NO importa el orden

          Si el orden SI es importante el algoritmo cambiaria un poco y seria algo mas o menos asi

          Código:
          LOOP AT TABLA1 INTO LINEA1.
          
            LOOP AT TABLA2 INTO LINEA2.
          
              LOOP AT TABLA3 INTO LINEA3.
          
              CONCATENATE LINEA1-FAMILIA LINEA1-VALOR
                                      LINEA2-FAMILIA LINEA2-VALOR 
                                      LINEA3-FAMILIA LINEA3-VALOR
              INTO LV_COMBINACION. " (aqui podriamos añadir separated by)
          
              ENDLOOP.
          
            ENDLOOP.
          
          ENDLOOP.
          
          LOOP AT TABLA2 INTO LINEA2.
          
            LOOP AT TABLA3 INTO LINEA3.
          
              LOOP AT TABLA1 INTO LINEA1.
          
              CONCATENATE LINEA2-FAMILIA LINEA2-VALOR
                                      LINEA3-FAMILIA LINEA3-VALOR 
                                      LINEA1-FAMILIA LINEA1-VALOR
              INTO LV_COMBINACION. " (aqui podriamos añadir separated by)
          
              ENDLOOP.
          
            ENDLOOP.
          
          ENDLOOP.
          
          LOOP AT TABLA3 INTO LINEA3.
          
            LOOP AT TABLA1 INTO LINEA1.
          
              LOOP AT TABLA2 INTO LINEA2.
          
              CONCATENATE LINEA3-FAMILIA LINEA3-VALOR
                                      LINEA1-FAMILIA LINEA1-VALOR 
                                      LINEA2-FAMILIA LINEA2-VALOR
              INTO LV_COMBINACION. " (aqui podriamos añadir separated by)
          
              ENDLOOP.
          
            ENDLOOP.
          
          ENDLOOP.
          
          .
          .
          .
          y asi con tantas tablas como tuvieramos

          Comentario

          • miko-lynn
            Junior Member
            • abr
            • 21

            #6
            creando tablas dinamicamente

            Gracias Ballan!
            Otra cosa, al crear las tablas, como lo tengo que hacer dinamicamente para cada "familia" tengo problemas al asignarles un nombre.
            Me explico, creo el field symbol apuntando a una estructura del tipo de mis datos (en este caso familia-valor) pero no sé cómo asignarle un nombre diferente por cada familia.

            He intentado varias cosas, entre ellas algo así como:

            Código:
            DATA: contador TYPE i,
                    contachar(2),
                    nombrefs TYPE string.
            
            LOOP AT t_familias.
            
            AT NEW familia.
            ADD 1 TO contador.
            contachar = contador.
            concatenate 'TAB_FAMILIA' contachar TO nombrefs.
            CALL METHOD cl_alv_table_create=>create_dynamic_table
              EXPORTING
               it_fieldcatalog = gt_fieldcat 
              IMPORTING
               ep_table = nombrefs
              EXCEPTIONS
               generate_subpool_dir_full = 1
              OTHERS = 2.
            
            ENDLOOP.
            Pero claro, me dice que nombrefs no es compatible con ep_table.
            ¿¿¿Cómo puedo asignar este nombre dinámico a la tabla creada dinámicamente???

            Comentario

            • ballan
              Senior Member
              • oct
              • 671

              #7
              Hay un tema tecnico que necesitas tener en cuenta

              Como ves el metodo para crear la tabla dinamica contempla la excepcion generate_subpool_dir_full

              Esta excepcion si no recuerdo mal salta a la vez numero 37 que quieras crear una tabla, es decir, si tu tabla de familias tuviera 37 familias diferentes tu codigo daria dump

              Te pongo un ejemplo de como lo haria, te advierto que es un poco farragoso y es facil perderse por lo que se estricto y hazlo como te indico, si tienes dudas pregunta

              Lo primero de todo vas a tener que hacer lo siguiente

              En la se11 create una estructura que se llame ZTY_S_FAMVAL, esta estructura contendra dos campos: familia y valor, a cada uno ponle la longitud correspondiente

              Una vez que hayas creado la estructura ZTY_S_FAMVAL en la se11 tienes que crear un tipo tabla que llamaras ZTY_T_FAMVAL cuyo tipo de linea sera ZTY_S_FAMVAL

              En la se11 crea otra estructura que se llame ZTY_S_TABFAMVAL, esta estructura tendra una unico campo que se llamara TABLA, dicho campo sera del tipo ZTY_T_FAMVAL, es decir, esta estructura tendria un unico campo pero ese campo a su vez seria una tabla (con las columnas familia y valor)

              Por ultimo en la se11 tienes que crear un tipo tabla que se llame ZTY_T_TABFAMVAL, este tipo de tabla tendra como tipo de linea la estructura ZTY_S_TABFAMVAL, es decir, tendremos una TABLA cuyas lineas contendran un unico campo que a su vez sera una tabla

              La idea fundamental es que para evitar el dump generate_subpool_dir_full lo que haces es crear la tabla una unica vez y luego "reutilizarla" porque al final vas a tener muchas tablas (tantas como familias) pero todas las tablas tienen la misma estructura

              Ahora te adjunto un ejemplo de codigo de como podrias trabajar con esto, adaptalo a tus necesidades

              Código:
              REPORT zpruebasdyn.
              
              DATA: go_tref TYPE REF TO data, "esta referencia sera la orignal de la tabla, *servira para reutilizar
                    go_lref2 TYPE REF TO data, "referencia para la linea
                    go_tref2 LIKE go_tref. "referencia copia para la tabla
              
              DATA: gt_tabfamval TYPE zty_t_tabfamval, "tabla cuyas lineas contienen un *campo que a su vez es una tabla (familia y valor)
                    gs_tabfamval LIKE LINE OF gt_tabfamval, "linea que contiene un campo *que es una tabla (familia y valor)
                    gt_fcat TYPE lvc_t_fcat. "tabla para el fieldcat
              
              FIELD-SYMBOLS: <tabla> TYPE zty_t_famval, "fieldsymbols auxiliares
                             <linea> LIKE LINE OF <tabla>. "fieldsymbols auxiliares
              
              *Con esto obtengo automaticamente el fieldcat
              CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
                EXPORTING
                  i_structure_name = 'ZTY_S_FAMVAL'
                CHANGING
                  ct_fieldcat      = gt_fcat.
              
              *Con esto creo la tabla dinamica en la referencia go_tref
              CALL METHOD cl_alv_table_create=>create_dynamic_table
                EXPORTING
                  it_fieldcatalog           = gt_fcat
                IMPORTING
                  ep_table                  = go_tref
                EXCEPTIONS
                  generate_subpool_dir_full = 1
                  OTHERS                    = 2.
              
              *Asigno a otra variable de referencia para no "gastar" la referencia go_tref y *tener siempre el original
              go_tref2 = go_tref.
              
              *Asigno la referencia a un fieldsymbol que es una tabla (con familia y valor)
              ASSIGN go_tref2->* TO <tabla>.
              
              *Creo una referencia que sea una linea de la tabla para poder introducir los *valores
              CREATE DATA go_lref2 LIKE LINE OF <tabla>.
              *Asigno la referencia a un fieldsymbol
              ASSIGN go_lref2->* TO <linea>.
              
              *Relleno los valores y hago append
              <linea>-familia = 'A'.
              <linea>-valor   = '1'.
              APPEND <linea> TO <tabla>.
              
              <linea>-familia = 'A'.
              <linea>-valor   = '2'.
              APPEND <linea> TO <tabla>.
              
              *Al final de esto lo que voy a obtener es una tabla con una linea
              *Dicha linea solo tendra un campo llamado TABLA que a su vez sera una *tabla
              *Esa tabla tendra las columnas FAMILIA y VALOR, habra dos lineas, 
              *una con la familia A y valor 1
              *otra con la familia A y valor 2
              gs_tabfamval-tabla = <tabla>.
              
              APPEND gs_tabfamval TO gt_tabfamval.
              Al final si explotas la logica que te he puesto encima el objetivo es que obtengas una tabla del tipo ZTY_T_TABFAMVAL que tendra tantas lineas como familias diferentes hay

              A su vez cada linea contendra un campo llamado TABLA y dicha tabla contendra tantas lineas como valores tenga la familia correspondiente

              Como estas almacenando todo en lineas de tablas no tienes que preocuparte de nombres asignados a las tablas ni nada de eso

              Comentario

              • miko-lynn
                Junior Member
                • abr
                • 21

                #8
                Genial!

                Ballan, dime en que pueblo vives que vengo y te pongo una placa o una estatua o algo!

                Genial!
                Lo he tenido que montar un pelín diferente para poder hacer un loop a la tabla donde leo los valores y a cada familia nueva, hacer el append a la tabla de tablas, limpiar los valores y continuar. Algo así cómo:

                Código:
                *Aquí tengo los datos.
                LOOP AT t_datos INTO S_datos.
                * si estamos en el primer registro o en el primer registro de "otra familia"
                   IF ( SY-TABIX = 1 ) OR ( familia <> familia_actual ).
                     familia_actual = s_datos-familia. "guardamos el valor de familia
                     IF SY-TABIX gt 1. "si NO estamos en el primer registro
                          APPEND  gs_tabfamval TO gt_tabfamval.
                [I]* Una vez guardado el registro he de limpiar los datos para que la nueva tabla no herede los datos de laanterior[/I]
                            CLEAR gs_tabfamval.
                            CLEAR go_tref2.
                            CLEAR go_tref.
                            go_tref2 = go_tref.
                            REFRESH <tabla>.
                            CREATE DATA go_lref2 LIKE LINE OF <tabla>.
                            ASSIGN go_lref2->* TO <linea>.
                      ENDIF.
                   ENDIF
                    <linea>-familia = s_datos-familia.
                    <linea>-valor   = s_datos-valor.
                    APPEND <linea> TO <tabla>.
                    CLEAR <linea>.
                ENDLOOP.
                [I]*una vez fuera del loop, he de guardar los datos otra vez para que me guarde la ultima tabla.[/I]
                    gs_tabfamval-tabla = <tabla>.
                    APPEND  gs_tabfamval TO gt_tabfamval.
                    CLEAR gs_tabfamval.
                EEEEEEEEEE.... VOILÀ!


                Ahora, he de leer los datos linea por linea. espero que no me cueste demasiado, porqué esto me está costando mas que el parto de la burra!
                Editado por última vez por miko-lynn; 12/09/2013, 14:46:17.

                Comentario

                • ballan
                  Senior Member
                  • oct
                  • 671

                  #9
                  Jeje, gracias hombre !!

                  Bueno lamento comunicarte que lo de leer la informacion no es algo nada sencillo

                  De hecho hay una cosa que ya te comente que es algo FUNDAMENTAL y es el tema de si importa el orden o no

                  Es decir

                  ¿ A1+B1+C1 es lo mismo que B1+A1+C1 o no?

                  Si NO importa el orden simplemente es hacer loop a cada una de las tablas y guardando todas las combinaciones

                  Si el orden SI importa y tienes que generar todas las combinaciones...

                  Lo primero es que hay que ser consciente de lo que estamos hablando

                  Por ejemplo suponiendo que tenemos 3 tablas (osea, 3 familias) todas las combinaciones posibles en las que podriamos ir recorriendo las tablas para obtener el producto cartesiano serian

                  TABLA1,TABLA2,TABLA3
                  TABLA1,TABLA3,TABLA2
                  TABLA2,TABLA3,TABLA1
                  TABLA2,TABLA1,TABLA3
                  TABLA3,TABLA1,TABLA2
                  TABLA3,TABLA2,TABLA1

                  Es decir para 3 elementos tenemos 6 posibilidades diferentes o dicho de una manera estricta las posibles permutaciones de 3 elementos son 3! (3 factorial) que es 3*2*1 = 6

                  Si fuera 5 familias diferentes seria 5! = 120 posibilidades diferentes

                  Pero es que ya si hubiera por ejemplo 10 familias seria 10! = 3.628.800 posibilidades diferentes que ya es un numero considerablemente grande (y solo estamos hablando de 10 familias que no es algo descabellado)

                  Para que te hagas una idea, las calculadoras de toda la vida suelen tener capacidad de calculo para calcular hasta 20! a partir de ahi ya salen numeros tan grandes que no son capaces de computarlos

                  Lo que quiero decir con esto es que si por ejemplo tienes 70 familias diferentes (cada una con n valores diferentes) y quieres obtener todas las posibles combinaciones y TENIENDO EN CUENTA EL ORDEN

                  Eso es un calculo monstruoso que puede tomar muchisimo tiempo aunque hagas un algoritmo hiper-mega-optimo

                  Comentario

                  • miko-lynn
                    Junior Member
                    • abr
                    • 21

                    #10
                    Originalmente publicado por ballan
                    Jeje, gracias hombre !!

                    Bueno lamento comunicarte que lo de leer la informacion no es algo nada sencillo
                    ...
                    ...
                    Eso es un calculo monstruoso que puede tomar muchisimo tiempo aunque hagas un algoritmo hiper-mega-optimo
                    Buf, si!
                    pero...
                    [EDITO] !!!! LO CONSEGUI!!!!

                    Después de montar todos los datos en la "tabla de Tablas", he hecho lo siguiente:

                    Código:
                    *Leemos la primera tabla en tabladetablas
                      READ TABLE  gt_tabfamval INTO gs_tabfamval INDEX 1.
                    *añadimos todos sus registrosen t_combis
                      LOOP AT gs_tabfamval-tabla INTO tablacharval.
                        s_combis-combi = tablacharval-char_value.
                        APPEND s_combis TO t_combis.
                      ENDLOOP.
                    *  contamos los registros maximos a tratar durante el siguiente pase y los 
                    *  colocamos en una variable.
                      DESCRIBE TABLE t_combis LINES tot.
                    *  La siguiente tabla a tratar será la 2 (si, así, tal cual)
                      ind = 2.
                    *  Y leeremos los datos de t_combis desde el registro 1 (porqué hemos tratado la tabla nº1).
                      from = 1.
                      PERFORM recursiva5 USING ind
                                               from
                                               tot.
                    Entonces, después de romperme los cuernos mil horas y varios minutos de regalo, he conseguido hacer el form recursivo, de manera sencilla y, mejor aún, que funciona. Por el camino, vamos recogiendo muchos datos superfluos, pero como no hacemos nada con ellos ya que llevamos los contadores FROM y TOT para tratar sólo los datos que nos interesa tratar, la ejecución es bastante rápida. (menos de un segundo para cerca de mil registros).

                    Código:
                    *&---------------------------------------------------------------------*
                    *&      Form  recursiva5
                    *&---------------------------------------------------------------------*
                    *    Donde vamos añadiendo los diferentes valores a un string que
                    * añadimos a la tabla t_combis.
                    *----------------------------------------------------------------------*
                    FORM recursiva5 USING ind
                                          from
                                          tot.
                      DATA: combina TYPE string.
                    
                    *leeremos todos los registros guardados en t_combis y a estos, les
                    *   concatenaremos los nuevos registros de la tabla ind
                      LOOP AT t_combis INTO s_combisori.
                    * si el registro a tratar está entre FROM y TO
                        IF ( sy-tabix GE from ) AND ( sy-tabix LE tot ).
                    *Leemos la tabla ind
                          READ TABLE  gt_tabfamval INTO gs_tabfamval INDEX ind.
                    * loopeamos dentro de ella para leer todos sus registros
                          LOOP  AT gs_tabfamval-tabla INTO tablacharval.
                    * concatenamos
                            CONCATENATE s_combisori-combi '.' tablacharval-char_value INTO combina.
                    * Guardamos el resultado en t_combis.
                            s_combis-combi = combina.
                            APPEND s_combis TO t_combis.
                          ENDLOOP.
                        ENDIF.
                      ENDLOOP.
                    *  añadimos 1 a ind que será la proxima tabla a leer.
                      ADD 1  TO ind.
                    * limpiamos variables
                      CLEAR: combina.
                    * El valor 'desde' (FROM) de la proxima pasada será el TOT de la tabla presente más '1'.
                      from = tot.
                      ADD 1 TO from.
                    
                    * El valor 'hasta' (TOT) hasta de la siguiente pasada será el nº de lineas 
                    * actual.
                      DESCRIBE TABLE t_combis LINES tot.
                    * Y  si aún tenemos familias para leer.volvemos al form recursivo
                      IF ind LE familias.
                        PERFORM recursiva5 USING ind
                                                 from
                                                 tot.
                      ELSE.
                    *si hemos llegado al limite de familias. cargamos los datos definitivos, los 
                    * registros que hemos tratado en esta pasada en una tabla definitiva.
                        CLEAR s_combis.
                        LOOP AT t_combis INTO s_combis.
                          IF ( sy-tabix GE from ) AND ( sy-tabix LE tot ).
                            append s_combis to t_combisdef.
                          ENDIF.
                        ENDLOOP.
                    *  y borramos la tabla combis para eliminar los registros superfluos.
                        REFRESH t_combis.
                      ENDIF.
                    ENDFORM.                                                    "recursiva5
                    Hale, aquí lo dejo para que las futuras generaciones que tengan que lidiar con un form recursivo puedan usarlo (y seguro mejorarlo )
                    MUCHAS GRACIAS!
                    Editado por última vez por miko-lynn; 17/09/2013, 16:31:15.

                    Comentario

                    • ballan
                      Senior Member
                      • oct
                      • 671

                      #11
                      La combinatoria es una ciencia compleja y ABAP no es un lenguaje orientado a ello por lo que la solucion se ha enrevesado bastante pero creo que al final ha quedado ha quedado un aporte excelente, tambien es de agradecer que hayas pegado el codigo final por si en el futuro alguien se encuentra en esta situacion

                      Felicitaciones !!

                      Comentario

                      Trabajando...