Group by same value and contiguous date - c#

var myDic = new SortedDictionary<DateTime,int> ()
{ { new DateTime(0), 0 },
{ new DateTime(1), 1 },
{ new DateTime(2), 1 },
{ new DateTime(3), 0 },
{ new DateTime(4), 0 },
{ new DateTime(5), 2 }
};
How can group these items (with a LINQ request) like this :
group 1 :
startDate: 0, endDate:0, value:0
group 2 :
startDate: 1, endDate:2, value:1
group 3 :
startDate: 3, endDate:4, value:0
group 4 :
startDate: 5, endDate:5, value:2
group are defined by contiguous date and same values.
Is it possible with a groupby ?

Just use a keyGenerating function. This example presumes your dates are already ordered in the source with no gaps.
int currentValue = 0;
int groupCounter = 0;
Func<KeyValuePair<DateTime, int>, int> keyGenerator = kvp =>
{
if (kvp.Value != currentValue)
{
groupCounter += 1;
currentValue = kvp.Value;
}
return groupCounter;
}
List<IGrouping<int, KeyValuePair<DateTime, int>> groups =
myDictionary.GroupBy(keyGenerator).ToList();

It looks like you are trying to group sequential dates over changes in the value. I don't think you should use linq for the grouping. Instead you should use linq to order the dates and iterate over that sorted list to create your groups.
Addition 1
While you may be able to build your collections with by using .Aggregate(). I still think that is the wrong approach.
Does your data have to enter this function as a SortedDictionary?
I'm just guessing, but these are probably records ordered chronologically.
If so, do this:
public class Record
{
public DateTime Date { get; set; }
public int Value { get; set; }
}
public class Grouper
{
public IEnumerable<IEnumerable<Record>> GroupRecords(IEnumerable<Record> sortedRecords)
{
var groupedRecords = new List<List<Record>>();
var recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
foreach (var record in sortedRecords)
{
if (recordGroup.Count > 0 && recordGroup.First().Value != record.Value)
{
recordGroup = new List<Record>();
groupedRecords.Add(recordGroup);
}
recordGroup.Add(record);
}
return groupedRecords;
}
}

Related

How to loop through the properties of anonymous type in a list

If I have the following LINQ query:
var outstandingDataTotalData = from t1 in dtTotal.AsEnumerable()
join t2 in dtOutstandingData.AsEnumerable() on
new
{
priv_code = t1["priv_code"],
pri_ded = t1["pri_ded"].ToString().Trim()
} equals
new
{
priv_code = t2["priv_code"],
pri_ded = t2["pri_ded"].ToString().Trim()
}
into ps
from t2 in ps.DefaultIfEmpty()
select new
{
adjustment_value = t2 == null ? string.Empty : t2["adjustment_value"].ToString(),
amount_outstanding = t2 == null ? string.Empty : t2["amount_outstanding"].ToString(),
amount_outstanding_priv = t2 == null ? string.Empty : t2["amount_outstanding_priv"].ToString(),
amount_outstanding_ded = t2 == null ? string.Empty : t2["amount_outstanding_ded"].ToString(),
diff_outstanding = t2 == null ? string.Empty : t2["diff_outstanding"].ToString(),
exchange_rate = t2 == null ? string.Empty : t2["exchange_rate"].ToString(),
SalYear = t2 == null ? string.Empty : t2["sal_year"].ToString(),
SalMonth = t2 == null ? string.Empty : t2["sal_mon"].ToString()
};
Now outstandingDataTotalData is a list of anonymous type. And I have the following class:
public class AdjustmentTotal
{
public string SalYear { get; set; }
public string SalMonth { get; set; }
public string Value { get; set; }
}
How to loop through outstandingDataTotalData properties to fill List<AdjustmentTotal> as the following example:
If the result set of outstandingDataTotalData =
[0]{ adjustment_value = "100.00", amount_outstanding = "80.00", amount_outstanding_priv = "60.00", amount_outstanding_ded = "30.52", diff_outstanding = "0.36", exchange_rate = "", SalYear = "2018", SalMonth = "1" }
[1]{ adjustment_value = "1500.00", amount_outstanding = "5040.00", amount_outstanding_priv = "", amount_outstanding_ded = "", diff_outstanding = "0.36", exchange_rate = "", SalYear = "2018", SalMonth = "1" }
I want the result set of List<AdjustmentTotal> as:
2018 1 100.00
2018 1 1500.00
2018 1 80.00
2018 1 5040.00
2018 1 60.00
2018 1
2018 1 30.52
2018 1
2018 1 0.36
2018 1 0.36
2018 1
2018 1
Make your life easy, don't extract to separate properties. Make the extract as an array:
select new {
someArray = new[]{
t2["adjustment_value"].ToString(),
t2["amount_outstanding"].ToString(),
t2["amount_outstanding_priv"].ToString(),
t2["amount_outstanding_ded"].ToString(),
...
},
SalYear = ...,
}
So you end up with an object with 3 properties, two strings SalXxx and an array of strings (the other values). The array of strings means you can use the LINQ SelectMany it to flatten it. You'll see in the msdn example they have owners with lists of pets (variable number of pets but your values are fixed number) and after selectmany it's flattened to a list where the owner repeats and there is one pet. Your lowercases values are the pets, the SalXxx values are the owners
Once you get a working query you can actually integrate it into the first query ..
Sorry for not posting a full example (and I skipped the null checks for clarity) - the code is very hard to work with on a cellphone
Edit:
So, you say you want the results in a particular order. Both Select and SelectMany have a version where they will give the index of the item, and we can use that.. because you basically want to have these objects:
var obj = new [] {
new { SalYear = 2018, SalMonth = 1, C = new[] { "av1", "ao1", "aop1", "aod1" } },
new { SalYear = 2018, SalMonth = 2, C = new[] { "av2", "ao2", "aop2", "aod2" } }
};
Be like av1, av2, ao1, ao2.. so we want to sort the results first by the index of the inner array, then by the index of the outer array
We use a SelectMany to dig out the inner array and then for every item in the inner array we make a new object that has the data and the indexes (of the inner and the outer):
obj.SelectMany((theOuter, outerIdx) =>
theOuter.C.Select((theInner, innerIdx) =>
new {
SalYear = theOuter.SalYear,
SalMonth = theOuter.SalMonth,
DataItem = theInner,
OuterIdx = outerIdx,
InnerIdx = innerIdx
}
)
).OrderBy(newObj => newObj.InnerIdx).ThenBy(newObj => newObj.OuterIdx)
You will probably find you don't need the ThenBy; sorting by the InnerIdx will leave a tie (every InnerIdx in my list is represented twice -there are two innerIdx=0 etc) and things in linq sort as far as they can then no futher - because theyre sorted by OuterIdx already (when they went into the query) they should remain sorted by OuterIdx after they tie on InnerIdx.. If that makes sense. Belt and braces!
outstandingDataTotalData.Select(s => new AdjustmentTotal {
SalYear = s.SalYear,
SalMonth = s.SalMonth,
Value = s.adjustment_value
}).ToList();
Use a eum :
class Program
{
static void Main(string[] args)
{
List<AdjustmentTotal> totals = new List<AdjustmentTotal>();
for (int i = 0; i < (int)VALUE.END; i++)
{
foreach (var data in outstandingDataTotalData)
{
AdjustmentTotal total = new AdjustmentTotal();
totals.Add(total);
total.SalMonth = data.SalMonth;
total.SalYear = data.SalYear;
total._Type = (VALUE)i;
switch ((VALUE)i)
{
case VALUE.adjustment_value :
total.Value = data.adjustment_value;
break;
case VALUE.amount_outstanding:
total.Value = data.amount_outstanding;
break;
case VALUE.amount_outstanding_ded:
total.Value = data.mount_outstanding_ded;
break;
case VALUE.amount_outstanding_priv:
total.Value = data.amount_outstanding_priv;
break;
case VALUE.diff_outstanding:
total.Value = data.diff_outstanding;
break;
case VALUE.exchange_rate:
total.Value = data.exchange_rate;
break;
}
}
}
}
}
public enum VALUE
{
adjustment_value = 0,
amount_outstanding = 1,
amount_outstanding_priv = 2,
amount_outstanding_ded = 3,
diff_outstanding = 4,
exchange_rate = 5,
END = 6
}
public class AdjustmentTotal
{
public string SalYear { get; set; }
public string SalMonth { get; set; }
public string Value { get; set; }
public VALUE _Type { get; set; }
}

remove duplicate values from a list with multiple properties

I have a list of MyClass:
class MyClass
{
public DateTime? DueDate;
public string Desc;
public Decimal Amount;
}
var sample = new List<MyClass>();
This is how sample data looks like :
DueDate Desc Amount
06-29-2015 ABC 100
06-29-2015 DEF 200
01-15-2015 ABC 100
01-15-2015 DEF 200
Output I want in this format
DueDate Desc Amount
06-29-2015 ABC 100
DEF 200
01-15-2015 ABC 100
DEF 200
So basically I would like to remove duplicate DueDate values but keeping its adjacent Desc & Amount field values
I tried this but it will remove values from adjacent column as well :
var test = sample.GroupBy(d => d.DueDate).Select(a => a.First()).ToList();
Any suggestions?
Here's how to "remove" (set to null) duplicate, adjacent DueDates from the sample list:
sample.GroupBy(d => d.DueDate).ToList()
.ForEach(g => g.Skip(1).ToList().ForEach(o => o.DueDate = null));
This is done by Group-ing by DueDate, and for each group, Skip-ing the first element, setting the remainder of the elements in the group DueDates to null.
Output with format:
Console.WriteLine("DueDate Desc Amount");
foreach (var item in sample)
{
var dateString = item.DueDate != null
? item.DueDate.Value.ToString("MM-dd-yyyy")
: string.Empty;
Console.WriteLine(dateString.PadRight(12) + item.Desc + " " + item.Amount);
}
Result:
DueDate Desc Amount
06-29-2015 ABC 100
DEF 200
01-15-2015 ABC 100
DEF 200
var finalData = data
.GroupBy(d=>d.DueDate)
.Select(g=>
new {
DueDate = g.Key,
Values = g.Select(d2=>new{d2.Desc, d2.Amount})})
The Final Structure would be
finalDate = [
{
DueDate:'06-29-1015',
Values:[{Desc:"ABC", Amount:100}, {Desc:"DEF", Amount:200}]
},
{...}
]
EDIT:-
var finalData = data
.GroupBy(d=>d.DueDate)
.Select(g=>
new {
DueDate = g.Key,
Values = g.Select(d2=>d2)
})
.ToDictionary(o=>o.DueDate, o=>o.Values)
What you want is a pivot table. this is how it is done :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.Load();
myClass.CreatePivotTable();
}
}
class MyClass
{
public static List<MyClass> samples = new List<MyClass>();
public DateTime dueDate { get; set; }
public string desc { get; set; }
public Decimal amount { get; set; }
public static DataTable dt = new DataTable();
public void Load()
{
samples = new List<MyClass>() {
new MyClass() { dueDate = DateTime.Parse("06-29-2015"), desc = "ABC", amount = 100},
new MyClass() { dueDate = DateTime.Parse("06-29-2015"), desc = "DEF", amount = 200},
new MyClass() { dueDate = DateTime.Parse("01-15-2015"), desc = "ABC", amount = 100},
new MyClass() { dueDate = DateTime.Parse("01-15-2015"), desc = "DEF", amount = 100}
};
}
public void CreatePivotTable()
{
string[] uniqueDescription = samples.Select(x => x.desc).Distinct().ToArray();
dt.Columns.Add("Due Date", typeof(DateTime));
foreach (string desc in uniqueDescription)
{
dt.Columns.Add(desc, typeof(decimal));
}
var groups = samples.GroupBy(x => x.dueDate);
foreach(var group in groups)
{
DataRow newRow = dt.Rows.Add();
newRow["Due Date"] = group.Key;
foreach (string col in uniqueDescription)
{
newRow[col] = group.Where(x => x.desc == col).Sum(x => x.amount);
}
}
}
}
}
I'd simply prefer that you loop through your records after you got them in the correct order. Just start with an empty variable and keep the last date in it. If the next value is the same, just don't plot it out. If you find another date value the next iteration, plot it and overwrite your variable for further iterations.
Yeah I know, Linq and Lambdas are cool and stuff (and I love them too) but in this case it seems to be appropriate to me.
var last = DateTime.MinValue;
foreach (var f in sample.OrderBy(x => x.DueDate))
{
if (f.DueDate.Equals(last))
Console.WriteLine("{0}\t{1}\t{2}", "SKIP DATE", f.Desc, f.Amount);
else
{
Console.WriteLine("{0}\t{1}\t{2}", f.DueDate.ToShortDateString(), f.Desc, f.Amount);
last = f.DueDate;
}
}
Based on your latest comments I have edited my answer.
As I am understanding, your requirements are:
Group by DueDate, and only allow the first of the group to have a
DueDate.
The results have to be the same structure.
If you want to remove the DueDate property from all i>0 items in a group then you need to make your property nullable: public DateTime? DueDate;. This way you can assign the value of null to subsequent items in the group.
//New list to hold our new items
var outputList = new List<MyClass>();
//Groups all the items together by DueDate
foreach(var grouping in samples.GroupBy(d => d.DueDate))
{
//Iterates through all items in a group (selecting the index as well)
foreach(var item in grouping.Select((Value, Index) => new { Value, Index }))
{
//If this is any item after the first one, we remove the due date
if(item.Index > 0)
{
item.Value.DueDate = null;
}
outputList.Add(item.Value);
}
}
Fiddle here.

How to pass the current index iteration inside a select new MyObject

This is my code:
infoGraphic.chartData = (from x in db.MyDataSource
group x by x.Data.Value.Year into g
select new MyObject
{
index = "", // here I need a string such as "index is:" + index
counter = g.Count()
});
I need the current index iteration inside the select new. Where do I pass it?
EDIT - My current query:
var test = db.MyData
.GroupBy(item => item.Data.Value.Year)
.Select((item, index ) => new ChartData()
{
index = ((double)(3 + index ) / 10).ToString(),
value = item.Count().ToString(),
fill = index.ToString(),
label = item.First().Data.Value.Year.ToString(),
}).ToList();
public class ChartData
{
public string index { get; set; }
public string value { get; set; }
public string fill { get; set; }
public string label { get; set; }
}
Use IEnumerable extension methods, I think the syntax is more straightforward.
You need the 2nd overload, that receives the IEnumerable item and the index.
infoGraphic.chartData.Select((item, index) => {
//what you want to do here
});
You want to apply grouping on your chartData, and afterwards select a subset / generate a projection on the resulting data ?
your solution should look like:
infoGraphic.chartData
.GroupBy(...)
.Select((item, index) => {
//what you want to do here
});
abstracting the dataSource as x:
x.GroupBy(item => item.Data.Value.Year)
.Select((item, index) => new { index = index, counter = item.Count() });
As a follow up to your new question...
here is a simple working scenario with a custom type (like your ChartData):
class Program
{
static void Main(string[] args)
{
List<int> data = new List<int> { 1, 872, -7, 271 ,-3, 7123, -721, -67, 68 ,15 };
IEnumerable<A> result = data
.GroupBy(key => Math.Sign(key))
.Select((item, index) => new A { groupCount = item.Count(), str = item.Where(i => Math.Sign(i) > 0).Count() == 0 ? "negative" : "positive" });
foreach(A a in result)
{
Console.WriteLine(a);
}
}
}
public class A
{
public int groupCount;
public string str;
public override string ToString()
{
return string.Format("Group Count: [{0}], String: [{1}].", groupCount, str);
}
}
/* Output:
* -------
* Group Count: [6], String: positive
* Group Count: [4], String: negative
*/
Important: Make sure the data type you are to use the extension methods is of type IEnumerable (inherits IEnumerable), otherwise you will not find this Select overload my solution is talking about, exposed.
you can do something like this:
let currIndex = collection.IndexOf(collectionItem)
Your code would then become:
infoGraphic.chartData =
(from x in db.MyDataSource group x by x.Data.Value.Year into g
// Get Iterator Index Here
let currIndex = db.MyDataSource.IndexOf(x)
select new MyObject
{index = currIndex.ToString(), // Your Iterator Index
counter = g.Count()
});

linq group by contiguous blocks

Let's say I have following data:
Time Status
10:00 On
11:00 Off
12:00 Off
13:00 Off
14:00 Off
15:00 On
16:00 On
How could I group that using Linq into something like
[On, [10:00]], [Off, [11:00, 12:00, 13:00, 14:00]], [On, [15:00, 16:00]]
Create a GroupAdjacent extension, such as the one listed here.
And then it's as simple as:
var groups = myData.GroupAdjacent(data => data.OnOffStatus);
You could also do this with one Linq query using a variable to keep track of the changes, like this.
int key = 0;
var query = data.Select(
(n,i) => i == 0 ?
new { Value = n, Key = key } :
new
{
Value = n,
Key = n.OnOffFlag == data[i - 1].OnOffFlag ? key : ++key
})
.GroupBy(a => a.Key, a => a.Value);
Basically it assigns a key for each item that increments when the current item does not equal the previous item. Of course this assumes that your data is in a List or Array, otherwise you'd have to try a different method
Here is a hardcore LINQ solution by using Enumerable.Zip to compare contiguous elements and generate a contiguous key:
var adj = 0;
var t = data.Zip(data.Skip(1).Concat(new TimeStatus[] { null }),
(x, y) => new { x, key = (x == null || y == null || x.Status == y.Status) ? adj : adj++ }
).GroupBy(i => i.key, (k, g) => g.Select(e => e.x));
It can be done as.
Iterate over collection.
Use TakeWhile<Predicate> condition is text of first element of collection On or Off.
Iterate over the subset of from point one and repeat above step and concatenate string.
Hope it helps..
You could parse the list and assign a contiguous key e.g define a class:
public class TimeStatus
{
public int ContiguousKey { get; set; }
public string Time { get; set; }
public string Status { get; set; }
}
You would assign values to the contiguous key by looping through, maintaining a count and detecting when the status changes from On to Off and so forth which would give you a list like this:
List<TimeStatus> timeStatuses = new List<TimeStatus>
{
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "10:00"},
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "11:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "12:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "13:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "14:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "15:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "16:00"}
};
Then using the following query you can extract the Status and grouped Times:
var query = timeStatuses.GroupBy(t => t.ContiguousKey)
.Select(g => new { Status = g.First().Status, Times = g });

Group by in linq + select

say I have this data
1 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 2 3
2 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 3 1
3 757f27a2-e997-44f8-b2c2-6c0fd6ee2c2f 2 2
column 1 // pk
column 2 // userId
column 3 // courseId
column 4 // permissionId
I have this class
class CoursePermissions
{
public string Prefix { get; set; }
public bool OwnerPermission { get; set; } // permissionId 1
public bool AddPermission { get; set; } // permissionId 2
public bool EditPermission { get; set; } // permissionId 3
}
I want to group all the 3 rows by courseId(or Prefix) and then take that information and make a class out Of it
So the end result would be
List<CoursePermissions> permissions = new List<CoursePermissions>();
CoursePermissions a = new CoursePermissions
{
Prefix = "comp101";
OwnerPermission = false,
AddPermission = true,
EditPermission = true
};
CoursePermissions b = new CoursePermissions
{
Prefix = "comp102";
OwnerPermission = true,
AddPermission = false,
EditPermission = false
};
permissions.Add(a);
permissions.Add(b);
So the above is how the object would look if I took all the row data from the db and manually made it the way I wanted it too look. Of course I need to do it somehow as a query.
In my example I have 2 students. They both belong to the same course. Student 1has edit and Add permission for Comp101 but only owner permissions for comp102.
I want to get all the rows back for Comp101 and put it into CoursePermissions. Then I want to get all the rows back for Comp102 and put it into CoursePermissions. Then store all these in a collection and use them.
The only thing I can do is something like this
var list = session.Query<PermissionLevel>().Where(u => u.Student.StudentId == studentId).ToList();
IEnumerable<IGrouping<string, PermissionLevel>> test = list.GroupBy(x => x.Course.Prefix);
foreach (var t in test)
{
CoursePermissions c = new CoursePermissions();
foreach (var permissionLevel in t)
{
if (permissionLevel.PermissionLevelId == 1)
{
c.OwnerPermission = true;
}
}
}
It would nice if I could get rid of the nest for each loop and do it all when the data comes from the query.
Here's an approach that I think is quite functional.
First set up a dictionary of actions that will set the appropriate course permission given a permission level id.
var setPermission = new Dictionary<int, Action<CoursePermissions>>()
{
{ 1, cps => cps.OwnerPermission = true },
{ 2, cps => cps.AddPermission = true },
{ 3, cps => cps.EditPermission = true },
};
Now create a function that will turn the course prefix and a list of permission level ids into a new CoursePermissions object.
Func<string, IEnumerable<int>, CoursePermissions>
buildCoursePermission = (prefix, permissionLevelIds) =>
{
var cps = new CoursePermissions() { Prefix = prefix };
foreach (var permissionLevelId in permissionLevelIds)
{
setPermission[permissionLevelId](cps);
}
return cps;
};
Now all you have left is a simple query that turns your list of permission levels into a list of course permissions.
var coursePermissionsList =
(from pl in list
group pl.PermissionLevelId by pl.Course.Prefix into gcpls
select buildCoursePermission(gcpls.Key, gcpls)).ToList();
How does that work for you?

Categories