I get a question and have no idea about it ....
I have a list here called list<Promotion>
The structure of Promotion is like this:
new promotion()
{
string id,
money amount,
date startDate,
date endDate
}
The many elements of this list is like this,
list[0] = {id =1, amount = 2, startDate = 2015-10-14, endDate= 2015-12-31}
list[1] = {id =1, amount = 3, startDate = 2015-11-01, endDate= 2015-11-15}
list[2] = {id =3, amount = 10, startDate = 2015-11-01, endDate= 2015-12-01}
list[3] = {id =5, amount = 32, startDate = 2015-11-01, endDate= 2015-12-01}
So I want to merge list[0] and list[1] together but also keep list[2] and list[3], the result will be something like this
list[0] = {id =1, amount = 5, startDate = 2015-10-14, endDate= 2015-11-15}
list[1] = {id =3, amount = 10, startDate = 2015-11-01, endDate= 2015-12-01}
list[2] = {id =5, amount = 32, startDate = 2015-11-01, endDate= 2015-12-01}
Because list[0] and list[1] has the same id, they are merged. I want to add amount together, and keep the lower date time for both startDate and endDate. After merging list[0] and list[1] become list[0]. And list[2] and list[3] became list[1] and list[2]
Any idea? Thank you!
It seems like you are looking for a simple group by query like this:
var promotions = new List<Promotion>();
promotions.Add(new Promotion { Id = 1, Amount = 2, StartDate = new DateTime(2015, 10, 14), EndDate = new DateTime(2015, 12, 31) });
promotions.Add(new Promotion { Id = 1, Amount = 3, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 11, 15) });
promotions.Add(new Promotion { Id = 3, Amount = 10, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 12, 01) });
promotions.Add(new Promotion { Id = 5, Amount = 32, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 12, 01) });
promotions = promotions.GroupBy(p => p.Id).Select(p => new Promotion
{
Id = p.Key,
Amount = p.Sum(i => i.Amount),
StartDate = p.Min(i => i.StartDate),
EndDate = p.Min(i => i.EndDate)
}).ToList();
First group them by the id. Then for each Group, select a new element according to your rules:
IEnumerable<promotion> merged = list.GroupBy(p => p.id)
.Select(g => new Promotion
{
id = g.Key,
amount = g.Sum(p => p.amount),
startDate = g.Min(p => p.startDate),
endDate = g.Min(p => p.endDate)
};
(Note that lower-case property and class names is not the usual convention in .Net. It's technically fine, but not the style generally used).
Related
I have a table called campaign which has a foreign key from a table called Snapshot.
I want to bring the records from the table Campaigns and for each foreign key (SnapShotID) to distinct them by the column CampaignName. So from the Foreign Key SnapShotID I want to use the DatasetID and then distinct by the CampaignName.
If I use distinctBy instead of distinct, if a campaignName belongs to different foreign Keys it will distinct them in all cases. However I want to distinct the value of the campaignName that corresponds to the same DatasetID
Concluding, as an outcome I am trying to: I have a campaignName called Upstream which belongs to foreign key (SnapshotID) 1,2 and 5. SnapshotID 1 and 2 corresponds to Planning and 5 corresponds to Production. So, I want to bring all records and filter the campaign name for each DataSetID. So Upstream should come twice. One that is connected to Production and one that is connected to Planning. However Upstream in Planning it will be distincted as it exists twice.
Snapshot Table
Campaign Table
As sample: I tried
var campaigns = db.Campaigns.Include(c => c.Snapshot)
.OrderBy(i => i.Snapshot.DatasetID)
.ThenBy(i => i.CampaignName.Distinct());
The above one throws me an exception => DbDistinctExpression requires a collection argument.
Parameter name: argument
var campaigns = db.Campaigns.Include(c => c.Snapshot)
.GroupBy(i => i.Snapshot.DatasetID)
.Select(i => i.CampaignName.Distinct());
The above does not compile
So, I tried many combinations but also did not work.
If it is possible i would like help so that the query to be written mainly in lambda and then the same query in LINQ
I simulated your database with classes to show correct syntax. You have to check that creation date is between start and end dates.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication11
{
class Program
{
const string FILENAME = #"c:\temp\test.txt";
static void Main(string[] args)
{
DataBase db = new DataBase()
{
Snapshot = new List<Snapshot>() {
new Snapshot() { Id = 1, CreationDate = new DateTime(2019, 10, 1, 8,0,0), DatasetID = "Planning"},
new Snapshot() { Id = 2, CreationDate = new DateTime(2019, 10, 2, 8,0,0), DatasetID = "Planning"},
new Snapshot() { Id = 3, CreationDate = new DateTime(2019, 10, 15, 13,0,0), DatasetID = "Production"},
new Snapshot() { Id = 4, CreationDate = new DateTime(2019, 10, 16, 14,0,0), DatasetID = "Production"},
new Snapshot() { Id = 5, CreationDate = new DateTime(2019, 10, 16, 17,0,0), DatasetID = "Production"}
},
Campaign = new List<Campaign>() {
new Campaign() { Id = 1, CampaignName = "Upstream", StartDate = new DateTime(2019, 11, 1, 8,0,0), EndDate = new DateTime(2019, 11, 2, 17,0,0), SnapshotID = 1},
new Campaign() { Id = 2, CampaignName = "Downstream", StartDate = new DateTime(2019, 11, 3, 8,0,0), EndDate = new DateTime(2019, 11, 6, 15,0,0), SnapshotID = 2},
new Campaign() { Id = 3, CampaignName = "Upstream", StartDate = new DateTime(2019, 11, 1, 10,0,0), EndDate = new DateTime(2019, 11, 2, 18,0,0), SnapshotID = 2},
new Campaign() { Id = 4, CampaignName = "BufferPrep", StartDate = new DateTime(2019, 12, 1, 6,0,0), EndDate = new DateTime(2019, 12, 5, 15,0,0), SnapshotID = 3},
new Campaign() { Id = 5, CampaignName = "Product1", StartDate = new DateTime(2019, 12, 6, 8,0,0), EndDate = new DateTime(2019, 12, 7, 19,0,0), SnapshotID = 4},
new Campaign() { Id = 6, CampaignName = "Product2", StartDate = new DateTime(2019, 12, 8, 8,0,0), EndDate = new DateTime(2019, 12, 9, 20,0,0), SnapshotID = 5},
new Campaign() { Id = 7, CampaignName = "BufferPrep", StartDate = new DateTime(2019, 12, 1, 12,0,0), EndDate = new DateTime(2019, 12, 6, 10,0,0), SnapshotID = 5},
new Campaign() { Id = 9, CampaignName = "Upstream", StartDate = new DateTime(2019, 11, 5, 0,0,0), EndDate = new DateTime(2019, 11, 9, 0,0,0), SnapshotID = 5}
}
};
var groups = (from s in db.Snapshot
join c in db.Campaign on s.Id equals c.SnapshotID
select new { snapshot = s, campaign = c }
)
.GroupBy(x => x.snapshot.Id)
.ToList();
var results = groups.Select(x => new
{
snapshot = x.First().snapshot,
campaign = x.GroupBy(y => y.campaign.CampaignName).Select(y => y.First().campaign).ToList()
}).ToList();
}
}
public class DataBase
{
public List<Snapshot> Snapshot { get; set; }
public List<Campaign> Campaign { get; set; }
}
public class Snapshot
{
public int Id { get; set; }
public DateTime CreationDate { get; set; }
public string DatasetID { get; set; }
}
public class Campaign
{
public int Id { get; set; }
public string CampaignName { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int SnapshotID { get; set; }
}
}
Initial Result without distinct
I configured the query: var campaigns = db.Campaigns.Include(c => c.Snapshot) .GroupBy(i => i.Snapshot.DatasetID).AsEnumerable() .SelectMany(i =>i.DistinctBy(z=>z.CampaignName)); which does the distinct that i want but this time does not display me the value of the DataSetID
New Result
This time, how can i display the DataSetID value ?
This is the View .cshtml
I am struggling to do a group by. I have the following list:
Date = 10/03 06:40 AM, Val = 10
Date = 10/03 08:55 PM, Val = 5
Date = 11/03 06:40 AM, Val = 5
Date = 11/03 10:50 AM, Val = 9
Date = 11/03 06:40 PM, Val = 14
And I want this list:
Date = 10/03, Val = 5
Date = 11/03, Val = 14
So a list grouped by Date.Date but with Val depending on Max(d => d.Date).
I did it with a foreach but I am pretty sure that we can do something better using LINQ (groupby,select). Any ideas?
Cheers
I believe you want something like
static T MaxBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
where TKey: IComparable<TKey> =>
source.OrderByDescending(selector).First();
Then
from item in items
group item by item.Date.Date into byDate
select new
{
Date = byDate.Key,
byDate.MaxBy(item => item.Date).Val
}
If you do not wish to create a dedicated method you can also do it inline of course
from item in items
group item by item.Date.Date into byDate
let ordered = from item in byDate
orderby item.Date descending
select item.Val
select new
{
Date = byDate.Key,
Val = ordered.First()
}
Note: Thanks to mjwills who pointed out a bug which I was then able to correct
It should be something like this:
var result = from d in list
group d by d.Date.Date into grouped
let max = grouped.Max(d => d.Date)
select new {
Date = grouped.Key,
list.FirstOrDefault(c => c.Date == max)?.Val
};
Something along the line of:
values
.GroupBy(value => value.Date.Date)
.Select(grouped => new
{
Date = grouped.Key,
Val = values.First(value => value.Date == grouped.Date.Max()).Val
});
Here's what i have end up with :
Example entity :
public class ExampleEntity
{
public DateTime Date { get; set; }
public int Val { get; set; }
}
Example code :
var listOfExampleEntity = new List<ExampleEntity>()
{
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 4, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 5, 40, 00),
Val =5
},new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 7, 40, 00),
Val =3
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 8, 40, 00),
Val =4
},
};
var temp = listOfExampleEntity.GroupBy(
p => p.Date.Date,
p => p.Val,
(date, val) => new { Date = date, Value = val.Last() });
I have LINQ sql (see below, thanks to Cameron ). I am trying to get a property (ItemCode) from class First without using that in Group by clause.
How do I do that?
Don't use First.ItemCode in group by but still want it in output by First.Begin, First.End order by decending.
public class First
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
public string ItemCode;
}
public class Second
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
}
List<First> firstAccount = new List<First>();
List<Second> secondAccount = new List<Second>();
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 7),
Amount = 1000,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 0,
ItemCode = ""
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 7, 7),
End = new DateTime(2014, 7, 14),
Amount = 1000,
ItemCode = "BBB"
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 2000
});
var result = from account in (from first in firstAccount
join second in secondAccount
on first.Account equals second.Account
where
((first.Begin >= second.Begin && first.Begin <= second.Begin) &&
(first.End >= second.Begin && first.End <= second.End))
select new
{
first.Account,
second.Begin,
second.End,
first.Amount,
first.ItemCode
})
group account by new {account.Account, account.Begin, account.End }
into groupedAccounts
select new
{
groupedAccounts.Key.Account,
groupedAccounts.Key.Begin,
groupedAccounts.Key.End,
Sum = groupedAccounts.Sum(a => a.Amount)
};
One way to get the itemcode is to change the last select.
Add this line
Itemcode = String.Join(" ",groupedAccounts.Select(q=> q.ItemCode))
after Sum = groupedAccounts.Sum(a => a.Amount),
It should produce itemcode
foreach (var data in result)
{
Console.WriteLine(data.Account + " " + data.Itemcode);
}
Output
1234 AAA
1234 AAA
Say I have the below set of data where I wish to group the Description column based on whether the dates for matching Descriptions are within a few seconds of each other.
The output I'm looking for is the Description and the minimum Date for that group. It is possible that the Description could be the same but the dates might be days different, in this case I would want two outputted rows.
In the case above take a look at the Description "TEST s" where I would want two outputted grouped rows
TEST s 2014-12-04 16:27:44.903
TEST s 2014-12-04 17:21:21.233
Is this possible using Linq?
Try this:
var q = from item in lstMyTable
group item by item.ItemDate.ToString("MM/dd/yyyy HH:mm") into ItemGroup
select new
{
Description = ItemGroup.First().Description,
Date = ItemGroup.OrderBy(x => x.ItemDate).First().ItemDate
};
The above linq query groups by date + hour + minutes, ignoring seconds. The OrderBy applied to ItemGroup ensures that we get the mininum date, as described in the OP.
With this input:
List<MyTable> lstMyTable = new List<MyTable>()
{
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 16, 27, 11) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 16, 27, 12) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 16, 27, 13) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 17, 21, 11) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 17, 21, 12) },
new MyTable() { Description = "TEST s", ItemDate = new DateTime(2014, 12, 4, 17, 21, 13) }
};
you get this result:
[0] = { Description = "TEST s", Date = {4/12/2014 4:27:11 pm} }
[1] = { Description = "TEST s", Date = {4/12/2014 5:21:11 pm} }
If you want to also group by description then simply substitue the group by clause with sth like this:
group item by new
{
a = item.ItemDate.ToString("MM/dd/yyyy HH:mm") ,
b = item.Description
} into ItemGroup
Finally, by converting the Datetime field to Ticks, you can fiddle with the required time distance between separate records, e.g.
group item by new
{
a = item.ItemDate.Ticks.ToString().Substring(0, item.ItemDate.Ticks.ToString().Length - 10), // instead of .ToString("MM/dd/yyyy HH:mm") ,
b = item.Description
} into ItemGroup
You can play around by varying the number of digits subtracted from item.ItemDate.Ticks, substituting e.g. 10 with 9 or 8, etc.
If you want left hand side to be a list then you can try this
var query = (from t in db.Table
group t by new {t.Description, t.Date }
into grp
select new
{
grp.Key.Description,
grp.Key.Date,
}).ToList();
I have 2 lists in C#:
public class AvailableSlot
{
public DateTime DateTime;
public string Name
}
List<AvailableSlot> list1 = GetList();
List<AvailableSlot> list2 = GetAnotherList();
I want to call intersect on these lists to find out where there are items in both lists for the same date. I know i can use .Intersect to get this info but I have a slightly more complicated requirement. I want to return a intersected list but i want to this list to contain a list of objects with all of the name in them. so something like this:
List<AvailableSlot2> intersectedList . ..
where AvailableSlot2 is this below:
public class AvailableSlot2
{
public DateTime DateTime;
public string[] Names;
}
Is there any ability to do this transformation after trying to intersect between two lists?
I would just union the two lists, group by DateTime and then pull out the names from the group:
var list1 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Alpha" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bravo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Charlie" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Delta" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Echo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Foxtrot" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 4), Name = "Golf" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hotel" }
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Apple" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bannana" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Dog" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Egg" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hi" }
};
var list3 = list1.Where (l => list2.Where (li => l.DateTime == li.DateTime).Any ())
.Union(list2.Where (l => list1.Where (li => l.DateTime == li.DateTime).Any ()));
var groupedItems = from slot in list3
group slot by slot.DateTime into grp
select new AvailableSlot2 {
DateTime = grp.Key,
Names = grp.Select (g => g.Name).ToArray()
};
foreach(var g in groupedItems)
{
Console.WriteLine(g.DateTime);
foreach(var name in g.Names)
Console.WriteLine(name);
Console.WriteLine("---------------------");
}
Output:
2/1/2013 12:00:00 AM
Alpha
Delta
Apple
Dog
---------------------
2/2/2013 12:00:00 AM
Bravo
Echo
Bannana
Egg
---------------------
2/5/2013 12:00:00 AM
Hotel
Hi
---------------------
You can use a LINQ to Objects Join() to line up items with the same DateTime property and then collect all the names into an array
var joinedItems = from slot1 in list1
join slot2 in list2
on slot1.DateTime equals slot2.DateTime into g
where g.Any()
select new AvailableSlot2
{
DateTime = slot1.DateTime,
Names = Enumerable.Range(slot1.Name,1).Union(g.Select(s => s.Name)).ToArray()
}
You can make use of ToLookup:
DateTime dt1 = new DateTime(2013, 2, 1);
DateTime dt2 = new DateTime(2013, 3, 1);
DateTime dt3 = new DateTime(2013, 4, 1);
var list1 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt1, Name = "n3",},
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt3, Name = "n3",},
};
var intersected = list1.Select (l => l.DateTime).
Intersect(list2.Select (l => l.DateTime));
var lookup = list1.Union(list2).ToLookup (
slot => slot.DateTime, slot => slot);
lookup.Where (l => intersected.Contains(l.Key)).Select (
slot => new
{
DateTime=slot.Key,
Names=slot.Select (s => s.Name)
});
Which in this case gives the result:
DateTime Names
01/02/2013 00:00 n1
n3
n1
01/03/2013 00:00 n2
n2
You could of course use Names=slot.Select(s => s.Name).Distinct() to get a distinct list of names.