Generar elementos usando multiplicación por duplicación

En un proyecto he tenido que generar una rejilla, siendo cada casilla una instancia del mismo elemento/objeto. La idea inicial ha sido usar dos bucles, uno anidado dentro del otro e iterar generando una instancia en cada posición, lo cual es muy fácil de implementar pero lento como ello solo y es que el método GameObject.Instantiate no es sinónimo de velocidad.

¿Posibles alternativas? Tras pensarlo un poco se me ha ocurrido que igual se podría acelerar el proceso instanciando cierta cantidad de casillas, que no de una en una. Aquí ha tocado investigar un poco para ver que posibles cálculos me ayudarían en la tarea y me he decantado por utilizar la multiplicación por duplicación.

f(n) 2^n  -> 1 2 4 8...

// Usando como coeficiente el '5'
f(n) 2^nB -> 2^n5 = 5 10 20 40...

Si por ejemplo usase una rejilla con cinco casillas de alto, podría generar esas cinco casillas, que componen la primera columna, usando un mismo objeto padre para todas ellas y después clonarlo e ir acumulando e incrementando el «tamaño» ( cantidad de hijos o casillas en este contexto ) de dicho objeto padre, consiguiendo ahorrarme muchas iteraciones.

El cálculo inverso de Math.pow, que se usa para elevar una cantidad/base a un determinado exponente/potencia, es Math.log y con este dato y un poco de razonamiento es fácil llegar a la solución 🙂

// Eleva una base a una determinada potencia
// a = Math.pow ( b,p )

// Calcula cual seria la potencia necesaria para alcanzar
// el anterior resultado usando la base indicada
// p = Math.log ( a ) / Math.log ( b )

b = 2
a = Math.pow ( b, 9 ) = 512
p = Math.log ( a ) / Math.log ( b ) = 2.7 / 0.3 = 9

La información con la que parto es el número total de casillas de la rejilla y la cantidad de ellas que componen cada columna. He usado la multiplicación por duplicación para calcular cuantas veces he de duplicar ( y acumular ) la columna inicial para conseguir generar igual o mayor cantidad de casillas que las que componen la rejilla.

// Una rejilla imaginaria cuenta con 8640 casillas
// Cada columna esta compuesta por 27 casillas
f(n) = 2^nB = 2^n27 >= 8640

// Numero total de casillas entre numero de casillas por columna,
// devolviendo el numero total de columnas
a = 8640 / 27 = 320
b = 2
p = Math.log ( a ) / Math.log ( b ) = 2.505 / 0.301 = 8.322

// Comprobacion
a = Math.pow ( b, 8.322 ) = 320.016 ≈ 320

En el ejemplo del fragmento de código anterior se necesitaría duplicar 8+ ( en la práctica eso significa 9 ) veces la columna inicial, para llegar a contar con tantos objetos como requiere la rejilla para representar todas sus casillas.

Lo que se hace no es clonar n veces la columna inicial, si no que se duplicará el total de casillas creadas en cada instante, partiendo de la columna inicial. En el ejemplo anterior para alcanzar una cantidad de elementos igual o superior al total que componen la rejilla, hay que duplicar 9 veces el 27, dando como resultado la siguiente sucesión..
27 x 2➝54➝108➝216➝432➝864➝1728➝3456➝6912➝13824 >= 8640

El tiempo de ejecución resultante es menor con respecto a instanciar casilla a casilla y mejora cuanto mayor es el número de elementos a generar. Generalmente se crearán elementos de más pero no es ningún problema, pues basta con desechar los sobrantes, que siguiendo con el mismo ejemplo serían ( 13824 – 8640 = ) 5184 elementos de más…

//... Crear primera columna
int rowCount    = 0;
int columnCount = 0;
GameObject cell;
GameObject cellBase = Resources.Load<GameObject>( "Prefabs/Cell" );
for ( rowCount = 0; rowCount < cellsPerColumn; rowCount++ )
{
    cell = GameObject.Instantiate ( cellBase, parentCell );
    //...
}

Transform tempParent;
int childCount = selectedCoeffi;
for ( int p = 1; p <= power; p++ )
{
    tempParent = GameObject.Instantiate ( parentCell.gameObject ).transform;

    for ( int c = tempParent.childCount - 1; c >= 0; c-- )
    {
        // Solo se recupera la cantidad de hijos necesaria
        if ( ++childCount <= totalCells )
        {
            ( cell = tempParent2.GetChild ( c ).gameObject )
                .transform.parent = this.parentCell;
            //...
        }
        else break;
    }

    Destroy ( tempParent.gameObject );
}

cell       = null;
cellBase   = null;
tempParent = null;

…y por eso solo faltaba poder comprobar que potencia y coeficiente generarían la menor cantidad de elementos extra. El caso es que no hay porque usar justamente el tamaño ( número de casillas ) de la columna de la rejilla, sino que puede ser una cantidad inferior…porque cuantas menos casillas «únicas» se tengan que crear, mejor, no teniendo sentido crear más que el tamaño de la columna.

// Se comprueba que base es la que generaria menos excedente de casillas a generar
int power = 0;
int powerTemp;
int minDifTemp;
int minDiff = int.MaxValue;
int coeffi  = numRows;
for ( int c = this.numRows; c > 0; c-- )
{
    // Aqui!!! es donde esta lo realmente interesante de toda esta entrada
    powerTemp  = Mathf.CeilToInt ( Mathf.Log ( totalCells / c ) / Mathf.Log ( 2 ) );
    minDifTemp = ( ( int )Mathf.Pow ( 2, powerTemp ) * c ) - totalCells;

    if ( minDifTemp < minDiff )
    {
        power   = powerTemp;
        minDiff = minDifTemp;
        coeffi  = b;
    }
}

// Crear primera columna
int rowCount    = 0;
int columnCount = 0;
GameObject cell;
GameObject cellBase = Resources.Load<GameObject>( "Prefabs/Cell" );
for ( rowCount = 0; rowCount < coeffi; rowCount++ )
{
    //...

Volviendo por última vez al ejemplo planteado, la mejor opción sería decantarse por utilizar como potencia el 9 y como coeficiente 17, consiguiendo generar un excedente de tan solo 64 elementos.

Potencia | Coeficiente
  9            27      = 5184
  9            26      = 4672
  9            25      = 4160
  9            24      = 3648
  9            23      = 3136
  9            22      = 2624
  9            21      = 2112
  9            20      = 1600
  9            19      = 1088
  9            18      = 576
  9            17      = 64   <- WIN!
  9            15      = 7744
  9            14      = 6720
etc...

Iteraciones..
01| 17   x 2 = 34
02| 32   x 2 = 68
03| 68   x 2 = 136
04| 136  x 2 = 272
05| 272  x 2 = 544
06| 544  x 2 = 1088
07| 1088 x 2 = 2176
08| 2176 x 2 = 4352
09| 4352 x 2 = 8704 - 8640 = 64 elementos de mas

 

 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *