How to transpose data in Linq? - c#

I get below data from Postgresql table, need to sum the ListDate field for each month and transpose.
I tried this link Transpose a datatable using linq query , but not feasible or right approach to achieve it.
ListDate
MM
FC
AMS
KS
2023-01-01
12
13
34
26
2023-01-01
22
23
44
46
2022-12-01
32
13
34
26
2023-12-01
42
13
64
16
2023-11-10
62
13
94
36
2023-11-23
02
13
34
46
Expected Result -
.
JAN-23
DEC-22
NOV-22
MM
34
74
64
FC
36
26
26
AMS
78
98
118
KS
72
42
82

this is my class
public class Data
{
public string Date { get; set; }
public int MM { get; set; }
public int FC { get; set; }
public int AMS { get; set; }
public int KS { get; set; }
}
var dataList=new List<Data>
{
new Data
{
AMS=34, Date="2023-01-01", FC=13, KS=26, MM=12
}, new Data
{
AMS=44, Date="2023-01-01", FC=23, KS=46, MM=22
}, new Data
{
AMS=34, Date="2022-12-01", FC=13, KS=26, MM=32
}, new Data
{
AMS=64, Date="2023-12-01", FC=13, KS=16, MM=42
}, new Data
{
AMS=94, Date="2023-11-10", FC=13, KS=36, MM=62
}, new Data
{
AMS=34, Date="2023-11-23", FC=13, KS=46, MM=02
}
}.ToList();
var query = dataList
.GroupBy(g =>
g.Date.ToString()
)
.Select(group => new
{
Date= DateTime.Parse(group.Key).ToString("dd MMMM", CultureInfo.InvariantCulture),
MM = group.Sum(s => s.MM),
FC = group.Sum(s => s.FC),
AMS = group.Sum(a => a.AMS),
KS = group.Sum(c => c.KS)
});

The first point:
12-01-2023 should be 2022-12-01 in order, the question data to match the Expected Result.
If we have the following list:
public class item
{
public item(string _dt,int _MM,int _FC,int _AMS,int _KS)
{
dt = _dt;
MM = _MM;
FC = _FC;
AMS = _AMS;
KS = _KS;
}
public string dt { get; set; }
public int MM { get; set; }
public int FC { get; set; }
public int AMS { get; set; }
public int KS { get; set; }
}
List<item> _list=new List<item>();
_list.Add(new item("2023-01-01", 12, 13, 24, 26));
_list.Add(new item("2023-01-01", 22, 23, 44, 46));
_list.Add(new item("2022-12-01", 32, 13, 24, 26));
_list.Add(new item("2022-12-01", 42, 13, 64, 26));
_list.Add(new item("2023-11-10", 62, 13, 94, 36));
_list.Add(new item("2023-11-23", 02, 13, 34, 46));
We can use Substring(0,7) to group by year and month.
Now the key of group by year-month(ex: 2023-12). We can get the month name using DateTimeFormat.GetMonthName
and calculate the last two digits of the year using the substring(2,2)
And finally, collect the parameters and convert them into a list
var result = _list
.GroupBy(g =>
g.dt.Substring(0,7)
)
.Select(group => new
{
dateOfMonth = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(int.Parse(group.Key.Split('-')[1]))
+ "-" + group.Key.Split('-')[0].Substring(2,2),
MM = group.Sum(s => s.MM),
FC = group.Sum(s => s.FC),
AMS = group.Sum(a => a.AMS),
KS = group.Sum(c => c.KS)
}).ToList();
Result:

Related

C# occurrence counting generic list with different property types

I need to count the occurrences of each element in the "ID" field of a generic list. The result should be the list with an added field called "Quantity".
Suppose the following class is used for the list:
public class InfoList
{
public string ID { get; set; }
public DateTime PurchaseDate { get; set; }
public double Amount { get; set; }
public int Quantity { get; set; }
}
Imagine an initial list like the following:
ID
Purchase date
Amount (USD)
DDD
30 jul 2025
258,225.11
AXC
10 nov 2023
982,383.95
AXC
12 feb 2031
-439,130.87
TPV
05 mar 2023
439,715.32
DDD
8 apr 2024
-153,893.38
KYR
24 mar 2023
-153,893.38
AXC
10 sep 2026
638,031.66
SPM
26 oct 2023
-401,815.59
DDD
08 mar 2023
-315,099.43
HGP
30 nov 2025
-474,749.80
DDD
02 jul 2024
-253,726.59
NDS
06 sep 2029
490,035.01
HGP
24 dec 2026
468,006.38
The final result should be the following:
ID
Purchase date
Amount (USD)
Quantity
DDD
30 jul 2025
258,225.11
4
AXC
10 nov 2023
982,383.95
3
AXC
12 feb 2031
-439,130.87
3
TPV
05 mar 2023
439,715.32
1
DDD
8 apr 2024
-153,893.38
4
KYR
24 mar 2023
-153,893.38
1
AXC
10 sep 2026
638,031.66
3
SPM
26 oct 2023
-401,815.59
1
DDD
08 mar 2023
-315,099.43
4
HGP
30 nov 2025
-474,749.80
2
DDD
02 jul 2024
-253,726.59
4
NDS
06 sep 2029
490,035.01
1
HGP
24 dec 2026
468,006.38
2
Result should be of type "InfoList" not in a different list or variable.
Here's an approach that preserves the original order of the list.
var initial = new[]
{
new { ID = "DDD", PurchaseDate = DateTime.Parse("30 jul 2025"), Amount = 258225.11 },
new { ID = "AXC", PurchaseDate = DateTime.Parse("10 nov 2023"), Amount = 982383.95 },
new { ID = "AXC", PurchaseDate = DateTime.Parse("12 feb 2031"), Amount = -439130.87 },
new { ID = "TPV", PurchaseDate = DateTime.Parse("05 mar 2023"), Amount = 439715.32 },
new { ID = "DDD", PurchaseDate = DateTime.Parse("8 apr 2024"), Amount = -153893.38 },
new { ID = "KYR", PurchaseDate = DateTime.Parse("24 mar 2023"), Amount = -153893.38 },
new { ID = "AXC", PurchaseDate = DateTime.Parse("10 sep 2026"), Amount = 638031.66 },
new { ID = "SPM", PurchaseDate = DateTime.Parse("26 oct 2023"), Amount = -401815.59 },
new { ID = "DDD", PurchaseDate = DateTime.Parse("08 mar 2023"), Amount = -315099.43 },
new { ID = "HGP", PurchaseDate = DateTime.Parse("30 nov 2025"), Amount = -474749.80 },
new { ID = "DDD", PurchaseDate = DateTime.Parse("02 jul 2024"), Amount = -253726.59 },
new { ID = "NDS", PurchaseDate = DateTime.Parse("06 sep 2029"), Amount = 490035.01 },
new { ID = "HGP", PurchaseDate = DateTime.Parse("24 dec 2026"), Amount = 468006.38 },
};
var lookup = initial.ToLookup(x => x.ID);
InfoList[] final =
initial
.Select(i =>
new InfoList()
{
ID = i.ID,
PurchaseDate = i.PurchaseDate,
Amount = i.Amount,
Quantity = lookup[i.ID].Count(),
})
.ToArray();
That gives:
If preserving the initial order isn't important then this works:
InfoList[] final =
(
from i in initial
group i by i.ID into gis
from gi in gis
select new InfoList()
{
ID = gi.ID,
PurchaseDate = gi.PurchaseDate,
Amount = gi.Amount,
Quantity = gis.Count(),
}
).ToArray();
To do this with an extension method, you could try this:
public static IEnumerable<R> SelectCount<T, U, R>(this IEnumerable<T> source, Func<T, U> countBy, Func<T, int, R> project) =>
from t in source
group t by countBy(t) into gts
from gt in gts
select project(gt, gts.Count());
Then the code looks like this:
InfoList[] final =
initial
.SelectCount(i => i.ID, (i, q) => new InfoList()
{
ID = i.ID,
PurchaseDate = i.PurchaseDate,
Amount = i.Amount,
Quantity = q,
})
.ToArray();
If I'm understanding this correctly, you can track quantities in a dictionary as a separate data structure.
void main()
{
List<ItemList> purchaseList = buildItemList();
Dictionary<string,int> quantity = buildQuantityDictionary(purchaseList);
printTable(purchaseList, quantity);
}
List<ItemList> buildItemList()
{
//however you build the itemlist
}
Dictionary<string,int> buildQuantityDictionary(List<ItemList> purchaseList)
{
Dictionary<string,int> quantity = new Dictionary<string,int>();
foreach(ItemList item in purchaseList)
{
if(!quantity.ContainsKey(item.id))
{
quantity.add(item.id,0);
}
quantity[item.id]++;
}
return quantity;
}
void printTable(List<ItemList> purchaseList, Dictionary<string,int> quantity)
{
foreach(ItemList item in purchaseList)
{
//You'll need to write a ToString() override for your ItemList object.
Console.WriteLine($"{item.ToString()},{quantity[item.id]}");
}
}

Create the hierarchical structure parent-child starting from string definition

I have a list of object defined in the following way
public class MyObject
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
where the Id is the PK, and code contains a string made of numbers which defines the parent-child relationship between the objects:
id | Code
1 10 15
2 10 15 10
3 10 15 20
4 10 15 30
5 10 15 30 10
6 10 15 30 20
7 10 20
8 10 20 30
9 10 20 30 40
In the above example, object 1 is a root, 2,3,4 are children of 1; 5 and 6 are children of 4; 7 is a root; 8 is a child of 7 and 9 is a child of 8.
I'm trying to write an efficient algorithm that can fill in an additional field in the object, called ParentId, which defines the relation parent-children, to have something like this:
id | Code | ParentId
1 10 15 NULL
2 10 15 10 1
3 10 15 20 1
4 10 15 30 1
5 10 15 30 10 4
6 10 15 30 20 4
7 10 20 NULL
8 10 20 30 7
9 10 20 30 40 8
The list could contain up to 10000 objects, so it's quite large.
I'd like to avoid parsing the Code field as it could slow down a lot the loop.
Is there an alternative way?
public class MyObject
{
public int Id { get; set; }
public string Code { get; set; }
}
public class Result
{
public int Id { get; set; }
public string Code { get; set; }
public int? ParentId { get; set; }
}
var source = new List<MyObject> {
new MyObject { Id = 1, Code = "10 15" },
new MyObject { Id = 2, Code = "10 15 10" },
new MyObject { Id = 3, Code = "10 15 20" },
new MyObject { Id = 4, Code = "10 15 30" },
new MyObject { Id = 5, Code = "10 15 30 10" },
new MyObject { Id = 6, Code = "10 15 30 20" },
new MyObject { Id = 7, Code = "10 20" },
new MyObject { Id = 8, Code = "10 20 30" },
new MyObject { Id = 9, Code = "10 20 30 40" }
};
var result = new List<Result>(source.Count);
result.Add(new Result { Id = source[0].Id, Code = source[0].Code, ParentId = null });
for (int i = 1; i < source.Count; i++)
{
var cur = source[i];
if (cur.Code.Length < source[i - 1].Code.Length)
{
result.Add(new Result { Id = cur.Id, Code = cur.Code, ParentId = null });
continue;
}
for (int j = i - 1; j >= 0; j--)
{
if (cur.Code.StartsWith(source[j].Code))
{
result.Add(new Result { Id = cur.Id, Code = cur.Code, ParentId = source[j].Id });
break;
}
}
}
foreach (var x in result)
Console.WriteLine($"{x.Id} {x.Code,-12} {x.ParentId?.ToString() ?? "NULL"}");

LINQ to Object - How to implement dynamic SELECT projection of sub elements

I have several classes of business logic:
public class Client {
public string Code { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
public Total Total { get; set; } = new Total();
public List<Month> Months { get; set; } = new List<Month>();
}
public class Month {
public int Number { get; set; } = 0;
public string Name { get; set; } = string.Empty;
public DateTime Start { get; set; } = new DateTime();
public DateTime End { get; set; } = new DateTime();
public Total Summary { get; set; } = new Total();
}
public class Total {
public int Count { get; set; } = 0;
public decimal Sum { get; set; } = 0.0m;
}
which are instanced as follows:
List<Client> clients = new List<Client>() {
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.915940702810005800001093",
Total = new Total {
Count = 9,
Sum = 172536.45m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 6,
Sum = 17494.50m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 3,
Sum = 155041.95m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
}
}
},
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.800540702810205800001093",
Total = new Total {
Count = 4,
Sum = 16711.21m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 4,
Sum = 16711.21m
}
}
}
}
};
I'm trying to arrange aggregate data of a view like this:
+---------------+--------+------------------+-------------------+------------------+-------------------+
| Code | Status | January | February | March | Total |
| | +-------+----------+-------+-----------+-------+----------+-------+-----------+
| | | Count | Sum | Count | Sum | Count | Sum | Count | Sum |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
| 7002.70020604 | Active | 6 | 17494.50 | 3 | 155041.95 | 4 | 16711.21 | 13 | 189247.66 |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
using projection like this:
clients
.GroupBy(x => x.Code)
.Select(y => new {
Code = y.First().Code,
Status = y.First().Status,
Account = y.First().Account,
Total = new {
Count = y.Sum(z => z.Total.Count),
Sum = y.Sum(z => z.Total.Sum)
},
Months = new {
/*
?
*/
}
});
But I can't project the data by month. Assuming the date range (months) can be more than just this example. Please help!
Full interactive code listing at dotnetfiddle
You can use SelectMany to get months out of y and then group by month similarly as you group by code:
//...
Months = y
.SelectMany(client => client.Months)
.GroupBy(month => month.Name, (_, months) => new {
Number = months.First().Number,
Name = months.First().Name,
Start = months.First().Start,
End = months.First().End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}).ToList()
//...
That being said I don't suggest to use y.First() or months.First() more than once in each function because it makes an enumeration each time it is used. The following should in general have better performance:
(_, months) => {
var month = months.First();
return new {
Number = month.Number,
Name = month.Name,
Start = month.Start,
End = month.End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}
}
which is also not ideal because we're still making 3 enumerations here (1 enumeration in .First() and 1 enumeration for every .Sum(...)).
Even better approach would be to use Aggregate function which will do only a single enumeration:
(_, months) => months
.Aggregate((res, nextVal) => new Month {
Number = nextVal.Number,
Name = nextVal.Name,
Start = nextVal.Start,
End = nextVal.End,
Summary = new Total {
Count = res.Summary.Count + nextVal.Summary.Count,
Sum = res.Summary.Sum + nextVal.Summary.Sum
}
})
This LINQ query should prepare data for visualization:
clients
.GroupBy(x => new {x.Code, x.Status})
.Select(g => new
{
Code = g.Key
MonthsSummary = g.SelectMany(x => x.Months)
.OrderBy(x => x.Start)
.GroupBy(x => new {x.Start, x.Name})
.Select(gm => new
{
gm.Key.Name,
Count = gm.Sum(x => x.Summary.Count),
Sum = gm.Sum(x => x.Summary.Sum),
})
.ToList()
});

Dynamic Pivoting in Linq c# MVC

I have below data in my linq list.
StatusID Count MonthYear
======== ===== =========
1 0 Jan 2014
2 1 Feb 2013
1 2 Jan 2013
3 1 Dec 2014
2 0 Nov 2014
5 6 Jun 2015
Now my requirement is i need above list in below format. Where MonthYear column data is not fix. It can be any month and year data.
StatusID Jan 2013 Feb 2013 Jan 2014 Nov 2014 Dec 2014 Jun 2015
======== ======== ======== ======== ======== ======== ========
1 2 0 0 0 0 0
2 0 1 0 0 0 0
3 0 0 0 0 1 0
5 0 0 0 0 0 6
I read lots of solution on stackoverflow and even tried my own way but i was not get success. Below is code which i tried last.
var pvtData = new PivotData(new[] { "StatusID", "MonthYear" }, new SumAggregatorFactory("count"));
pvtData.ProcessData(finalList, (o, f) =>
{
var custData = (MontlyChartModified)o;
switch (f)
{
case "StatusID": return custData.StatusID;
case "MonthYear":
return custData.MonthYear;
case "count": return custData.count;
}
return null;
});
Please help me if you have any idea how to do dynamic pivoting.
Below is my code which i implemented. but when i debug and put watch on result var it shows me error "result The name 'result' does not exist in the current context"
public class MontlyChartModified
{
public int? StatusID { get; set; }
public int count { get; set; }
public string MonthYear { get; set; }
}
List<MontlyChartModified> finalList = new List<MontlyChartModified>();
finalList = (from x in Okdata
select new MontlyChartModified
{
StatusID = x.StatusID,
count = x.count,
MonthYear = x.Month.ToString() + " " + x.Year.ToString()
}).ToList();
var columns = new[] { "StatusID" }.Union(finalList.Select(a => a.MonthYear).OrderBy(a => a.Split(' ')[1]).ThenBy(a => a)).ToList();
var result = finalList.GroupBy(g => g.StatusID).OrderBy(g => g.Key).Select(g => columns.Select(c =>
{
if (c == "StatusID") return g.Key;
var val = g.FirstOrDefault(r => r.MonthYear == c);
return val != null ? val.count : 0;
}).ToList()).ToList();
my final result.
I managed to do it with grouping:
First, I create list of columns, sorted by year and month alphabetically:
var columns = new[] { "StatusID" }.Union(lst.Select(a => a.MonthYear).OrderBy(a => a.Split(' ')[1]).ThenBy(a => a)).ToList();
Second, results are groupped by StatusID and for each group I create List of values for each MonthYear column:
var result = lst.GroupBy(g => g.StatusID).OrderBy(g => g.Key).Select(g => columns.Select(c =>
{
if (c == "StatusID") return g.Key;
var val = g.FirstOrDefault(r => r.MonthYear == c);
return val != null ? val.Count : 0;
}).ToList()).ToList();
Input list is defined as follows:
var lst = new List<MontlyChartModified>()
{
new MontlyChartModified(){StatusID = 1, Count = 0, MonthYear = "Jan 2014"},
new MontlyChartModified(){StatusID = 2, Count = 1, MonthYear = "Feb 2013"},
new MontlyChartModified(){StatusID = 1, Count = 2, MonthYear = "Jan 2013"},
new MontlyChartModified(){StatusID = 3, Count = 1, MonthYear = "Dec 2014"},
new MontlyChartModified(){StatusID = 2, Count = 0, MonthYear = "Nov 2014"},
new MontlyChartModified(){StatusID = 5, Count = 6, MonthYear = "Jun 2015"},
};
class MontlyChartModified
{
public int StatusID { get; set; }
public int Count { get; set; }
public string MonthYear { get; set; }
}
result is List<List<int>>

How can I merge contiguous periods?

Is there a simple way to merge contiguous periods (StartDate to EndDate) having the same Value?
Input:
ID StartDate EndDate Value
1 2014-01-01 2014-01-31 71
2 2014-02-01 2014-02-28 71
3 2014-03-01 2014-03-31 71
4 2014-04-01 2014-04-30 50,12
5 2014-05-01 2014-05-31 50,12
6 2014-06-01 2014-06-30 71
7 2014-08-01 2014-08-31 71 (a month is skipped here)
8 2014-09-01 2014-09-30 71
So those lines will be merged as follows:
1, 2 and 3 to 01-01-2014 03-31-2014 71
4 and 5 to 2014-04-01 05-31-2014 71
6 will remain the same
7 and 8 to 2014-08-01 2014-09-30 71
Output should be:
StartDate EndDate Value
2014-01-01 2014-03-31 71
2014-04-01 2014-05-31 50,12
2014-06-01 2014-06-30 71
2014-08-01 2014-09-30 71
I have tried this:
public List<PeriodInterval> MergePeriods(List<PeriodInterval> samples)
{
var merged = samples.OrderBy(s => s.StartDate)
.ThenBy(s => s.StartDate)
//select each item with its index
.Select((s, i) => new
{
sample = s,
index = i
})
// group by date miuns index to group consecutive items
.GroupBy(si => new
{
date = si.StartDate.AddDays(1),
content = si.Valeur
})
.Select(g => new PeriodInterval
{
StartDate = g.Min(s => s.StartDate),
EndDate = g.Max(s => s.EndDate),
Valeur = g.First().Valeur
});
return merged.ToList();
}
Create extension method which batches sequential itemd by some condition, which checks two sequential items in source sequence:
public static IEnumerable<IEnumerable<T>> SequentialGroup<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using(var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> batch = new List<T> { iterator.Current };
while (iterator.MoveNext())
{
if (!predicate(batch[batch.Count - 1], iterator.Current))
{
yield return batch;
batch = new List<T>();
}
batch.Add(iterator.Current);
}
if (batch.Any())
yield return batch;
}
}
With this method you can create batches of items which have sequential date and same value:
items.SequentialGroup((a, b) =>
a.Value == b.Value && (b.StartDate - a.EndDate).Days <= 1)
Creating aggregated items from these groups is easy. Assume your items look like:
public class Item
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Value { get; set; }
public string Line { get; set; }
}
Query:
var query = items.SequentialGroup((a, b) =>
a.Value == b.Value && (b.StartDate - a.EndDate).Days <= 1)
.Select((g,i) => new Item {
Value = g.First().Value,
StartDate = g.Min(f => f.StartDate),
EndDate = g.Max(f => f.EndDate),
Line = String.Format("mergedLine_{0}", i + 1)
});
For your sample input output will be:
[
{
StartDate: "2014-01-01T00:00:00",
EndDate: "2014-03-31T00:00:00",
Value: "71",
Line: "mergedLine_1"
},
{
StartDate: "2014-04-01T00:00:00",
EndDate: "2014-05-31T00:00:00",
Value: "50,12",
Line: "mergedLine_2"
},
{
StartDate: "2014-06-01T00:00:00",
EndDate: "2014-06-30T00:00:00",
Value: "71",
Line: "mergedLine_3"
},
{
StartDate: "2014-08-01T00:00:00",
EndDate: "2014-09-30T00:00:00",
Value: "71",
Line: "mergedLine_4"
}
]

Categories