Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
De nombreuses opérations de système de fichiers sont essentiellement des requêtes et sont donc bien adaptées à l’approche LINQ. Ces requêtes sont non destructrices. Ils ne modifient pas le contenu des fichiers ou dossiers d’origine. Les requêtes ne doivent pas provoquer d’effets secondaires. En général, tout code (y compris les requêtes qui effectuent des opérations de création/mise à jour/suppression) qui modifie les données sources doit être conservé séparément du code qui interroge simplement les données.
La création d’une source de données implique une certaine complexité pour représenter avec précision le contenu du système de fichiers et gérer les exceptions de manière élégante. Les exemples de cette section créent une collection d’objets instantanés FileInfo qui représente tous les fichiers situés dans un dossier racine spécifié ainsi que dans tous ses sous-dossiers. L’état réel de chacun FileInfo peut changer dans le temps entre le début et la fin de l’exécution d’une requête. Par exemple, vous pouvez créer une liste d’objets à utiliser comme source de FileInfo données. Si vous essayez d’accéder à la Length
propriété dans une requête, l’objet FileInfo tente d’accéder au système de fichiers pour mettre à jour la valeur de Length
. Si le fichier n'existe plus, votre requête inclut un FileNotFoundException, même si vous ne demandez pas directement au système de fichiers.
Comment interroger des fichiers avec un attribut ou un nom spécifié
Cet exemple montre comment rechercher tous les fichiers qui ont une extension de nom de fichier spécifiée (par exemple, «.txt») dans une arborescence de répertoires spécifiée. Il montre également comment retourner le fichier le plus récent ou le plus ancien dans l’arborescence en fonction de l’heure de création. Vous devrez peut-être modifier la première ligne de nombreux exemples, que vous exécutiez ce code sur Windows, Mac ou un système Linux.
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
var fileQuery = from file in fileList
where file.Extension == ".txt"
orderby file.Name
select file;
// Uncomment this block to see the full query
// foreach (FileInfo fi in fileQuery)
// {
// Console.WriteLine(fi.FullName);
// }
var newestFile = (from file in fileQuery
orderby file.CreationTime
select new { file.FullName, file.CreationTime })
.Last();
Console.WriteLine($"\r\nThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}");
Comment regrouper des fichiers par extension
Cet exemple montre comment LINQ peut être utilisé pour effectuer des opérations de regroupement et de tri avancées sur des listes de fichiers ou de dossiers. Il montre également comment paginer la sortie dans la fenêtre de console à l’aide des méthodes Skip et Take.
La requête suivante montre comment regrouper le contenu d’une arborescence de répertoires spécifiée par l’extension de nom de fichier.
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
int trimLength = startFolder.Length;
DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
var queryGroupByExt = from file in fileList
group file by file.Extension.ToLower() into fileGroup
orderby fileGroup.Count(), fileGroup.Key
select fileGroup;
// Iterate through the outer collection of groups.
foreach (var filegroup in queryGroupByExt.Take(5))
{
Console.WriteLine($"Extension: {filegroup.Key}");
var resultPage = filegroup.Take(20);
//Execute the resultPage query
foreach (var f in resultPage)
{
Console.WriteLine($"\t{f.FullName.Substring(trimLength)}");
}
Console.WriteLine();
}
La sortie de ce programme peut être longue, en fonction des détails du système de fichiers local et de la valeur à laquelle le startFolder
est défini. Pour activer l’affichage de tous les résultats, cet exemple montre comment parcourir les résultats. Une boucle imbriquée est requise, car chaque groupe est énuméré foreach
séparément.
Comment interroger le nombre total d’octets dans un ensemble de dossiers
Cet exemple montre comment récupérer le nombre total d’octets utilisés par tous les fichiers d’un dossier spécifié et tous ses sous-dossiers. La Sum méthode ajoute les valeurs de tous les éléments sélectionnés dans la select
clause. Vous pouvez modifier cette requête pour récupérer le fichier le plus grand ou le plus petit dans l’arborescence de répertoires spécifiée en appelant la méthode Min ou Max au lieu de Sum.
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
var fileList = Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories);
var fileQuery = from file in fileList
let fileLen = new FileInfo(file).Length
where fileLen > 0
select fileLen;
// Cache the results to avoid multiple trips to the file system.
long[] fileLengths = fileQuery.ToArray();
// Return the size of the largest file
long largestFile = fileLengths.Max();
// Return the total number of bytes in all the files under the specified folder.
long totalBytes = fileLengths.Sum();
Console.WriteLine($"There are {totalBytes} bytes in {fileList.Count()} files under {startFolder}");
Console.WriteLine($"The largest file is {largestFile} bytes.");
Cet exemple étend l’exemple précédent pour effectuer les opérations suivantes :
- Comment récupérer la taille en octets du plus grand fichier.
- Comment récupérer la taille en octets du plus petit fichier.
- Comment récupérer le FileInfo plus grand ou le plus petit fichier d’un ou plusieurs dossiers sous un dossier racine spécifié.
- Comment récupérer une séquence telle que les 10 fichiers les plus volumineux.
- Comment classer les fichiers en groupes en fonction de leur taille de fichier en octets, en ignorant les fichiers inférieurs à une taille spécifiée.
L’exemple suivant contient cinq requêtes distinctes qui montrent comment interroger et regrouper des fichiers, en fonction de leur taille de fichier en octets. Vous pouvez modifier ces exemples pour baser la requête sur une autre propriété de l’objet FileInfo .
// Return the FileInfo object for the largest file
// by sorting and selecting from beginning of list
FileInfo longestFile = (from file in fileList
let fileInfo = new FileInfo(file)
where fileInfo.Length > 0
orderby fileInfo.Length descending
select fileInfo
).First();
Console.WriteLine($"The largest file under {startFolder} is {longestFile.FullName} with a length of {longestFile.Length} bytes");
//Return the FileInfo of the smallest file
FileInfo smallestFile = (from file in fileList
let fileInfo = new FileInfo(file)
where fileInfo.Length > 0
orderby fileInfo.Length ascending
select fileInfo
).First();
Console.WriteLine($"The smallest file under {startFolder} is {smallestFile.FullName} with a length of {smallestFile.Length} bytes");
//Return the FileInfos for the 10 largest files
var queryTenLargest = (from file in fileList
let fileInfo = new FileInfo(file)
let len = fileInfo.Length
orderby len descending
select fileInfo
).Take(10);
Console.WriteLine($"The 10 largest files under {startFolder} are:");
foreach (var v in queryTenLargest)
{
Console.WriteLine($"{v.FullName}: {v.Length} bytes");
}
// Group the files according to their size, leaving out
// files that are less than 200000 bytes.
var querySizeGroups = from file in fileList
let fileInfo = new FileInfo(file)
let len = fileInfo.Length
where len > 0
group fileInfo by (len / 100000) into fileGroup
where fileGroup.Key >= 2
orderby fileGroup.Key descending
select fileGroup;
foreach (var filegroup in querySizeGroups)
{
Console.WriteLine($"{filegroup.Key}00000");
foreach (var item in filegroup)
{
Console.WriteLine($"\t{item.Name}: {item.Length}");
}
}
Pour retourner un ou plusieurs objets complets FileInfo , la requête doit d’abord examiner chacune d’elles dans la source de données, puis les trier par la valeur de leur propriété Length. Ensuite, il peut retourner le seul élément ou la séquence avec la plus grande longueur. Permet First de retourner le premier élément d’une liste. Permet Take de retourner le premier n nombre d’éléments. Spécifiez un ordre de tri décroissant pour placer les plus petits éléments au début de la liste.
Comment interroger des fichiers en double dans une arborescence de répertoires
Parfois, les fichiers portant le même nom peuvent se trouver dans plusieurs dossiers. Cet exemple montre comment interroger ces noms de fichiers en double sous un dossier racine spécifié. Le deuxième exemple montre comment interroger des fichiers dont la taille et les heures lastWrite correspondent également.
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
DirectoryInfo dir = new DirectoryInfo(startFolder);
IEnumerable<FileInfo> fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
// used in WriteLine to keep the lines shorter
int charsToSkip = startFolder.Length;
// var can be used for convenience with groups.
var queryDupNames = from file in fileList
group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
where fileGroup.Count() > 1
select fileGroup;
foreach (var queryDup in queryDupNames.Take(20))
{
Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty ? "[none]" : queryDup.Key.ToString())}");
foreach (var fileName in queryDup.Take(10))
{
Console.WriteLine($"\t{fileName}");
}
}
La première requête utilise une clé pour déterminer une correspondance. Il recherche des fichiers portant le même nom, mais dont le contenu peut être différent. La deuxième requête utilise une clé composée pour correspondre à trois propriétés de l’objet FileInfo . Cette requête est beaucoup plus susceptible de trouver des fichiers portant le même nom et un contenu similaire ou identique.
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
// Make the lines shorter for the console display
int charsToSkip = startFolder.Length;
// Take a snapshot of the file system.
DirectoryInfo dir = new DirectoryInfo(startFolder);
IEnumerable<FileInfo> fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
// Note the use of a compound key. Files that match
// all three properties belong to the same group.
// A named type is used to enable the query to be
// passed to another method. Anonymous types can also be used
// for composite keys but cannot be passed across method boundaries
//
var queryDupFiles = from file in fileList
group file.FullName.Substring(charsToSkip) by
(Name: file.Name, LastWriteTime: file.LastWriteTime, Length: file.Length )
into fileGroup
where fileGroup.Count() > 1
select fileGroup;
foreach (var queryDup in queryDupFiles.Take(20))
{
Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty ? "[none]" : queryDup.Key.ToString())}");
foreach (var fileName in queryDup)
{
Console.WriteLine($"\t{fileName}");
}
}
}
Comment interroger le contenu des fichiers texte dans un dossier
Cet exemple montre comment interroger tous les fichiers d’une arborescence de répertoires spécifiée, ouvrir chaque fichier et inspecter son contenu. Ce type de technique peut être utilisé pour créer des index ou des index inverses du contenu d’une arborescence d’annuaires. Une recherche de chaîne simple est effectuée dans cet exemple. Toutefois, des types plus complexes de correspondance de modèle peuvent être effectués avec une expression régulière.
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
string searchTerm = "change";
var queryMatchingFiles = from file in fileList
where file.Extension == ".txt"
let fileText = File.ReadAllText(file.FullName)
where fileText.Contains(searchTerm)
select file.FullName;
// Execute the query.
Console.WriteLine($"""The term "{searchTerm}" was found in:""");
foreach (string filename in queryMatchingFiles)
{
Console.WriteLine(filename);
}
Comment comparer le contenu de deux dossiers
Cet exemple montre trois façons de comparer deux listes de fichiers :
- En interrogeant une valeur booléenne qui spécifie si les deux listes de fichiers sont identiques.
- En effectuant une requête d'intersection pour récupérer les fichiers présents dans les deux dossiers.
- En interrogeant la différence définie pour récupérer les fichiers qui se trouvent dans un dossier, mais pas l’autre.
Les techniques présentées ici peuvent être adaptées pour comparer des séquences d’objets de n’importe quel type.
La FileComparer
classe présentée ici montre comment utiliser une classe de comparateur personnalisée avec les opérateurs de requête standard. La classe n’est pas destinée à être utilisée dans des scénarios réels. Il utilise simplement le nom et la longueur en octets de chaque fichier pour déterminer si le contenu de chaque dossier est identique ou non. Dans un scénario réel, vous devez modifier ce comparateur pour effectuer un contrôle d’égalité plus rigoureux.
// This implementation defines a very simple comparison
// between two FileInfo objects. It only compares the name
// of the files being compared and their length in bytes.
class FileCompare : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo? f1, FileInfo? f2)
{
return (f1?.Name == f2?.Name &&
f1?.Length == f2?.Length);
}
// Return a hash that reflects the comparison criteria. According to the
// rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
// also be equal. Because equality as defined here is a simple value equality, not
// reference identity, it is possible that two or more objects will produce the same
// hash code.
public int GetHashCode(FileInfo fi)
{
string s = $"{fi.Name}{fi.Length}";
return s.GetHashCode();
}
}
public static void CompareDirectories()
{
string pathA = """C:\Program Files\dotnet\sdk\8.0.104""";
string pathB = """C:\Program Files\dotnet\sdk\8.0.204""";
DirectoryInfo dir1 = new DirectoryInfo(pathA);
DirectoryInfo dir2 = new DirectoryInfo(pathB);
IEnumerable<FileInfo> list1 = dir1.GetFiles("*.*", SearchOption.AllDirectories);
IEnumerable<FileInfo> list2 = dir2.GetFiles("*.*", SearchOption.AllDirectories);
//A custom file comparer defined below
FileCompare myFileCompare = new FileCompare();
// This query determines whether the two folders contain
// identical file lists, based on the custom file comparer
// that is defined in the FileCompare class.
// The query executes immediately because it returns a bool.
bool areIdentical = list1.SequenceEqual(list2, myFileCompare);
if (areIdentical == true)
{
Console.WriteLine("the two folders are the same");
}
else
{
Console.WriteLine("The two folders are not the same");
}
// Find the common files. It produces a sequence and doesn't
// execute until the foreach statement.
var queryCommonFiles = list1.Intersect(list2, myFileCompare);
if (queryCommonFiles.Any())
{
Console.WriteLine($"The following files are in both folders (total number = {queryCommonFiles.Count()}):");
foreach (var v in queryCommonFiles.Take(10))
{
Console.WriteLine(v.Name); //shows which items end up in result list
}
}
else
{
Console.WriteLine("There are no common files in the two folders.");
}
// Find the set difference between the two folders.
var queryList1Only = (from file in list1
select file)
.Except(list2, myFileCompare);
Console.WriteLine();
Console.WriteLine($"The following files are in list1 but not list2 (total number = {queryList1Only.Count()}):");
foreach (var v in queryList1Only.Take(10))
{
Console.WriteLine(v.FullName);
}
var queryList2Only = (from file in list2
select file)
.Except(list1, myFileCompare);
Console.WriteLine();
Console.WriteLine($"The following files are in list2 but not list1 (total number = {queryList2Only.Count()}:");
foreach (var v in queryList2Only.Take(10))
{
Console.WriteLine(v.FullName);
}
}
Comment réorganiser les champs d’un fichier délimité
Un fichier de valeurs séparées par des virgules (CSV) est un fichier texte qui est souvent utilisé pour stocker des données de feuille de calcul ou d’autres données tabulaires représentées par des lignes et des colonnes. En utilisant la Split méthode pour séparer les champs, il est facile d’interroger et de manipuler des fichiers CSV à l’aide de LINQ. En fait, la même technique peut être utilisée pour réorganiser les parties d’une ligne de texte structurée ; il n’est pas limité aux fichiers CSV.
Dans l’exemple suivant, supposons que les trois colonnes représentent le « nom de famille », « prénom » et « ID ». Les champs sont dans l’ordre alphabétique en fonction des noms de famille des étudiants. La requête produit une nouvelle séquence dans laquelle la colonne ID apparaît en premier, suivie d’une deuxième colonne qui combine le prénom et le nom de famille de l’étudiant. Les lignes sont réorganisées en fonction du champ ID. Les résultats sont enregistrés dans un nouveau fichier et les données d’origine ne sont pas modifiées. Le texte suivant montre le contenu du fichier spreadsheet1.csv utilisé dans l’exemple suivant :
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121
Le code suivant lit le fichier source et réorganise chaque colonne du fichier CSV pour réorganiser l’ordre des colonnes :
string[] lines = File.ReadAllLines("spreadsheet1.csv");
// Create the query. Put field 2 first, then
// reverse and combine fields 0 and 1 from the old field
IEnumerable<string> query = from line in lines
let fields = line.Split(',')
orderby fields[2]
select $"{fields[2]}, {fields[1]} {fields[0]}";
File.WriteAllLines("spreadsheet2.csv", query.ToArray());
/* Output to spreadsheet2.csv:
111, Svetlana Omelchenko
112, Claire O'Donnell
113, Sven Mortensen
114, Cesar Garcia
115, Debra Garcia
116, Fadi Fakhouri
117, Hanying Feng
118, Hugo Garcia
119, Lance Tucker
120, Terry Adams
121, Eugene Zabokritski
122, Michael Tucker
*/
Comment fractionner un fichier en plusieurs fichiers à l’aide de groupes
Cet exemple montre une façon de fusionner le contenu de deux fichiers, puis de créer un ensemble de nouveaux fichiers qui organisent les données d’une nouvelle manière. La requête utilise le contenu de deux fichiers. Le texte suivant montre le contenu du premier fichier, names1.txt:
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
Le deuxième fichier, names2.txt, contient un ensemble différent de noms, dont certains sont communs au premier jeu :
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Le code suivant interroge les deux fichiers, prend l’union des deux fichiers, puis écrit un nouveau fichier pour chaque groupe, défini par la première lettre du nom de famille :
string[] fileA = File.ReadAllLines("names1.txt");
string[] fileB = File.ReadAllLines("names2.txt");
// Concatenate and remove duplicate names
var mergeQuery = fileA.Union(fileB);
// Group the names by the first letter in the last name.
var groupQuery = from name in mergeQuery
let n = name.Split(',')[0]
group name by n[0] into g
orderby g.Key
select g;
foreach (var g in groupQuery)
{
string fileName = $"testFile_{g.Key}.txt";
Console.WriteLine(g.Key);
using StreamWriter sw = new StreamWriter(fileName);
foreach (var item in g)
{
sw.WriteLine(item);
// Output to console for example purposes.
Console.WriteLine($" {item}");
}
}
/* Output:
A
Aw, Kam Foo
B
Bankov, Peter
Beebe, Ann
E
El Yassir, Mehdi
G
Garcia, Hugo
Guy, Wey Yuan
Garcia, Debra
Gilchrist, Beth
Giakoumakis, Leo
H
Holm, Michael
L
Liu, Jinghao
M
Myrcha, Jacek
McLin, Nkenge
N
Noriega, Fabricio
P
Potra, Cristina
T
Toyoshima, Tim
*/
Comment joindre du contenu à partir de fichiers dissimilar
Cet exemple montre comment joindre des données à partir de deux fichiers délimités par des virgules qui partagent une valeur commune utilisée comme clé correspondante. Cette technique peut être utile si vous devez combiner des données à partir de deux feuilles de calcul, ou à partir d’une feuille de calcul et d’un fichier qui a un autre format, dans un nouveau fichier. Vous pouvez modifier l’exemple pour utiliser n’importe quel type de texte structuré.
Le texte suivant montre le contenu de scores.csv. Le fichier représente les données de feuille de calcul. La colonne 1 est l’ID de l’étudiant et les colonnes 2 à 5 sont des scores de test.
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
Le texte suivant montre le contenu de names.csv. Le fichier représente une feuille de calcul qui contient le nom de la famille, le prénom et l’ID d’étudiant de l’étudiant.
Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122
Joignez du contenu à partir de fichiers dissimilar qui contiennent des informations connexes. Le fichier names.csv contient le nom de l’étudiant ainsi qu’un identifiant. Le fichier scores.csv contient l’ID et un ensemble de quatre scores de test. La requête suivante joint les scores aux noms des étudiants à l’aide de l’ID comme clé correspondante. Le code est illustré dans l’exemple suivant :
string[] names = File.ReadAllLines(@"names.csv");
string[] scores = File.ReadAllLines(@"scores.csv");
var scoreQuery = from name in names
let nameFields = name.Split(',')
from id in scores
let scoreFields = id.Split(',')
where Convert.ToInt32(nameFields[2]) == Convert.ToInt32(scoreFields[0])
select $"{nameFields[0]},{scoreFields[1]},{scoreFields[2]},{scoreFields[3]},{scoreFields[4]}";
Console.WriteLine("\r\nMerge two spreadsheets:");
foreach (string item in scoreQuery)
{
Console.WriteLine(item);
}
Console.WriteLine($"{scoreQuery.Count()} total names in list");
/* Output:
Merge two spreadsheets:
Omelchenko, 97, 92, 81, 60
O'Donnell, 75, 84, 91, 39
Mortensen, 88, 94, 65, 91
Garcia, 97, 89, 85, 82
Garcia, 35, 72, 91, 70
Fakhouri, 99, 86, 90, 94
Feng, 93, 92, 80, 87
Garcia, 92, 90, 83, 78
Tucker, 68, 79, 88, 92
Adams, 99, 82, 81, 79
Zabokritski, 96, 85, 91, 60
Tucker, 94, 92, 91, 91
12 total names in list
*/
Calcul des valeurs de colonne dans un fichier texte CSV
Cet exemple montre comment effectuer des calculs agrégés tels que Sum, Average, Min et Max sur les colonnes d’un fichier .csv. Les exemples de principes présentés ici peuvent être appliqués à d’autres types de texte structuré.
Le texte suivant montre le contenu de scores.csv. Supposons que la première colonne représente un ID d’étudiant et que les colonnes suivantes représentent les scores de quatre examens.
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
Le texte suivant montre comment utiliser la Split méthode pour convertir chaque ligne de texte en tableau. Chaque élément de tableau représente une colonne. Enfin, le texte de chaque colonne est converti en sa représentation numérique.
public static class SumColumns
{
public static void ProcessColumns(string filePath, string seperator)
{
// Divide each exam into a group
var exams = from line in MatrixFrom(filePath, seperator)
from score in line
// Identify the column number
let colNumber = Array.FindIndex(line, t => ReferenceEquals(score, t))
// The first column is the student ID, not the exam score
// so it needs to be excluded
where colNumber > 0
// Convert the score from string to int
// Group by column number, i.e. one group per exam
group double.Parse(score) by colNumber into g
select new
{
Title = $"Exam#{g.Key}",
Min = g.Min(),
Max = g.Max(),
Avg = Math.Round(g.Average(), 2),
Total = g.Sum()
};
foreach (var exam in exams)
{
Console.WriteLine($"{exam.Title}\t"
+ $"Average:{exam.Avg,6}\t"
+ $"High Score:{exam.Max,3}\t"
+ $"Low Score:{exam.Min,3}\t"
+ $"Total:{exam.Total,5}");
}
}
// Transform the file content to an IEnumerable of string arrays
// like a matrix
private static IEnumerable<string[]> MatrixFrom(string filePath, string seperator)
{
using StreamReader reader = File.OpenText(filePath);
for (string? line = reader.ReadLine(); line is not null; line = reader.ReadLine())
{
yield return line.Split(seperator, StringSplitOptions.TrimEntries);
}
}
}
// Output:
// Exam#1 Average: 86.08 High Score: 99 Low Score: 35 Total: 1033
// Exam#2 Average: 86.42 High Score: 94 Low Score: 72 Total: 1037
// Exam#3 Average: 84.75 High Score: 91 Low Score: 65 Total: 1017
// Exam#4 Average: 76.92 High Score: 94 Low Score: 39 Total: 923
Si votre fichier est un fichier séparé par des onglets, mettez simplement à jour l’argument dans la SumColumns.ProcessColumns
méthode \t
.