Join and subtract values from 2 lists using linq - c#

I have 2 lists that have objects of { DT (date), Value (double) }.
I want to join on date and subtract the 2 values. However, sometimes one list won't have any records for a given DT in which case I'd want to just use the value from the list that does. However, because I'm joining what ends up happening is I get no record at all for that DT. Is there any way to represent this using sql like linq?
I know I could loop over 1 list myself and search for that date in the other, but if I could do it all in 1 linq line it just seems cleaner.

I believe this is what you can do:
var result = (from x in list1 select new Item() { date = x.date, value = x.value - (from y in list2 where x.date.Equals(y.date) select y.value).FirstOrDefault() }).ToList();
Feel free to run the test ConsoleApp I wrote:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverFlowConsoleApplication
{
class Program
{
static void Main(string[] args)
{
List<Item> list1 = new List<Item>()
{
new Item(){date = DateTime.Today, value=100},
new Item(){date = DateTime.Today.AddDays(-1), value=100}
};
List<Item> list2 = new List<Item>()
{
new Item(){date = DateTime.Today, value=50}
};
var result = (from x in list1 select new Item() { date = x.date, value = x.value - (from y in list2 where x.date.Equals(y.date) select y.value).FirstOrDefault() }).ToList();
}
class Item
{
public DateTime date { get; set; }
public double value { get; set; }
}
}
}

Say your class is named Blub and looks something like this:
public class Blub
{
public DateTime DT { get; set; }
public double Value { get; set; }
}
And you have two lists of it:
var list1 = new List<Blub>();
var list2 = new List<Blub>();
Then you can find the difference for each date using this LINQ query:
var differences = from x1 in list1
join x2 in list2 on x1.DT equals x2.DT into temp
from x2 in temp.DefaultIfEmpty()
select new Blub
{
DT = x1.DT,
Value = x1.Value - (x2 != null ? x2.Value : 0.0)
};
The DefaultIfEmpty() method turns the join into an outer join, ensuring you get a join pair of (x1, null) if there is no matching x2 for any given DT.
PS: Surely a matter of personal taste, but I don't think that this isn't readable..

Related

Join multiple lists of objects in c#

I have three lists that contain objects with following structure:
List1
- Status
- ValueA
List2
- Status
- ValueB
List3
- Status
- ValueC
I want to joint the lists by status to get a final list that contains object with following structure:
- Status
- ValueA
- ValueB
- ValueC
Not every list has all the status. So a simple (left) join won't do it. Any ideas how to achieve the desired result? I tried with
var result = from first in list1
join second in list2 on first.Status equals second.Status into tmp1
from second in tmp1.DefaultIfEmpty()
join third in list3 on first.Status equals third.Status into tmp2
from third in tmp2.DefaultIfEmpty()
select new { ... };
But result is missing a status. Here is a full MRE:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<A> first = new List<A>() { new A("FOO", 1), new A("BAR", 2) };
List<B> second = new List<B>() { new B("FOO", 6), new B("BAR", 3) };
List<C> third = new List<C>() { new C("BAZ", 5) };
var result = from f in first
join s in second on f.Status equals s.Status into tmp1
from s in tmp1.DefaultIfEmpty()
join t in third on f.Status equals t.Status into tmp2
from t in tmp2.DefaultIfEmpty()
select new
{
Status = f.Status,
ValueA = f.ValueA,
ValueB = s.ValueB,
ValueC = t.ValueC,
};
}
}
public record A(string Status, int ValueA);
public record B(string Status, int ValueB);
public record C(string Status, int ValueC);
Unfortunately it is unclear, what should happen, if a status occurs multiple times within one list, cause your aggregate can only hold one value per status.
One possibility to solve this issue would be:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<A> first = new List<A>() { new A("FOO", 1), new A("BAR", 2) };
List<B> second = new List<B>() { new B("FOO", 6), new B("BAR", 3) };
List<C> third = new List<C>() { new C("BAZ", 5) };
var allStates = first.Select(a => a.Status)
.Concat(second.Select(b => b.Status))
.Concat(third.Select(c => c.Status))
.Distinct();
var result = allStates
.Select(Status => new
{
Status,
ValueA = first.FirstOrDefault(a => a.Status == Status),
ValueB = second.FirstOrDefault(b => b.Status == Status),
ValueC = third.FirstOrDefault(c => c.Status == Status),
});
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
public record A(string Status, int ValueA);
public record B(string Status, int ValueB);
public record C(string Status, int ValueC);
Depending on the amount of items that have to be aggregated and the premise that each status occurs only once or never it could make sense to convert your lists to a Dictionary<string, A>, Dictionary<string, B>, etc. to improve the lookup and do something like this in the aggregate:
ValueA = dictFirst.ContainsKey(Status) ? dictFirst[Status] : null
For further improvements (this line makes the lookup twice) you could also factor out a method like this
private static T GetValueOrDefault<T>(IReadOnlyDictionary<string, T> dict, string status)
{
dict.TryGetValue(status, out T value);
return value;
}
And within the .Select() method call it with
ValueA = GetValueOrDefault(firstDict, Status);
Creating the dictionary for the list could be done with:
var firstDict = first.ToDictionary(a => a.Status);
With assumption that status names are unique per list here is a solution
in a single query with help of switch expressions (available since C# 8.0):
using System;
using System.Linq;
using System.Collections.Generic;
List<A> first = new List<A>() { new A("FOO", 1), new A("BAR", 2) };
List<B> second = new List<B>() { new B("FOO", 6), new B("BAR", 3) };
List<C> third = new List<C>() { new C("BAZ", 5) };
var result = first
// concat lists together
.Cast<object>()
.Concat(second)
.Concat(third)
// group on Status value with help of switch expression
.GroupBy(el => el switch {
A a => a.Status,
B b => b.Status,
C c => c.Status,
},
// project groups with anonymous type
(Status, group) => new {
Status,
ValueA = group.OfType<A>().Select(a => a.ValueA).Cast<int?>().FirstOrDefault(),
ValueB = group.OfType<B>().Select(b => b.ValueB).Cast<int?>().FirstOrDefault(),
ValueC = group.OfType<C>().Select(c => c.ValueC).Cast<int?>().FirstOrDefault()
});
public record A(string Status, int ValueA);
public record B(string Status, int ValueB);
public record C(string Status, int ValueC);
This can't using left join.First you must get all keies,then using all keies left join other lists:
var keys = first.Select(item => item.Status).ToList();
keys.AddRange(second.Select(item => item.Status));
keys.AddRange(third.Select(item => item.Status));
keys = keys.Distinct().ToList();
var result = (from k in keys JOIN
f in first on k equals f.Status into tmp0
from f in tmp0.DefaultIfEmpty()
join s in second on k equals s.Status into tmp1
from s in tmp1.DefaultIfEmpty()
join t in third on k equals t.Status into tmp2
from t in tmp2.DefaultIfEmpty()
select new {
Status = k,
ValueA = f?.ValueA,
ValueB = s?.ValueB,
ValueC = t?.ValueC,
}
).ToList();

Order two different list of objects by date

I have 2 Lists each of different objects. Each list contains a date element. What I am trying to do is pull items from each list in sequence and do something.
Object1
{
string description
date updateDate
int value
}
Object2
{
string description
date updateDate
string descritpion2
}
IE
List<object1>
object1.date = 10/1/2017
object1.date = 9/3/2017
List<object2>
object2.date = 10/15/2017
object2.date = 9/1/2017
I want to process these in order so i would do List 2 object 9/1, List 1 object 9/2, List 1 object 9/3, List 2 object 10/5
How can one achieve this?
How about this?
var list1 = new List<Object1>();
var list2 = new List<Object2>();
var newOrderedByDateCollection = list1
.Select(i => new TempClass(i, i.updateDate))
.Concat(list2
.Select(j => new TempClass(j, j.updateDate)))
.OrderBy(tmp => tmp.Date)
.Select(tmp => tmp.OrigItem);
//This could be replaced by a tuple like Tuple<object, DateTime> but I thought this would come across clearer
public class TempClass
{
public TempClass(object origItem, DateTime date)
{
OrigItem = origItem;
Date = date;
}
public object OrigItem { get; set; }
public DateTime Date { get; set; }
}
You now have a ordered list of type object. Which I can't see a way of getting around, So as you iterate through that list, you'll need to cast each object appropriately back by doing a switch and some pattern matching
Edit: for comepleteness here is the tuple version (I think its probably the best way to do it)
var newOrderedByDateCollection = list1
.Select(i => new Tuple<object,DateTime>(i, i.updateDate))
.Concat(list2
.Select(j => new Tuple<object, DateTime>(j, j.updateDate)))
.OrderBy(tmp => tmp.Item2)
.Select(tmp => tmp.Item1);
If you want to keep type safety (avoid object) and don't mind sorting the lists to new lists, you can do a loop with both indexes:
var l1count = l1.Count;
var l2count = l2.Count;
var ocount = l1count + l2count;
var l1o = l1.OrderBy(o => o.updateDate).ToList();
var l2o = l2.OrderBy(o => o.updateDate).ToList();
for (int j1 = 0, j2 = 0; j1 + j2 < ocount;) {
if (j1 < l1count && (l1o[j1].updateDate <= l2o[j2].updateDate || j2 >= l2count)) {
// process l1o[j1]
++j1;
}
else {
// process l2o[j2]
++j2;
}
}

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.

Filter List From List Using Linq

I have retrieved list of my specific class with 150 records.
Now, i want only those records which have Licenseid which are in my another int List.
For example My MainList
List<CustomerDocument> _list = GetListByID(CustomerID);
In this list i have column LicenseID,CustomerID,CustomerName,Type,Age e.t.c
And SecontList
List<int> type = new List<int>();
In Int list i add LicenseID one by one dynamically.
Public class CustomerDocument
{
public int LicenseID{get;set;};
public int CustomerID{get;set;};
public string CustomerName{get;set;};
public int Age{get;set;};
}
This is my CustomerDocument class for which i am getting list.
And now suppose, If Int list has three records , then i want those records from my Main List which have these three LicenseID in my Int List using Linq.
_list = ???
List<CustomerDocument> list = new List<CustomerDocument>();
List<Int> types = new List<Int>();
MapSearchSalesEntities datacontext = new MapSearchSalesEntities();
var collection = ddlLicenseType.CheckedItems;
if (collection.Count > 0)
{
foreach (var item in collection)
{
int value = Convert.ToInt32(item.Value);
types .Add(value);
}
}
var query = (from t1 in datacontext.Licenses
select new CustomerDocument
{
LicenseID = t1.LicenseID,
CustomerID = t1.CustomerID,
CustomerName= t1.CustomerName,
Age= t1.Age,
});
list = query.ToList(); ---gives 150 Records
if (types != null && types.Count > 0)
{
list = list.Where(c => types.Contains(c.LicenseID)).ToList(); --- Gives 0 Records
}
The most efficient approach is to use Enumerable.Join:
var documents = from doc in _list
join licenseID in type
on doc.LicenseID equals licenseID
select doc;
if you want to replace the list:
_list = documents.ToList();
You could also use Enumerable.Where + List.Contains which is not as efficient but shorter:
_list = _list.Where(d => type.Contains(d.LicenseID)).ToList();
Using the LinQ Where method, this is very easy:
_list = _list.Where(c => type.Contains(c.LicenseID)).ToList();
Here is a linq query
var result = (from cust in _list join id in type on cust.LicenseID equals id select cust).ToArray();

Counting instances of a date

I'm sure there is an easy answer to this, but I can't seem to phrase the search to get the right results.
In my controller, lets say I have a list of instances of class x, which in turn has a member variable of class y, which contains a date variable. What I am trying to do is count how many instances of each date there are, to build a graph. So all I want out of this is an array with 1 row for each different date, and a count of the number of times that date occurred.
Any suggestions on the best way to do this would be appreciated.
It sounds like you want something like this.
var countByDate = items.GroupBy(x => x.Invoice.ReceivedDate) // Or whatever
.Select(g => new { Date = g.Key, Count = g.Count() })
.OrderBy(pair => pair.Date);
.ToArray();
LINQ rocks :)
You can use Linq's Enumerable.GroupBy:
var dayGroups = listX.GroupBy(x => x.Y.DateTimeVar.Date)
.Select(g => new { Day = g.Key, Count = g.Count() })
.ToArray();
Now you have all you need, the date and the occurence:
foreach(var dayGroup in dayGroups)
{
Console.WriteLine("Day: {0} Count: {1}", dayGroup.Day.ToString(), dayGroup.Count);
}
Assuming DateTimeVar is the property and you want to group by the day.
Using extensions methods should help. This is the best I can do without knowing the types you have:
ListOfx.Select(x => x.ClassY.Date)
The previous line will give you all dates. Then you can use GroupBy to group them by their value. This should give you a list of lists.
ListOfx.Select(x => x.ClassY.Date).GroupBy(x => x.Date)
I think this should work. I cannot try the code at the moment.
The code might be clunky but this should work for you:
using System;
using System.Collections.Generic;
using System.Linq;
namespace App
{
class Program
{
static void Main(string[] args)
{
List<X> xs = new List<X>
{
new X { Y = new Y {D = DateTime.Now}},
new X { Y = new Y {D = DateTime.Now}},
new X { Y = new Y {D = DateTime.Now}},
};
IEnumerable<DateTime> ds = xs.Select(x => x.Y.D).Distinct();
var q = from d in ds
select new
{
D = d,
Count = xs.Count(x => x.Y.D.Equals(d))
};
foreach (var i in q)
{
Console.WriteLine(i);
}
}
class X
{
public Y Y { get; set; }
}
class Y
{
public DateTime D { get; set; }
}
}
}

Categories