Toon posts:

[JS] 2D-arrays in verschillende functions worden gewist?

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik probeer een matrix aan te maken met onbepaalde hoogte en breedte (een berekening vult getallen in en van tevoren is niet te bepalen hoe groot de matrix wordt). Ik heb daarbij allereerst een initierende functie (die vanuit de body moet worden aangeroepen) die wat begingetallen geeft. Dan volgt een functie die eerst een (voor de visualisatie) horizontale array A aanmaakt en aan het eind van de berekening de array A opslaat op een positie in array B. Vervolgens worden nieuwe voorwaarden berekend en wordt de berekening opnieuw uitgevoerd met een nieuwe A, die op de volgende positie in de verticale B wordt opgeslagen. Heel kort dus als volgt:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function begin() {
   var A = new Array(); var B = new Array();
   a = 1, b = 2;
   een(A,B,a,b);
}
function een(A,B,a,b) {
   berekent A met lengte A.length
   slaat A op in B[n]
   twee(A,B,a,b);
}
function twee(A,B,a,b) {
   evalueert wat getallen en geeft nieuwe a en b
   een(A,B,a,b);
}


Nu lijkt het tot mijn verbazing zo te zijn dat bij verandering van A (waar immers steeds de getallen van worden vervangen bij een nieuwe berekening) de eerder opgeslagen waarden in matrix B óók veranderen, alsof in B[n] werkelijk de info "=A" wordt opgeslagen i.p.v. de sequentie (bijv. 1,2,3,4) die in A zat. Dat lijkt zo omdat hij aan het eind van elke berekening elke entry van B hetzelfde heeft gemaakt (namelijk elke entry is gelijk aan de laatste entry die is berekend, dus de laatste array A) en de eerdere arrays A dus heeft overschreven. Daarnaast, als ik een slotfunctie aanroep als alle berekeningen gedaan zijn, is B weer helemaal leeg indien ik voor de netheid A leegmaak na het opslaan in B en voor het beginnen van een nieuwe berekening. Nu heb ik die verwijdering weggelaten en is de output (helemaal onderaan) gelijk aan een veelvoud van de laatste array A.

Het script is te zien op http://sublex.endoria.net/script2.html. Omdat het >3 jaar geleden is dat ik druk bezig was met html/js/css etc. moest ik weer ff inkomen en heb ik dus veel tussentijdse feedback laten genereren, maar dat maakt het wel overzichtelijk voor problemen.

Wat gaat er fout..?

[ Voor 5% gewijzigd door Verwijderd op 12-10-2006 21:00 ]


  • benoni
  • Registratie: November 2003
  • Niet online
Verwijderd schreef op donderdag 12 oktober 2006 @ 20:58:
... Nu lijkt het tot mijn verbazing zo te zijn dat bij verandering van A (waar immers steeds de getallen van worden vervangen bij een nieuwe berekening) de eerder opgeslagen waarden in matrix B óók veranderen, alsof in B[n] werkelijk de info "=A" wordt opgeslagen ...
Die gevolgtrekking klopt volgens mij inderdaad.

Je definieert vooraf 2 bepaalde arrays, A en B, en stelt daarna meerdere malen B[zoveel] = A. Javascript heeft A als object in het geheugen opgeslagen, verwijst daarna voor B[zoveel] naar dat object.

Wil je het anders, dan zou je 'A = new Array()' niet éénmalig vooraf moeten definiëren, maar als lokaal object in de countdown functie (zodat die bij elke aangeroepen instantie van die functie een nieuw object krijgt) en die toevoegen aan de global B.

Onee, wacht even... ik zie dat je door allerlei recursieve scriptwendingen heen aan de array A zit te bouwen en die pas aan B gaat toevoegen als iets een bepaalde waarde heeft bereikt. Dan kun je beter géén A gebruiken en gewoon de waardes direct in B[s] wegschrijven. Moet je wel even 'B[++s] = new Array()' doen in plaats van 's++'.

Nog wat losse opmerkingen bij je pagina:
- 'cyclus' wordt in een functie gedefinieerd en vervolgens in een andere functie verwerkt alsof het een global is;
- Het script leest moeilijk doordat je recursief werkt met 2 functies die elkaar oproepen. Is het niet overzichtelijker als je de evolueer functie gewoon laat return'en naar de countdown functie? Dan hoef je ook niet met zo'n lelijke 'stop' in printhet() te werken.

  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

Ik krijg sowieso 'too much recursion' in Firefox buiten het feit dat ik uit de code niet kan uitmaken wat nu precies de bedoeling is.
Ik zie een mix van globals (probeer dat zoveel mogelijk te vermijden) en variabelen die volgens mij local zouden moeten zijn maar niet netjes local gedeclareerd worden en dus ook ook global zijn.
Verder ben je bekent met het feit dat een referentie naar een object of array een referentie is en geen kopie?
JavaScript:
1
2
3
4
var a = [1,2,3];
var b = a; // reference, not a copy
b[0] = 5;
alert(a[0]); // 5

Intentionally left blank


  • benoni
  • Registratie: November 2003
  • Niet online
Het lijkt erop dat je de recursieve aanroepen helemaal niet nodig hebt, want er wordt nergens teruggekeerd naar een vorig niveau. Het kan m.i. dus netzogoed met zoiets:


JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var hopping = false, cycling = true;
var that = new Object();
that.d=document; that.i=7; that.l=0; that.b=35; that.n=that.b; that.s=0;
that.B = new Array(); that.B[0] = new Array();
that.cyclus = 0;
while (hopping) {
    hopping = false;
    cycling = true;
    while (cycling && that.cyclus<99) {
        that.cyclus++;
        cycling = countdown(that);
    }
    hopping = hopback(that);
}

function countdown(that) {
    ...
    if (that.i==1) return false; // Dead end, we need to go hopping
    ...
    return true; // Keep on cycling
}

function hopback(that) {
    ...
    if (verschil==0) return false; // Ready
    ...
    return true; // Back on cycling
}


Of helemaal in één lus:
edit:

En dat heb ik maar opgeschoond, u wordt vriendelijk doch dringend doorverwezen naar de volgende posting O-)

[ Voor 55% gewijzigd door benoni op 14-10-2006 10:23 ]


Verwijderd

Topicstarter
Ik stuit dus inderdaad op het feit dat ik objectreferenties aanmaak en ze niet kopieer. Het maken van een kopie is niet eenvoudig te bereiken?

Ik zal even uitleggen wat ik probeer te bereiken, dat maak het misschien wat makkelijker. Ik begrijp de oplossingen van benoml niet, hoe elegant ze er ook uitzien, maar dat ligt aan mijn gebrekkige ervaring.
Een grafisch resultaat van het script is HIER te zien. Wat is het? Ik laat met een geïmproviseerde zoekboom alle combinaties van elementen met lengte 1, 2 of 3 (vertegenwoordigd in de variabele i) berekenen die in een blok van 8 (=b) passen. Toepassing: als je een ritme hebt van acht tellen lang en je neemt je voor dat je slagen van 1, 2 en 3 tellen gebruikt; welke mogelijkheden heb je dan? (Ik heb een specifieke reden om combinaties en niet permutaties te gebruiken.)
Ik ben niet slim genoeg om een elegantere zoekboom te schrijven (als dat kan), dus ik laat het als volgt doen: tel steeds de slagen op (beginnend met de grootste), en blijf onder de 8. Dus eerst 3 + 3 = 6, rest 2 dus moet daar een slag van 2 tellen in. Dan zoek ik steeds verder naar andere opties, waarbij ik eerst de laatste posities door kortere slagen laat vervangen. Het script levert dus dit resultaat op:
3,3,2 (Wordt bepaald door enkele malen countdown() te draaien)
3,3,1,1 (Moet nu terug want kleiner dan 1 kan niet, daarvoor gebruik ik hopback() om de eerstvolgende positie >1 vanaf de rechterkant te bepalen)
3,2,2,1
3,2,1,1,1
3,1,1,1,1,1,1
2,2,2,2
etc.

Als je misschien kort kan beschrijven hoe "géén A gebruiken en gewoon de waardes direct in B[s] wegschrijven" moet (en dus "'B[++s] = new Array()' doen in plaats van 's++'"), ben ik waarschijnlijk weer een stap geholpen. De syntaxis ++s nieuw voor me, en ik weet ook niet hoe je de waardes kan onthouden zonder die eerst in een array op te slaan alvorens het geheel in B te stoppen.

  • benoni
  • Registratie: November 2003
  • Niet online
Verwijderd schreef op vrijdag 13 oktober 2006 @ 19:02:
Als je misschien kort kan beschrijven hoe "géén A gebruiken en gewoon de waardes direct in B[s] wegschrijven" moet (en dus "'B[++s] = new Array()' doen in plaats van 's++'"), ben ik waarschijnlijk weer een stap geholpen. De syntaxis ++s nieuw voor me, en ik weet ook niet hoe je de waardes kan onthouden zonder die eerst in een array op te slaan alvorens het geheel in B te stoppen.
Ik moet nog even dubben over een écht elegante oplossing, eerst maar even je vraag beantwoorden:

- De notatie 's++' verhoogt de waarde van s met 1, maar als je het als onderdeel van een toekenning gebruikt wordt de waarde van s pas verhoogd nadat de toekenning is uitgevoerd. Dus 's=1; a=s++' geeft a==1, s==2.
- De notatie '++s' verhoogt de waarde van s met 1, en neemt die verhoogde waarde mee in de berekening. Dus 's=1; a=++s' geeft a==2, s==2.

Je had al een tellertje meelopen die aangeeft bij welke sub-array van B je gebleven was. Daarom kun je in plaats van array A te vullen 'A[x]' en die aan array B vast te plakken 'B[y]=A' (wat als een referentie wordt verwerkt), ook alvast een lege array aan B toevoegen 'B[y]' en vervolgens die vullen 'B[y][x]'.

  • benoni
  • Registratie: November 2003
  • Niet online
Nu ik zie waarvoor je het script eigenlijk gebruikt, begrijp ik dat een recursieve functie het meest logisch is. De hopback functie is echter wel overbodig als je op elk niveau maar netjes return'ed naar een lus die de cyclus op dat niveau afloopt. Ik heb je script een beetje herschreven:


JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Stel beginwaarden in en maak array aan
var get = new Object();
get.i = 3;
get.n = 8;
get.c = 0; // column counter for A
get.A = new Array();
var out = new Object();
out.r = 0; // row counter for B
out.B = new Array();
out.doc = document;

run(get, out);

function run(got, out) {
    var t;
    var get = new Object();
    get.c = got.c + 1;
    get.A = new Array();
    for (t = 0; t < got.c; t++) get.A[t] = got.A[t];

    for (get.i = got.i; get.i > 0; get.i--) {
        get.n = got.n - get.i;
        get.A[got.c] = get.i; 

        if (get.n > 0) {
            run(get, out);
        }
        else if (get.n == 0) {
            for (t = 0; t < get.c; t++) {
                out.doc.write("<img src = '" + get.A[t] + "mid.gif'>");
            }
            out.doc.write("<br />A = '" + get.A + "'<br />");
            out.B[out.r] = new Array();
            for (t = 0; t < get.c; t++) out.B[out.r][t] = get.A[t];
            out.r++;
        }
    }
}

out.doc.write("<br />Einde countdown-script<br />");
for (var t = 0; t < out.r; t++) {
    out.doc.write("B[" + t + "] = '" + out.B[t] + "'<br />");
}


Ik zit overigens wel met hetzelfde probleem dat toevoegen van een Array() object aan een andere array altijd een verwijzing maakt naar de originele array, en niet de objecten in de array kopieert. Een functie als concat() kopieert misschien wel, maar ja dan krijg je de arrays aaneengeregen en dat wil je niet... ik heb het array-overkopieren maar in een for-lusje gedaan :/

Addit:
Mmm.... new Array().concat(A) is natuurlijk wel te proberen. Nu begint het een elegant scriptje te worden:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Start values
var i = 3; // Max block size
var n = 8; // Max row length

// Globals
var mix = false;
    // True: list all possible block combinations
    // False: only list block combinations sorted long-to-short

var r = 0; // Row counter for B
var B = new Array();
var doc = document;

run(i, n, new Array());

function run(i, n, A) {
    var t, j, diff;
    var C = new Array().concat(A);

    for (j = i; j > 0; j--) {
        diff = n - j;
        C[A.length] = j;
        if (diff > 0) {
            run((mix ? i : j), diff, C);
        }
        else if (diff == 0) {
            for (t = 0; t < C.length; t++) {
                doc.write("<img src='" + C[t] + "mid.gif' />");
            }
            doc.write("<br />C = '" + C + "'<br />");
            B[r++] = new Array().concat(C);
        }
    }
}

doc.write("<br />Einde countdown-script<br />");
for (var t = 0; t < r; t++) {
    doc.write("B[" + t + "] = '" + B[t] + "'<br />");
}


Addit:
Als je trouwens voor n een grotere waarde invult (bijvoorbeeld 50) dan zie je in de uitdraai van de B array een patroon ontstaan. Zo te zien is het algoritme ook van onder naar boven te schrijven, dus als n=50 begin je met 50 1'tjes, op de volgende regel zet je 1 2'tje en vul je tot de 50 met 1'en, de regel daarna zet je 2 2'tjes en vul je tot de 50 met 1'en, en zo verder totdat je met de 2'tjes op 50 uitkomt. Dan begin je met 1 3'tje en oplopend aantal 2'tjes, 2 3'tjes met oplopend aantal 2'tjes enzovoort... het zou dus in principe met loops moeten kunnen in plaats van recursie.

[ Voor 37% gewijzigd door benoni op 14-10-2006 10:56 ]


Verwijderd

Topicstarter
Benoni: geniaal!! Erg mooi kort scriptje met juiste resultaat, en wat bovendien mooi is, is dat je de 'mix' op true kunt zetten zodat je naast combi's ook permutaties krijgt, en dat was nodig voor een vervolgscript dat ik wilde maken. Maar dat heb je dr zomaar even ingebakken!
Nu ga ik gauw proberen de code te begrijpen. Moderators, aub nog geen slotje zodat ik later het eindresultaat met 'complete functionaliteit' kan posten (en eventueel tussentijds ook nog vragen..)

  • benoni
  • Registratie: November 2003
  • Niet online
Deze pagina vond ik er bij - How To: Making an independant copy of a JavaScript array:

code:
1
2
3
4
5
6
7
8
// JavaScript syntax
var landArray = new Array("Asia", "Africa");
var continentArray = landArray;
landArray.push("Australia"); // this also adds "Australia" to continentList

// JavaScript syntax
var oldArray = ["a", "b", "c"];
var newArray = oldArray.slice(); // makes an independent copy of oldArray


Die kun je dus ook gebruiken, het principe is ongeveer gelijk aan new Array().concat(A) :>
Pagina: 1