Hintergrund
Ich bin in letzter Zeit dabei, anstrengende Tech-Interviews für Positionen zu ertragen, die den .NET-Stack verwenden, von denen einige dumme Fragen wie diese enthalten , und einige Fragen, die gültiger sind. Ich bin kürzlich auf ein Problem gestoßen, das möglicherweise gültig ist, aber ich möchte mich hier bei der Community erkundigen, um sicherzugehen.
Auf die Frage eines Interviewers, wie ich die Häufigkeit von Wörtern in einem Textdokument zählen und die Ergebnisse bewerten würde, antwortete ich, dass ich dies tun würde
- Verwenden Sie ein Stream-Objekt, um die Textdatei als Zeichenfolge zu speichern.
- Teilen Sie die Zeichenfolge in ein Array auf Leerzeichen auf, während Sie die Interpunktion ignorieren.
- Verwenden Sie LINQ für das Array
.GroupBy()
und.Count()
dann dieOrderBy()
Anzahl.
Ich habe diese Antwort aus zwei Gründen falsch verstanden:
- Das Streamen einer gesamten Textdatei in den Speicher kann katastrophal sein. Was wäre, wenn es eine ganze Enzyklopädie wäre? Stattdessen sollte ich jeweils einen Block streamen und mit dem Erstellen einer Hash-Tabelle beginnen.
- LINQ ist zu teuer und erfordert zu viele Verarbeitungszyklen. Ich hätte stattdessen eine Hash-Tabelle erstellen und für jede Iteration nur ein Wort zur Hash-Tabelle hinzufügen sollen, wenn es sonst nicht vorhanden wäre, und dann die Anzahl erhöhen.
Der erste Grund scheint vernünftig. Aber der zweite gibt mir mehr Pause. Ich dachte, dass eines der Verkaufsargumente von LINQ darin besteht, dass es einfach Operationen auf niedrigerer Ebene wie Hash-Tabellen abstrahiert, aber dass es unter dem Schleier immer noch dieselbe Implementierung ist.
Frage
Erfordert LINQ, abgesehen von einigen zusätzlichen Verarbeitungszyklen zum Aufrufen abstrahierter Methoden, erheblich mehr Verarbeitungszyklen, um eine bestimmte Dateniterationsaufgabe auszuführen, als dies bei einer untergeordneten Aufgabe (z. B. beim Erstellen einer Hash-Tabelle) der Fall wäre?
quelle
Antworten:
Ich würde sagen, die Hauptschwäche dieser Antwort ist weniger die Verwendung von Linq als vielmehr die Auswahl der spezifischen Operatoren.
GroupBy
Nimmt jedes Element und projiziert es auf einen Schlüssel und einen Wert, die in eine Suche einfließen. Mit anderen Worten, jedes Wort fügt der Suche etwas hinzu.Die naive Implementierung
.GroupBy(e => e)
speichert eine Kopie jedes Wortes im Quellmaterial, wodurch die endgültige Suche fast so groß ist wie das ursprüngliche Quellmaterial. Selbst wenn wir den Wert mit projizieren,.GroupBy(e => e, e => null)
erstellen wir eine große Suche nach kleinen Werten.Was wir wollen, ist ein Operator, der nur die benötigten Informationen beibehält, dh eine Kopie jedes Wortes und die Anzahl dieses Wortes. Dafür können wir verwenden
Aggregate
:Von hier aus gibt es verschiedene Möglichkeiten, dies zu beschleunigen:
quelle
Ich denke, Sie hatten eine knappe Flucht, der Interviewer wusste nicht wirklich, wovon er sprach. Schlimmer noch, er glaubt, dass es eine "richtige" Antwort gibt. Wenn er jemand wäre, für den Sie arbeiten möchten, würde ich erwarten, dass er Ihre erste Antwort entgegennimmt, herausfindet, warum Sie sie gewählt haben, und Sie dann herausfordert, sie zu verbessern, wenn er Probleme damit findet.
LINQ macht den Menschen Angst, weil es wie Magie aussieht. Es ist eigentlich sehr einfach (so einfach, dass man ein Genie sein müsste, um es zu erfinden)
Ist zusammengestellt in:
(Bitte schreien Sie nicht, wenn ich die falsche Syntax habe, es ist nach Mitternacht und ich bin im Bett :))
Wo ist eine Erweiterungsmethode auf IEnumerable, die ein Lambda nimmt. Das Lambda wird einfach (in diesem Fall) zu einem Delegierten zusammengestellt:
Die Where-Methode fügt einfach ALLE Instanzen von Elementen hinzu, bei denen AMethod einer zurückgegebenen Sammlung true zurückgibt.
Es gibt keinen Grund, der langsamer wäre, als einen foreach durchzuführen und alle übereinstimmenden Elemente zu einer Sammlung in dieser Schleife hinzuzufügen. Tatsächlich macht die Where-Erweiterungsmethode wahrscheinlich genau das. Die wahre Magie kommt, wenn Sie eine Alternative injizieren müssen, wo Kriterien.
Wie ich oben in meinem Kommentar erwähnt habe, sind einige der verknüpften Beispiele sehr falsch. Und es ist diese Art von Fehlinformation, die die Probleme verursacht.
Wenn das Interview Ihnen eine Chance gegeben hätte, hätten Sie Folgendes sagen können:
LINQ ist leicht zu lesen, insbesondere wenn Sie interessante Projektionen und Gruppierungen einführen. Einfach zu lesender Code ist einfacher zu pflegen, was ein Gewinn ist.
Es wäre wirklich einfach, die Leistung zu messen, wenn es sich tatsächlich um einen Engpass handelt, und ihn durch etwas anderes zu ersetzen.
quelle
Count
für ein paar enge Szenarien zu optimieren , die mit der Kapselung schlecht funktionieren. Verketten Sie eine Liste mit Millionen Elementen und einen Iterator mit vier Elementen undCount
sollten ungefähr 5 Operationen erfordern, aber stattdessen eine Million. Ich wünschte, MS würdeIEnhancedEnumerator
mit einerint Move(int)
Methode definieren , die im Erfolgsfall 0 oder im Fehlerfall den Fehlbetrag zurückgibt (Move(1000003)
bei einer neu erstelltenList<T>.Enumerator
Liste aus Millionen Elementen würde dies 3 ergeben). Jede Implementierung ...IEnumerable<T>
könnte in eine Implementierung von eingewickelt werdenIEnhancedEnumerator
, aber Typen, dieIEnhancedEnumerator
direkt implementieren, könnten eine Beschleunigung um Größenordnungen bei vielen Operationen ermöglichen, und selbst Dinge wie die Rückkehr vonAppend
könnten die Fähigkeit der Bestandteile zur schnellen Suche aufdecken .