Parsing A Complex, Multiline CSV File in C# - c#
I have searched the site, and found multiple examples for how to accomplish C# parsing using a variety of methods...but have found none that can help me in this specific scenario. I have a complex CSV file that needs parsing. Here is a sampling of some of the header data...
REPORT TITLE,New Query,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
REPORT DESCRIPTION,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
GENERATED,12/20/2019 7:33 AM ET,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Client Name,Client A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Time Frame,Last Completed Period,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,Calendar year,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Received Date,Custom,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,01/01/2015 - 12/31/2015,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Service Date,Custom,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,01/01/2015 - 12/31/2015,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Adjustments,CHOICE(S),,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,Phone Calibration,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
View,N/A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
SERVICE LINE,Service Line Example A,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
SITE,General 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,General 2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,General 3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,General 4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,General 5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,General 7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
FILTER,CHOICE(S),,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Client ID,'00001',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,'00002',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,'00003',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,'00004',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,'00005',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,'00006',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
There is NOTHING I can do about the formatting of the CSV file, as it is part of a legacy system. The 40 commas placed at the end of each row, as well as those used as row separators, are placed by the system.
Here is where I am with my code so far...
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.VisualBasic.FileIO;
using System.Text.RegularExpressions;
namespace ConsoleUI
{
class Program
{
static void Main(string[] args)
{
var sourcePath = #"L:\sourceData.csv";
var delimiter = ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,";
var tempPath = Path.GetTempFileName();
var lineNumber = 0;
var splitExpression = new Regex(#"(" + delimiter + #")(,)(?=(?:[^""]|""[^""]*"")*$)");
using (var writer = new StreamWriter(tempPath))
using (var reader = new StreamReader(sourcePath))
{
string line = null;
while ((line = reader.ReadLine()) != null)
{
lineNumber++;
var rows = splitExpression.Split(line).Where(s => s != delimiter).ToArray();
// This is where I need to place the parsed data into objects
writer.WriteLine(string.Join(delimiter, rows));
}
}
}
}
}
Ultimately, I need to move each parsed piece of data into its own defined object. I have that class already built.
ANY help that can be provided would be considered a holiday miracle at this point! Thanks for your time.
try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.csv";
static void Main(string[] args)
{
StreamReader reader = new StreamReader(FILENAME);
string line = "";
Report report = new Report();
string header = "";
while ((line = reader.ReadLine()) != null)
{
List<string> data = new List<string>();
string[] row = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
if (row.Length > 0)
{
if (line.StartsWith(","))
{
data = row.ToList();
}
else
{
header = row[0];
data = row.Skip(1).ToList();
}
if (data.Count == 0) continue;
switch (header)
{
case "REPORT TITLE":
report.title = data[0];
break;
case "REPORT DESCRIPTION":
report.description = data[0];
break;
case "GENERATED":
report.generated = data[0];
break;
case "Client Name":
report.name = data[0];
break;
case "Time Frame":
if (report.timeFrame == null) report.timeFrame = new List<string>();
report.timeFrame.AddRange(data);
break;
case "Received Date":
if (report.receivedDate == null) report.receivedDate = new List<string>();
report.receivedDate.AddRange(data);
break;
case "Service Date":
if (report.serviceDate == null) report.serviceDate = new List<string>();
report.serviceDate.AddRange(data);
break;
case "Adjustments":
if (report.adjustments == null) report.adjustments = new List<string>();
report.adjustments.AddRange(data);
break;
case "View":
if (report.view == null) report.view = new List<string>();
report.view.AddRange(data);
break;
case "SERVICE LINE":
if (report.serviceLine == null) report.serviceLine = new List<string>();
report.serviceLine.AddRange(data);
break;
case "SITE":
if (report.site == null) report.site = new List<string>();
report.site.AddRange(data);
break;
case "FILTER":
if (report.filter == null) report.filter = new List<string>();
report.filter.AddRange(data);
break;
case "Client ID":
if (report.clientId == null) report.clientId = new List<string>();
report.clientId.AddRange(data);
break;
}
}
}
}
}
public class Report
{
public string title { get; set; }
public string description { get; set; }
public string generated { get; set; }
public string name { get; set; }
public List<string> timeFrame { get; set; }
public List<string> receivedDate { get; set; }
public List<string> serviceDate { get; set; }
public List<string> adjustments { get; set; }
public List<string> view { get; set; }
public List<string> serviceLine { get; set; }
public List<string> site { get; set; }
public List<string> filter { get; set; }
public List<string> clientId { get; set; }
}
}
Related
How to import csv file which contains qoute signs and the delimiter is coma?
So i am having a problem importing a csv file, i want to make an object from the columns but i cant read in properly. So the header line looks like this: Title,Year,Genre,Rating,Votes,Directors The data line looks like this: The Last of Us: Mass Effect 2,2010,"Action, Adventure, Drama",9.5,19961,Casey Hudson The problem is that, i get the exception "Input string is not in correct form" I am using coma as delimiter, is there a way to make quotes as delimiters too? Also, what are in the qoutes belongs to the Genre attribute. I am using this code as the CsvParser right now: using Games.Models; using System.Globalization; using System.Text; namespace Games.Utils { public class CsvParser { private readonly string _path; public char Delimiter { get; set; } = ','; public bool SkipFirst { get; set; } = true; public bool Verbose { get; set; } = true; public NumberFormatInfo NumberFormatInfo { get; private set; } = new NumberFormatInfo(); public Encoding Encoding { get; set; } = Encoding.Default; public CsvParser(string path) => _path = path; public IEnumerable<Game> StreamParseGames() => GenerateGames(Enumerables.EnumerateStreamReaderLines(new(_path, Encoding))); public IEnumerable<Game> TextParseGames() => GenerateGames(File.ReadAllLines(_path, Encoding)); private IEnumerable<Game> GenerateGames(IEnumerable<string> lineProvider) { if (SkipFirst) lineProvider = lineProvider.Skip(1); int lineNum = SkipFirst ? 1 : 0; foreach (var line in lineProvider) { string[] parts = line.Split(Delimiter); Game game; try { game = new() { Title = parts[0], Year = Convert.ToInt32(parts[1], NumberFormatInfo), Genre = parts[2], Rating = Convert.ToDouble(parts[3], NumberFormatInfo), Votes = Convert.ToDouble(parts[4], NumberFormatInfo), Directors = parts[5], }; } catch (FormatException e) { if (Verbose) Console.WriteLine($"Line {lineNum + 1:000000} omitted due: {e.Message}"); continue; } catch (IndexOutOfRangeException e) { if (Verbose) Console.WriteLine($"Line {lineNum + 1:000000} omitted due: {e.Message}"); continue; } finally { ++lineNum; } yield return game; } } } }
I'd suggest you use CsvHelper which can deal with that instead of rolling your own CSV parser. using CsvHelper; using CsvHelper.Configuration; var config = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ",", }; using (var reader = new StreamReader("path\\to\\file.csv")) using (var csv = new CsvReader(reader, config)) { var records = csv.GetRecords<Foo>(); }
Order lines from a text file by number found in a certain spot of the line
I'm sorry that I interrupt you in this manner, I'm new to C# and I've been struggling with this problem for days... Maybe it will seem easy for you :) I have this text file in this format name|ID|domain|grade|verdict Ryan|502322|Computers|9,33|Undefined Marcel|302112|Automatics|6,22|Undefined Alex|301234|Computers|5,66|Undefined Leo|201122|Automatics|3,22|Undefined How can I sort the text file using any methods (including LINQ) so that the list from the text file will be ordered by domain, and then descending by the grade column? Like this: name|ID|domain|grade|verdict Marcel|302112|Automatics|6,22|Undefined Leo|201122|Automatics|3,22|Undefined Ryan|502322|Computers|9,33|Undefined Alex|301234|Computers|5,66|Undefined To read the file, I'm using var Students = File.ReadAllLines(#"filepath");, I don't know if it's the smartest approach, and then I write using File.WriteAllLines Thanks in advance! Sorry once again, I know it should be easy, but for me is really tuff :(
You can use some thing like this: var students= File.ReadAllLines(#"filepath"); var headers = lines[0]; students = lines.Skip(1).ToArray(); var orders = lines.Select(x => x.Split('|')) .Select(x => new { Domain = x[2], Grade = int.Parse(x[3].Replace(",", "")), All = x }) .OrderBy(x => x.Domain).ThenByDescending(x => x.Grade).Select(x => string.Join("|", x.All)).ToList(); orders.Insert(0, headers); students=orders.ToArray();
try following code: private void ReadFile() { char Delimiter = '|'; string[] Lines = File.ReadAllLines(#"E:\RaftehHa.txt", Encoding.Default); List<string[]> FileRows = Lines.Select(line => line.Split(new[] { Delimiter }, StringSplitOptions.RemoveEmptyEntries)).ToList(); DataTable dt = new DataTable(); dt.Columns.AddRange(FileRows[0].Select(col => new DataColumn() { ColumnName = col }).ToArray()); FileRows.RemoveAt(0); FileRows.ForEach(row => dt.Rows.Add(row)); DataView dv = dt.DefaultView; dv.Sort = " ID ASC "; dt = dv.ToTable(); dataGridView1.DataSource = dt; }
A bit the same as already mentioned above, but as you mention you are new to C#, I have tried to add a little bit of structure to the code, but leaving the completion to you. public class Data { public Data(string inputLine) { var split = inputLine.Split('|'); Name = split[0]; Id = int.Parse(split[1]); Domain = split[2]; Grade = double.Parse(split[3].Replace(",", ".")); Verdict = split[4]; } public string Name { get; } public int Id { get; } public string Domain { get; } public double Grade { get; } public string Verdict { get; } } public class DataFile { public static IEnumerable Read(string fileName) { var input = File.ReadAllLines(fileName); return input.Skip(1).Select(p => new Data(p)); // skip header } public static void Write(IEnumerable data) { // todo :) } } void Main() { var input = DataFile.Read(#"C:\Temp\ExampleData.txt"); var result = input.OrderBy(p => p.Domain).ThenByDescending(p => p.Grade); DataFile.Write(result); }
using System; using System.IO; using System.Linq; using System.Collections.Generic; enum DomainType { Automatics, // 0 Computers // 1 } class Data { public int Id { get; set; } public string Name { get; set; } public string Verdict { get; set; } public DomainType Domain { get; set; } public Tuple<int, int> Grade { get; set; } } public static class Program { static IEnumerable<Data> FileContent(string path) { string line; using (var reader = File.OpenText(path)) { bool skipHeader = false; while((line = reader.ReadLine()) != null) { if (!skipHeader) { skipHeader = true; continue; } var fields = line.Split('|'); string name = fields[0]; int id = int.Parse(fields[1]); var domain = (DomainType)Enum.Parse(typeof(DomainType), fields[2]); var grade = Tuple.Create(int.Parse(fields[3].Split(',')[0]), int.Parse(fields[3].Split(',')[1])); string verdict = fields[4]; var data = new Data() { Name = name, Id = id, Domain = domain, Grade = grade, Verdict = verdict }; yield return data; } } } public static void Main() { var result = FileContent("path_to_file").OrderBy(data => data.Domain); foreach (var line in result) { Console.WriteLine(line.Name); } } }
How to parse text file and connect to database in C#?
I'm trying to parse the text file but unable to parse the contents under "Columns:", "Records:", "Relationships:" headings. My text file format: Database Type: SQL Server Connection String:Server=localhost\SQLEXPRESS;Database=master;Trusted_Connection=True; DBName:FirstDataBase Tables Table Name:TableName1 Columns: ID,numeric,4 Name,name,20 Designation,varchar,20 Relationships: Records: 9001,XYZ,Director 8038,MNO,Associate 9876,LOP,HR Table Name:TableName2 Columns: ID,numeric,4 Name,name,20 Designation,varchar,20 Relationships: TableName1,TableName2,FK,ID Records: 8038,MNO,Associate My C# code: static void Main(string[] args) { Console.WriteLine("Enter file name"); string filep = Console.ReadLine(); string filePath = $"C:\\Files\\{filep}.txt"; List<Columns> col = new List<Columns>(); List<TableName> tn = new List<TableName>(); string[] lines = File.ReadAllLines(filePath); foreach (var line in lines) { if (line.StartsWith("\t" + "\t" + "Name:")) { TableName newTN = new TableName(); string s = line.Split(':')[1]; newTN.TName = s; tn.Add(newTN); Console.WriteLine(s); } if (line.StartsWith("\t" + "\t" + "Columns:")) { // Here I'm stuck } } } One of my model classes: public class Columns { public string CName { get; set; } public string CType { get; set; } public string CSize { get; set; } public string Ckey { get; set; } } I've tried various codes but still, I'm unable to parse it. Solutions are welcome and thanks in advance. I'm stuck at how to make the program read the next lines of particular heading and store it in a list. If it is solved then I can easily make queries and connect to the database.
Try following : sing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace ConsoleApp1 { class Program { const string FILENAME = #"c:\temp\test.txt"; static void Main(string[] args) { Database database = new Database(FILENAME); } } public enum STATE { GET_DATABASE, GET_TABLES } public enum TABLE_FIELD { NONE, NAME, COLUMNS, RELATIONSHIPS, RECORDS } public class Database { public string type { get; set; } public string connection { get; set; } public string name { get; set; } public List<Table> tables { get; set; } public Database(string filename) { StreamReader reader = new StreamReader(filename); string line = ""; STATE state = STATE.GET_DATABASE; TABLE_FIELD tableField = TABLE_FIELD.NONE; Table table = null; string[] splitLine; while((line = reader.ReadLine()) != null) { line = line.Trim(); if (line.Length > 0) { switch (state) { case STATE.GET_DATABASE: if (line == "Tables") { state = STATE.GET_TABLES; } else { if (line.Contains(":")) { splitLine = line.Split(new char[] { ':' }); switch (splitLine[0]) { case "Type": type = splitLine[1].Trim(); break; case "Connection String": connection = splitLine[1].Trim(); break; case "DBName": name = splitLine[1].Trim(); break; } } } break; case STATE.GET_TABLES: if(line == "Table") { if (tables == null) tables = new List<Table>(); table = new Table(); tables.Add(table); } else { if (line.Contains(":")) { splitLine = line.Split(new char[] { ':' }); switch (splitLine[0]) { case "Name": table.name = splitLine[1].Trim(); tableField = TABLE_FIELD.NAME; break; case "Columns": connection = splitLine[1].Trim(); tableField = TABLE_FIELD.COLUMNS; break; case "Relationships": name = splitLine[1].Trim(); tableField = TABLE_FIELD.RELATIONSHIPS; break; case "Records": name = splitLine[1].Trim(); tableField = TABLE_FIELD.RECORDS; break; } } else { switch (tableField) { case TABLE_FIELD.COLUMNS: if (table.columns == null) table.columns = new List<Column>(); Column column = new Column(); table.columns.Add(column); splitLine = line.Split(new char[] { ',' }); column.name = splitLine[0]; column.type = (COLUMN_TYPE)Enum.Parse(typeof(COLUMN_TYPE), splitLine[1]); column.length = int.Parse(splitLine[2]); break; case TABLE_FIELD.RECORDS: splitLine = line.Split(new char[] { ',' }); if (table.records == null) table.records = new List<List<object>>(); List<object> row = new List<object>(); table.records.Add(row); for(int i = 0; i < splitLine.Length; i++) { switch(table.columns[i].type) { case COLUMN_TYPE.numeric: row.Add(int.Parse(splitLine[i])); break; case COLUMN_TYPE.name: row.Add(splitLine[i]); break; case COLUMN_TYPE.varchar: row.Add(splitLine[i]); break; } } break; default: break; } } } break; } } } } } public class Table { public string name { get; set; } public List<Column> columns { get; set; } public List<Relationship> relationships { get; set; } public List<List<object>> records { get; set; } } public enum COLUMN_TYPE { numeric, name, varchar } public class Column { public string name { get; set; } public COLUMN_TYPE type { get; set; } public int length { get; set; } } public class Relationship { } }
How to parse INI file?
I have ini files that contains data from server. Map.ini [MAP_1] MapType = 1 MapWar = 1 Position = 42.03,738.2,737.3 [MAP_2] MapType = 1 MapWar = 1 Position = 42.03,738.2,737.3 How to read map.ini that contains this kind of files and save it in Dictionary or List.
Try following : using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace ConsoleApplication1 { class Program { const string FILENAME = #"c:\temp\test.txt"; static void Main(string[] args) { List<Map> maps = new List<Map>(); Map map = null; StreamReader reader = new StreamReader(FILENAME); string line = ""; while ((line = reader.ReadLine()) != null) { line.Trim(); if (line.Length > 0) { if(line.StartsWith("[")) { string name = line.Trim(new char[] { '[', ']' }); map = new Map(); maps.Add(map); map.name = name; } else { string[] data = line.Split(new char[] { '=' }); switch (data[0].Trim()) { case "MapType" : map.mapType = int.Parse(data[1]); break; case "MapWar": map.mapWar = int.Parse(data[1]); break; case "Position": string[] numbers = data[1].Split(new char[] { ',' }); map.x = decimal.Parse(numbers[0]); map.y = decimal.Parse(numbers[1]); map.z = decimal.Parse(numbers[2]); break; default : break; } } } } Dictionary<string, Map> dict = maps .GroupBy(x => x.name, y => y) .ToDictionary(x => x.Key, y => y.FirstOrDefault()); } } public class Map { public string name { get; set; } public int mapType { get; set; } public int mapWar { get; set; } public decimal x { get; set; } public decimal y { get; set; } public decimal z { get; set; } } }
Pairwise matching and windowing using Deedle in C#
Say we want to calculate how the price of some fruits are changing. Starting from a CSV file: Day,Name,Kind,Price 2019-09-04,"apple","red delicious",63.09 2019-09-04,"apple","ginger crisp",52.14 2019-09-04,"orange","navel",41.18 2019-09-03,"apple","red delicious",63.07 2019-09-03,"apple","ginger crisp",52.11 2019-09-03,"orange","navel",41.13 2019-09-02,"apple","red delicious",63.00 2019-09-02,"apple","ginger crisp",52.00 2019-09-02,"orange","navel",41.00 with an unknown number of fruits and varieties, we can read the dataframe and build an extra column to use for matching. var fruits_file = Path.Combine(root, "fruits.csv"); Deedle.Frame<int, string> df = Frame.ReadCsv(fruits_file); Series<int, string> name = df.GetColumn<string>("Name"); Series<int, string> kind = df.GetColumn<string>("Kind"); var namekind = name.ZipInner(kind).Select(t => t.Value.Item1 + t.Value.Item2); df.AddColumn("NameKind", namekind); but the problem remains. Deedle.Series.Window() and Deedle.Series.Pairwise() make it possible to perform first-order differences, but not matching based on some string (namekind). What is the right way to copy over a column LastPrice and subsequently calculate the Change? Day,Name,Kind,Price,LastPrice,Change 2019-09-04,"apple","red delicious",63.09,63.07,0.02 2019-09-04,"apple","ginger crisp",52.14,52.11,0.03 2019-09-04,"orange","navel",41.18,41.13,0.05 2019-09-03,"apple","red delicious",63.07,63.00,0.07 2019-09-03,"apple","ginger crisp",52.11,52.00,0.11 2019-09-03,"orange","navel",41.13,41.00,0.13 2019-09-02,"apple","red delicious",63.00,, 2019-09-02,"apple","ginger crisp",52.00,, 2019-09-02,"orange","navel",41.00,,
See code below : using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.IO; namespace ConsoleApplication137 { class Program { const string FILENAME = #"c:\temp\test.csv"; static void Main(string[] args) { Fruit fruit = new Fruit(FILENAME); Fruit.PrintFruits(); Console.ReadLine(); } } public class Fruit { public static List<Fruit> fruits { get; set; } public DateTime day { get; set; } public string name { get; set; } public string kind { get; set; } public decimal price { get; set; } public Fruit() { } public Fruit(string filename) { int count = 0; StreamReader reader = new StreamReader(filename); string line = ""; while ((line = reader.ReadLine()) != null) { if (++count > 1) { string[] splitLine = line.Split(new char[] { ',' }).ToArray(); Fruit newFruit = new Fruit(); if (fruits == null) fruits = new List<Fruit>(); fruits.Add(newFruit); newFruit.day = DateTime.Parse(splitLine[0]); newFruit.name = splitLine[1]; newFruit.kind = splitLine[2]; newFruit.price = decimal.Parse(splitLine[3]); } } reader.Close(); } public static void PrintFruits() { var groups = fruits.OrderBy(x => x.day) .GroupBy(x => new { name = x.name, kind = x.kind }) .ToList(); foreach (var group in groups) { for (int i = 0; i < group.Count() - 1; i++) { Console.WriteLine("Old Date : '{0}', New Date : '{1}', Name : '{2}', Kind : '{3}', Old Price '{4}', New Price '{5}', Delta Price '{6}'", group.ToList()[i].day.ToString("yyyy-MM-dd"), group.ToList()[i + 1].day.ToString("yyyy-MM-dd"), group.ToList()[i].name, group.ToList()[i].kind, group.ToList()[i].price.ToString(), group.ToList()[i + 1].price.ToString(), (group.ToList()[i + 1].price - group.ToList()[i].price).ToString() ); } } } } }