ugrás a tartalomhoz

Fa transzformáció

inf · 2011. Aug. 9. (K), 21.47
Sziasztok!

Van egy általános jellegű probléma, amire a leghatékonyabb megoldást igyekszem megtalálni.

Arról van szó, hogy van egy fa adatszerkezetem, amit át szeretnék alakítani egy másik fává, úgy, hogy az egyes node-okat feldolgozom. Arra vagyok kíváncsi, hogy ti hogyan oldanátok meg ezt a problémát?

példa:

{
	d: "{$c.d}"
	c: "{$a.b.c}",
	a:
	{
		b:
		{
			c:
			{
				d:1
			}
		}
	}
}
=>

{
	d: 1,
	c: {d:1},
	a:
	{
		b:
		{
			c:
			{
				d:1
			}
		}
	}
}
// tree.a.b.c===tree.c: true
(Egyelőre nem árulok el abból semmit, hogy én merre indulnék, hátha valami gyökeresen új ötletet kapok.)
 
1

No igazából ez nem csak fákra

inf · 2011. Aug. 10. (Sze), 17.22
No igazából ez nem csak fákra érvényes probléma, hanem minden bejárható gyűjteményre. A lényeg, hogy be kell járni a gyűjteményt, aztán minden egyes elemet egyesével feldolgozni. Kell egy referencia, amiben le kell tárolni a már feldolgozott elemeket, meg kell egy queue, amiben az olyanokat kell letárolni, amik olyan elemet használnak, ami még nincs meg a referenciában. Ezek után ha végigmentünk a gyűjteményen, akkor sorba kell tenni a queue-t függőség szerint, és azt is feldolgozni.
Persze mindezt lehet úgy is, hogy nem tesszük sorba, csak addig toljuk végig egy while-al, amíg minden elem el nem fogy belőle. Meg lehet úgy is, hogy már a feldolgozás közben úgy adjuk hozzá az új elemeket, hogy eleve sorban legyen, meg úgy is lehet, hogy már a feldolgozás közben minden körben ellenőrizzük, hogy az új elem, ami bekerült a referenciába szükséges volt e valamelyikhez, ami a queue-ben van.

Most tákolok javascriptben egy kisebb modult, ami ilyet használ, majd ha elkészült, akkor ismertetem.
2

Kör

Poetro · 2011. Aug. 10. (Sze), 17.27
Az a baj ezzel, hogy könnyen kialakulhat körkörös referencia, és azt nehéz feldolgozni. Ahogy én csinálnám, valami elválasztó jel mentén bepakolnám a már feldolgozottakat egy objektumba (pl processed['a.b.c'] = a.b.c) és ebben keresnék. Ha megvan az elem, akkor egyszerűen visszaadnám az értékét, ha nincs, akkor bekerülne egy feldolgozandó objektumba / tömbbe, és minden egyes feldolgozás esetén végigmennék és, és az aktuális elem már fel lett dolgozva, akkor kivenném a feldolgozandók közül.
3

Egyáltalán nem nehéz a

inf · 2011. Aug. 10. (Sze), 18.08
Egyáltalán nem nehéz a körkörös referenciát feldolgozni. Ha mondjuk a queue-n csak iterálunk, és nem rendezünk, akkor figyeljük a ciklus elején az elemek számát és a végén is, és ha nem csökkent, akkor körkörös referencia van, és dobunk egy exceptiont. Ha sorba rendezünk, akkor a rendező algoritmus meg olyan, hogy az se kerülhet végtelen ciklusba, szóval ott sincs gond.

már feldolgozottakat egy objektumba (pl processed['a.b.c'] = a.b.c) és ebben keresnék

Jaja, én ezt hívtam referenciának. Amúgy érdemes csinálni feldolgozás közben egy függőség alapú referenciát is. Mondjuk ha "a" függ "b"-től meg "c"-től, akkor lehet csinálni egy map-et valahogy így: {b: ["a"], c: ["a"]}, és ha bekerül a "b" vagy a "c" elem a referenciába, akkor ki lehet törölni a függőségekből, ha meg egy elemnek elfogyott az összes függősége, akkor az feldolgozásra kerül, és szintén bekerül a referenciába. Így nem kell a végén a rendezéssel foglalkozni, hanem feldolgozás közben lehet nézni az ilyesmit. Azt hiszem csinálok három változatot, az egyik addig iterál a queue-n a végén, amíg el nem fogynak az elemek, a másik rendezi a queue-t függőségek szerint, a harmadik meg menet közben nézi a függőségeket, és úgy dolgoz fel. Kíváncsi vagyok, hogy a három változat közül melyik a legátláthatóbb és melyik a leggyorsabb...
4

No megtákoltam az utólagos

inf · 2011. Aug. 11. (Cs), 16.34
No megtákoltam az utólagos rendezős megvalósítást:

tesztek: qunit

module("command collection");

test("array iterator",function ()
{
	var iterator=new ArrayIterator();
	iterator.setIterable(["a","b","c"]);
	deepEqual(iterator.getIterable(),["a","b","c"],"should iterable getter and setter work");
	
	var copy=[];
	iterator.forEach(function ()
	{
		copy[this.getKey()]=this.getValue();
	});
	deepEqual(copy,["a","b","c"],"should iterator iterate on every array element");
});

test("queue sorter", function ()
{
	var sorter=new MyQueueSorter();
	var item={};
	var commands=
	[
		{command: "pointer", key: 1, value: 0, dependencyList: [0]},
		{command: "pointer", key: 2, value: 1, dependencyList: [1]},
		{command: "put", key: 0, value: item},
		{command: "put", key: 4, value: item},
		{command: "pointer", key: 3, value: 4, dependencyList: [4]}
	];
	equal(sorter.sortStep(commands[1],commands[0]),-1,"sortstep: {2,pointer(1)}, {1,..}");
	equal(sorter.sortStep(commands[0],commands[1]),1,"sortstep: {1,..}, {2,pointer(1)}");
	
	equal(sorter.sortStep(commands[1],commands[2]),-1,"sortstep: {2,pointer(1)}, {0,..}");
	equal(sorter.sortStep(commands[2],commands[1]),1,"sortstep: {0,..}, {2,pointer(1)}");
	
	equal(sorter.sortStep(commands[3],commands[2]),0,"sortstep: {4,..}, {0,..}");
	equal(sorter.sortStep(commands[2],commands[3]),0,"sortstep: {0,..}, {4,..}");
	
	equal(sorter.sortStep(commands[4],commands[3]),-1,"sortstep: {3,pointer(4)}, {4,..}");
	equal(sorter.sortStep(commands[3],commands[4]),1,"sortstep: {4,..}, {3,pointer(4)}");
	
	equal(sorter.sortStep(commands[0],commands[4]),0,"sortstep: {1,pointer(0)}, {3,pointer(4)}");
	equal(sorter.sortStep(commands[4],commands[0]),0,"sortstep: {3,pointer(4)}, {1,pointer(0)}");
	
	var queue=
	{
		source: [commands[0],commands[1],commands[2]]
	};
	sorter.sort(queue);
	deepEqual(queue.source,[commands[1],commands[0],commands[2]],"Queue array sorting.");
});

test("command queue", function ()
{
	var item={};
	var queue=new MyCommandQueue();
	var commands=
	[
		{command: "pointer", key: 1, value: 0, dependencyList: [0]},
		{command: "pointer", key: 2, value: 1, dependencyList: [1]},
		{command: "put", key: 0, value: item}
	];
	queue.push(commands[0]);
	queue.push(commands[1]);
	queue.push(commands[2]);
	deepEqual([queue.pull(),queue.pull(),queue.pull()],[commands[2],commands[0],commands[1]],"Queue ordering commands.");
});

test("command array parser", function ()
{
	var item={};
	var iteratorInterface={getKey: function (){return this.key;},getValue:function (){return this.value}};
	var parser=new MyCommandArrayParser();
	
	deepEqual(
		parser.parseCommand({key:0,value:item,getKey:iteratorInterface.getKey,getValue:iteratorInterface.getValue}),
		{command: "put", key: 0, value: item},
		"parse put command"
	);
	
	deepEqual(
		parser.parseCommand({key:1,value:"@0",getKey:iteratorInterface.getKey,getValue:iteratorInterface.getValue}),
		{command: "pointer", key: 1, value: 0, dependencyList:[0]},
		"parse pointer command"
	);
	
	var commandQueue=parser.parseCommandCollection([item,"@0"]);
	deepEqual(commandQueue.source,[{command: "put", key: 0, value: item},{command: "pointer", key: 1, value: 0, dependencyList:[0]}],"command array parsing");
});

test("array builder", function ()
{
	var builder=new MyArrayBuilder();
	builder.newCollection();
	deepEqual(builder.getCollection(),[],"creating empty array");
	var item={};
	var item2={};
	builder.executeCommand({command: "put", key: 0, value: item});
	deepEqual(builder.getCollection(),[item],"execute put");
	builder.executeCommand({command: "put", key: 2, value: item2});
	deepEqual(builder.getCollection(),[item,undefined,item2],"execute put - index after length");
	builder.executeCommand({command: "pointer", key: 1, value: 2, dependencyList: [2]});
	deepEqual(builder.getCollection(),[item,item2,item2],"execute pointer");
});

test("command collection parser",function ()
{
	var processor=new CommandCollectionProcessor();
	processor.setCommandCollectionParser(new MyCommandArrayParser());
	processor.setCollectionBuilder(new MyArrayBuilder());
	var item={};
	deepEqual(processor.processCommandCollection([item,"@0"]),[item,item],"should processor process collection with ordered dependency");
	deepEqual(processor.processCommandCollection(["@1","@2",item]),[item,item,item],"should processor process collection with unordered dependency");
});
osztályok:
Még lehetne tákolni rajta, hogy minden objektum dependency injectionnel kerüljön a helyére...


var global=this;
var Class=
{
	breakInitialization: {},
	create: function (methods)
	{
		var targetClass=function ()
		{
			if ((this.initialize instanceof Function) && (!arguments.length || arguments[0]!==Class.breakInitialization))
			{
				this.initialize.apply(this,arguments);
			}
		};
		return this.override(targetClass,methods);
	},
	extend: function (sourceClass,methods)
	{
		var targetClass=this.create();
		targetClass.prototype=new sourceClass(this.breakInitialization);
		targetClass.prototype.constructor=targetClass;
		return this.override(targetClass,methods);
	},
	override: function (targetClass,methods)
	{
		if (methods)
		{
			for (var property in methods)
			{
				if (methods.hasOwnProperty(property))
				{
					targetClass.prototype[property]=methods[property];
				}
			}
		}
		return targetClass;
	}
};

var CommandCollectionProcessor=Class.create(
{
	processCommandCollection: function (commandCollection)
	{
		var builder=this.getCollectionBuilder();
		builder.newCollection();
		builder.executeCommandQueue(this.getCommandCollectionParser().parseCommandCollection(commandCollection));
		return builder.getCollection();
	},
	setCommandCollectionParser: function (commandCollectionParser)
	{
		this.commandCollectionParser=commandCollectionParser;
	},
	getCommandCollectionParser: function ()
	{
		return this.commandCollectionParser;
	},
	setCollectionBuilder: function (collectionBuilder)
	{
		this.collectionBuilder=collectionBuilder;
	},
	getCollectionBuilder: function ()
	{
		return this.collectionBuilder;
	}
});

var MyQueueSorter=Class.create(
{
	initialize: function ()
	{},
	sort: function (queue)
	{
		queue.source.sort(this.sortStep);
	},
	sortStep: function (leftCommand,rightCommand)
	{
		var stepOrder=0;
		if (leftCommand.command==rightCommand.command)
		{
			if (leftCommand.command=="pointer")
			{
				if (leftCommand.dependencyList.indexOf(rightCommand.key)!=-1)
				{
					stepOrder=-1;
				}
				else if (rightCommand.dependencyList.indexOf(leftCommand.key)!=-1)
				{
					stepOrder=1;
				}
			}
		}
		else if (leftCommand.command=="put")
		{
			stepOrder=1;
		}
		else
		{
			stepOrder=-1;
		}
		return stepOrder;
	}
});

var MyCommandQueue=Class.create(
{
	unordered: true,
	initialize: function ()
	{
		this.sorter=new MyQueueSorter();
		this.source=[];
	},
	push: function (command)
	{
		this.source.push(command);
		if (!this.unordered)
		{
			this.unordered=true;
		}
	},
	pull: function ()
	{
		if (this.unordered)
		{
			this.order();
		}
		return this.source.pop();
	},
	order: function ()
	{
		this.sorter.sort(this);
		this.unordered=false;
	}
});

var MyCommandArrayParser=Class.create(
{
	getCommandIterator: function (commandCollection)
	{
		var iterator=new ArrayIterator();
		iterator.setIterable(commandCollection);
		return iterator;
	},
	getCommandQueue: function ()
	{
		return new MyCommandQueue();
	},
	parseCommandCollection: function (commandCollection)
	{
		var queue=this.getCommandQueue();
		var parser=this;
		this.getCommandIterator(commandCollection).forEach(function ()
		{
			queue.push(parser.parseCommand(this));
		});
		return queue;
	},
	pointerPattern: new RegExp("^@(\\d+)$","m"),
	parseCommand: function (iterator)
	{
		if (this.pointerPattern.test(iterator.getValue()))
		{
			return this.parsePointerCommand(iterator);
		}
		else
		{
			return this.parsePutCommand(iterator);
		}
	},
	parsePointerCommand: function (iterator)
	{
		var pointerPatternMatch=this.pointerPattern.exec(iterator.getValue());
		var pointsTo=parseInt(pointerPatternMatch[1]);
		return (
		{
			command: "pointer",
			key: iterator.getKey(),
			value: pointsTo,
			dependencyList: [pointsTo]
		});
	},
	parsePutCommand: function (iterator)
	{
		return (
		{
			command: "put",
			key: iterator.getKey(),
			value: iterator.getValue()
		});
	}
});

var MyArrayBuilder=Class.create(
{
	newCollection: function ()
	{
		this.collection=[];
	},
	getCollection: function ()
	{
		return this.collection;
	},
	executeCommandQueue: function (commandQueue)
	{
		var command;
		while(command=commandQueue.pull())
		{
			this.executeCommand(command);
		}
	},
	executeCommand: function (command)
	{
		switch(command.command)
		{
			case "put":
				return this.putCommand(command);
			case "pointer":
				return this.pointerCommand(command);
		}
		throw new SyntaxError("Invalid command name.");
	},
	putCommand: function (command)
	{
		this.collection[command.key]=command.value;
	},
	pointerCommand: function (command)
	{
		this.collection[command.key]=this.collection[command.value];
	}
});

var ArrayIterator=Class.create(
{
	setIterable: function (iterable)
	{
		this.iterable=iterable;
	},
	getIterable: function ()
	{
		return this.iterable;
	},
	forEach: function (callback)
	{
		if (!this.iterable)
		{
			throw new SyntaxError("You have to set an iterable before the forEach.");
		}
		for (var index=0,count=this.iterable.length; index<count; ++index)
		{
			this.key=index;
			this.value=this.iterable[index];
			callback.call(this);
		}
	},
	getKey: function ()
	{
		return this.key;
	},
	getValue: function ()
	{
		return this.value;
	}
});

6

Formázási tanács

Poetro · 2011. Aug. 11. (Cs), 17.19
Szerintem hasznos lenne betartani pár formázási szabályt, hogy hasonló legyen más keretrendszerekben megszokottakkal, a leggyakoribb a K&R féle és annak valamelyik variánsa, mint a BSD KNF style. A másik hogy legalább a , után mindig rakj szóközt, mert a következő könnyen azonosnak olvasható:
this,arguments;
this.arguments;
9

Öö hát ez szövegszerkesztő

inf · 2011. Aug. 11. (Cs), 17.44
Öö hát ez szövegszerkesztő függő, de jó, ezentúl szóközt teszek a vessző után... :-)
10

A rendezést lehet tovább

inf · 2011. Aug. 11. (Cs), 17.55
A rendezést lehet tovább egyszerűsíteni:

	sortStep: function (leftCommand,rightCommand)
	{
		if (leftCommand.dependencyList && leftCommand.dependencyList.indexOf(rightCommand.key)!=-1)
		{
			return -1;
		}
		else if (rightCommand.dependencyList && rightCommand.dependencyList.indexOf(leftCommand.key)!=-1)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}
Ez viszont már egy nagyon általános forma, szóval a QueueSorter osztályra nincs is szükség innentől.
5

A fánál egyébként valami

inf · 2011. Aug. 11. (Cs), 16.42
A fánál egyébként valami ilyesmit akarok:

$(
{
	"package tests":
	{
		"class MyAbstractTest":
		{
			a: function ()
			{
				alert("a");
			}
		},
		"class MyTest extends $package.MyAbstractTest":
		{
			b: function ()
			{
				this.a();
			}
		}
	}
});

var test=new test.MyTest();
test.b(); //alert("a")
Elég sokat agyaltam, hogy hogy lehetne a prototípusok létrehozását meg az öröklést a legátláthatóbban megoldani, a végén erre jutottam... Elméletileg az összes keretrendszerhez fel lehet használni ezt a mintát.
7

Hack

Poetro · 2011. Aug. 11. (Cs), 17.23
Ha már ilyet hackelsz, miért nem használsz CoffeeScript-et, ott egyértelmű a felírás, és nem kell feleslegesen parsert se írni, ami futásidőben fordítja a kódot, hanem elég egyszer.
8

Kérdés, hogy a coffeescriptet

inf · 2011. Aug. 11. (Cs), 17.41
Kérdés, hogy a coffeescriptet hozzá lehet e illeszteni tetszőleges keretrendszerhez?
pl: mootools-ban new Class() -el hozol létre osztályt, prototype-ban és jquery-ben Class.create()-el, stb...


Mindenesetre a fenti kódot majd refaktorálom úgy, hogy egy általánosabb minta jöjjön ki belőle. Olyan helyeken lehet felhasználni, ahol bonyolultabb adatszerkezetet kell generáltatni, mondjuk bonyolultabb regexek generáltatásánál, vagy összetettebb sablonoknál.
11

coffeescript

Poetro · 2011. Aug. 11. (Cs), 19.13
A CoffeeScript JavaScript-et generál, és saját megoldása van az öröklődésre. De természetesen együtt tud működni bármilyen JavaScript keretrendszerrel, mivel meghívhatsz benne akármit, úgyis JavaScript-et generál.
12

Ahm, akkor úgy néz ki, hogy

inf · 2011. Aug. 11. (Cs), 22.03
Ahm, akkor úgy néz ki, hogy az jobb megoldás...
13

Csak halkan jegyzem meg, hogy

inf · 2011. Aug. 18. (Cs), 02.42
Csak halkan jegyzem meg, hogy ez a minta általánosan használható olyan gyűjtemények értelmezésénél, amelyeknél belső hivatkozások vannak. Mik is ezek? Legtöbbször config objectek.

pl:

<resources xmlns:require="require" xmlns="base">
	<mysql>
		<username>super</username>
		<password>12345:-)</password>
		<hostname>localhost</hostname>
		<type>mysql</type>
	<mysql>
	<sitedb1>
		<require:mysql />
		<database>db1</database>
	<sitedb1>
	<sitedb2>
		<require:mysql />
		<database>db2</database>
	<sitedb2>
</resources>

$resources->sitedb1->sql('SELECT ...');