Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Molte operazioni del file system sono essenzialmente query e sono quindi adatte all'approccio LINQ. Queste query non sono distruttive. Non modificano il contenuto dei file o delle cartelle originali. Le query non devono causare effetti collaterali. In generale, qualsiasi codice (incluse le query che eseguono operazioni di creazione/aggiornamento/eliminazione) che modifica i dati di origine deve essere mantenuto separato dal codice che esegue solo query sui dati.
C'è qualche complessità nella creazione di un'origine dati che rappresenta accuratamente il contenuto del file system e gestisce correttamente le eccezioni. Negli esempi di questa sezione viene creata una raccolta snapshot di FileInfo oggetti che rappresenta tutti i file in una cartella radice specificata e in tutte le relative sottocartelle. Lo stato effettivo di ogni FileInfo oggetto può cambiare nel tempo compreso tra l'inizio e la fine dell'esecuzione di una query. Ad esempio, è possibile creare un elenco di FileInfo oggetti da usare come origine dati. Se si tenta di accedere alla Length
proprietà in una query, l'oggetto FileInfo tenta di accedere al file system per aggiornare il valore di Length
. Se il file non esiste più, si ottiene un oggetto FileNotFoundException nella query, anche se non si esegue direttamente una query sul file system.
Come eseguire una query per i file con un attributo o un nome specificato
Questo esempio mostra come trovare tutti i file con un'estensione di file specificata (ad esempio ".txt") in un albero di directory specificato. Viene inoltre illustrato come restituire il file più recente o meno recente nell'albero in base all'ora di creazione. Potrebbe essere necessario modificare la prima riga di molti degli esempi, sia che si esegua questo codice in Windows, Mac o in un sistema 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}");
Come raggruppare i file per estensione
In questo esempio viene illustrato come usare LINQ per eseguire operazioni avanzate di raggruppamento e ordinamento in elenchi di file o cartelle. Viene inoltre illustrato come paginare l'output nella finestra della console usando i Skip metodi e Take .
Nella query seguente viene illustrato come raggruppare il contenuto di un albero di directory specificato in base all'estensione del nome file.
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();
}
L'output di questo programma può essere lungo, a seconda dei dettagli del file system locale e dell'impostazione di startFolder
. Per abilitare la visualizzazione di tutti i risultati, in questo esempio viene illustrato come scorrere i risultati. È necessario un ciclo annidato foreach
perché ogni gruppo viene enumerato separatamente.
Come eseguire una query per il numero totale di byte in un set di cartelle
In questo esempio viene illustrato come recuperare il numero totale di byte utilizzati da tutti i file in una cartella specificata e tutte le relative sottocartelle. Il Sum metodo aggiunge i valori di tutti gli elementi selezionati nella select
clausola . È possibile modificare questa query per recuperare il file più grande o più piccolo nella directory dell'albero specificato chiamando il metodo Min o Max anziché il metodo 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.");
Questo esempio estende l'esempio precedente per eseguire le operazioni seguenti:
- Come recuperare le dimensioni in byte del file più grande.
- Come recuperare le dimensioni in byte del file più piccolo.
- Come recuperare l'oggetto FileInfo più grande o più piccolo file da una o più cartelle in una cartella radice specificata.
- Come recuperare una sequenza, ad esempio i 10 file più grandi.
- Come ordinare i file in gruppi in base alle dimensioni del file in byte, ignorando i file con dimensioni inferiori a quelle specificate.
L'esempio seguente contiene cinque query separate che illustrano come eseguire query e raggruppare i file, a seconda delle dimensioni del file in byte. È possibile modificare questi esempi per basare la query su un'altra proprietà dell'oggetto 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}");
}
}
Per restituire uno o più oggetti completi FileInfo , la query deve prima esaminarne ognuna nell'origine dati e quindi ordinarle in base al valore della relativa proprietà Length. Quindi può restituire il singolo o la sequenza con le lunghezze più grandi. Utilizzare First per restituire il primo elemento di un elenco. Utilizzare Take per restituire il primo numero n di elementi. Specificare un ordinamento decrescente per inserire gli elementi più piccoli all'inizio dell'elenco.
Come eseguire una query per i file duplicati in un albero di directory
In alcuni casi i file con lo stesso nome possono trovarsi in più cartelle. In questo esempio viene illustrato come eseguire una query per i nomi di file duplicati in una cartella radice specificata. Il secondo esempio mostra come eseguire una query per i file le cui dimensioni e le ore LastWrite corrispondono anche.
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 prima query usa una chiave per determinare una corrispondenza. Trova i file con lo stesso nome, ma il cui contenuto potrebbe essere diverso. La seconda query usa una chiave composta per trovare una corrispondenza con tre proprietà dell'oggetto FileInfo . Questa query è molto più probabile trovare file con lo stesso nome e contenuto simile o identico.
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}");
}
}
}
Come eseguire query sul contenuto dei file di testo in una cartella
In questo esempio viene illustrato come eseguire una query su tutti i file in un albero di directory specificato, aprire ogni file ed esaminarne il contenuto. Questo tipo di tecnica può essere usato per creare indici o indici inversi del contenuto di un albero di directory. In questo esempio viene eseguita una semplice ricerca di stringhe. Tuttavia, con un'espressione regolare è possibile eseguire confronti di modelli più complessi.
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);
}
Come confrontare il contenuto di due cartelle
Questo esempio illustra tre modi per confrontare due liste di file:
- Eseguendo una query per un valore booleano che specifica se i due elenchi di file sono identici.
- Eseguendo una query per l'intersezione per recuperare i file presenti in entrambe le cartelle.
- Eseguendo una query sulla differenza di set per recuperare i file che si trovano in una cartella ma non nell'altra.
Le tecniche illustrate di seguito possono essere adattate per confrontare sequenze di oggetti di qualsiasi tipo.
La FileComparer
classe illustrata di seguito illustra come usare una classe di operatore di confronto personalizzata insieme agli operatori di query standard. La classe non è destinata all'uso in scenari reali. Usa solo il nome e la lunghezza in byte di ogni file per determinare se il contenuto di ogni cartella è identico o meno. In uno scenario reale, è necessario modificare questo operatore di confronto per eseguire un controllo di uguaglianza più rigoroso.
// 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);
}
}
Come riordinare i campi di un file delimitato
Un file con valori delimitati da virgole (CSV) è un file di testo che viene spesso usato per archiviare i dati del foglio di calcolo o altri dati tabulari rappresentati da righe e colonne. Usando il Split metodo per separare i campi, è facile eseguire query e modificare i file CSV usando LINQ. Infatti, la stessa tecnica può essere usata per riordinare le parti di qualsiasi riga strutturata di testo; non è limitato ai file CSV.
Nell'esempio seguente si supponga che le tre colonne rappresentino il "nome della famiglia" degli studenti, "nome" e "ID". I campi sono in ordine alfabetico in base ai nomi della famiglia degli studenti. La query produce una nuova sequenza in cui la colonna ID viene visualizzata per prima, seguita da una seconda colonna che combina il nome e il nome della famiglia dello studente. Le righe vengono riordinate in base al campo ID. I risultati vengono salvati in un nuovo file e i dati originali non vengono modificati. Il testo seguente mostra il contenuto del file spreadsheet1.csv usato nell'esempio seguente:
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
Il codice seguente legge il file di origine e riorganizzerà ogni colonna nel file CSV per riorganizzare l'ordine delle colonne:
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
*/
Come suddividere un file in molti file usando i gruppi
In questo esempio viene illustrato un modo per unire il contenuto di due file e quindi creare un set di nuovi file che organizzano i dati in un nuovo modo. La query usa il contenuto di due file. Il testo seguente mostra il contenuto del primo file ,names1.txt:
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
Il secondo file, names2.txt, contiene un set di nomi diverso, alcuni dei quali sono in comune con il primo set:
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Il codice seguente esegue query su entrambi i file, accetta l'unione di entrambi i file, quindi scrive un nuovo file per ogni gruppo, definito dalla prima lettera del nome della famiglia:
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
*/
Come aggiungere contenuto da file diversi
In questo esempio viene illustrato come unire dati da due file delimitati da virgole che condividono un valore comune usato come chiave corrispondente. Questa tecnica può essere utile se è necessario combinare i dati da due fogli di calcolo o da un foglio di calcolo e da un file con un altro formato, in un nuovo file. È possibile modificare l'esempio in modo che funzioni con qualsiasi tipo di testo strutturato.
Il testo seguente illustra il contenuto di scores.csv. Il file rappresenta i dati del foglio di calcolo. La colonna 1 è l'ID dello studente e le colonne da 2 a 5 sono punteggi di 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
Il testo seguente illustra il contenuto di names.csv. Il file rappresenta un foglio di calcolo contenente il cognome, il nome e l'ID studente dello studente.
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
Unire il contenuto di file diversi che contengono informazioni correlate. Il filenames.csv contiene il nome dello studente più un numero ID. Il filescores.csv contiene l'ID e un set di quattro punteggi di test. La query seguente unisce i punteggi ai nomi degli studenti usando l'ID come chiave corrispondente. Il codice è illustrato nell'esempio seguente:
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
*/
Come calcolare i valori delle colonne in un file di testo CSV
In questo esempio viene illustrato come eseguire calcoli aggregati, ad esempio Sum, Average, Min e Max nelle colonne di un file .csv. I principi di esempio illustrati di seguito possono essere applicati ad altri tipi di testo strutturato.
Il testo seguente illustra il contenuto di scores.csv. Si supponga che la prima colonna rappresenti un ID studente e le colonne successive rappresentino i punteggi di quattro esami.
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
Il testo seguente illustra come usare il Split metodo per convertire ogni riga di testo in una matrice. Ogni elemento della matrice rappresenta una colonna. Infine, il testo in ogni colonna viene convertito nella relativa rappresentazione numerica.
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
Se il file è un file separato da tabulazioni, è sufficiente aggiornare l'argomento nel SumColumns.ProcessColumns
metodo a \t
.