Udostępnij za pośrednictwem


Instrukcje: używanie linQ do wykonywania zapytań dotyczących ciągów

Ciągi są przechowywane jako sekwencja znaków. W ramach sekwencji znaków można wykonywać zapytania za pomocą LINQ. W tym artykule istnieje kilka przykładowych zapytań, które wysyłają zapytania o ciągi dla różnych znaków lub wyrazów, ciągów filtrów lub mieszają zapytania z wyrażeniami regularnymi.

Jak wykonywać zapytania o znaki w ciągu

Poniższy przykład wysyła zapytanie do ciągu w celu określenia liczby cyfr, które zawiera.

string aString = "ABCDE99F-J74-12-89A";

// Select only those characters that are numbers
var stringQuery = from ch in aString
                  where Char.IsDigit(ch)
                  select ch;

// Execute the query
foreach (char c in stringQuery)
    Console.Write(c + " ");

// Call the Count method on the existing query.
int count = stringQuery.Count();
Console.WriteLine($"Count = {count}");

// Select all characters before the first '-'
var stringQuery2 = aString.TakeWhile(c => c != '-');

// Execute the second query
foreach (char c in stringQuery2)
    Console.Write(c);
/* Output:
  Output: 9 9 7 4 1 2 8 9
  Count = 8
  ABCDE99F
*/

Poprzednie zapytanie pokazuje, jak można traktować ciąg jako sekwencję znaków.

Jak zliczyć wystąpienia wyrazu w ciągu

W poniższym przykładzie pokazano, jak użyć zapytania LINQ do zliczenia wystąpień określonego słowa w ciągu. Aby wykonać liczbę, najpierw wywoływana Split jest metoda w celu utworzenia tablicy wyrazów. Metoda ma koszt Split wydajności. Jeśli jedyną operacją w ciągu jest zliczenie wyrazów, rozważ użycie Matches metod lub IndexOf .

string text = """
    Historically, the world of data and the world of objects 
    have not been well integrated. Programmers work in C# or Visual Basic 
    and also in SQL or XQuery. On the one side are concepts such as classes, 
    objects, fields, inheritance, and .NET APIs. On the other side 
    are tables, columns, rows, nodes, and separate languages for dealing with 
    them. Data types often require translation between the two worlds; there are 
    different standard functions. Because the object world has no notion of query, a 
    query can only be represented as a string without compile-time type checking or 
    IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to 
    objects in memory is often tedious and error-prone. 
    """;

string searchTerm = "data";

//Convert the string into an array of words
char[] separators = ['.', '?', '!', ' ', ';', ':', ','];
string[] source = text.Split(separators, StringSplitOptions.RemoveEmptyEntries);

// Create the query.  Use the InvariantCultureIgnoreCase comparison to match "data" and "Data"
var matchQuery = from word in source
                 where word.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase)
                 select word;

// Count the matches, which executes the query.
int wordCount = matchQuery.Count();
Console.WriteLine($"""{wordCount} occurrences(s) of the search term "{searchTerm}" were found.""");
/* Output:
   3 occurrences(s) of the search term "data" were found.
*/

Poprzednie zapytanie pokazuje, jak można wyświetlać ciągi jako sekwencję wyrazów po podzieleniu ciągu na sekwencję wyrazów.

Jak sortować lub filtrować dane tekstowe według dowolnego wyrazu lub pola

W poniższym przykładzie pokazano, jak sortować wiersze tekstu strukturalnego, takie jak wartości rozdzielane przecinkami, według dowolnego pola w wierszu. Pole można określić dynamicznie w czasie wykonywania. Załóżmy, że pola w scores.csv reprezentują numer identyfikacyjny ucznia, a następnie serię czterech wyników testu:

111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

Następujące zapytanie sortuje wiersze na podstawie wyniku pierwszego egzaminu przechowywanego w drugiej kolumnie:

// Create an IEnumerable data source
string[] scores = File.ReadAllLines("scores.csv");

// Change this to any value from 0 to 4.
int sortField = 1;

Console.WriteLine($"Sorted highest to lowest by field [{sortField}]:");

// Split the string and sort on field[num]
var scoreQuery = from line in scores
                 let fields = line.Split(',')
                 orderby fields[sortField] descending
                 select line;

foreach (string str in scoreQuery)
{
    Console.WriteLine(str);
}
/* Output (if sortField == 1):
   Sorted highest to lowest by field [1]:
    116, 99, 86, 90, 94
    120, 99, 82, 81, 79
    111, 97, 92, 81, 60
    114, 97, 89, 85, 82
    121, 96, 85, 91, 60
    122, 94, 92, 91, 91
    117, 93, 92, 80, 87
    118, 92, 90, 83, 78
    113, 88, 94, 65, 91
    112, 75, 84, 91, 39
    119, 68, 79, 88, 92
    115, 35, 72, 91, 70
 */

Poprzednie zapytanie pokazuje, jak można manipulować ciągami, dzieląc je na pola i wykonując zapytania dotyczące poszczególnych pól.

Jak wykonywać zapytania dotyczące zdań z określonymi wyrazami

W poniższym przykładzie pokazano, jak znaleźć zdania w pliku tekstowym zawierającym dopasowania dla każdego z określonych zestawów wyrazów. Chociaż tablica terminów wyszukiwania jest trwale zakodowana, może być również wypełniana dynamicznie w czasie wykonywania. Zapytanie zwraca zdania zawierające słowa "Historycznie", "dane" i "zintegrowane".

string text = """
Historically, the world of data and the world of objects 
have not been well integrated. Programmers work in C# or Visual Basic 
and also in SQL or XQuery. On the one side are concepts such as classes, 
objects, fields, inheritance, and .NET APIs. On the other side 
are tables, columns, rows, nodes, and separate languages for dealing with 
them. Data types often require translation between the two worlds; there are 
different standard functions. Because the object world has no notion of query, a 
query can only be represented as a string without compile-time type checking or 
IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to 
objects in memory is often tedious and error-prone.
""";

// Split the text block into an array of sentences.
string[] sentences = text.Split(['.', '?', '!']);

// Define the search terms. This list could also be dynamically populated at run time.
string[] wordsToMatch = [ "Historically", "data", "integrated" ];

// Find sentences that contain all the terms in the wordsToMatch array.
// Note that the number of terms to match is not specified at compile time.
char[] separators = ['.', '?', '!', ' ', ';', ':', ','];
var sentenceQuery = from sentence in sentences
                    let w = sentence.Split(separators,StringSplitOptions.RemoveEmptyEntries)
                    where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
                    select sentence;

foreach (string str in sentenceQuery)
{
    Console.WriteLine(str);
}
/* Output:
Historically, the world of data and the world of objects have not been well integrated
*/

Zapytanie najpierw dzieli tekst na zdania, a następnie dzieli każde zdanie na tablicę ciągów, które przechowują poszczególne wyrazy. Dla każdej z tych tablic metoda usuwa wszystkie zduplikowane wyrazy Distinct , a następnie zapytanie wykonuje operację Intersect na tablicy wyrazów i tablicy wordsToMatch . Jeśli liczba przecięcia jest taka sama jak liczba wordsToMatch tablicy, wszystkie wyrazy zostały znalezione w słowach i zwracane jest oryginalne zdanie.

Wywołanie do Split użycia znaków interpunkcyjnych jako separatorów w celu usunięcia ich z ciągu. Jeśli nie usunięto interpunkcji, na przykład możesz mieć ciąg "Historycznie", który nie pasuje do "Historycznie" w tablicy wordsToMatch . Może być konieczne użycie dodatkowych separatorów w zależności od typów interpunkcji znalezionych w tekście źródłowym.

Sposób łączenia zapytań LINQ z wyrażeniami regularnymi

W poniższym przykładzie pokazano, jak za pomocą Regex klasy utworzyć wyrażenie regularne w celu bardziej złożonego dopasowania w ciągach tekstowych. Zapytanie LINQ ułatwia filtrowanie dokładnie plików, które mają być wyszukiwane za pomocą wyrażenia regularnego, oraz kształtowanie wyników.

string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

// Take a snapshot of the file system.
var fileList = from file in Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories)
                let fileInfo = new FileInfo(file)
                select fileInfo;

// Create the regular expression to find all things "Visual".
System.Text.RegularExpressions.Regex searchTerm =
    new System.Text.RegularExpressions.Regex(@"microsoft.net.(sdk|workload)");

// Search the contents of each .htm file.
// Remove the where clause to find even more matchedValues!
// This query produces a list of files where a match
// was found, and a list of the matchedValues in that file.
// Note: Explicit typing of "Match" in select clause.
// This is required because MatchCollection is not a
// generic IEnumerable collection.
var queryMatchingFiles =
    from file in fileList
    where file.Extension == ".txt"
    let fileText = File.ReadAllText(file.FullName)
    let matches = searchTerm.Matches(fileText)
    where matches.Count > 0
    select new
    {
        name = file.FullName,
        matchedValues = from System.Text.RegularExpressions.Match match in matches
                        select match.Value
    };

// Execute the query.
Console.WriteLine($"""The term "{searchTerm}" was found in:""");

foreach (var v in queryMatchingFiles)
{
    // Trim the path a bit, then write
    // the file name in which a match was found.
    string s = v.name.Substring(startFolder.Length - 1);
    Console.WriteLine(s);

    // For this file, write out all the matching strings
    foreach (var v2 in v.matchedValues)
    {
        Console.WriteLine($"  {v2}");
    }
}

Możesz również wykonać zapytanie dotyczące MatchCollection obiektu zwróconego RegEx przez wyszukiwanie. W wynikach są generowane tylko wartości każdego dopasowania. Istnieje jednak również możliwość użycia linQ do wykonywania wszystkich rodzajów filtrowania, sortowania i grupowania w tej kolekcji. Ponieważ MatchCollection jest to niegeneryczna IEnumerable kolekcja, musisz jawnie stwierdzić typ zmiennej zakresu w zapytaniu.