Doppelte Array-Einträge entfernen
Vor kurzem hatte ich das kleine Problemchen dass in einem Array die Werte (Strings und Integer) mehrfach vorkamen, ich diese aber nicht gebrauchen konnte. Mit array_unique() kann man doppelte bzw. mehrfach vorkommende Werte auf ein Vorkommen reduzieren, aber es gibt keine direkte Methode um mehrfach vorkommende Werte ganz zu entfernen.
Beispiel: Aus dem Array (1, 5, 8, ‚Michael‘, 5, 4, 9, ‚Martin‘, 18, 12, ‚Michael‘, 4, 12) sollen die mehrfach vorkommenden Werte entfernt werden, sodass nur noch das Array (1, 8, 9, ‚Martin‘, 18) übrig bleibt.
Meine einfache und erste Lösung ist die folgende:
function removeDuplicates($array) { $counts = array_count_values($array); foreach ($counts as $value => $counter) { if ($counter > 1) { unset($counts[$value]); } } return array_keys($counts); }
Der Aufruf sieht dann wie folgt aus:
$a = array(1, 5, 8, 'Michael', 5, 4, 9, 'Martin', 18, 12, 'Michael', 4, 12); var_dump(removeDuplicates($a));
Habt ihr eine andere/bessere Funktion? Gerade bei sehr großen Arrays wäre es evtl. interessant schnellere Lösungen zu kennen, denn meine durchläuft das Array 3 Mal (array_count_values(), meine foreach Schleife und array_keys()), es müßte auch mit einem oder maximal 2 Schleifendurchläufen gehen…
Edit:
Mir ist gerade noch nach dem Schreiben des Artikels eine zweite Lösung eingefallen, die aber bei sehr großen Arrays genauso langsam sein dürfte und etwas schwerer zu durchschauen ist:
function removeDuplicates($array) { return array_diff($array, array_diff_assoc($array, array_unique($array))); }
Hallo!
Ich würde mal vorschlagen, aus Werten Schlüssel zu machen (so was wie: array_flip(array_flip($arr)) ). Aber nur, wenn die Reihenfolge egal ist und Werte Ints oder Strings sind…
Wäre das eine Lösung?
Heringartige Grüße
Umberto
UmbertoKo
22 Feb 12 at 11:23
man könnte array_count_values durch eine eigene Funktion ersetzen, die vorher prüft, ob der Schlüssel schon angelegt wurde und diesen direkt löschen, wenn das der Fall ist.
Dann würde das Ergebnis nur noch die Schlüssel enthalten, die nur einmal vorkommen und man könnte sich das foreach sparen.
Ray-D
22 Feb 12 at 11:27
array_keys() müßte man sich sparen können, wenn man sich ein anfangs leeres Rückgabe-Array schafft und dort alle Elemente reinpackt, wo $counter == 1 gilt.
Karl Valentin
22 Feb 12 at 11:35
Wenn ich mich da nicht verbrezelt hab, dürfte ein recht simples foreach den Job auch erledigen: https://gist.github.com/1884088
Rodney Rehm
22 Feb 12 at 11:43
Also, um das noch erklärt zu haben…
Bei meinem foreach() wird genau ein einziges Mal über das Array gelaufen. Beim array_count_values() und array_diff() Ansatz mehr oder weniger drei Mal.
Ist zwar alles linearer Aufwand, aber man muss ja nicht mehr Laufen als nötig, oder?
Rodney Rehm
22 Feb 12 at 11:49
Hab mich auch mal probiert:
http://pastebin.com/ReEBAy8V
Funktionsweise sollte eigentlich relativ ersichtlich sein, zumal ja auch kommentiert.
Diese Funktion ist auch bei relativ grossen Arrays noch einigermassen zügig (0.067s bei 100’000 Werten).
DukeNightcrawler
22 Feb 12 at 12:48
Hmm, mein Ansatz mit array_filter() ist wohl weder der schnellste noch der langsamste. Aber der Vollständigkeit halber: https://gist.github.com/1884528
Matthias Gutjahr
22 Feb 12 at 13:07
Ich muss mich korrigieren: die Reihenfolge ist die Reihenfolge des ersten Vorkommens eines Wertes… (also nicht zufällig oder so.) Nur endet es in einem sinnlos assoziativen Array. So schlage ich (immer noch nur für Strings und Ints)
array_values(array_flip(array_flip($a))))
vor
UmbertoKo
22 Feb 12 at 14:48
Ich hab die Geschwindigkeit nicht getestet, wie schnell die Funktionsaufrufe print_r und isset sind, aber es kommt mit einem Schleifendurchlauf aus: http://pastebin.com/HQw197L3
Simon
22 Feb 12 at 15:28
@UmbertoKo: Mit den array_flips machst du das selbe wie array_unique(). Die Aufgabenstellung ist jedoch mehrfach vorkommende Elemente ganz zu entfernen, du reduzierst sie nur auf ein Vorkommen.
Michael Kliewe
22 Feb 12 at 15:32
Sorry. Jetzt merke ich, dass ich nicht ausgeschlafen bin. Lösche bitte meine Kommentare. Seit Facebook „entfernt aber nicht löscht“ verstehe ich die Welt sowieso nicht mehr 😉
UmbertoKo
22 Feb 12 at 15:56
@UmbertoKo: Ich lösche und entferne keine Kommentare, du bist also aufgerufen eine richtige Lösung zu präsentieren 😉
Michael Kliewe
22 Feb 12 at 16:00
Das finde ich ganz schön unfaire unausgeschalfenen jungen Vätern gegenüber 😉
Dann schlage ich folgendes vor:
$a2 = array();
foreach($a as $o)
{
if(count(array_keys($a, $o)) == 1)
{
$a2[] = $o;
}
}
unset($a);
echo „Unsere Arrays sind umgezogen. $a wohnt jetzt bei $a2. Bitte dort anklopfen.“;
Sinnvollere Lösungen gibt es hoffentlich morgen.
UmbertoKo
22 Feb 12 at 16:36
Ps. Es föllt mir grad auf: Im Grunde ist es eine Variation Deiner Lösung. Sieht aber auf den ersten Blick nicht so zu-Guttenberg-isch aus…
UmbertoKo
22 Feb 12 at 16:41
Und für Arrays, die nicht gerne umziehen…
foreach($a as $o){ $a2 = array_keys($a, $o); if(count($a2) > 1){foreach($a2 as $i){unset($a[$i]);}}}
UmbertoKo
22 Feb 12 at 17:28
Und wer macht jetzt die Benchmarks? 😉
Fabian
22 Feb 12 at 20:06
Der Sandmann? Wenn er es schafft, bei Dir seinen Sand früher los zu werden, hast Du gewonnen (etwas Sand jedenfalls).
Im Ernst: Jede Funktion, die in C geschieben ist, wird wohl schneller, als eine die in PHP geschrieben ist. (Das lehrt mich die Erfahrung. Lasse mich aber auch vom Gegenteil überzeugen.)
Umberto
22 Feb 12 at 20:15
http://pastebin.com/AGBrRWL2
Hab mich auch mal probiert.
Die Frage ist sollen die Key beibehalten werden oder einfach durchnummeriert werden?
Daniel
22 Feb 12 at 22:29
mit einer schleife
$singles = array();
foreach($arr as $key => $val) {
if (!isset($singles[$key])) { $singles[$key] = $val; }
else { unset($singles[$key]; }
}
Weiß nicht ob die Lösung schon irgendwo in den verlinkten codes vorkam…
Kjell
23 Feb 12 at 11:05
Moin,
wie kommt denn das Array zustande Kannst Du vielleicht vorher mit array_search() gucken, ob der Wert schon vorhanden ist und ihn dann gar nicht erst speichern?
Gruss
Chris
24 Feb 12 at 09:28
@Chris: In der Realität ist das Problem etwas anders, habe es hier entsprechend vereinfacht. Das Array kann als gegeben angesehen werden, ich bekomme es aus einer externen Quelle die das nicht filtern kann.
Michael Kliewe
25 Feb 12 at 11:52
Obs schneller ist, weiss ich nicht. Aber das währe mein Ansatz: array_count_values -> array_filter -> array_keys
$counts = array_count_values($items);
$filtered = array_filter($counts, create_function(‚$value‘, ‚return $value===1;‘));
$result = array_keys($filtered);
Yaslaw
5 Mrz 12 at 12:38
http://php.net/manual/de/function.array-unique.php
Klaus Schwarzkopf
28 Feb 13 at 09:37