Kirjautuminen

Haku

Tehtävät

Keskustelu: Nettisivujen teko: Puun rakentaminen rekursiivisesti JSON-datoista

Sivun loppuun

Multibyte [10.05.2016 10:58:50]

#

Moi

Pitäisi ratkoa ongelma, jossa rajapinnalta pyydetään "rekursiivisesti" uutta dataa eli rajapinnalta kutsutaan niin kauan dataa, että saadaan rakennettua polku tiedostoon, joka on päämäärä.
Käytännössä rajapinta palauttaa hakemistorakenteen yhdeltä tasolta, jonka osia pitää kutsua, jotta saadan seuraavan tason tiedot ja rakennettua "hakemistopuu".

Ensin tehdään alustava kutsu rajapinnalle, joka palauttaa JSONia, jossa on listattu joko seuraavat kutsuosoitteet tai ladattavat tiedostot:
{id: "hakemisto1",id:"hakemisto2"...} tai {id:"my.file",id:"another.file"}
Tiedoissa on ainoastaan tiedostonimiä tai seuraavia polkuja, muttei koskaan molempia.

Skriptin pitää siis nuuskia, että onko kyseessä seuraava "URL-taso" vai tiedostolista ja toimia sen mukaan.
Jos kyseessä on url, niin sitten se looppaa sen läpi GETillä ja taas nuuskii palautuvaa jsonia.
Jos jsonissa on viittauksia tiedostoihin, niin se lataa sen ja tekee siitä hötömölöä.

Eli ensin api.fi/
ja sitten api.fi/hakemisto1/ tai api.fi/my.file (riipuen rajapinnan palauttamasta datasta)
Tämä "puu" voi jatkua ja haarautua äärettömästi api.fi/hakemisto1/hakemisto1.1/
api.fi/hakemisto1/hakemisto1.2/ jne.

Yritin jqueryn getillä jonkinlaista rekursiota, mutta osoite meni aina lopulta ihan sekaisin.

Sen lisäksi silmukat pyörivät hyvin omituisesti eli ensinnä aloitettu silmukka(isäntä) käydään läpi ennen kuin silmukan sisällä aloitettu silmukka.

Lisäys:

Nykyinen säätö:

function rekursio(uusiUrl){
    $.get(rajapinta+juurihakemisto+'/'+uusiUrl.join('/'),function(data,status){
        for (var hakemisto in data) {
            if (!regexPattern.test(data[hakemisto].id)) { // Tässä tarkistetaan regexillä, että onko kyseessä hakemisto- vai tiedostonimi
                uusiUrl.push(data[hakemisto].id);
                rekursio(uusiUrl);
            } else { // Jos regex palauttaa false, niin kutsutaan funkkaria, joka lataa tiedoston ja tekee siitä hötömölöä
                hotomoloa(rajapinta+juurihakemisto+'/'+uusiUrl.join('/')+'/'+data[hakemisto].id);
            }
        }
    });
}

Grez [10.05.2016 11:52:12]

#

Auttaisko jos lisäisit tuohon rivin 6 jälkeen uusiUrl.pop(); ?

Nythän silmukassa lisäät tuohon uusiurl -taulukkoon aina kaikki löytyvät hakemistot, mutta et poista niitä missään vaiheessa.

Aika vammainen rajapinta muuten, jos regexillä pitää tunnistaa onko tiedosto vai hakemisto

Multibyte [10.05.2016 11:59:59]

#

Grez kirjoitti:

Auttaisko jos lisäisit tuohon rivin 6 jälkeen uusiUrl.pop(); ?

Nythän silmukassa lisäät tuohon uusiurl -taulukkoon aina kaikki löytyvät hakemistot, mutta et poista niitä missään vaiheessa.

Taisin sitä kyllä kokeilla, mutta kokeilenpa vielä varmuuden välttämiseksi.

Grez kirjoitti:

Aika vammainen rajapinta muuten, jos regexillä pitää tunnistaa onko tiedosto vai hakemisto

:D niin, tämä tosiaan palauttaa vain sen yhden tason rakenteen eikä muuta.
Ja ohjelmisto, johon tämä rajapinta liittyy, nojaa pitkälti noihin hakemistorakenteisiin...

Lisäys:

Grez kirjoitti:

Auttaisko jos lisäisit tuohon rivin 6 jälkeen uusiUrl.pop(); ?

Eipä auttanut tämäkään. Mun mieletsä tähän pitäisi saada joku laskuri tms. jonka perusteella se sitten muokkaan tuota uusiUrl taulukkoa (ts. silmukkakohtaisesti).
Ja jotenkin tuntuu, että sen pitäisi tietää kulloinkin palautuneen jsonin koko ja toimia sen mukaan...evot.

Kutsujen perusteella se tekee ensin 1. silmukan ja vasta sen jälkeen ko. silmukan sisällä aloitetun silmukan, jolloin hakemistorakenteen osalta haut kohdistuu ihan väärin.
So. se ei ikinä pääsee noihin tiedostoihin asti.

...Muutenkin aivot niin maitohapoilla, ettei taho kyetä käsittämään tätä solmua vaikka melko triviaalilta kuullostaa...

Grez [10.05.2016 12:21:28]

#

Ai niin tietenkään noi kutsut ei oo synkronisia ja toi uusiURL on kaikille funktioille yhteinen muuttuja. (Ei mulla aivot kytkeneet että JS vaikka selvästi viestissä niin lukikin)

Yksinkertaisinta olis varmaan kloonata toi uuriURL ennen kutsua.

var polku = $.clone(uusiUrl)
polku.push(data[hakemisto].id);
rekursio(polku);

jlaire [10.05.2016 14:43:39]

#

Olisiko helpompaa, jos uusiUrl olisi merkkijono taulukon sijaan? Tarpeen tullen sen saa kuitenkin splitillä taulukoksi ihan yhtä helposti kuin joinilla toiseen suuntaan. Rekursiivinen kutsu voisi olla yksinkertaisesti

rekursio(uusiUrl + '/' + data[hakemisto].id);

Multibyte [10.05.2016 14:58:04]

#

En osaa, en ymmärrä. Mitenkään päin en saa tätä toimimaan.

Lopulta näköjään palauttaa jo 429 - too many requests (30 taitaa olla maksimi).
Mitenkähän tuotakin lasketaan... Jos hakemistopuu onkin todella iso, niin eikös tuo tule vastaan melko nopeasti? Vai laskeeko tuo yksittäisiä api-kutsuja eli 30 kutakin kutsua tms. 30 / minuutti ...

Lisäksi tuo rajapinta palauttaa virheellisen kutsun kohdalla tuon juurihakemiston jsonin ja antaa ymmärtää, että kaikki on ihan ok. Huoh.

jlaire kirjoitti:

(10.05.2016 14:43:39): Olisiko helpompaa, jos uusiUrl olisi merk­ki­jo­no...

Näin se oli hyvin varhaisessa vaiheessa, mutta onko sillä toiminallisuuden kannalta eroa?

Lisäys:

Päivitän tähän nyt tämän hetkisen tuloksen:

Kun juurihakemiston tiedot haetaan, niin se palauttaa jsonin, jossa on kolme tulosta.

Tämän jälkeen se kutsuu APIa json[0] arvon mukaisesti, jonka jälkeen se lisää seuraaviin kutsuihin aina seuraavan eli:

api.fi/json[0]/
api.fi/json[0]/json[1]/
api.fi/json[0]/json[1]/json[2]/

eli ihan väärin.

Tämän jälkeen se siirtyy json[0] alta löytyneen jsonin kimppuun eli silmukoi sen.
Ja nämäkin kutsut menevät tietysti väärin, kun nyt se hakee ylläolevan jatkeena niitä vaikka sen olisi pitänyt tehdä se jo 1. silmukassa kutsuttavassa silmukassa, muttei tee.
Miksei? Miksi se "hyppää" sen silmukan yli ja suorittaakin sen eri kohdassa kuin missä sitä kutsutaan?

Lisäys:

Lisäämällä silmukoihin breakit, koodi tulostaa oikeaoppisesti, mutta vain 1. tuloksen:

function rekursio(uusiUrl){
    $.get(rajapinta+juurihakemisto+'/'+uusiUrl.join('/'),function(data,status){
        for (var hakemisto in data) {
            if (!regexPattern.test(data[hakemisto].id)) { // Tässä tarkistetaan regexillä, että onko kyseessä hakemisto- vai tiedostonimi
                uusiUrl.push(data[hakemisto].id);
                rekursio(uusiUrl);
break;
            } else { // Jos regex palauttaa false, niin kutsutaan funkkaria, joka lataa tiedoston ja tekee siitä hötömölöä
                hotomoloa(rajapinta+juurihakemisto+'/'+uusiUrl.join('/')+'/'+data[hakemisto].id);
break;
            }
        }
    });
}

Lisäys:

Poistin 2. breakin ja nyt se yrittää myös seuraava tiedostoa.
Jostain syystä se tekee sen rekursio funktion avulla vaikka sitä ei kutsuta missään vaiheessa.

Eli for silmukassa sen pitäisi tunnistaa tiedosto ja kutsuta sen kohdalla vain hotomoloa.
Nyt se hyppää toisen tiedoston kohdalla yhtäkkiä rekursio funktioon.
Ja käyttää tietysti tiedostopolkua uusiUrlina vaikka sitä ei aseteta eikä lisätä mihinkään muuttujaan, joka kulkee rekursio-funktiossa.

Tää olis pitäny hoitaa meidän koodareiden puolesta ja mielellään backendissä...

Multibyte [10.05.2016 17:08:34]

#

Ei helevata.
Nyt tuo regex ei muka täsmää toisen tiedoston kanssa vaikka ne ovat käytännössä samalla tavoin nimetty. En ymmärrä.

Lisäys:

001-filename.ext true
002-filename.ext false

regex = /ext$/igm;

jlaire [10.05.2016 19:32:30]

#

Ota molemmat break:t pois, niissä ei ole mitään järkeä. Kun koodi ei toimi oikein, ei kannata kokeilla tehdä tällaisia satunnaisia muutoksia, koska ne vain vaikeuttavat ymmärtämistä.

Ennen rekursiivista kutsua sinun pitää lisätä joko Grez:n ehdottama kloonaus tai siirtyä käyttämään merkkijonoja.

Ota regexistä g-flägi pois niin toimii.

Multibyte [11.05.2016 10:26:27]

#

Moi

Kiitti! g:n poisto auttoi, mutta miksi?

Breakit lisäsin osittain siitä syystä, että koodin suoritus pysähtyy eikä tule niin hirveästi dataa kerralla.

En tunne tuota clonea ja ainakaan noin suoraviivaisesti sitä ei voinut käyttää tuossa yhteydessä.

a.cloneNode is not a function

Grez [11.05.2016 11:02:45]

#

Se liittyy tuohon javascriptin regex olion toteutukseen. Tuolla g-määrittimellä on tarkoitus ajaa samaa merkkijonoa monta kertaa, jolloin se säilöö mistä etsimistä jatketaan.

Nyt vaikka vaihdat merkkijonoa, niin se ei aloita etsimistä alusta.

Jos siis tuota regexpiä moneen kertaan niin mätsiä seuraavalla ajolla saat käytännössä aina false, ellei uusi merkkijono ole selkeästi pidempi kuin edellinen.

http://stackoverflow.com/questions/18462784/why-is-javascript-regex-matching-every-second-time

Multibyte kirjoitti:

En tunne tuota clonea ja ainakaan noin suoraviivaisesti sitä ei voinut käyttää tuossa yhteydessä.

Näköjään se ei tosiaan suoraan toimi taulukolle.. No suoraan perus javascriptillä vaan tälläinen epäintuitiivisen näköinen taulukon taikakloonaus:

var polku = uusiUrl.slice()

Multibyte [11.05.2016 18:10:06]

#

Noh, kun ei tämä kerran alkuperäisen suunnitelmaan mukaan toiminut, niin vaihdoin lähestymistapaa.

Purin tämän kahteen funktioon, joista 1. hoitaa sen jsonin noutamisen ja 2. ao. mukaisesti päättelee, että haetaanko uusi kierros ja kutsuu kutsujaansa vai pitäisikö ulostaa ruudulle kutsuen tulostus funktiota. Ja tämä toimii, mikä on ihan ihmeellistä.

function olenSilmukoija(url, silmukkaData){
    for (var simosilmu in silmukkaData){
        if (re.test(silmukkaData[simosilmu].id)){
            hotomoloa(api+url+'/'+silmukkaData[simosilmu].id);
        }
       else if (!re.test(silmukkaData[simosilmu])) {
        rekursio(url+'/'+silmukkaData[simosilmu].id);
    }
}
}

jlaire [11.05.2016 18:18:28]

#

Tuon silmukan siirtäminen rekursio:n sisälle toimisi kyllä myös. Ongelma korjaantui sillä, että muutit parametrin taulukosta merkkijonoksi, ei kahteen funktioon pilkkomisella. Mutta hyvä että toimii.

Tuo "else if (...)" olisi muuten selkeämpi pelkkänä "else"nä.

Multibyte [11.05.2016 19:11:39]

#

jlaire kirjoitti:

(11.05.2016 18:18:28): Tuon silmukan siirtäminen rekursio:n sisälle...

Hmm, pitääpä varsin huomenna kokeilla tuota taulukko vs mjono teoriaa.
En nimittäin ymmärrä, että mikä ero sillä on...

Juu, tuo Else IF on tässä yhteydessä turhan "vaativa", kun poikkeuksia ei lähtökohtaisesti ole.
Ja on se muutenkin hieman väärä ja puutteellisesti toteutettu tässä yhteydessä.

Multibyte [16.05.2016 09:09:37]

#

Tästä tuli nyt niin perus insinöörityö kuin olla ja taitaa. Enkä edes ole insinööri.

Koodi siis toimii, mutta ei mitään hajua, että miten.

Konsolista näkee, että se ensin silmukoi koko 1. jsonin.
Sen jälkeen näiden alkioiden sisällöt ja niin edelleen.

Tarkoituksenani on kuitenkin ollut luoda koodi joka ottaisi 1. jsonin ja sen 1. alkion ja etenee sitä pitkin kunnes tulee aika vaihtaa seuraavaan.

Jos tämän jotenkin pseudona ilmaisi niin:
nyt menee näin: 1 2 3 1.1 2.1 3.1
kun pitäisi mennä näin: 1 1.1 2 2.1 3 3.1

groovyb [16.05.2016 09:57:10]

#

Vastaukset rajapinnasta saattaa tulla eri järjestyksessä kuin kutsut joita sinne lähetät. Jos kutsut rajapintaa järjestyksessä kutsuilla A,B,C,D - saattaa vastaukset tulla takaisin järjestyksessä B,D,A,C. Tämä on riippuvainen siitä miten rajapinnan toteutus on tehty (esim. mikäli siellä on queue -tyyppinen käsittely kutsuille, palautuu vastauksetkin kutsujärjestyksessä).

ja koska $.get:in success suoritetaan luonnollisesti vasta kun vastaus palautuu, on mahdollista että successin suoritusjärjestys voi näyttää hämärältä ja erilaiselta kuin loopissa ajettava kutsujärjestys.

Multibyte [16.05.2016 11:25:05]

#

Toinen, mitä en nyt käsitä/saa toimimaan, on setTimeout()

Jostain syystä tuon getin yhteydessä setTimeout jätetään ikään kuin huomiotta ja kaikki kutsut lähetetään sillä samalla punaisella sekunnilla.

Eli ts. kutsuttaessa:

window.setTimeout(loadURL, 500, url); // Tällä haetaan siis Jsonista löydetty tiedosto

silti se tykittää nuo kaikki kutsut (tässä tapauksessa löydetyt tiedostot) kerralla.

...Jaa vai puuttuukos tästä jokin lenkki. Kaikki lataukset jonoutuvat skriptissä muutaman millisekunnin välein, johon nykyisellään lisätään tuo 500ms koskien niitä kaikkia.
Eli ainoa "aikaero", mikä latausten välille tulee, on se normaali skriptissä syntynyt ero.
Tuo 500ms lykkääkin niitä vain kokonaisuutena.

The Alchemist [16.05.2016 13:22:04]

#

SetTimeout lykkää vain sitä funktiokutsua, minkä sille antaa parametrina. Mitäs muuta olisit odottanut? Jos haluat suorittaa joukon funktiokutsuja viivästetyin aikavälein, niin sinun täytyy lisätä setTimeout jokaiseen kutsuun erikseen. Muutos olisi aika helppoa tehdä, ellet olisi valinnut rekursiota toteutustavaksi.

Multibyte [17.05.2016 09:15:58]

#

The Alchemist kirjoitti:

(16.05.2016 13:22:04): SetTimeout lykkää vain sitä funk­ti­o­kutsua...

Hmm, tässä on nyt kolme funktiota ja kolme setTimeout kutsua.
Ensin kutsutaan rekursiota, jossa päätellään seuraava steppi.
Sitä kutsutaan timeoutin avulla, joiden sisällä olevia kutsutaan niin ikään timeoutin avulla.

Eli lähtökohtaisesti seuraavaa rekursiota kutsutaan vasta timeoutin jälkeen, jossa taas asetetaan uusi rekursio tai toiminto timeoutin kanssa.

Noh, muutin sen eilen niin, että tiedostopoluista muodostetaankin oma taulukko, joka loopataan lopuksi. Eli rekursiota käytetään ainoastaan polkujen selvittämiseen.
setTimeoutia käytetään sitten latausfunktion kutsumiseen.
Ei tämäkään tosin toiminut niin kuin ajatus, mutta ehkä se tänään tai huomenna ratkeaa.

Metabolix [17.05.2016 16:57:40]

#

Toimiva ratkaisu menisi tähän tapaan:

function aloita() {
  var data = []

  function silmukka() {
    // Otetaan taulukosta yhdet parametrit.
    var p = data.pop()
    // Tehdään niillä parametreilla jotain, mutta rekursion sijaan
    // laitetaankin seuraavan "funktiokutsun" parametrit taulukkoon.
    if (plaaplaa(p[0], p[1])) {
      data.push([p[0] + "/toiset", p[1] + "/parametrit"])
    }
    // Jos dataa on yhä, suoritetaan tämä funktio kohta uudestaan.
    if (data.length > 0) {
      setTimeout(silmukka, 100)
    }
  }

  // Aloitetaan em. silmukka.
  data.push(["ensimmäiset", "parametrit"])
  silmukka()
}

The Alchemist [18.05.2016 12:36:37]

#

Minusta olisi hyvä opetella tekemään vaikeista välivaiheista geneerisiä funktioita, joita voi sen jälkeen käyttää missä tahansa ilman saman koodin ja samojen vaikeiden yksityiskohtien toistamista. Koodistakin tulee siistimpää, kun geneerinen osa on jossain muualla poissa silmistä, ja voi keskittyä ymmärtämään kulloisenkin tehtävän välivaiheet.

Uusimmassa javascriptin / ecmascriptin standardissa on mukana Promise-luokka, jolla asynkronisen map-funktion tekeminen on melko simppeliä. Promiseille on kattavasti polyfillejä, joten niitä voi käyttää myös sellaisessa koodissa, joka on tarkoitettu toimimaan vanhempienkin selainten kanssa. Myöhemmin esitelty ES6-syntaksin käyttö vaatii taas aivan viimeisimmät versiot selaimista, joten se ei välttämättä maksa vaivaa.

"use strict";

function mapWaitES5(items, callback) {
  var result = new Array(items.length);
  var index = 0;

  return new Promise(function(resolve, reject) {
    function process() {
      if (items.length) {

        /*
         * Promise.resolve() sallii callback-funktion palauttaa suoraan
         * tuloksena käytettävän arvon Promise-instanssin sijaan.
         * (Katso alempi esimerkki funktion mapWaitES5-funktion käytöstä.)
         */
        Promise.resolve(callback(items.shift(), index))
          .then(function(mapped_value) {
            result[index++] = mapped_value;
            process();
          })
          .catch(reject);
      } else {
        resolve(result);
      }
    }

    process();
  });
}

// Esimerkki asynkronisen koodin ajamisesta yksi rivi listan arvo kerrallaan.
mapWaitES5(["A", "B", "C", "D"], function(value, index) {
  return new Promise(function(resolve, reject) {
    console.log(index + " -> " + value);
    setTimeout(resolve, 1000, index + value);
  });
}).then(function(result) {
  console.log("Got result", result);
}, function(error) {
  console.error("Got error", error);
});

// Käsittelyfunktio voi palauttaa suoraan tuloksen, mikäli asynkronisuutta ei tarvita.
mapWaitES5(["A", "B", "C", "D"], function(value, index) {
  return value + index;
}).then(function(result) {
  console.log("Got result", result);
}, function(error) {
  console.error("Got error", error);
});

Alla vielä sama mapWait-funktio ES6-standardin sallimalla luettavammalla syntaksilla.

"use strict";

function mapWaitES6(items, callback) {
  var result = new Array(items.length);
  var index = 0;

  return new Promise((resolve, reject) => {
    let run = () => {
      if (items.length) {
        Promise.resolve(callback(items.shift(), index)).then(mapped => {
          result[index++] = mapped;
          run();
        }).catch(reject);
      }
    };

    run();
  });
}

mapWaitES6(["Q", "X", "Y", "Z"], (value, index) => {
  if (index == 2) {
    throw new Error("Failed!");
  }
  console.log(index + " -> " + value);
  return value + index;
}).then(
  result => console.log("RESULT", result),
  error => console.error("ERROR", error)
);

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta