Autor Zpráva
mackopu
Profil
Mám pole např. o 2150 prvcích. A potřebuji ho zredukovat na max. 1000 prvků. Ne prvních nebo posledních nebo náhodných, ale prostě tak, aby zůstal každý n-tý prvek.
Jde to nějak elegantně?
Dan Charousek
Profil
mackopu:
Můžeš si původní pole projet foreachem a pukládat si zvolené hodnoty do nového pole:

$n = 5; // každý pátý prvek
$i = 0;
$newArray = [];

foreach($oldArray as $key => $value) {
    $i++;
    if($i%$n != 0)
        continue;
    $newArray[$key] = $value;
}
juriad
Profil
Pokud se jedná o pole a nikoli o asociativní pole, můžeš postupovat takto:
function everyNth($array, $nth, $mod = 0) {
  $new = array();
  for ($i = $mod; $i <= count($array); $i += $nth) {
    $new[] = $array[$i];
  }
  return $new;
}

Dan Charousek:
$i++ patří až na konec smyčky. Ale máš tam tu nevhodnou podmínku s continue, takže by šlo: if ($i++ % $n != 0).
Dan Charousek
Profil
juriad:
Poku by bylo $i++ až na konci smyčky, tak se v případě splněné splněné podmínky, jak jsi uvedl, neprovede, ale hlavně vzalo by to i první prvek, což předpokládám, není žádané.
juriad
Profil
Dan Charousek:
Ono záleží na definici n-tý. Já za každý pátý považuji prvky s indexem 0, 5, 10... Ale vím, že někteří nebudou souhlasit, proto je tam v mé funkci ten paramer $mod.
Aneb, pokud má pole 6 prvků a ty chceš každý pátý, očekáváš, že nové pole bude obsahovat jeden nebo dva prvky?
mackopu
Profil
juriad:
Očekávám, že bez ohledu na počet prvků původního pole bude mít nové pole 1000 prvků, samozřejmě pokud jich má původní pole více. A bylo by fajn, kdyby první prvek nového pole byl totožný s prvním prvkem pole původního.
Keeehi
Profil
V rámci toho jednoho pole
$array = [1,2,3,4,5,6,7,8];
$count = count($array);
$limit = 3; // pokud chceš max 1000 prvků, přiřaď sem 1000
$mod = ceil($count/$limit);

for ($i = 0 ; $i < $count ; $i++) {
    if($i % $mod != 0) {
        unset($array[$i]);
    }
}
juriad
Profil
mackopu:
Co vlastně chceš? Chceš každý n-tý prvek? Pak jich ale nemusí být 1000.
Chceš z původního pole vybrat rovnoměrně 1000 reprezentantů? Pak od sebe nebudou vzdálení stejně.
Chceš vybrat každý n-tý, oříznout pole na 1000, pro takové n, aby se zahodilo nejméně prvků?
Musí nové pole mít 1000 prvků, nebo může mít méně?
Co když původní pole mělo 1999 prvků? Které bys chtěl v takovém případě?
mackopu
Profil
juriad:
Ano, chci tu druhou tebou uvedenou možnost.
Dan Charousek
Profil
mackopu:

<?php

function reduceArray($array, $reduceTo) {
    
    $mod = count($array)/$reduceTo;
    $keys = array_keys($array);
    $newArray = [];

    for($i = 0; $i < count($keys); $i+= $mod) {
        $newArray[$keys[ceil($i)]] = $array[$keys[ceil($i)]];
    }

    return $newArray;

}

$arr = range(1, 2150);
$new = reduceArray($arr, 1000);
mackopu
Profil
Všem děkuji, pomohli jste mi a problém je vyřešen.
Keeehi
Profil
Dan Charousek:
Má to problém. Pro reduceArray($arr, 2149); to šahá mimo pole.
Dan Charousek
Profil
Keeehi:
Pravda,

    for($i = 0; $i < count($keys) - floor($mod); $i+= $mod) {
        $newArray[$keys[ceil($i)]] = $array[$keys[ceil($i)]];
    }

by to mělo opravit.
Keeehi
Profil
Výborně, a teď ještě toto:
$arr = range(0, 101);
$new = reduceArray($arr, 4); // 0, 26, 51, 77
Myslím si, že distribuce 0, 33, 67, 100 by byla mnohem hezčí. Takto to favorizuje prvky, které jsou více vlevo. Je to krásně cidět, když se spočítá medán. 0 - 100 má medián 50. Posloupnost 0, 33, 67, 100 taky, ale posloupnost 0, 16, 51, 77 ho má 38,5.

Ještě jeden problém vidím $i+= $mod, v mod je desetinné číslo, které se velmi často musí zaokrouhlit kvůli paměti v počítači. Takže je lehce nepřesné. No a protože se to cyklicky sčítá, tato nepřesnost se kumuluje a roste. U 1000 se to nejspíše neprojeví, ale u vyšších řádů by mohlo.

Proto bych navrhoval
<?php

function reduceArray(&$array, $reduceTo) {
    $count = count($array)-1;
    $mod = $count/($reduceTo-1);

    $cnt = 0;
    for ($i = 0 ; $i < $count ; $i++) {    
        if($i < round($mod*$cnt)) {
            unset($array[$i]);
        } else {
            $cnt++;    
        }
    }
}

$array = range(0,100);
reduceArray($array, 4);
?>
Alphard
Profil
Vy jste hrozní kouzelníci :-) Můj pohled na věc je takový, že v prvním kroku si vytvořím funkci linspace (známá z Pythonu, Matlabu, ...) a v druhém kroku ji použiji.

Zmíněná funkce linspace by implementačně byla už velmi podobná řešení [#14] Keeehi, ale očekával bych, že poslední prvek pole doplní tak, aby přesně odpovídal zadání (tím rozhodně nemyslím násobení desetinným $mod). Cvičně si to programovat nebudu, příklad implementace v pythonu je např. na github.com/numpy/numpy/blob/v1.10.0/numpy/core/function_base.py#L9-L120, zdůraznil bych řádky 114-115, které řeší poslední prvek (v této implementaci je škoda toho přetypování na float, což vynucuje numpy, ale v PHP by tento problém neyl).

Když tuto funkci budu mít hotovou, zbyde triviální část úkolu
function reduceArray($input, $outputSize) {
    $output = [];
    foreach (linspace(0, count($input)-1, $outputSize) as $key) {
        $output[] = $input[round($key)]; // změnil jsem floor na round
    }
    return $output;
}

Výsledkem bude [0, 34, 67, 101], což bych očekával.

Vaše odpověď

Mohlo by se hodit

Odkud se sem odkazuje


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: