| 

.NET C# Java Javascript Exception

2
Eigentlich dachte ich ja, dass sich mit der Einführung von LINQ das Iterieren mit for und foreach (fast völlig) erübrigt hat - weil man mit LINQ eher ausdrücken kann, WAS für ein Ergebnis man sucht, während explizite Schelifen eher darstellen, WIE das Ergebnis zustande kommen.
Aber jetzt habe ich doch eine, eigentlich recht simple, Problemstellung, zu der ich keine akzeptable LINQ-Lösung finde.

"Klassisch" programmiert sieht das ganze so aus:

int index = -1;
for (int j = 0; j < arr.Length; j++)
{
if (MyPredicate(arr[j]))
{
index = j;
break;
}
}

Was ich mir erhoffe, ist Code, der nicht wesentlich länger und komplizierter ist, ohne eine explizite Schleife auskommt, und der schon durch seinen Programmtext ausdrückt, was hier passiert: "Suche den Index des ersten Elements in arr, das MyPredicate erfüllt; falls keines existiert, liefere -1."
(Gleich vorab: mit First() zu arbeiten und bei leerer Treffermenge die InvalidOperationException zu fangen, trifft meine Vorstellungen nicht.)
01.06.2011
Matthias Hlawatsch 13,2k 4 9
4 Antworten
1
Hallo,

für diesen Fall weiß ich auch nicht dass es bereits für LINQ was gibt. Du kannst aber selbst eine "linqige" Lösung erstellen indem eine Erweiterungsmethode erstellt wird. Somit wird der imperative Teil durch die Erweiterungsmethode deklarativ gekapselt.

Das Beispiel ist allgemein gehalten (IList<T>). Wenn du jedoch immer nur Arrays hast kann - wenn es schneller gehen soll* - auch auf T[] und for-Schleife umgeschrieben werden.
public static class MyExtensions
{
public static int IndexByPredicate<T>(this IList<T> source, Func<T, bool> predicate)
{
int index = -1;

for (int i = 0; i < source.Count; ++i)
{
if (predicate(item))
{
index = i;
break;
}
}

return index;
}
}


* Knuth: "premature optimization is the root of all evil"
PS: Die Where-Methode von Linq funktioniert ähnlich ;-)


mfG Gü
01.06.2011
gfoidl 9,4k 3 5
gfoidl 9,4k 3 5
+1 Das war die andere Alternative, die mir dann auch noch in den Sinn gekommen war. Wenn man's häufiger braucht, ist das wohl auf jeden Fall die schönere Lösung. Wär vielleicht auch was für's dotnetpro-Extension-Projekt?
Matthias Hlawatsch 01.06.2011
Da dotnetpro-Extension-Projekt kenn ich gar nicht ;-)
Ich finde solche Erweiterungsmethoden sehr praktisch da die Verwendung deklarativen Hauch hat.
gfoidl 02.06.2011
http://dnpextensions.codeplex.com/
Und wer hätte das gedacht: für IList<T> gibt es dort schon die Erweiterungsmethode
public static int IndexOf<T>(this IList<T> list, Func<T, bool> comparison)
mit genau dieser Funktionalität. IList ist irgendwie auch sinnvoll: wenn ich nach einem Index frage, sollte ich eine definierte Reihenfolge haben.
Matthias Hlawatsch 02.06.2011
Hast recht dass IList sinnvoller ist als IEnumerable. Habs oben auch geändert. Danke für den Hinweis (und den Link).
gfoidl 02.06.2011
1
So eine Methode gibt es so weit ich weiß nicht, weil der kleinste gemeinsame Nenner bei Linq ja IEnumerable ist. Falls du den Index nicht unbedingt brauchst könnte eventuell auch die Methode FirstOrDefault(...) helfen.
01.06.2011
sebastianstehle 444 5
Sorry, den ersten Satz verstehe ich nicht. Und der Index ist für mich in dem Fall wesentlich.
Matthias Hlawatsch 01.06.2011
Ich auch nicht mehr, das war auch kein Deutsch, habs korrigiert.
sebastianstehle 02.06.2011
1
Eine Linq-Alternative wäre noch folgendes:

Enumerable.Range(0, arr.Length)
.Where(i => MyPredicate(arr[i]))
.DefaultIfEmpty(-1)
.First();


Aber leserlicher ist es vermutlich auch nicht ;).
01.07.2011
Tachyon 690 7
Immerhin übersichtlicher als mein Ansatz mit dem anonymen Objekt. Aber wohl trotzdem kein Code, dem ein unvorbereiteter Leser ohne Kommentar ansieht, was das eigentliche Ziel ist. Wobei - wenn man das Ganze auf zwei Zeilen aufteilt, könnte es leserlich werden. Ich werde mal meine Antwort erweitern.
Auf DefaultIfEmpty war ich noch nicht gestoßen, danke für den Hinweis. Warum Microsoft hier eine Überladung spendiert hat, die die Vorgabe des gewünschten Defaults ermöglicht, bei FirstOrDefault() hingegen nicht, wird wohl ihr Geheimnis bleiben.
Matthias Hlawatsch 01.07.2011
0
Auf dem Heimweg ist mir dann noch was eingefallen. So geht es mit "LINQ-Bordmitteln":

var hit = arr.Select((item, index) => new { item, index }).Where((anon) => MyPredicate(anon.item)).FirstOrDefault();
int theIndex = hit == null ? -1 : hit.index;


Wirkt auf mich dann aber doch eher wie eine technische Machbarkeitsstudie. Einen echten Gewinn an Lesbarkeit kann ich da nicht erkennen. Was meint ihr?

Update
Tachyons Vorschlag hat mich auf folgende Variante gebracht:

var indices = Enumerable.Range(0, arr.Length);
int theIndex = indices.Where(i => MyPredicate(arr[i])
.DefaultIfEmpty(-1)
.First();

Durch die explizite Benennung wird klarer, dass in der Query eigentlich nach einem Index gesucht wird. Zwar nicht so knapp und ausdrucksstark wie die Extension Method, aber wenn es um eine ad-hoc-Lösung geht, finde ich, dass der Code-Text dem eigentlichen Problem zumindest ein Stück näher ist als die for-Schleife, ohne dabei komplizierter zu werden.
01.06.2011
Matthias Hlawatsch 13,2k 4 9
Auf das bin auch gekommen, aber leserlicher ist dann doch die for-Schleife ;-)
gfoidl 02.06.2011

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH