Sdílet prostřednictvím


Postupy: Použití LINQ k dotazování řetězců

Řetězce se ukládají jako posloupnost znaků. Jako posloupnost znaků je možné je dotazovat pomocí LINQ. V tomto článku existuje několik ukázkových dotazů, které dotazují řetězce pro různé znaky nebo slova, filtrovací řetězce nebo směšují dotazy s regulárními výrazy.

Jak zadat dotaz na znaky v řetězci

Následující příklad dotazuje řetězec k určení počtu číselných číslic, které obsahuje.

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
*/

Předchozí dotaz ukazuje, jak můžete s řetězcem zacházet jako s posloupností znaků.

Jak spočítat výskyty slova v řetězci

Následující příklad ukazuje, jak pomocí dotazu LINQ spočítat výskyty zadaného slova v řetězci. Chcete-li provést počet, nejprve Split je volána metoda pro vytvoření pole slov. Metoda má náklady na Split výkon. Pokud jedinou operací v řetězci je spočítat slova, zvažte místo toho použití Matches metod nebo IndexOf metod.

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.
*/

Předchozí dotaz ukazuje, jak můžete zobrazit řetězce jako posloupnost slov po rozdělení řetězce na posloupnost slov.

Jak řadit nebo filtrovat textová data podle libovolného slova nebo pole

Následující příklad ukazuje, jak řadit řádky strukturovaného textu, například hodnoty oddělené čárkami, podle libovolného pole na řádku. Pole lze dynamicky zadat za běhu. Předpokládejme, že pole v scores.csv představují číslo ID studenta, následované řadou čtyř skóre 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

Následující dotaz seřadí řádky podle skóre první zkoušky uložené ve druhém sloupci:

// 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
 */

Předchozí dotaz ukazuje, jak můžete manipulovat s řetězci jejich rozdělením na pole a dotazováním jednotlivých polí.

Jak se dotazovat na věty s konkrétními slovy

Následující příklad ukazuje, jak najít věty v textovém souboru, který obsahuje shody pro každou zadanou sadu slov. I když je pole hledaných termínů pevně zakódované, může být také dynamicky vyplněno za běhu. Dotaz vrátí věty, které obsahují slova "Historicky", "data" a "integrovaná".

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
*/

Dotaz nejprve rozdělí text na věty a poté každou větu rozdělí do pole řetězců, které obsahují jednotlivá slova. U každé z těchto polí Distinct metoda odebere všechna duplicitní slova a potom dotaz provede Intersect operaci s polem slov a wordsToMatch polem. Pokud je počet průniku stejný jako počet wordsToMatch matice, všechna slova byla nalezena ve slovech a vrátí se původní věta.

Volání, které Split používá interpunkční znaménka jako oddělovače, aby je bylo možné z řetězce odebrat. Pokud jste interpunkci neodebrali, mohli byste mít například řetězec "Historicky", který by se v wordsToMatch poli neshodoval s historicky. V závislosti na typech interpunkce nalezených ve zdrojovém textu možná budete muset použít nadbytečné oddělovače.

Jak kombinovat dotazy LINQ s regulárními výrazy

Následující příklad ukazuje, jak pomocí Regex třídy vytvořit regulární výraz pro složitější porovnávání v textových řetězcích. Dotaz LINQ usnadňuje filtrování přesně souborů, které chcete hledat pomocí regulárního výrazu, a tvarování výsledků.

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}");
    }
}

Můžete také dotazovat objekt MatchCollection vrácený hledáním RegEx . Ve výsledcích se vytvoří pouze hodnota každé shody. LinQ je ale také možné použít k provádění všech druhů filtrování, řazení a seskupování v této kolekci. Vzhledem k tomu MatchCollection , že jde o negenerickou IEnumerable kolekci, musíte explicitně uvést typ proměnné rozsahu v dotazu.