Procedure: LINQ gebruiken om query's uit te voeren op tekenreeksen
Tekenreeksen worden opgeslagen als een reeks tekens. Als een reeks tekens kunnen ze worden opgevraagd met BEHULP van LINQ. In dit artikel zijn er verschillende voorbeeldquery's die query's uitvoeren op tekenreeksen voor verschillende tekens of woorden, filtertekenreeksen of query's combineren met reguliere expressies.
Query's uitvoeren op tekens in een tekenreeks
In het volgende voorbeeld wordt een tekenreeks opgevraagd om het aantal numerieke cijfers te bepalen dat deze bevat.
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
*/
In de voorgaande query ziet u hoe u een tekenreeks kunt behandelen als een reeks tekens.
Exemplaren van een woord in een tekenreeks tellen
In het volgende voorbeeld ziet u hoe u een LINQ-query gebruikt om het aantal exemplaren van een opgegeven woord in een tekenreeks te tellen. Als u het aantal wilt uitvoeren, wordt eerst de Split methode aangeroepen om een matrix met woorden te maken. Er zijn prestatiekosten voor de Split methode. Als de enige bewerking voor de tekenreeks is om de woorden te tellen, kunt u overwegen om in plaats daarvan de Matches of IndexOf methoden te gebruiken.
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.
*/
In de voorgaande query ziet u hoe u tekenreeksen kunt weergeven als een reeks woorden, nadat u een tekenreeks hebt gesplitst in een reeks woorden.
Tekstgegevens sorteren of filteren op een woord of veld
In het volgende voorbeeld ziet u hoe u regels met gestructureerde tekst, zoals door komma's gescheiden waarden, sorteert op een veld in de regel. Het veld kan dynamisch worden opgegeven tijdens runtime. Stel dat de velden in scores.csv het id-nummer van een student vertegenwoordigen, gevolgd door een reeks van vier testscores:
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
Met de volgende query worden de regels gesorteerd op basis van de score van het eerste examen, opgeslagen in de tweede kolom:
// 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
*/
In de voorgaande query ziet u hoe u tekenreeksen kunt bewerken door ze te splitsen in velden en de afzonderlijke velden op te vragen.
Query's uitvoeren op zinnen met specifieke woorden
In het volgende voorbeeld ziet u hoe u zinnen in een tekstbestand kunt vinden die overeenkomsten bevatten voor elk van een opgegeven set woorden. Hoewel de matrix met zoektermen in code is vastgelegd, kan deze ook dynamisch worden ingevuld tijdens runtime. De query retourneert de zinnen die de woorden 'Historisch', 'gegevens' en 'geïntegreerd' bevatten.
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
*/
De query splitst eerst de tekst in zinnen en splitst vervolgens elke zin in een matrix met tekenreeksen die elk woord bevatten. Voor elk van deze matrices verwijdert de Distinct methode alle dubbele woorden en voert de query vervolgens een Intersect bewerking uit op de woordmatrix en de wordsToMatch
matrix. Als het aantal snijpunten hetzelfde is als het aantal van de wordsToMatch
matrix, zijn alle woorden gevonden in de woorden en wordt de oorspronkelijke zin geretourneerd.
De aanroep voor Split het gebruik van interpunctiemarkeringen als scheidingstekens om deze uit de tekenreeks te verwijderen. Als u interpunctie niet hebt verwijderd, kunt u bijvoorbeeld een tekenreeks 'Historisch' hebben die niet overeenkomt met 'Historisch' in de wordsToMatch
matrix. Mogelijk moet u extra scheidingstekens gebruiken, afhankelijk van de typen interpunctie in de brontekst.
LINQ-query's combineren met reguliere expressies
In het volgende voorbeeld ziet u hoe u de Regex klasse gebruikt om een reguliere expressie te maken voor complexere overeenkomsten in tekenreeksen. Met de LINQ-query kunt u eenvoudig filteren op precies de bestanden die u wilt doorzoeken met de reguliere expressie en om de resultaten vorm te geven.
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}");
}
}
U kunt ook een query uitvoeren op het MatchCollection object dat wordt geretourneerd door een RegEx
zoekopdracht. Alleen de waarde van elke overeenkomst wordt geproduceerd in de resultaten. Het is echter ook mogelijk om LINQ te gebruiken om allerlei soorten filters, sortering en groepering op die verzameling uit te voeren. Omdat MatchCollection dit een niet-generische IEnumerable verzameling is, moet u het type bereikvariabele in de query expliciet aangeven.