"We are back" « oc.at

[JS] Unique Werte aus Array-Columns

ill 16.06.2011 - 20:20 3387 15
Posts

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
Hallo.

Ich hab hier ein Problem mit der Performance der JS-Engine von Internet Explorer 7.

Ich habe ein Array mit ca. 300 - 350 verschiedenen Einträgen. In manchen Columns liegen Strings, die mit IDs gefüllt sind und sich mit den Einträgen in anderen überschneiden :

Eintrag 1: "101;102;103;104;"
Eintrag 2: "101;103;106;" etc.

Nun möchte ich aus allen Einträgen im Array, nachdem eine Filterung passiert ist, alle übergebliebenen IDs einmalig in einem Array abgespeichert haben. Mittels diesen werden dann Filterkategorien ein- oder ausgeblendet. Für DOM-Manipulation kommt jQuery 1.6 zum Einsatz.

Hab mir hier kurz etwas gebastelt, bin mir aber ziemlich sicher, dass diese Lösung in Hinsicht auf Performance noch einiges zu wünschen übrig lässt:

Code: JS
var _uniques = documentFinder.datTable.fnGetFilteredData();
var lists = $('#tableHead .itunes .list-holder ul');
var ct = 1;
var uCol = [];
while(ct <= 4){
    var uniqueCol = "";
    var li= ct-1;
    var x=0;
    for(x; x <_uniques.length; x=x+1){
	if(ct===1){
	  uniqueCol += (_uniques[x][ct])+";";  
	}else{
	  uniqueCol += (_uniques[x][ct]); 
	}
    }
    uCol[li] = uniqueCol.split(";");
    uCol[li] = uCol[li].unique();
    uCol[li].pop();
	    
    var itemCt = 0;
    lists.eq(li).find('li').each(function(){
	var reqtitle = "";
	if(itemCt != 0){
	    if(ct===1){
		reqtitle = $(this).find("a").text();
	    }else{
		reqtitle = $(this).attr("itemid");
	    }
	    
	    var hide = true;
	    if($.inArray(reqtitle, uCol[li]) >= 0){
		hide = false;
	    }
	    
	    if (!hide) {
		$(this).show();
	    } else {
		$(this).hide();
	    }
	}
	itemCt++;
    });
    ct++;
}

Array.prototype.unique = function () {
    var o = {},
        i, l = this.length,
        r = [];
    for (i = 0; i < l; i += 1) o[this[i]] = this[i];
    for (i in o) r.push(o[i]);
    return r;
};

Eventuell kennt ja jemand einen guten Weg, so etwas performant hinzubekommen, damit man sich das ganze auch in etwas betagteren Browser ansehen kann, ohne selbst alt dabei zu werden.

tia

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Deine Lösung lässt vor allem Dokumentation zu wünschen übrig.

Würd mal sagen dass du bessere Performance haben wirst wenn du mit dem Object aus "Array.prototype.unique" weiter arbeitest statt mit einem Array. Wegen
"32: [...] $.inArray(...)"
Object-Properties sind in ner HashMap gespeichert, dadurch haben die konstante Laufzeit beim Zugriff, inArray sucht dir im Schnitt jedes mal das halbe Array durch, egal wie gut die Suche is.

Dein zweites Problem dürfte (je nach Dokumentgröße) die Zeile sein:
22: lists.eq(li).find('li').each(...)
da weiss ich aber im Moment keine Lösung

DirtyHarry

aka robobimbo
Avatar
Registered: Apr 2001
Location: outer space
Posts: 464
Wenn Du eh jQuery verwendest, hast Du schon mit der jQuery Funktion getestet? http://api.jquery.com/jQuery.unique/

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Zitat von DirtyHarry
Wenn Du eh jQuery verwendest, hast Du schon mit der jQuery Funktion getestet? http://api.jquery.com/jQuery.unique/
"Note that this only works on arrays of DOM elements"

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
so, sorry, hatte wahnsinnig viele andere Dinge um die Ohren die letzten Tage, danke schon mal für die Antworten.

Habe die ganze Funktion noch mal umgekrempelt, weil ich pro Kategorie-Liste einmal über das ganze Daten-Array iteriert habe.

Sorry für die miese Kommentierung des ganzen, habe versucht die wichtigsten Schritte nun ein bisschen zu erklären.

Code: JS
    filtering: function (c, sStr) {
	var searchString = "";
	
	//Filter DataTable
	//First col -> plain string
	if(c === 1){
	    documentFinder.datTable.fnFilter(sStr, 1, true, true, true);
	//Additional cols -> regex
	}else{
	    searchString = documentFinder.selectedCols[0]+"_"+documentFinder.selectedCols[1]+"_"+documentFinder.selectedCols[2];
	    documentFinder.datTable.fnFilter(searchString, 5, true, true, true);   
	}
        
	//Get filtered data-Array
        var _uniques = documentFinder.datTable.fnGetFilteredData();
	//Get DOM-Elements of sorting-lists
	var lists = $('#tableHead .itunes .list-holder ul');
	
	//Declaration of data-arrays
	var sysFHT = [];
	var bwHT = [];
	var btHT = [];
	var anfHT = [];
	var cohHT = [];
	var uniqueCol = [];
	uniqueCol[0] = "";uniqueCol[1] = "";uniqueCol[2] = "";uniqueCol[3] = "";uniqueCol[4] = "";
	
	
	//Add all data-strings together in specific uniqueCol-Array
	var x, max;
	for(x=0,max=_uniques.length; x < max; x+=1){
	    var y;
	    for(y=1; y<6; y+=1){
		if(y===1){
		    uniqueCol[y-1] += _uniques[x][y] + ";"; //Add ; to end of first-col data for splitting
		}
		else{
		   uniqueCol[y-1] += _uniques[x][y]; 
		}
	    }
	}
	
	//Create string that holds all unique coherency-strings for regex-search
	var ustr = uniqueCol[4].split(";");
	cohHT = ustr.unique();
	cohHT.pop();
	var strstr = cohHT.join("");
	
	//Iterate over all ul-sorting columns
	var z;
	for(z=0;z<4;z+=1){
	    var clmStr = uniqueCol[z].split(";");
	    var uCol = [];
	    
	    //adding column-specific data to uCol
	    if(z===0){
		sysFHT = clmStr.hash();
		uCol = sysFHT;
	    }else if(z===1){
		bwHT = clmStr.hash();
		uCol = bwHT;
	    }else if(z===2){
		btHT = clmStr.hash();
		uCol = btHT;
	    }else if(z===3){
		anfHT = clmStr.hash();
		uCol = anfHT;
	    }
	    
	    if(z!==4){
		var itemCt = 0;
		//Iterate over all li-elements
		lists.eq(z).find('li').each(function(){
			
		    var reqtitle = "", nsStr= "";
		    if(itemCt != 0){
			//get filtering data
			if(z===0){
			    reqtitle = $(this).find("a").text();    //string for col 1
			}else{
			    reqtitle = $(this).attr("itemid");	    //given id for other columns
			    
			    //Build column-specific regex
			    if(z===1){
				nsStr = reqtitle+"_"+documentFinder.selectedCols[1]+"_"+documentFinder.selectedCols[2];
			    }else if(z===2){
				nsStr = documentFinder.selectedCols[0]+"_"+reqtitle+"_"+documentFinder.selectedCols[2];
			    }else if(z===3){
				nsStr = documentFinder.selectedCols[0]+"_"+documentFinder.selectedCols[1]+"_"+reqtitle;
			    }
			}
			
			//see if searchItem still exists in filtered data as plain string
			if((reqtitle in uCol) === false){
			    $(this).addClass("hidden");
			}else{
			    //see if searchItem exists in any coherency in the product-list
			    if(z!==0){
				var found=false;
				
				if(strstr.search(nsStr) != -1){
				    found = true;
				}
				
				if(!found){
				    $(this).addClass("hidden");
				}else{
				    $(this).removeClass("hidden");
				}
			    }else{
				$(this).removeClass("hidden");
			    }
			}
		    }
		    itemCt++;
		});
	    }
	}
    };

//Returns Object : unique values as keys
Array.prototype.hash = function () {
    var o = {},
        i, l = this.length,
        r = [];
    for (i = 0; i < l; i += 1) o[this[i]] = null;
    return o;
};

//Returns Array: unique data in value
Array.prototype.unique = function () {
    var o = {},
        i, l = this.length,
        r = [];
    for (i = 0; i < l; i += 1) o[this[i]] = null;
    for (i in o) r.push(i);
    return r;
};

Größtes Problem das ich habe:
Um die ganzen unique values aus den Daten zu bekommen, füge ich zuerst alle Strings einer bestimmten "Column" von allen Datensätzen zusammen, splitte diese dann auf und kann aus dem resultierenden Array die unique einträge bestimmen.

Da die Strings sehr, sehr lang werden können, halte ich selbst nicht ziemlich viel von dieser Lösung, mir will aber nichts einfallen, womit ich das besser hinbekommen.

watchout, du hast sicherlich recht mit der Schleife über die li-Elemente, doch auch hier fällt mir keine andere Lösung ein, da ich die Daten eines jeden einzelnen li-Elements im Hinblick auf die gesamten gefilterten Daten überprüfen muss.

Hat da irgendjemand eine Idee?

edit:

Grundsätzlich sei dazu gesagt: In modernen Browsern performt das ganze (no na ned) perfekt, im IE7 bin ich derzeit auf ca. 2-3 Sekunden Response-Time herunten, was mir hinsichtlich Usability jedoch noch nicht gefällt, weiters scheint es mit page-reload langsamer zu werden, keine Ahnung warum.
Wie sieht es eigentlich mit Garbage Collection aus? Würde es etwas bringen, nach dem Funktionsdurchlauf alle Objekte/Arrays auf null zu setzen?
Bearbeitet von ill am 22.06.2011, 11:08

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Code: JS
                //Build column-specific regex
                if(z===1){
                nsStr = reqtitle+"_"+documentFinder.selectedCols[1]+"_"+documentFinder.selectedCols[2];
                }else if(z===2){
                nsStr = documentFinder.selectedCols[0]+"_"+reqtitle+"_"+documentFinder.selectedCols[2];
                }else if(z===3){
                nsStr = documentFinder.selectedCols[0]+"_"+documentFinder.selectedCols[1]+"_"+reqtitle;
                }
Bin grad nicht sicher ob ich was überseh, aber könnte man das nicht ausserhalb der .each(...) funktion auch machen?

Was du willst is ja diese Funktion so klein wie möglich zu bekommen

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Weisst was, poste mal Beispieldaten, und beschreibe nochmal was du erreichen willst anhand der Beispieldaten, ich glaub dass du zu kompliziert denkst

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
Der reqtitle ändert sich ja mit jedem einzelnen List-Element.
In diesem Code-Segment wird für jedes noch nicht gefilterte li-Element eine Regex-Suche zusammengestellt, die überprüft, ob dieses eine List-Element noch in irgendeiner vorhandenen Zusammenhängigkeit vorkommt.

Daten sehen folgendermaßen aus:

Code:
["Testdata", "TestCategory", "100;101;102;103;", "1001;1002;1003;1004;1006;1007;", "10001;10002;10003;10004;10005;", 
	"100_1001_10001;101_1001_10002;102_1001_10003;103_1001_10004;102_1002_10001;104_1003_10004;101_1003_10005;102_1001_10002;102_1004_10001;103_1003_10005;101_1005_10005;103_1005_10002;102_1006_10005;101_1007_10002;103_1007_10001;101_1008_10005;101_1009_10002;102_1010_10001;103_1009_10005;101_1007_10002;103_1009_10001;",
	"", ""]

String 1:
Produktname

String 2:
Produktkategorie/Produktfamilie

String 3, 4 und 5:
Verschiedene Kategorien, in denen dieses Produkt zum Einsatz kommt

String 6:
Verfügbare Zusammenhängigkeiten unter diesen Kategorien

Nun wird im Script nach einer Datenfilterung geprüft, ob eine Kategorie überhaupt noch irgendwo in den gefilterten Daten vorkommt. Wenn nicht -> hide.

Danach wird bei den noch vorhandenen mittels regex auf alle zusammengefassten Zusammenhängigkeiten überprüft, ob die Kategorie in Hinsicht auf andere ausgewählte Filterkategorien (die werden über documentFinder.selectedCols ausgelesen) noch relevant ist. Sollte sie nicht in einer verfügbaren Zusammenhängigkeit aufscheinen -> hide

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Ich meinte DOM-Code ;)

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
Die Auswahllisten sind im Grunde nichts besonderes, so sehen sie aus nachdem sie von JavaScript befüllt worden sind

Code: HTML
<div class="itunes">
<div class="list-holder">
<ul>
<li itemid="1" class="active"><a href="#">Alle</a></li>
<li itemid="2"><a href="#">Test 1</a></li>
<li itemid="3"><a href="#">Test 2</a></li>
<li itemid="4"><a href="#">Test 3</a></li>
<li itemid="5"><a href="#">Test 4</a></li>
<li itemid="6"><a href="#">Test 5</a></li>

<div class="list-holder">
<ul>
<li itemid="100" class="active"><a href="#">Alle</a></li>
<li itemid="101"><a href="#">Test 1</a></li>
<li itemid="102"><a href="#">Test 2</a></li>
<li itemid="103"><a href="#">Test 3</a></li>
<li itemid="104"><a href="#">Test 4</a></li>
<li itemid="105"><a href="#">Test 5</a></li>
</ul>
</div>
<div class="list-holder">
<ul>
<li itemid="1000" class="active"><a href="#">Alle</a></li>
<li itemid="1001"><a href="#">Test 1</a></li>
<li itemid="1002"><a href="#">Test 2</a></li>
<li itemid="1003"><a href="#">Test 3</a></li>
<li itemid="1004"><a href="#">Test 4</a></li>
<li itemid="1005"><a href="#">Test 5</a></li>
</ul>
</div>
<div class="list-holder">
<ul>
<li itemid="10000" class="active"><a href="#">Alle</a></li>
<li itemid="10001"><a href="#">Test 1</a></li>
<li itemid="10003"><a href="#">Test 2</a></li>
<li itemid="10004"><a href="#">Test 3</a></li>
<li itemid="10005"><a href="#">Test 4</a></li>
<li itemid="10006"><a href="#">Test 5</a></li>
</ul>
</div>
</div>

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
So, hab die Funktion jetzt nochmal umgekrempelt, um weniger Schleifendurchläufe und String-Operationen drin zu haben.

Funktioniert mit 300 Einträgen einigermaßen zufriedenstellend, 600 dauern dann schon etwas länger, ist aber noch im Bereich des ertragbaren (ich verlang ja von meiner 55 PS - Schüssel auch nicht, dass sie 250 fährt)

Code: JS
    filtering: function (c, sStr) {
	var searchString = "";
	
	//Filter DataTable
	//First col -> plain string
	if(c === 1){
	    documentFinder.datTable.fnFilter(sStr, 1, true, true, true);
	//Additional cols -> regex
	}else{
	    searchString = documentFinder.selectedCols[0]+"_"+documentFinder.selectedCols[1]+"_"+documentFinder.selectedCols[2]+";";
	    documentFinder.datTable.fnFilter(searchString, 2, true, true, true);   
	}
        
	//Get filtered data-Array
        var _uniques = documentFinder.datTable.fnGetFilteredData();
	
	//Declaration of data-arrays
	var sysFHT = [], cohHT = [], uniqueCol = [];
	uniqueCol[0] = "";uniqueCol[1] = "";
	
	
	//Add all data-strings together in specific uniqueCol-Array
	var x, max, y;
	for(x=0,max=_uniques.length; x < max; x+=1){
	    uniqueCol[0] += _uniques[x][1] + ";";
	    uniqueCol[1] += _uniques[x][2];
	}
	
	//Create string that holds all unique coherency-strings for regex-search
	var ustr = uniqueCol[1].split(";");
	cohHT = ustr.unique();
	cohHT.pop();
	var strstr = cohHT.join(";");
	strstr += ";"
	
	//Iterate over all ul-sorting columns
	var z;
	for(z=0;z<4;z+=1){
	    
	    //adding column-specific data to uCol
	    if(z===0){
		var clmStr = uniqueCol[0].split(";");
		sysFHT = clmStr.hash();
	    }
	    
	    var itemCt = 0;
	    //Iterate over all li-elements
	    documentFinder.lists.eq(z).find('li').each(function(){
		var reqtitle = "", nsStr= "";
		if(itemCt !== 0){
		    //get filtering data
		    if(z===0){
			reqtitle = $(this).find("a").text();    //string for col 1
		    }else{
			reqtitle = $(this).attr("itemid");	    //given id for other columns

			//Build column-specific regex
			switch(z){
			    case 1: nsStr = reqtitle+"_"+documentFinder.selectedCols[1]+"_"+documentFinder.selectedCols[2]+";"; break;
			    case 2: nsStr = documentFinder.selectedCols[0]+"_"+reqtitle+"_"+documentFinder.selectedCols[2]+";"; break;
			    case 3: nsStr = documentFinder.selectedCols[0]+"_"+documentFinder.selectedCols[1]+"_"+reqtitle+";"; break;
			}
		    }

		    if(z!==0){
			var found=false;
			//see if searchItem exists in any coherency in the product-list
			if(strstr.search(nsStr) !== -1){
			    found = true;
			}
			
			if(!found){
			    $(this).addClass("hidden");
			}else{
			    $(this).removeClass("hidden");
			}
		    }else{
			//see if searchItem still exists in filtered data as plain string
			if((reqtitle in sysFHT) === false){
			    $(this).addClass("hidden");
			}else{
			    $(this).removeClass("hidden");
			}
		    }
		}
		itemCt++;
	    });
	}
	$('#tableHead .itunes .list-holder').jScrollPane();
	sysFHT = null; cohHT = null; uniqueCol = null; 
    }

Interessant wär jetzt noch, wieso die Funktion nach Page-Reload fast doppelt so langsam läuft. Kann das wirklich am Script liegen oder ist da die Maschine, auf der mein nativer IE 7 läuft, etwa schuld daran?

Nach IE 7 - Restart läufts wieder wie gehabt...

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Code: JS
	(Zeile 31)
	var ustr = uniqueCol[1].split(";");
	cohHT = ustr.unique();
	cohHT.pop();
	var strstr = cohHT.join(";");
	strstr += ";"
[...]
(Zeile 69)
	if(strstr.search(nsStr) !== -1){
		found = true;
	}
Warum?

Code: JS
	(Zeile 31)
	var ustr = uniqueCol[1].split(";");
	var ustr_last = ustr.pop();
	cohHT = ustr.hash();
	cohHT[ustr_last] = null;
	var strstr = cohHT.join(";");
	strstr += ";"
[...]
(Zeile 69)
	if(strstr[nsStr]){
		found = true;
	}
Geht das nicht auch so?

Hashtables, use them! ;)

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
Naja, das Problem ist, dass die Variable "nsStr" eine regex-Abfrage gespeichert hat, da wird mit Zugriff per Key etwas schwierig, weshalb ich auch die .search() - Methode verwende

watchout

Legend
undead
Avatar
Registered: Nov 2000
Location: Off the grid.
Posts: 6845
Code: PHP
nsStr = reqtitle+"_"+documentFinder.selectedCols[1]+"_"+documentFinder.selectedCols[2]+";"
was is hier regex?

ill

...
Avatar
Registered: Nov 2003
Location: Salzburg
Posts: 2059
ist ne äußerst einfache. Teilweise stehen halt dann strings wie 101_...._.....; drin
Kontakt | Unser Forum | Über overclockers.at | Impressum | Datenschutz