ugrás a tartalomhoz

Számítás önmagukat felüldefiniáló és memoizációt használó JavaScript függvényekkel

Poetro · 2011. Jún. 30. (Cs), 17.41

Ádám majd úgy is ki fog javítani, hogy ezeket hogyan lehetne magyarul írni: self defining function és memoization. Mindenesetre felmerült a probléma, hogy hogyan lehetne optimalizálni rekurzív függvényeket, úgy hogy egyfajta iterátorként is működhessenek. Ehhez írtam egy nagyszerű kis függvényt, ami a Fibonacci-sort számítja ki, de kicsit érdekesen. Ami meglepő lehet, hogy a függvény az első sorban felüldefiniálja és bővíti magát pár tulajdonsággal és metódussal. Például gyorstárazza a már eddig kiszámolt értékeket, mindezt egy hordozható formában.

Várom róla a véleményeket:


/**
 * Kiszámolja az n. Fibonacci-számot.
 * 
 * @param {Number} n
 *   Az a természetes szám, amelyik elemre a Fibonacci-sorban szükségünk van.
 * @return {Number}
 *   Az n. Fibonacci-szám.
 */
function fib(n) {
  /**
   * Felüldefiniáljuk a függvényünket, mely visszaadja az n. Fibonacci-számot.
   */
  fib = function (n) {
    if (fib.cache[n] === undefined) {
      fib.cache[n] = fib(n - 1) + fib(n - 2);
    }
    return fib.cache[n];
  }
  
  // Bővítjük saját tulajdonsággal és metódussal.
  fib.cache = [0, 1];

  /**
   * A következő nem gyorsítótárazott Fibonacci-számot számolja ki.
   * 
   * @return {Number}
   *   A következő még ki nem számított Fibonacci-szám.
   */
  fib.next = function () {
    return fib(fib.cache.length);
  }
  
  return fib(n);
}

(function(n) {
  // Lekérdezzük a 0. elemet.
  fib(0);
  
  while (n--) {
    // Meghívjuk a next metódust 10-szer.
    console.log(fib.next());
  }
  
  console.log('A 20. Fibonacci-szám:', fib(20));
}(10));

Kimenet:

1
2
3
5
8
13
21
34
55
89
A 20. Fibonacci szám: 6765
 
1

Ezeket általában úgy szokás,

Joó Ádám · 2011. Jún. 30. (Cs), 21.02
Ezeket általában úgy szokás, hogy a függvény minden futáskor leellenőrzi, létezik-e már a gyorsítótár. Ezt a feltételvizsgálatot akarod megspórolni az újradefiniálással?

Egyébként én mindegyszerűen öníró (nektek öndefiniáló) függvényeknek nevezném őket. A memoizációra akadémiai körökben lehet, hogy létezik már valamilyen szép magyar fordítás, én hirtelenjében mondjuk az emlékező függvényt adnám nekik a keresztségben.
3

Gyorsítás

Poetro · 2011. Jún. 30. (Cs), 21.40
Ezeket általában úgy szokás, hogy a függvény minden futáskor leellenőrzi, létezik-e már a gyorsítótár. Ezt a feltételvizsgálatot akarod megspórolni az újradefiniálással?

Hát igazából ez egy tervezési minta, amivel az inicializálást és ellenőrzést lehet elkerülni. A lényeg, hogy egészen addig nem történik meg a függvény előkészítése, amíg meg nem hívódik. Ezzel memóriát, illetve amennyiben az inicializáció költséges akkor teljesítményt javíthatunk. Amennyiben a függvényünket nem hívják meg, az inicializáció se fut le feleslegesen. És igen a függvényben minden egyes futáskor nem kell ellenőrizni, hogy megtörtént-e már az inicializáció, mert ebben biztosak lehetünk.
Ugyanakkor vannak más tényezők is. Például idegen kód törölheti a gyorsítótárunkat, de erre felkészülhetünk, ha hozzáadunk a függvényhez egy init metódust, ami elvégzi az újbóli inicializációt, és így konzisztens maradhat a működés, és a gyorsítótár is üríthető, amennyiben a működés azt megkívánja.
Például:
function fib(n) {
  var cache;
  fib = function (n) {
    if (cache[n] === undefined) {
      cache[n] = fib(n - 1) + fib(n - 2);
    }
    return cache[n];
  }
  
  fib.init = function () {
    // Valami intenzív tevékenység.
    cache = [0, 1];
  };
  fib.next = function () {
    return fib(cache.length);
  }

  return fib.init(), fib(n);
}
2

Príma

joed · 2011. Jún. 30. (Cs), 21.24
Príma, most kérnék szépen egy életszerű példát! :)
4

Böngésző független eseménykezelés

Poetro · 2011. Jún. 30. (Cs), 21.56
Például tegyük fel, hogy meg akarsz valósítani egy böngésző független eseménykezelést.
function addEvent(element, event, callback) {
  addEvent = window.addEventListener ?
    // W3C szabványos eseménykezelés
    function (element, event, callback) {
      element.addEventListener(event, callback);
    } :
    window.attachEvent ?
    // MS eseménykezelés
    function (element, event, callback) {
      element.attachEvent('on' + event, callback, false);
    } :
    // DOM0 eseménykezelés 
    function (element, event, callback) {
      element['on' + event] = callback;
    };
  addEvent(element, event, callback);
}
Ekkor nem kell minden egyes futáskor megnézni, a böngésző milyen eseménykezelési modellt támogat, hanem elég egyszer, a függvény első futásakor.
6

Érdekes megoldás, én ezt alap

inf · 2011. Jún. 30. (Cs), 23.12
Érdekes megoldás, én ezt alapból closure-ban csinálnám, vagy csinálnék külön ie és w3c fájlokat, de ez is egész jó.
5

Ezért jó nevet adni a

Karvaly84 · 2011. Jún. 30. (Cs), 22.52
Ezért jó nevet adni a függvényeinknek, plusz lett pár ötletem!
7

A memoizálásban az a szép,

tgr · 2011. Júl. 1. (P), 01.18
A memoizálásban az a szép, hogy nem kell minden függvényhez kézzel megírni:

function fib(n) {
    return (n < 2) ? n : fib(n-1) + fib(n-2);
}

Function.prototype.memoize = function(fun) {
    var cache = {};
    return function() {
         var args = Array.prototype.slice.call(arguments);
         return (args in cache) ? cache[args] : (cache[args] = fun.apply(this, arguments));
    }
}

fib = fib.memoize();
8

fun

T.G · 2011. Júl. 1. (P), 08.20
A fun változó nem paraméterből jön, hanem az a this lenne, nemdebár?
11

De, persze.

tgr · 2011. Júl. 1. (P), 09.14
De, persze.
12

Yepp, innentől meg ugyanott

inf · 2011. Júl. 1. (P), 11.50
Yepp, innentől meg ugyanott vagyunk, mint a bind, meg a hasonlók. Én pl anno csináltam automatikus típusellenőrzést is metódusokhoz hasonló elven. Valahogy így működött:

Color=Class(
{
	red: Number,
	green: Number,
	blue: Number,
	rgb: Null
},
{
	rgb: function (red,green,blue)
	{
		//...
	},
	//...
});
Nagyon sok dolgot lehet automatizálni beágyazott függvényekkel...
9

Inicializálás…

presidento · 2011. Júl. 1. (P), 08.23
Nekem csak az nem szimpatikus benne, hogy indirekt hivatkozás esetén minden alkalommal lefut az inicializálás…
var myFun = fib;
myFun(10); // inicializál
myFun(10); // inicializál
13

var myFun = (fib(0), fib);

Poetro · 2011. Júl. 1. (P), 11.52
var myFun = (fib(0), fib);  // Előre inicializálunk
myFun(10); // nem inicializál  
myFun(10); // nem inicializál  
10

init csak egyszer

T.G · 2011. Júl. 1. (P), 08.47
Nálam gyakori a következő megoldás:

new Button({
	handler: function () {
		this.initHandler();
		this.handler = this.doHandler;
		this.doHandler();
	}
});
Az előkészítést csak akkor indítom el, amikor valóban szükség van rá, a későbbiekben meg ahelyett, hogy ellenőrizném, hogy már ez lefutott-e, egyszerűen kihagyom.
14

Hasznos cucc, már fel is

inf · 2011. Júl. 1. (P), 23.13
Hasznos cucc, már fel is használtam a gyakorlatban :-)