Merging and ordering data using Linq - c#

I have a list of objects as such:
public class ExceptionFolderEntries
{
public int Id { get; set; }
public string Data { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
}
I am trying to get the next "Time" whether it be a starttime of endtime so that I can take action.
I know how to order the list using LINQ based on either StartTime OR EndTime but don't know how to merge the data and then sort the new merged list.
Example:
With the data:
"ABC","1/1/2018 01:00", "1/2/2018 13:00"
"MNO","1/1/2018 01:30", "1/1/2018 08:00"
"XYZ","1/1/2018 09:00", "1/2/2018 13:00"
Would result in
ABC 1/1 01:00
MNO 1/1 01:30
MNO 1/1 08:00
XYZ 1/1 09:00
ABC 1/2
13:00
XYZ 1/2 13:00
Any suggestions?

You can try to make two FolderEntries new create collection one is for StartTime, another is for EndTime, then use linq Concat to combine two collections. then do order by
public class FolderEntries {
public string Data { get; set; }
public DateTime? FolderDateTime { get; set; }
}
var result =
(from s1 in list select new FolderEntries(){
Data = s1.Data,
FolderDateTime = s1.StartTime
}).Concat
(from s2 in list select new FolderEntries {
Data = s2.Data,
FolderDateTime = s2.EndTime
}).OrderBy(x=>x.FolderDateTime);
Result
ABC 1/1/2018 1:00:00 AM
MNO 1/1/2018 1:30:00 AM
XYZ 1/1/2018 9:00:00 AM
MNO 1/1/2018 1:08:00 PM
ABC 1/2/2018 1:00:00 PM
XYZ 1/2/2018 1:00:00 PM
c# online

My answer is perhaps too similar to Eriks answer, but one way to do it would be to select a new ExceptionFolderEntries (which should really be renamed ExceptionFolderEntry since it only represents a single item) for every non-null StartTime and EndTime, preserving whichever one we're reading (start or end) and leaving the other field null.
Then you can order that new (sizeable) list by the non-null value: StartTime ?? EndTime.
For example:
var sortedItems =
// First select a new item for all non-null StartTimes
items.Where(i => i.StartTime.HasValue)
.Select(i => new ExceptionFolderEntries {Data = i.Data, StartTime = i.StartTime})
// Then concat with a new item for all non-null EndTimes
.Concat(items
.Where(i => i.EndTime.HasValue)
.Select(i => new ExceptionFolderEntries {Data = i.Data, EndTime = i.EndTime}))
// And finally, order by the non-null field
.OrderBy(i => i.StartTime ?? i.EndTime)
.ToList();
// Now we can write out the data and the non-null field for each item
sortedItems.ForEach(i => Console.WriteLine($"{i.Data} {i.StartTime ?? i.EndTime}"));
Output
In the sample above, items was initialized as:
var items = new List<ExceptionFolderEntries>
{
new ExceptionFolderEntries
{
Data = "ABC",
StartTime = DateTime.Parse("1/1/2018 01:00"),
EndTime = DateTime.Parse("1/2/2018 13:00")
},
new ExceptionFolderEntries
{
Data = "MNO",
StartTime = DateTime.Parse("1/1/2018 01:30"),
EndTime = DateTime.Parse("1/1/2018 08:00")
},
new ExceptionFolderEntries
{
Data = "XYZ",
StartTime = DateTime.Parse("1/1/2018 09:00"),
EndTime = DateTime.Parse("1/2/2018 13:00")
},
};

ExceptionFolderEntries[] entries = [];
entries.Select(e => new DateTime?[] {e.StartTime, e.EndTime).SelectMany(dt => dt).OrderBy(//ordery by logic)
First select out the datetimes into an array (creating you an array of arrays) then use select many to flatten it.
Edit: can also shorten too
ExceptionFolderEntries[] entries = [];
entries.SelectMany(e => new DateTime?[] {e.StartTime, e.EndTime).OrderBy(//ordery by logic)

First off, your class should probably not end in an (s) because it's not a collection, so I'll be using ExceptionFolderEntry in my example.
I'd probably do something like:
class ExceptionFolderEntryMeta
{
public ExceptionFolderEntry ExceptionFolderEntry { get; set; }
public DateTime SortBy { get; set; }
public bool IsStartTime { get; set; }
}
var exceptionFolderEntries = new List<ExceptionFolderEntry>();
var mergeList = exceptionFolderEntries
.Where(efe => efe.StartTime.HasValue)
.Select(efe => new ExceptionFolderEntryMeta
{
ExceptionFolderEntry = efe,
SortBy = efe.StartTime,
IsStartTime = true,
})
.Concat(exceptionFolderEntries
.Where(efe => efe.EndTime.HasValue)
.Select(efe => new ExceptionFolderEntryMeta
{
ExceptionFolderEntry = efe,
SortBy = efe.EndTime,
IsStartTime = false,
}))
.OrderBy(efem => efem.SortBy)
.ToList())
I created the new class to allow correct sorting and to denote if it's a start time or end time (because you'll probably need to know that at some point). I'm also taking into account that you've created those properties as nullable, so if one property is null, it only appears once and if both are null then it never appears.

Related

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.

GROUPBY and SUM on List items using LINQ

I have a List of type DailySummary
public class DailySummary
{
public string AffiliateID { get; set; }
public string TotalCalls { get; set; }
public string Date{ get; set; }
}
with following sample data:
List<DailySummary> DealerTFNDatesTable = new List<DailySummary>();
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "10"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/13/2016", TotalCalls = "74"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="1", Date = "12/22/2016", TotalCalls = "63"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "58"});
Now I want to retrieve Date and TotalCalls grouped by AffiliateID and assign in another list.
for(int i =0; i < DealerTFNDatesTable.Count; i++)
{
List<NewList> newList = new List<NewList>();
newList.Date = //Assign Dintinct dates WHERE AffiliateId = 0
newList.AffiliateID = //AffiliateID=0
newList.TotalCalls= //TotalCalls SUM GROUPBY DATE and AffiliateID = 0
//For Date '12/12/2016' it will be 68, For '12/13/2016' it will be 74 and so on
}
I'm sorry, I'm new to LINQ. Can someone help me or share resources where I can get a hint to achieve this?
This should work for grouping by AffilateID and Date and then getting the sum (though it's weird to store a number as a string for something like this, but whatever floats your boat).
var results = DealerTFNDatesTable
.GroupBy(x => new { x.AffiliateID, x.Date })
.Select(x => new DailySummary {
AffiliateID = x.First().AffiliateID,
Date = x.First().Date,
TotalCalls = x.Sum(y => Convert.ToInt32(y.TotalCalls)).ToString()
});
If you now look at the result, for example with this code, you get exactly the values you wanted:
foreach (var x in results) {
Console.WriteLine($"id = {x.AffiliateID}, date = {x.Date}, totalCalls = {x.TotalCalls}");
}
> id = 0, date = 12/12/2016, totalCalls = 68
> id = 0, date = 12/13/2016, totalCalls = 74
> id = 1, date = 12/22/2016, totalCalls = 63
First off,
Since DealerTFNDatesTable is a variable, you should use camel case. Thus it is dealerTFNDatesTable
Then to complete #andy his answer, as you also want to do a select. You can select it as follows:
var newVariable = from item in dealerTFNDatesTable
group item by new
{
item.Date,
item.AffiliateID,
}
into g
select new
{
Date = g.Key.Date,
Id = g.Key.AffiliateID,
Total = g.Sum(a => a.TotalCalls)
};
This will give you an IEnumerable, of which you can put the relevant parts in a list by doing var otherList = new List<object>(newVariable
.Where(a => a.Total > 0)); or simply add .ToList() after the select if you want the collection as-is.
Note that this is simply another notation than LINQ, the result is the same.
var results = DealerTFNDatesTable.GroupBy(T => new { T.AffiliateID })
Link

Checking multiple DateTime of Interferences

I got a list of DateTime which contains StartDate and EndDate,
User Should Select one or more TimeSpans from this list.
They are also have a structure class Named Courses.
How can I check if any overlaps is Happening or not
For example I got this
Start Date End Date
#1 7/20/2016 7/27/2016 Selected
#2 6/18/2016 7/25/2016 Selected
#3 7/20/2016 7/27/2016
#4 6/5/2016 6/10/2016
In this Example user has selected 2 dates that contains overlaps .
I want to warn the user with a message box or some Using C#.
Any opinion
Thanks
Ok, first created a class TimePeriod like this:
public class TimePeriod
{
public int Id;
public DateTime FromDate
{
get; set;
}
public DateTime ToDate
{
get; set;
}
public static DateTime Parse(string date)
{
var dt = DateTime.Parse(date,
CultureInfo.CreateSpecificCulture("en-US"), DateTimeStyles.RoundtripKind);
return dt;
}
}
Then created a List with items of this class:
List<TimePeriod> list = new List<TimePeriod>();
Then added your examples of Dates (added all of them, for your need just add selected one's):
list.Add(new TimePeriod() { Id = 1, FromDate = TimePeriod.Parse("7/20/2016"), ToDate = TimePeriod.Parse("7/27/2016") });
list.Add(new TimePeriod() { Id = 2, FromDate = TimePeriod.Parse("6/18/2016"), ToDate = TimePeriod.Parse("7/25/2016") });
list.Add(new TimePeriod() { Id = 3, FromDate = TimePeriod.Parse("7/20/2016"), ToDate = TimePeriod.Parse("7/27/2016") });
list.Add(new TimePeriod() { Id = 4, FromDate = TimePeriod.Parse("6/5/2016"), ToDate = TimePeriod.Parse("6/10/2016") });
And last check with LINQ for overlapping:
var overlaps = from current in list
from compare in list
where
(
(compare.FromDate > current.FromDate &&
compare.FromDate < current.ToDate) ||
(compare.ToDate > current.FromDate &&
compare.ToDate < current.ToDate)
)
select new
{
Id1 = current.Id,
Id2 = compare.Id,
};
The result will be in this case 1/2 & 2/1 and 2/3 & 3/2. In your case it will be 1/2 & 2/1.
There is a very good library for working with time periods and their intersection on nuget.
Time Period Library
There is also an article on code project for it.
Time Period Library for .NET
You need to store which dates have been selected and if they occur in multiple selections right?
Store startedate and enddate of each selected timespan to a Tuple selectedTimeSpans
then:
List<int> useddays =new List<int>();
foreach (Tuple<DateTime, DateTime> selected in selectedTimeSpans)
{
DateTime start = selected.Value1;
DateTime end = selected.Value2;
DateTime current = start;
while(current <=end)
{
if(useddays.Contains((current-DateTime.MinValue).TotalDays)
MessageBox. Show("Already used!");
else
useddays.Add((current-DateTime.MinValue).TotalDays);
current.AddDays(1);
}
}
Thanks To all #c0d3b34n and #ThomasVoß ,
Also to this article https://stackoverflow.com/a/325964/3970128
This is All I Have Done
Ok, first created a class TimePeriod like this:
public class TimePeriod
{
public int Id;
public DateTime FromDate
{
get; set;
}
public DateTime ToDate
{
get; set;
}
public static DateTime Parse(string date)
{
var dt = DateTime.Parse(date,
CultureInfo.CreateSpecificCulture("en-US"), DateTimeStyles.RoundtripKind);
return dt;
}
}
Then created a List with items of this class:
List<TimePeriod> list = new List<TimePeriod>();
Then added your examples of Dates (added all of them, for your need just add selected one's):
list.Add(new TimePeriod() { Id = 1, FromDate = TimePeriod.Parse("7/20/2016"), ToDate = TimePeriod.Parse("7/27/2016") });
list.Add(new TimePeriod() { Id = 2, FromDate = TimePeriod.Parse("6/18/2016"), ToDate = TimePeriod.Parse("7/25/2016") });
list.Add(new TimePeriod() { Id = 3, FromDate = TimePeriod.Parse("7/20/2016"), ToDate = TimePeriod.Parse("7/27/2016") });
list.Add(new TimePeriod() { Id = 4, FromDate = TimePeriod.Parse("6/5/2016"), ToDate = TimePeriod.Parse("6/10/2016") });
Then
foreach (var variable in list)
{
foreach (var VARIABLE in list)
{
if (variable.Id == VARIABLE.Id)
{
continue;
}
if ((variable.FromDate <= VARIABLE.ToDate) && (variable.ToDate >= VARIABLE.FromDate))
{
Console.WriteLine("Problem Hapendes!! {0} <= {1} , {2} >= {3}", variable.FromDate.ToString(), VARIABLE.ToDate.ToString(), VARIABLE.ToDate.ToString(), VARIABLE.FromDate.ToString());
}
}
}

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 });

How to check all values in List of Class are same or not?

I need help to write a function or logic to find if all values in my List of type class (named Stack) are equal or not. So it will return either true or false.
public class Stack
{
public string Key { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
I have created a class with 3 properties as above.
List<Stack> Stack = new List<Stack>();
Stack WOKey = new Stack() { Key = Resource, StartDate = WorkOrderST, EndDate = WorkOrderED };
Stack.Add(WOKey);
I have created 2 objects with StartDate and EndDate assigned to them through variables.
So I need a logic or function that will return true, if all StartDate have all same values (eg DateTime(2018, 1, 1)) and as case for EndDate (eg DateTime (2018, 1, 30)).
Should I use foreach or is it possible with LINQ? I am new to C# so I am not sure how to implement it.
This is pretty simple with LINQ:
bool allSame = Unavailability.All(s => s.StartDate == new DateTime(2018, 1, 1) &&
s.EndDate == new DateTime(2018, 1, 30));
The .All returns true if every item in the sequence satisfies the condition. See .NET Enumerable.All.
If you want to see if they are all equal, just use the first value...
bool allSame = Unavailability.All(s => s.StartDate == Unavailability[0].StartDate &&
s.EndDate == Unavailability[0].EndDate);
I would use Linq
You can can do the following:
Don't forget to import using System.Linq;
List<Stack> Unavailability = new List<Stack>
{
new Stack{ Key = "A", StartDate = new DateTime(2018,1,1), EndDate = new DateTime(2018,1,30) },
new Stack{ Key = "B", StartDate = new DateTime(2018,1,1), EndDate = new DateTime(2018,1,30)},
new Stack{ Key = "C", StartDate = new DateTime(2018,1,1), EndDate = new DateTime(2018,1,30)}
};
bool allUnique = Unavailability.Select(_ => new { _.StartDate, _.EndDate }).Distinct().Count() <= 0;
What I did here was project the Stack list using the Select to a anonymous type with the objects in it that you want to compare.
Now we can use the Distinct operator to determin all the distinct values.
If the result is less than or equal to 0 that means all the values are unique and if it is something else that means multiple unique values were found.

Categories