Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: PHP: Järjestysnumeroiden lisäys taulukkoon

xxmss [27.03.2017 18:02:34]

#

Käytössä on PHP. Minulla on moniulotteinen taulukko.

    Array (
      [183] => Array (
        [info] => Array (
          [name] => Ben
          [points] => 4800
        )
      )

      [380] => Array (
        [info] => Array (
          [name] => Ruben
          [points] => 14500
        )
      )

      [450] => Array (
        [info] => Array (
          [name] => Alex
          [points] => 4800
        )
      )
    )

PHP-koodi järjestää/lajittelee taulukon ja se toimii ihan oikein.

Ensimmäinen lajittelukriteeri on points, jonka mukaan lajitellaan laskevasti. Toinen kriteeri on name, lajittelu nousevasti.

uasort($array, function($a, $b)
{
  if ($a['info']['points'] == $b['info']['points'])
  {
    return strcmp($a['info']['name'], $b['info']['name']);
  }

  return $b['info']['points'] - $a['info']['points'];
});

Kun PHP-koodi lajittelee taulukon, syntyvä taulukko on tällainen:

    Array (
      [380] => Array (
        [info] => Array (
          [name] => Ruben
          [points] => 14500
        )
      )

      [450] => Array (
        [info] => Array (
          [name] => Alex
          [points] => 4800
        )
      )

      [183] => Array (
        [info] => Array (
          [name] => Ben
          [points] => 4800
        )
      )
    )

Muuten homma toimii hienosti, mutta haluaisin saada lisättty myös position-arvot mieluummin lajittelun lomassa tai jos se ei onnistu, niin sitten vasta lajittelun jälkeen vaikkapa foreach-silmukassa.

Mitä enemmän pisteitä (points), sitä pienempi position. Jos points on sama, on myös position-arvojen oltavat samat.

Lopullinen taulukko pitäisi saada tällaiseksi:

    Array (
      [380] => Array (
        [info] => Array (
          [name] => Ruben
          [points] => 14500
          [position] => 1
        )
      )

      [450] => Array (
        [info] => Array (
          [name] => Alex
          [points] => 4800
          [position] => 2 /* koska Alexilla points on pienempi kuin Rubenilla */
        )
      )

      [183] => Array (
        [info] => Array (
          [name] => Ben
          [points] => 4800
          [position] => 2 /* koska Benillä points on sama kuin Alexilla */
        )
      )
    )

Mitä homma hoituisi mahdollisimman nopeasti foreach-silmukalla tai jollakin muulla tavalla?

Metabolix [27.03.2017 20:58:27]

#

$position = 0;
$position_for_points = 0;
$points = null;
foreach ($taulukko as $i => $_) {
	$position += 1;
	if ($taulukko[$i]["info"]["points"] !== $points) {
		$position_for_points = $position;
		$points = $taulukko[$i]["info"]["points"];
	}
	$taulukko[$i]["info"]["position"] = $position_for_points;
}

Sivuhuomio: Joku ehkä ihmettelee, miksi en käytä koodissa viittauksia (&$rivi). Syy on se, että viittauksilla tulee helposti virheitä, koska silmukan jälkeen viittaus jää viimeiseen riviin ja samannimisen muuttujan käyttäminen myöhemmin ilman viittausta muuttaakin odottamatta sitä taulukon viimeistä riviä.

xxmss [27.03.2017 21:10:08]

#

Kiitos. Miksi !== eikä != tuossa? Eikö muuttujan $points arvoa päivitetä välillä? En nyt pysty tätä testaamaan, serveri alhaalla...

Metabolix [27.03.2017 21:44:32]

#

xxmss kirjoitti:

Miksi !== eikä != tuossa?

Jotta koodi toimisi myös tilanteessa, jossa kärjellä on nolla pistettä. 0 == null mutta 0 !== null.

xxmss kirjoitti:

Eikö muuttujan $points arvoa päivitetä välillä?

Päivitetään. Poistin sen rivin vahingossa, kun muokkasin koodia. Nyt koodi on taas oikein edellisessä viestissä.

xxmss [27.03.2017 22:13:27]

#

Kiitos lisäinfosta. Voisiko alussa olla $points = 0; ja sitten if-lauseessa != $points?

The Alchemist [27.03.2017 22:34:31]

#

Oliomallinen ratkaisu iteraattoria käyttäen. Tällöin rankkaus tehdään lennosta taulukkoa loopatessa ja välttyy ylimääräiseltä silmukalta.

<?php

class RankIterator extends ArrayIterator {
  /**
   * Iterator index 0...n
   */
  protected $i;

  /**
   * Ranking
   */
  protected $pos;

  /**
   * Function used to compare two values.
   */
  protected $comparison;

  /**
   * Key to use for storing item ranking.
   */
  protected $rank_key;

  /**
   * Value from last iteration to be used for comparison.
   */
  protected $last;

  public function __construct(array $data, $rank_key, callable $comparison) {
    parent::__construct($data);
    $this->rank_key = $rank_key;
    $this->comparison = $comparison;
  }

  public function current() {
    $item = parent::current();

    if ($this->i > 0) {
      $delta = call_user_func($this->comparison, $this->last, $item);

      if ($delta > 0) {
        $this->pos = $this->i + 1;
      } elseif ($delta < 0) {
        throw new Error('Source data is not sorted');
      }
    }

    $item[$this->rank_key] = $this->pos;
    $this->last = $item;

    return $item;
  }

  public function next() {
    parent::next();
    $this->i++;
  }

  public function rewind() {
    parent::rewind();
    $this->i = 0;
    $this->pos = 1;
    $this->last = null;
  }
}


$data = [
  ['points' => 100, 'name' => 'Miska'],
  ['points' => 95, 'name' => 'Laura'],
  ['points' => 87, 'name' => 'Siiri'],
  ['points' => 87, 'name' => 'Jaakko'],
  ['points' => 45, 'name' => 'Petteri'],
];

$iterator = new RankIterator($data, 'rank', function($a, $b) {
  return $a['points'] - $b['points'];
});

print_r(iterator_to_array($iterator));

Vastaus

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

Tietoa sivustosta