Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: PHP: Yhden rivin haku moniulotteisessa taulukossa

krrish389 [18.04.2019 07:25:09]

#

Olen löytänyt Array_search-dokumentissa kommentin, joka saa halutun avaimen ulottuvuustaulukoiden joukosta. Oletan, että se ei ole totta kaikissa tilanteissa, ja kun olen lukenut foorumin ja tarkistanut sen itse, vahvistin epäilyn.

$user = array(1 => array('etunimi' => 'Matti', 'sukunimi' => 'Meikäläinen'),
              3 => array('etunimi' => 'Maija', 'sukunimi' => 'Meikäläinen'));

$key = array_search('Maija', array_column($user, 'etunimi'));
echo var_export($user[$key], true);

/*
 * array_column palauttaa array(0 => 'Matti', 1 => 'Maija'),
 * jolloin $key on 1 vaikka pitäisi olla 3
 * eli ulos tulee Matin taulukko eikä haluttu Maija.
*/

Tietysti tämä on ratkaistava käymällä läpi jokaisen pöydän foreach-silmukan ja suorittamalla se, mutta onko olemassa esimerkkinä lyhyt linjainen vastaus, joka ei lopulta toimi kaikissa tilanteissa? Ajattelin käyttää Array_map-toimintoa jotenkin, mutta en ymmärtänyt ajatusta siitä, miten se voidaan ottaa käyttöön.

Grez [18.04.2019 08:14:12]

#

Tuohan tekee juuri sen minkä käsket. Ensin muutat tuon taulukkosi array_column -funktiolla

array_column($user, 'etunimi')

muotoon

Array
(
    [0] => Matti
    [1] => Maija
)

Sitten etsit tuolta avaimen, joka vastaa Maijaa (eli 1)

Ja sitten otat $user -taulukosta avaimen 1 takana olevan.

Mitään tyylikästä ratkaisua tuohon ei taida olla. Foreach-silmukka lienee selkeä vaihtoehto. Toinen mahdollinen olisi laittaa noihin taulukon sisällä oleviin taulukoihin myös se Id-mukaan, jolloin voisit antaa array_columnille kolmanneksi parametriksi sen.

$user = array(1 => array('id' => 1, 'etunimi' => 'Matti', 'sukunimi' => 'Meikäläinen'),
3 => array('id' => 3, 'etunimi' => 'Maija', 'sukunimi' => 'Meikäläinen'));

$key = array_search('Maija', array_column($user, 'etunimi', 'id'));
echo var_export($user[$key], true);

The Alchemist [18.04.2019 08:43:19]

#

Suuremmalla datajoukolla tuo ratkaisu on ihan helvetin huono, koska sen muisti- että suoritusaikavaatimus on suuri. Järkevin ratkaisu olisi kirjoittaa oma funktio, joka iteroi taulukon läpi foreachilla.

Vähiten huono nopea ratkaisu on sisällyttää id-sarake datariveihin. On muutenkin todella lyhytnäköistä jättää id pois riveistä sen vuoksi, että taulukko on indeksoitu id-numeroita käyttäen. Tässä on juuri esimerkki siitä, milloin se puraisee takaisin.

Toinen laiska ratkaisu on ottaa avaimet talteen erilliseen taulukkoon ja hakea array_searchin palauttamasta indeksistä todellinen käyttäjän indeksi.

$match = array_search('Maija', array_column($users, 'first_name'));
$uids = array_keys($users);
$user = $users[$uids[$match]];

Sitten vielä yksi vaihtoehto koodille, joka ei ole niin kertakäyttöistä:

function createMatcher($column, $value) {
  return function (array $row) use ($column, $value) {
    return strcasecmp($row[$column], $value) == 0;
  };
}

$users = [
  ['first_name' => 'Maija'],
  ['first_name' => 'Matti'],
  ['first_name' => 'Seppo'],
  ['first_name' => 'Matti', 'last_name' => 'Toinen'],
];

$matit = array_filter($users, createMatcher('first_name', 'Matti'));

var_dump($matit);

P.S. Nimeä aina muuttujat järkeä käyttäen. "$user" on todella huono valinta kaikki käyttäjät sisältävälle taulukolle. Nyt et voi myöskään poimia enää yksittäistä käyttäjää $user-muuttujaan, koska se on jo varattu.

P.P.S. EI: "echo var_export($foo, true)"; KYLLÄ: "var_export($foo)".

P.P.P.S. Koodin vähyys ei ole osoitus laadusta. Hyvässä projektissa on paljon uudelleenkäytettävää, tehokasta koodia ja vähän outoja ja ihmeellisiä purkkaviritelmiä.

Grez [18.04.2019 09:03:14]

#

The Alchemist kirjoitti:

Hyvässä projektissa on paljon uudelleenkäytettävää, tehokasta koodia

Mielestäni jos (yksinkertaiseen) projektiin tarvitsee paljon koodia, niin se kielii vähintään huonosta kielivalinnasta ja/tai kyvyttömyydestä löytää soveltuvat kirjastot.

Sitten toinen juttu on se, että paljonko pienessä projektissa kannattaa tehokkuusoptimoida. Jos tuo taulukko kasvaa niin suureksi, että tehokkuusasia alkaa muodostua merkitykselliseksi, olisi koko roska kannattunut jo aikaa sitten tuupata tietokantaan.

Voisin myös sanoa, että on helppoa optimoida koodi mahdollisimman tehokkaaksi, paljon vaikeampaa on optimoida sitä paljonko työaikaa käyttää väkertämiseen. Saa olla aika epätehokasta koodia laajassa käytössä ennen kuin säästynyt koneaika nousee kustannuksissa ohi koodareiden palkan. Mutta ehkä tällaista kannattaa miettiä vain jos ylipäätään puhutaan kompetenteista koodareista. Olen nähnyt projekteja joissa tunarit on käyttänyt uskomattoman paljon aikaa kirjoittamaan uskomattoman epätehokasta koodia.

Siitä olen joka tapauksessa samaa mieltä että outoja purkkavirityksiä olisi syytä välttää.

Metabolix [18.04.2019 19:53:55]

#

The Alchemist kirjoitti:

Sitten vielä yksi vaihtoehto...

Hieman kyseenalaistan, onko hyötyä määritellä erikseen createMatcher-funktio (ainakaan tällä nimellä). Silloinhan pitää erikseen muistaa, että createMatcher käyttää strcasecmp-funktiota ja ottaa parametrina juuri taulukon. Anonyymi funktio ei ole mielestäni mitenkään liian pitkä.

$matit = array_filter($users, createMatcher('first_name', 'Matti'));

// PHP 5.3
$matit = array_filter($users, function($u) { return !strcasecmp($u["first_name"], "Matti"); });

// PHP 7.4 näillä näkymin (ks. https://wiki.php.net/rfc/arrow_functions_v2)
$matit = array_filter($users, fn($u) => !strcasecmp($u["first_name"], "Matti"));

krrish389 kirjoitti:

Olen löytänyt Array_search-dokumentissa kommentin, – – Onko olemassa esimerkkinä lyhyt linjainen vastaus, joka ei lopulta toimi kaikissa tilanteissa?

Kommentit ovat juuri käyttäjien kommentteja, eivät virallisia esimerkkejä. Osa on oikein, suuri osa on väärin. Tosin tuolla funktion array_search dokumentaation kommenteissa mielestäni nimenomaan kerrotaan, että array_column kadottaa avaimet ja ei siksi toimi haluamallasi tavalla.

Kohdallasi ehkä helpoin ratkaisu on kuitenkin foreach-silmukka.

function array_search_column($needle, $array, $column, $strict = false) {
  foreach ($array as $key => $row) {
    if ($strict ? $row[$column] === $needle : $row[$column] == $needle) {
      return $key;
    }
  }
}

$key = array_search_column("Maija", $users, "etunimi");

The Alchemist [19.04.2019 08:42:53]

#

Metabolix kirjoitti:

Hieman kyseenalaistan, onko hyötyä määritellä erikseen createMatcher-funktio (ainakaan tällä nimellä). Silloinhan pitää erikseen muistaa, että createMatcher käyttää strcasecmp-funktiota ja ottaa parametrina juuri taulukon. Anonyymi funktio ei ole mielestäni mitenkään liian pitkä.

No se oli vain esimerkki; toteutuksenhan voi jokainen valita itse. Sisäisesti kutsuttavasta funktiosta ei tarvitse "tietää" tai "muistaa" yhtään mitään vaan riittää tietää, että vertailu käyttäytyy loogisesti.

Kuitenkin strcasecmp:n etuna on se, että se toimii myös numeroiden kanssa ja kohtelee numeroa 0 eriarvoisesti NULL- ja FALSE-arvojen kanssa, eli käyttäytyy omasta mielestäni ns. luotettavasti. (Ja jos kuitenkin haluaisi että 0 === NULL, niin on helppoa lisätä koodiin muutama merkki konversion toteuttamiseksi.)

Se nyt on ihan turhaa nillittämistä, että parametriksi kelpaa vain taulukko, kun on triviaalia muuttaa funktio haldaamaan myös stdClass-oliot. Aika usein PHP:n kanssa käytetään kuitenkin kapseloivia olioita, joiden muuttujat eivät ole julkisia vaan niitä käpistellään get/set-funktioiden kautta, jolloin tarvitaan paljon kompleksimpaa koodia.

Silloin kun jaan forkalla koodiesimerkkejä, niin teen sen vaihtoehtojen tarjoamiseksi ja yleissivistyksen vuoksi, en siksi että yhteen ongelmaan olisi vain yksi täydellinen ratkaisu. Yksinkertaiset ja toimivat esimerkit rohkaisevat käyttämään niitä.

PHP:n nykyinen anonyymien funktioiden määritys on aivan liian verboosi ja rajoitettu ja siksi niiden käyttäminen on vaikeaa ilman ruman koodin kirjoittamista. Monta kertaa koodia rumentaa myös parametrien väärä järjestys. Tässä tapauksessa

Vastaus

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

Tietoa sivustosta