Dela via


Gör så här: Använd LINQ för att fråga efter strängar

Strängar lagras som en sekvens med tecken. Som en sekvens med tecken kan de frågas med LINQ. I den här artikeln finns det flera exempelfrågor som frågar efter strängar för olika tecken eller ord, filtrerar strängar eller blandar frågor med reguljära uttryck.

Så här frågar du efter tecken i en sträng

I följande exempel efterfrågas en sträng för att fastställa antalet numeriska siffror som den innehåller.

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

Föregående fråga visar hur du kan behandla en sträng som en sekvens med tecken.

Så här räknar du förekomster av ett ord i en sträng

I följande exempel visas hur du använder en LINQ-fråga för att räkna förekomster av ett angivet ord i en sträng. För att utföra antalet anropas först Split metoden för att skapa en matris med ord. Det finns en prestandakostnad för Split metoden. Om den enda åtgärden på strängen är att räkna orden bör du överväga att använda Matches metoderna eller IndexOf i stället.

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

Föregående fråga visar hur du kan visa strängar som en ordsekvens efter att du har delat upp en sträng i en ordsekvens.

Sortera eller filtrera textdata efter ord eller fält

I följande exempel visas hur du sorterar rader med strukturerad text, till exempel kommaavgränsade värden, efter valfritt fält på raden. Fältet kan anges dynamiskt vid körning. Anta att fälten i scores.csv representerar en elevs ID-nummer följt av en serie med fyra testresultat:

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

Följande fråga sorterar raderna baserat på poängen för det första provet, som lagras i den andra kolumnen:

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

Föregående fråga visar hur du kan ändra strängar genom att dela upp dem i fält och köra frågor mot de enskilda fälten.

Så här frågar du efter meningar med specifika ord

I följande exempel visas hur du hittar meningar i en textfil som innehåller matchningar för var och en av en angiven uppsättning ord. Även om matrisen med söktermer är hårdkodad kan den också fyllas i dynamiskt vid körning. Frågan returnerar de meningar som innehåller orden "Historically", "data" och "integrated".

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

Frågan delar först upp texten i meningar och delar sedan upp varje mening i en matris med strängar som innehåller varje ord. För var och en av dessa matriser Distinct tar metoden bort alla duplicerade ord och sedan utför frågan en Intersect åtgärd på ordmatrisen och matrisen wordsToMatch . Om antalet skärningspunkter är samma som antalet i matrisen wordsToMatch hittades alla ord i orden och den ursprungliga meningen returneras.

Anropet till Split använder skiljetecken som avgränsare för att ta bort dem från strängen. Om du inte tog bort skiljetecken kan du till exempel ha en sträng "Historically" som inte matchar "Historically" i matrisen wordsToMatch . Du kan behöva använda extra avgränsare, beroende på vilka typer av skiljetecken som finns i källtexten.

Kombinera LINQ-frågor med reguljära uttryck

I följande exempel visas hur du använder Regex klassen för att skapa ett reguljärt uttryck för mer komplex matchning i textsträngar. LINQ-frågan gör det enkelt att filtrera exakt de filer som du vill söka efter med det reguljära uttrycket och forma resultatet.

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

Du kan också fråga objektet som MatchCollection returneras av en RegEx sökning. Endast värdet för varje matchning skapas i resultatet. Men det är också möjligt att använda LINQ för att utföra alla typer av filtrering, sortering och gruppering i samlingen. Eftersom MatchCollection är en icke-generisk IEnumerable samling måste du uttryckligen ange typen av intervallvariabel i frågan.