Eindimensionales Array mehrfach sortieren

Hallo,

ich sitze gerade an einem Problem, dass ich zur besseren Ansicht ein Array mit Key (Ein Teilstring) und Value (Anzahl Vorkommnisse des Teilstrings im String) erst nach dem Value und dann noch nach dem Key sortieren möchte (genau in dieser Reihenfolge). Es scheint scheinbar ganz einfach zu sein, weil ich nur Posts zu mehrdimensionalen Arrays gefunden habe, dennoch komme ich nicht darauf.

Ich habe mein Array zuerst mit arsort($array); nach den Werten sortiert, dies hat auch ganz gut funktioniert. Ausgabe von var_dump():

array(5) {
  ["foo"]=>
  int(2)
  ["bar"]=>
  int(2)
  ["barfoo"]=>
  int(1)
  ["foobar"]=>
  int(1)
  ["blah"]=>
  int(1)
}

Nun wollte ich anschließend noch alphabetisch nach dem Schlüssel sortieren, habe dazu uksort($array, 'compare'); benutzt wobei die Funktion compare eine simple Vergleichsfunktion von a und b darstellt.

function compare($a, $b)  {
    if($a == $b)  {
        return 0;
    }
    return ($a < $b) ? 1 : -1;
}

Das Array wird nun wie gewünscht nach dem Schlüssel sortiert, natürlich aber ohne die vorherige Sortierung zu beachten.

array(5) {
  ["bar"]=>
  int(2)
  ["barfoo"]=>
  int(1)
  ["blah"]=>
  int(1)
  ["foo"]=>
  int(2)
  ["foobar"]=>
  int(1)
}

Man kann einer Sortierung ja keine Variable wie $sort = arsort($array) zuweisen, um die Sortierung weiter zu sortieren und die Funktion array_multisort ist nur für multidimensionale Arrays und nicht für multiple Sortier-Verfahren zuständig.

Denke, ich muss meine compare-Methode einfach erweitern (keys und values mit rein bringen), wüsste aber nicht wie. Hab mich durch die Dokumentation gewühlt, habe aber keine passende Funktion gefunden, die ich adaptieren könnte.

Wäre für Hinweise und Tipps dankbar.

LG, DMan

Sortiere erst nach den keys und dann nach den Werten. Bei identischen Werten sollte (theoretisch) die vorher sortierte Reihenfolge der keys unverändert bleiben. Vorrausgesetzt die php sortierfunktionen sind “stabil”, was ich auf der Schnelle nicht herausfinden konnte.

Wahrscheinlich gibt es aber auch irgendeine Möglichkeit das direkt mit einem rutsch uksort zu machen.

Also laut der Sortierfunktionsauflistung gibt es keine Funktion, die nach Keys und Values lexikographisch sortiert:
http://php.net/manual/de/array.sorting.php

Siehe Post von Balmung unten.


Mfg :wink:

@Balmung: Nein, leider nicht (Link oben, Bulletpoint 5: “If any of these sort functions evaluates two members as equal then the order is undefined (the sorting is not stable).”)

Dammit. Soviel dazu.

Anders könnte man doch auch etwas in der art machen, oder?:

$myArr = ['foo' => 2, 'bar' => 1, …];
uksort($myArr, function($a, $b) use ($myArr) {
    $s1 = strcmp($a, $b);
    if ($s1 == 0) {
        return $myArr[$a] - $myArr[$b];  // integer
    }
    return $s1;
});

Hab das natürlich nicht getestet, aber ich denke mein Gedanke ist verständlich. Das Array wird zwar verändert, da die Keys aber erhalten bleiben, sollte das keine Probleme machen.

Hab mir erlaubt das kurz anzutesten, funktioniert perfekt. Wieder was gelernt, die Verwendung von use ist mir neu.

@Mgier Interessant, denn bei mir hat das leider nicht funktioniert. Code:

echo "<pre>Normales Array:<br />";
var_dump($array);
echo "</pre>";

uksort($array, function($a, $b) use ($array) {
    $s1 = strcmp($a, $b);
    if ($s1 == 0) {
        return $array[$a] - $array[$b];  // integer
    }
    return $s1;
});

echo "<pre>Nach uksort:<br />";
var_dump($array);
echo "</pre>";

Ausgabe:

Normales Array:
array(5) {
  ["foo"]=>
  int(2)
  ["bar"]=>
  int(2)
  ["foobar"]=>
  int(1)
  ["blabla"]=>
  int(1)
  ["barfoo"]=>
  int(1)
}
Nach uksort:
array(5) {
  ["bar"]=>
  int(2)
  ["barfoo"]=>
  int(1)
  ["blabla"]=>
  int(1)
  ["foo"]=>
  int(2)
  ["foobar"]=>
  int(1)
}

An sich erkenne ich natürlich, dass die Reihenfolge der Schlüssel stimmt, nur halt nicht mit den Werten. Verwendung von use ist mir auch neu. Trotzdem Danke, @Balmung, hat mir auf jeden Fall weiter geholfen. :'D

LG, DMan

Stimmt, die Funktion sortiert eigentlich in der umgekehrten Reihenfolge als du es gerne hättest. Ich sehe auch momentan nicht, wie wir da wieder rauskommen. Ansonsten, der (zugegebenermaßen etwas hässliche) Workaround den ich vorhin schon mal erwähnt hatte:

$myArr = ['foo' => 2, 'bar' => 1, 'car' => 1, 'aar' => 1, 'aas' => 2,'foobar' => 1];

$ordering = array(1, 0);

function compareLexicographically($a, $b, $ordering)
{
    foreach($ordering as $comp)
    {
        //Compare strings
        if(is_string($a[$comp]) && is_string($b[$comp]))
        {
            if(strcmp($a[$comp], $b[$comp]) != 0)
            {
                return strcmp($a[$comp], $b[$comp]);
            }
        }
        //Compare anything else
        else
        {
            if($a[$comp] != $b[$comp])
            {
                return $a[$comp] - $b[$comp];
            }
        }
    }

    return 0;
}

function addKey(&$array)
{
    foreach($array as $key => $value)
    {
        $array[$key] = [$key, $value];
    }
}

function chopKey(&$array)
{
    foreach($array as $key => $value)
    {
        $array[$key] = $value[1];
    }
}

print_r($myArr);
addKey($myArr);
uasort($myArr, function($a, $b) use ($ordering){
    return compareLexicographically($a, $b, $ordering);
});
chopKey($myArr);
print_r($myArr);

Der sollte zumindest mal das tun, damit du weitermachen kannst.
Mfg :wink:

Wie sollte es sortiert werden? Zuerst nach den Werten und dann nach den Keys?

Dann musst du doch einfach nur die Reihenfolge in der compare function ändern:

$array = [
    'foo' => 2,
    'bar' => 2,
    'foobar' => 1,
    'blabla' => 1,
    'barfoo' => 1,
];

echo "<pre>Normales Array:<br />";
print_r($array);
echo "</pre>";

uksort($array, function($a, $b) use ($array) {
    $s1 = $array[$a] - $array[$b];
    if ($s1 == 0) {
        return strcmp($a, $b);
    }
    return $s1;
});

echo "<pre>Nach uksort:<br />";
print_r($array);
echo "</pre>";

Ausgabe:

Normales Array:
Array
(
    [foo] => 2
    [bar] => 2
    [foobar] => 1
    [blabla] => 1
    [barfoo] => 1
)
Nach uksort:
Array
(
    [barfoo] => 1
    [blabla] => 1
    [foobar] => 1
    [bar] => 2
    [foo] => 2
)

@Balmung Ja, genau so, nur dass die Schlüssel mit dem höhereren Wert weiter oben stehen. Also $array[$b] - $array[$a] statt andersrum. Danke dafür. :'D

uksort($array, function($a, $b) use ($array) {
    $s1 = $array[$b] - $array[$a];
    if ($s1 == 0) {
        return strcmp($a, $b);
    }
    return $s1;
});

Wie hast du es eigentlich geschafft, dass dein Code syntax-highlighting bekommt?

@Mgier Wow, Danke dass du dir da so viel Mühe gegeben hast, echt sehr lieb von dir. Hab selbst schon an etwas ähnliches gedacht, was wahrscheinlich hässlicher und ressourcenlastiger geworden wäre.

Vor dem Sortieren ist mein Array bereits nach dem höchsten Wert sortiert (siehe Ausgabe weiter oben). Hätte also den Value des ersten Indizes gespeichert und eine for-schleife drüber laufen lassen.

function lexicographicalOrder($array)  {
	$newarr = array();
	reset($array);
	$first_key = key($array);
	$amount = $array[$first_key];

	for($c = $amount; $c >= 0; $c--)  {
		$tmparr = array();
		foreach($array as $key => $value)  {
			if($value == $c)  {
				$tmparr[$key] = $value;
			}
		}
		arsort($tmparr);
		array_merge($newarr, $tmparr);
	}
return $newarr;
}

Quasi dass es jede Stufe zuerst sortiert und es danach ins Haupt-Array eingefügt wird. Hab das nicht getestet aber so würde ich es mir vorstellen.

$array = ['foo' => 2, 'foobar' => 1, 'blabla' => 1, 'bar' => 2,'barfoo' => 1];
$c = 2 -> $tmparr = ['bar' => 2, 'foo' => 2]; && $newarr =  ['bar' => 2, 'foo' => 2];
$c = 1 -> $tmparr = ['barfoo' => 1, 'blabla' => 1, 'foobar' => 1]; && $newarr = ['bar' => 2, 'foo' => 2, 'barfoo' => 1, 'blabla' => 1, 'foobar' => 1];
$c = 0 -> $tmparr = []; && $newarr = ['bar' => 2, 'foo' => 2, 'barfoo' => 1, 'blabla' => 1, 'foobar' => 1];

Trotzdem ganz herzlichen Dank dafür!

ich nutze nicht den Button sondern github flavoured markdown syntax (welches ich einfach durch ausprobieren herausgefunden habe), mit den backticks ( ` ascii 96).

```
$code = "abc";
```

wird zu (manchmal aber irgendwie nicht)

$code = "abc";

Ein Einrücken des codes ist dann auch nicht nötig.

Normal kann man hinter den ersten drei Backticks die Sprache angeben z.B.:

```php
code 
```

Aber das scheint hier keine wirkliche Auswirkung zu haben, bzw. da passieren komische Dinge.

@Balmung Werde mir das mal merken und benutzen, Danke. :'D

PS: Habe mal das kleine von mir ausgedachte Script getestet und die Fehler korrigiert, hier mal Script und Ausgabe:

// Sorts an one-dimensional array lexicographical after the keys but paying attention to the value first.
function lexicographicalOrder($array)  {
	// Save Array
	$mainarr = array();
	// Sort that the key with the highest value is at the beginning
	arsort($array);
	
	// Reset for secure to get the first key
	reset($array);
	$first_key = key($array);
	$amount = $array[$first_key];
	
	// Going through the Array with all appearances
	for($c = $amount; $c >= 0; $c--)  {
		// temporary array to save the data of once appearance level
		$tmparr = array();
		foreach($array as $key => $value)  {
			// Save the value in the temporary array at the key
			if($value == $c)  {
				$tmparr[$key] = $value;
			}
		}
		// Sort one appearance level lexicographical
		ksort($tmparr);
		
		// Add the temporary array to the main array
		$mainarr = array_merge($mainarr, $tmparr);
	}
	return $mainarr;
}

$test = array(	"foo"		=> 2,
				"bar"		=> 2,
				"foobar"	=> 1,
				"blabla"	=> 2,
				"barfoo"	=> 1,
			);

echo "<pre>Normal Array:<br />";
var_dump($test);
echo "</pre>";

$test = lexicographicalOrder($test);

echo "<pre>Sorted Array:<br />";
var_dump($test);
echo "</pre>";

Ausgabe:

Normal Array:
  array(5) {
  ["foo"]=>
  int(2)
  ["bar"]=>
  int(2)
  ["foobar"]=>
  int(1)
  ["blabla"]=>
  int(2)
  ["barfoo"]=>
  int(1)
}

Sorted Array:
array(5) {
  ["bar"]=>
  int(2)
  ["blabla"]=>
  int(2)
  ["foo"]=>
  int(2)
  ["barfoo"]=>
  int(1)
  ["foobar"]=>
  int(1)
}

PHP-Sortierfunktionen sind nicht stabil… (Siehe ersten Hinweis https://www.php.net/manual/de/function.usort.php)
Wenn man es mit PHP lösen möchte, würde ich für usort eine Funktion wählen, die gleichermaßen den Key wie auch den Wert auswertet. Selbst dann ist aber sicht sicher, dass du stabil in Bezug auf eine drite Variable sortierst.
Alternativ könnte man auch eine Merge-Sort-Variante verwenden. Ich habe unter porthd/avalanchesort auf github eine Version veröffentlicht, den man neben Arrays und ListArrays per Intrerface-injection ‚beliebige‘ selbst zu definierende Datenstrukturen zum Sortieren übergeben kann.