Comunidad de diseño web y desarrollo en internet

Obtener valores de funciones lineales en Actionscript 3

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 :(
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 :wink: )

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.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate