Cristalab

Obtener valores de funciones lineales en Actionscript 3

Por: HernanRivas + 28.01.2008

Trabajo, trabajo. Mucho tiempo alejado de estos foros. Pero ya vienen más experimentos de física, vehículos derrapando, y cosas por el estilo. Si no surge ningún contratiempo esta semana o la próxima voy a estar posteando el primero (si, genero expectativa porque puedo mwhahaha).

En este caso, una clase que escribí para un posible juego de carreras (obviamente tiene otros usos) luego de ver un libro de física.

Lo que vi fue un gráfico que representaba el torque (ver artículo en la Wikipedia) de un motor en función de las rpm. Algo muy similar a lo que se ve en ésta imagen (una coincidencia, que la primer imagen de Google sea de un Porsche, ya que en el libro el ejemplo era de un vehículo de la misma marca).

Pero el tema automovilístico no nos interesa por el momento (quizá en un futuro?). Lo que importa es ver como la curva de torque está formada por distintos datos experimentales que la convierten en una serie de funciones lineales, que tienen por ecuación y = ax + b. Nuestra clase va a representar una tabla de valores que nos va a permitir calcular el valor (aproximado) de, por ejemplo, el torque a determinadas rpm. Para ello, suponemos que dos puntos contiguos que hemos añadido a la lista (con la función addData), hay una línea recta, y así podremos inferir los valores intermedios. Esto lo podemos ver en éste gráfico, donde a partir de una serie de puntos generamos los segmentos que los unen, y sacamos a partir de ellas los valores de la función:

Click para generar un nuevo gráfico


Código :

package
{
 public class MultiPointLinearFunction
 {
 
  /*
  PRIVATE VARIABLES
  */
   private var tempArray:Array;
   private var indexArray:Array;
   private var valuesArray:Array;
   private var lenght:int;
 
  /*
  CONSTRUCTOR
  */
   public function MultiPointLinearFunction ()
   {
    tempArray = new Array ();
    lenght = 0;
   }
 
  /*
  METHODS
  */
   // Use this function to add new "experimental data"
   public function addValue (x:Number, y:Number):void
   {
    tempArray.push ([x, y]);
    lenght++;
   }
   // This function generates new arrays for faster calculations the process isn't very fast so it has to be used wiselly
   // Also it is neccessary to execute the function at least once before getting y coordinates
   public function generateTable ():void
   {
    tempArray.sort (multiSort);
    indexArray = new Array ();
    valuesArray = new Array ();
    for (var i:int = 0; i < lenght; i++)
    {
     indexArray.push (tempArray[i][0]);
     valuesArray.push (tempArray[i][1]);
    }
   }
   public function getY (x:Number):Number
   {
    var maxIndex:int;
    var minIndex:int;
    var rx:Number; // The closest x coordinate greater (rightX simplified as rx) than the provided x value
    var lx:Number; // The closest x coordinate lesser (leftX simplified as lx) than the provided x value
    var ry:Number; // The y value corresponding to the aforementioned rx
    var ly:Number; // The y value corresponding to the aforementioned lx
   
    for (var i:int = 0; i < lenght; i++)
    {
     if (indexArray[i] > x)
     {
      if (i == 0)
      {
       maxIndex = 1;
       minIndex = 0;
       rx = indexArray[maxIndex];
       lx = indexArray[minIndex];
       ry = valuesArray[maxIndex];
       ly = valuesArray[minIndex];
       return ((ry - ly) / (rx - lx)) * (x - lx) + ly;
      }
      else
      {
       maxIndex = i;
       minIndex = i - 1;
       rx = indexArray[maxIndex];
       lx = indexArray[minIndex];
       ry = valuesArray[maxIndex];
       ly = valuesArray[minIndex];
       return ((ry - ly) / (rx - lx)) * (x - lx) + ly;
      }
     }
    }
   
    maxIndex = lenght - 1;
    minIndex = lenght - 2;
    rx = indexArray[maxIndex];
    lx = indexArray[minIndex];
    ry = valuesArray[maxIndex];
    ly = valuesArray[minIndex];
    return ((ry - ly) / (rx - lx)) * (x - lx) + ly;
   }
   
   public function toString ():String
   {
    var str:String = "[object, MultiPointLinearFunction]\ninitialized = " + (indexArray != null).toString () + "\ntotalValues = " + lenght.toString ();
    return str;
   }
 
  /*
  IMPLEMENTATION
  */
   // Sorts the Array according to the first element of the nested Arrays
   private function multiSort (a:Array, b:Array):int
   {
    var xa:Number = a[0];
    var xb:Number = b[0];
    if (xa > xb)
    {
     return 1;
    }
    else if (xa < xb)
    {
     return -1;
    }
    else
    {
     return 0;
    }
   }
 }
}


La función no es muy complicada, lo que hace es detectar entre qué "valores experimentales" se halla el x dado y calcular su valor de acuerdo a la función lineal que describen.

El único problema (tengo pensado postear la versión v.2 en los próximos días, así que manténganse atentos a los comentarios) es que usamos fuerza bruta Triste
Lo ideal sería que en lugar de revisar los índices del Array uno por uno, tomaramos el del medio ( array[int (array.lenght / 2)] ) para ver en qué mitad debería estar nuestro valor. Seguir partiendo el Array en 2 para encontrar entre qué 2 números se halla nuestro valor.

Puede sonar excesivamente complicado, pero ahorra muchísimo tiempo al CPU.

Por ejemplo, con 1024 valores, el peor caso usando fuerza bruta nos obliga a revisar 1023 elementos inútilmente, usar el método de dividir por 2 requiere sólo 10 (porque 2^10 = 1024 si esto no resulta obvio, no se preocupen, o voy a explicar cuando termine el código Guiño )

Otra mejora que tengo planeada es precalcular b y m para cada par de valores (si no saben de qué hablo, no importa, también lo voy a explicar en el futuro), aprovechando que ya uso una etapa donde precalculo un par de cosas para acceder más rápido, a la información (un Array dos Array unidimensionales son más rápidos de acceder que uno bidimensional), lo que permitiría hacer las cuentas muchísimo más rápido.

Pero creo que por el momento, con el ejemplo anterior alcanza y si lo necesitan urgente, no creo que les cueste agregar las 2 cosas que comenté antes.

Etiquetas actionscript_3

Comentarios | Enviar un comentario
"Lo prometido es deuda" (dicen los que cumplen, los otros dicen "a las palabras se las lleva el viento"). En este caso, agrego la búsqueda binaria y el cacheado de los valores b y m de las distintas funcioones lineales.

Una pequeña explicación antes:

La búsqueda binaria consiste en tomar una lista de n valores ordenados y encontrar el índice de uno de ellos. Esto se podría hacer por fuerza bruta (como en mi primer ejemplo), pero la búsqueda binaria es más rápida. Básicamente partimos el Array en 2 y nos fijamos en qué mitad está nuestro valor (por eso es esencial que esté ordenado). Continuamos con esta técnica hasta encontrar el valor que buscamos.

Los valores m y b de una función lineal son su pendiente y su ordenada al origen, respectivamente. Para simplificar, m es la velocidad a la que cambia el valor de y respecto a x y b es el valor que tiene la función en x = 0.

Código :

package
{
 public class MultiPointLinearFunction
 {
  /*
  PRIVATE VARIABLES
  */
   private var tempArray:Array;
   //
   private var indexArray:Array;
   private var valuesArray:Array;
   private var indexes:int;
   private var currentFunction:Function;
   // Precalculate this values for faster operations
   private var globalM:Number;
   private var globalB:Number;
   private var minX:Number;
   private var maxX:Number;
 
  /*
  CONSTRUCTOR
  */
   public function MultiPointLinearFunction ()
   {
    tempArray = new Array ();
    indexes = 0;
   }
 
  /*
  METHODS
  */
   // Use this function to add new "experimental data"
   public function addValue (x:Number, y:Number):void
   {
    tempArray.push ([x, y]);
    indexes++;
    currentFunction = bruteForce;
   }
   // This function generates new arrays for faster calculations the process isn't very fast so it has to be used wiselly
   // Also it is neccessary to execute the function at least once before getting y coordinates
   public function generateTable ():void
   {
    tempArray.sort (multiSort);
    indexArray = new Array ();
    valuesArray = new Array ();
    var x1:Number, y1:Number, x2:Number, y2:Number, m:Number, b:Number;
    minX = tempArray[0][0];
    maxX = tempArray[indexes - 2][0];
    for (var i:int = 0; i < indexes - 1; i++)
    {
     x1 = tempArray[i][0];
     y1 = tempArray[i][1];
     x2 = tempArray[i + 1][0];
     y2 = tempArray[i + 1][1];
     m = (y2 - y1) / (x2 - x1);
     b = y1 - x1 * m;
     indexArray.push (x1);
     valuesArray.push (m, b);
    }   
    if (indexes == 0)
    {
     globalM = globalB = 0;
    }
    else
    {
     globalM = valuesArray[0];
     globalB = valuesArray[1];
    }
    // Choose the ideal algorithm
    if (indexes <= 2)
    {
     currentFunction = fastCalculation;
    }
    else if (indexes <= 8)
    {
     currentFunction = bruteForce;
    }
    else
    {
     currentFunction = binarySearch;
//     currentFunction = bruteForce;
    }
   }
   public function getY (x:Number):Number
   {   
    return currentFunction (x);
   }
   public function toString ():String
   {
    var str:String = "[object, MultiPointLinearFunction]\ninitialized = " + (indexArray != null).toString () + "\ntotalValues = " + indexes.toString ();
    return str;
   }
  /*
  IMPLEMENTATION
  */
   private function fastCalculation (x:Number):Number // Used only with up to two elements (saves time by avoiding array access)
   {
    return globalM * x + globalB;
   }
   private function binarySearch (x:Number):Number // The fastest version. Yet, there is some place for optimization, poor performance for small Arrays
   {
    var m:Number, b:Number;
    if (x <= minX)
    {
     m = valuesArray[0];
     b = valuesArray[1];
    }
    else if (x >= maxX)
    {
     m = valuesArray[(indexes - 1) * 2 - 2];
     b = valuesArray[(indexes - 1) * 2 - 1];
    }
    else
    {
     var sampleLenght:Number = indexes;
     var sampleLenghtInt:int;
     var sum:Number = 0;
     var midValue:Number = 0;
     
     while (sampleLenght > 2)
     {
      sampleLenght *= .5;
      sampleLenghtInt = int (sampleLenght); // Caching the interger allows faster Array lookups and bitwise operations
     
      midValue = indexArray[int(sampleLenghtInt + sum)];
           
      if (midValue > x)
      {
//       if (sum > 0) sum -= sampleLenghtInt;
      }
      else if (midValue < x)
      {
       sum += sampleLenght;
      }
      else
      {
       // The midValue equals our query!
       trace ("lucky");
       sum = Math.round (sum);
       m = valuesArray[sum * 2];
       b = valuesArray[sum * 2 + 1];
       return m * x + b;
      }
     }
     sum = int (sum);
     trace ("sum (i) = " + sum.toString ())
     m = valuesArray[sum * 2];
     b = valuesArray[sum * 2 + 1];
    }
    return m * x + b;
   }
   private function bruteForce (x:Number):Number // The brute force method, reccommended for any lenght between 2 and 8
   {
    var m:Number, b:Number;
   
    if (x <= minX)
    {
     m = valuesArray[0];
     b = valuesArray[1];
    }
    else if (x >= maxX)
    {
     m = valuesArray[(indexes - 1) * 2 - 2];
     b = valuesArray[(indexes - 1) * 2 - 1];
    }
    else
    {
     for (var i:int = 0; i < indexes - 1; i++)
     {
      if (indexArray[i + 1] > x)
      {       
       m = valuesArray[i * 2];
       b = valuesArray[i * 2 + 1];
       
       trace ("i = " + i)
       
       i = indexes; // End the loop
      }
     }
    }
    return m * x + b;
   }
   
   // Sorts the Array according to the first element of the nested Arrays
   private function multiSort (a:Array, b:Array):int
   {
    var xa:Number = a[0];
    var xb:Number = b[0];
   
    if (xa > xb)
    {
     return 1;
    }
    else if (xa < xb)
    {
     return -1;
    }
    else
    {
     return 0;
    }
   }
 }
}
 

Por: HernanRivas
A esto deberías añadirle una gráfica interactiva o algo para que se entienda mejor. Además estaría bien que soportara la clase Point (que quizá tenga algún método que ayude). Por lo demás, buen trabajo. Sonrisa

Glad to see you again! miau
Por: Zah
Zah habla con verdad, una pequeña gráfica que ilustre tu punto puede ayudar, sobre todo al noob promedio que se lanza al piso en convulsiones al ver tanto código, por autoexplicativo que sea.
Por: Freddie
*levanta la cabeza al sentirse identificado por el comenterio de F
Por: penHolder
Ahora entiendo porqué hay que venerar a Freddie Riendo.
Por: gustavogarzon

Click para generar un nuevo gráfico


Es interesante cómo calcula también los valores fuera de los límites.

Pero armando el ejemplo, encontré un error. Hay que reemplazar esta línea:

Código :

midValue = indexArray[sampleLenghtInt + sum];

Por esta otra:

Código :

midValue = indexArray[int (sampleLenghtInt + sum)];


Apenado
Por: HernanRivas
Corregido.
Por: Zah
estabueno esto
Por: karen garcia_blog
Deja un comentario
IMPORTANTE

Recuerda ser respetuoso, no insultes a otras personas, ni uses palabrotas, hay una persona al otro lado de la pantalla.

Habla bien, NO ESCRIBAS EN MAYUSCULA TODO, no escribas como en un SMS, evita cosas como "ke", "x q" y demás abreviaciones.

Aquí funcionan las etiquetas de los foros, puedes usar [b] para negrita, [img] para las imágenes, [url] para los enlaces, etc.

Si tienes preguntas técnicas, envíalas mejor al foro.