Mittendrin.

Zurück

Flurfunk der eXperts.

Hier erfahren Sie mehr über eXperts, Technologien und das wahre Leben in der SDX.

Stoppwörter mit LINQ

21.01.201408:01 Uhr , Matthias Malsy

Kleine aber feine Problemlösungen machen Spaß und immer öfters ist – zumindest bei mir – LINQ daran beteiligt. Heute war es die Problemstellung der “Stoppwörter”, die mir wieder einmal zeigten, wie gut das Konzept der LINQ-Abfragen funktioniert. 

Die Aufgabenstellung besteht darin, aus einer Liste von Strings diejenigen ohne Stoppwörter herauszufinden.

Beispiel:

Wortliste : "Das", "ist", "ein Stop1 mit", "ein", "-em Stop2 als", "Stopptext"

Stoppliste: "Stop1", "Stop2"

Ergebnis  : "Das", "ist", "ein", "Stopptext"

Die offensichtliche Lösung mit zwei verschachtelten Schleifen ist in diesem Fall nicht besonders schwer zu schreiben. Ein Blick auf die Problemstellung zeigt, dass zwei Mengen miteinander verarbeitet werden, was für mich ein guter Indikator für den Einsatz von LINQ ist.

LINQ 2 Objects

LINQ 2 Objects führt Abfragen auf Objekten im Speicher aus. Die Lösung für das Problem sieht dabei wie folgt aus:

Suche in der Wortliste words.Where()
Wörter, l =>
für die in der Stoppliste kein Stoppwort s =>
gefunden werden kann !stopWords.Where(…).Any()
das im Wort enthalten ist l.Contains(s)

   1: var words = new List<string> { 

   2:   "Das", "ist", "ein Stop1 mit", "ein", "-em Stop2 als", "Stopptext"};

   3: var stopWords = new List<string> { "Stop1", "Stop2" };

   4:  

   5: // Nur Texte ohne Stoppwörter ermitteln

   6: var result = words.Where(

   7:     l => !stopWords.Where(s => l.Contains(s)).Any()

   8: );

 
Eine gute Kommentierung von komplexen LINQ-Statements ist unumgänglich. “It was hard to write so it should be hard to read” ist kein verlässliches Prinzip in der Softwareentwicklung.

LINQ to Entities

LINQ to Entities kann über das Entity Framework Abfragen an den SQL-Server weitergeben, so dass sie dort ausgeführt werden und den Client entlasten. Ich war gespannt wie LINQ to Entities die Abfrage in SQL umsetzt. Hierzu habe ich zwei Tabellen “Words”, “StopWords” erzeugt, die jeweils die Wörter und Stoppwörter enthalten. Die Abfrage wird jeweils um die Selektion auf die Wort-Spalte erweitert:

   1: var dbWords = db.Words.Select(w => w.Word);

   2: var dbStopWords = db.StopWord.Select(w => w.StopWord1);

   3: var result = dbWords.Where(

   4:     l => !dbStopWords.Where(s => l.Contains(s)).Any()

   5: );

Das Ergebnis setzt LINQ to Entities perfekt um

   1: SELECT 

   2: [Extent1].[Word] AS [Word]

   3: FROM [dbo].[Words] AS [Extent1]

   4: WHERE  NOT EXISTS (SELECT 

   5:     1 AS [C1]

   6:     FROM [dbo].[StopWord] AS [Extent2]

   7:     WHERE (CHARINDEX([Extent2].[StopWord], [Extent1].[Word])) > 0

   8: )

Anmerkung: Ein Index auf die Wörter-Spalten ist für eine brauchbare Performance Pflicht.

Fazit

Zwei verschachtelte Schleifen mit Vergleichen und Zuordnungen können mit einem LINQ-Einzeiler ersetzt werden und ich habe mit Zufriedenheit festgestellt, dass LINQ to Entities die Anfragen hervorragend in SQL umsetzt.

Somit hoffe ich, dass zukünftig LINQ noch viel mehr eingesetzt wird. Es führt in der Regel zu kurzem und verständlichen Code.

2 Kommentare

21.01.201412:10 Uhr
DanielT

Anmerkung zur Anmerkung: Ein Index auf den jeweiligen Wort-Spalten bringt nicht allzuviel, weil keine Index-Suche vorgenommen werden kann. In diesem Fall hängt es davon ab, wie viele Spalten es noch in der Tabelle gibt. Ohne Index muss jede Tabelle komplett gescannt werden. Gibt es einen Index, muss nur dieser komplett gescannt werden, d.h. ich spare ggf. I/O. Besteht die Tabelle nur jeweils aus der Wort-Spalte, ist ein Index sogar kontraproduktiv, weil er zu jedem Wort-Eintrag noch Verwaltungsinformationen hinzufügt (Uniqueifier, Clustered Index Key bzw. Record-ID)

Was sich bei einer Read-Only-Tabelle noch anbieten würde, wäre ein ColumnStore-Index auf die Wort-Spalte. Dieser ist stärker komprimiert als ein normaler Index und würde daher den Scan-Vorgang noch einmal beschleunigen. Die wesentliche Zeit in dieser Query wird aber in der CPU stecken durch das vielfache Aufrufen von CHARINDEX.

14.02.201411:32 Uhr
Matthias Malsy

@DanielT: Danke für den Hinweis. Das kommt davon, wenn man kurz vor Schluss "Gleichheit" durch "Contains" austauscht, nur um zu sehen ob die Query weiterhin brauchbar in SQL umgesetzt wird. Die Nutzung des Index war somit hin …

Dein Kommentar wartet auf Freischaltung.

Artikel kommentieren

Zurück

Tag Cloud