Get dates that contain all selected products - c#

I have a calendar app where you select various combinations of products- a service goes out and gets the available dates based on the calendar date range. A date is only "Available" if ALL selected products are available on a particular date.
class SelectedProduct
{
public int ID { get; set; }
public int Qty { get; set; }
}
class AvailableInventory
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
// List of selected products from user
List<SelectedProduct> SelectedProducts;
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory;
I want to be able to say get list of Available Inventory for each date that contains inventory for all ID's in selected products.
This is (non-working) pusdo code of a possible solution, I just don't know linq well enough to get it right
var results = List<AvailableInventory>();
foreach (var group in AvailableInventory.GroupBy(x => x.Date))
{
if (group.Contains(ALL ID's in SelectedProducts)
{
results.AddRange(group);
}
}

This groups inventory by date (ignoring the date portion), then selects only those groups that contain all selected product IDs, and finally selects all available inventory for the matching groups.
var results =
AvailableInventory.GroupBy(i => i.Date.Date)
.Where(g => !SelectedProducts.Select(p => p.ID)
.Except(g.Select(i => i.ID))
.Any())
.SelectMany(g => g);
The result is a collection of AvailableInventory.

You can group by the date, then filter out groups that don't have all the SelectedProducts.
// List of selected products from user
List<SelectedProduct> SelectedProducts = new List<SelectedProduct> {
new SelectedProduct { ID = 1, Qty = 1 },
new SelectedProduct { ID = 2, Qty = 2 },
};
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory = new List<AvailableInventory> {
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 11) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 11) },
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 12) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 13) },
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 14) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 14) },
};
var query = AvailableInventory.GroupBy(i => i.Date)
.Where(g => SelectedProducts.All(s => g.Any(i => i.ID == s.ID)));
foreach(var group in query)
{
Console.WriteLine("Date: {0}", group.Key);
foreach(var inventory in group)
{
Console.WriteLine(" Available: {0}", inventory.ID);
}
}
This would output:
Date: 4/11/2014 12:00:00 AM
Available: 1
Available: 2
Date: 4/14/2014 12:00:00 AM
Available: 1
Available: 2

I think this is what you are looking for. Try this
var result = AvailableInventory.Where(i => SelectedProducts.Any(x => x.ID == i.ID)).GroupBy(o => o.Date)
.Select(g => g.First()).ToList();
This is the test data I used based on your class definition for AvailableInventory and SelectedProduct
// List of selected products from user
List<SelectedProduct> SelectedProducts = new List<SelectedProduct> {
new SelectedProduct { ID = 1, Qty = 2 },
new SelectedProduct { ID = 2, Qty = 4 },
new SelectedProduct { ID = 5, Qty = 10 }
};
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory = new List<AvailableInventory> {
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 01) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 02) },
new AvailableInventory { ID = 3, Date = new DateTime(2014, 04, 02) },
new AvailableInventory { ID = 4, Date = new DateTime(2014, 04, 10) },
new AvailableInventory { ID = 5, Date = new DateTime(2014, 04, 10) }
};
This should give you only the records with ID = 1, ID = 2 and ID = 5 because that's what common between both AvailableInventory and SelectedProducts lists.

It would help if you actually tried something.
Given this:
List<SelectedProduct> SelectedProducts ;
List<AvailableInventory> AvailableInventory ;
Something like this will probably get what you want:
int[] DatesWithAllSelectedProductsAvailable =
AvailableInventory
.GroupBy( x => x.Date )
.Where ( g => g.All( x => SelectedProducts.Any( p => p.ID == x.ID ) ) )
.Select( x => x.Key )
.Distinct()
.OrderBy( x => x )
.ToArray()
;

Related

Count occurrences of an event by date with C#

I am facing an issue with counting the number of occurrences by date in C#. Should I use Linq to filter it? Please advise. Thank you.
Date
Player ID
1/1/2001
23
1/1/2001
29
1/1/2001
24
3/1/2001
22
3/1/2001
23
My preferred output should be
Date
No. Of Players
1/1/2001
3
2/1/2001
0
3/1/2001
2
This is my current code, how can I do it within the select:
var convertTable = dataPageTable.AsEnumerable();
Records = new List<List<ContentOutputModel>>(convertTable.Select(dr =>
{
var playerId = dr.GetColumn<long>("PlayerID").ToString();
var dateInt = dr.GetColumn<int>("Date").ToString();
var dateStr = dateInt.Substring(6, 2) + "/" + dateInt.Substring(4, 2) + "/" + dateInt.Substring(0, 4);
var output = new List<ContentOutputModel>(new ContentOutputModel[] {
new ContentOutputModel() { Text = dateStr },
new ContentOutputModel() { Text = playerId },
});
return output;
}));
Here's the cleanest that I could come up with:
List<Player> players = new List<Player>()
{
new Player() { Date = new DateTime(2021, 1, 1), ID = 23 },
new Player() { Date = new DateTime(2021, 1, 1), ID = 29 },
new Player() { Date = new DateTime(2021, 1, 1), ID = 24 },
new Player() { Date = new DateTime(2021, 1, 3), ID = 22 },
new Player() { Date = new DateTime(2021, 1, 3), ID = 23 }
};
var first = players.Min(p => p.Date);
var last = players.Max(p => p.Date);
var days = last.Subtract(first).Days + 1;
var lookup = players.ToLookup(p => p.Date);
var output =
from n in Enumerable.Range(0, days)
let Date = first.AddDays(n)
select new
{
Date,
Count = lookup[Date].Count(),
};
That gives me:
You can achieve by Group() via System.Linq.
Order players by Date and get startDate and endDate.
Generate an array with dates from startDate to endDate.
3.1 With group to count player(s) by Date.
3.2 Left join result from (2) with the result (3.1) to get Date and Count.
using System.Linq;
using System.Collections.Generic;
List<Player> players = new List<Player>
{
new Player{Date = new DateTime(2021, 1, 1), ID = 23},
new Player{Date = new DateTime(2021, 1, 1), ID = 29},
new Player{Date = new DateTime(2021, 1, 1), ID = 24},
new Player{Date = new DateTime(2021, 1, 3), ID = 22},
new Player{Date = new DateTime(2021, 1, 3), ID = 23}
};
var startDate = players.OrderBy(x => x.Date)
.First()
.Date;
var endDate = players.OrderBy(x => x.Date)
.Last()
.Date;
var dates = Enumerable.Range(0, 1 + endDate.Subtract(startDate).Days)
.Select(offset => startDate.AddDays(offset))
.ToArray();
var result = (from a in dates
join b in
(
from p in players
group p by p.Date into g
select new { Date = g.Key, Count = g.Count() }
) on a.Date equals b.Date into ab
from b in ab.DefaultIfEmpty()
select new { Date = a.Date, Count = b != null ? b.Count : 0 }
);
Sample program
Output
Date: 1/1/2021, Count: 3
Date: 2/1/2021, Count: 0
Date: 3/1/2021, Count: 2
You can use linq to do this as long as it can enumerate through a list or some other IEnumerable. Try this:
var playersSorted = yourlist.GroupBy(x => x.Date)
.Where(g => g.Any())
.Select(y => new {Date = y.Key, Count = y.Count()}).ToList();
var playersgroup = from e in players
group e by Date into g
select new { Date= g.Key, NoOfPlayers = g.Count() };

need to construct a clean Linq query for Graph Data

i am trying to populate a graph of balances over the last 2 years. For example its 2020 i want to return a structure like
if it would have been 2021 today i need to returns data of 2019 and 2020.
my class structure looks like this
public class Transaction : BaseEntity
{
public TransactionType TransactionType { get; set; }
public DateTime TransactionDate { get; set; }
public double TransactionAmount { get; set; }
}
public enum TransactionType{
Deposit = 0,
Withdraw = 1
}
i populated this structure and thought it will be simple. i have
var transactions = new ICollection<Transaction>
here is an example seed data of January of 2018
modelBuilder.Entity<Transaction>(b =>
{
b.HasData(new
{
Id = Guid.NewGuid().ToString(),
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
TransactionAmount = 3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 20),
TransactionType = TransactionType.Deposit
});
b.HasData(new
{
Id = Guid.NewGuid().ToString(),
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
TransactionAmount = -3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 21),
TransactionType = TransactionType.Withdraw
});
b.HasData(new
{
Id = Guid.NewGuid().ToString(),
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
TransactionAmount = 1000.23,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 25),
TransactionType = TransactionType.Deposit
});
As you can see in January of 2018 3334.38 was added and The same amount was subtracted and 1000.23 was added so i should get 2018 under that January 1000.23
var transactions = await _unitOfWork.TransactionRepositoy.GetAllAsync();
transactions.GroupBy(x => x.TransactionDate.Year);
var data = transactions.Select(k => new { k.TransactionDate.Year, k.TransactionDate.Month, k.TransactionAmount }).GroupBy(x => new { x.Year, x.Month }, (key, group) => new
{
yr = key.Year,
mnth = key.Month,
tBalance = group.Sum(k => k.TransactionAmount)
}).ToList();
but in january of 2018 i am getting
I am trying to group by year and in those group i am trying to get Month and Total Balance.
i have a group on month and group.Sum(k => k.TransactionAmount) seems to be not working.
var transactions = new List<Transaction>()
{
new Transaction() {
TransactionAmount = 3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 20),
TransactionType = TransactionType.Deposit
},
new Transaction() {
TransactionAmount = -3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 21),
TransactionType = TransactionType.Withdraw
},
new Transaction() {
TransactionAmount = 1000.23,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 25),
TransactionType = TransactionType.Deposit
},
};
var data = from t in transactions
group t by new {t.TransactionDate.Year , t.TransactionDate.Month} into g
select new {
tBalance = g.Sum(x => x.TransactionAmount),
g.First().TransactionDate.Month,
g.First().TransactionDate.Year
};
// ----------------------
// result :
// tBalance 1000.23
// Month 1
// Year 2018

Join three list using multiple columns c# linq lambda

I have these lists:
var subjects = new List<SubjectModel>
{
new SubjectModel { subjId = 1, subjName = "Math" },
new SubjectModel { subjId = 2, subjName = "Science" },
new SubjectModel { subjId = 3, subjName = "History" },
new SubjectModel { subjId = 4, subjName = "Language" }
};
var quizzes = new List<QuizModel>
{
new QuizModel { quizId = 1, quizDate = DateTime.Parse("2016-11-25"), quizScore = 10, subjectId = 1 },
new QuizModel { quizId = 2, quizDate = DateTime.Parse("2016-11-25"), quizScore = 15, subjectId = 1 },
new QuizModel { quizId = 3, quizDate = DateTime.Parse("2016-11-25"), quizScore = 8, subjectId = 2 },
new QuizModel { quizId = 4, quizDate = DateTime.Parse("2016-11-26"), quizScore = 13, subjectId = 1 },
new QuizModel { quizId = 5, quizDate = DateTime.Parse("2016-11-26"), quizScore = 20, subjectId = 2 }
};
var exams = new List<ExamModel>
{
new ExamModel { examId = 1, examDate = DateTime.Parse("2016-11-25"), examScore = 90, subjectId = 1 },
new ExamModel { examId = 2, examDate = DateTime.Parse("2016-11-25"), examScore = 88, subjectId = 2 },
new ExamModel { examId = 3, examDate = DateTime.Parse("2016-11-25"), examScore = 92, subjectId = 4 },
new ExamModel { examId = , examDate = DateTime.Parse("2016-11-26"), examScore = 84, subjectId = 1 },
};
var exercises = new List<ExerciseModel>
{
new ExerciseModel { exerciseId = 1, exerciseDate = DateTime.Parse("2016-11-25"), exerciseScore = 17, subjectId = 1 },
new ExerciseModel { exerciseId = 2, exerciseDate = DateTime.Parse("2016-11-25"), exerciseScore = 15, subjectId = 2 },
new ExerciseModel { exerciseId = 3, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 15, subjectId = 1 },
new ExerciseModel { exerciseId = 4, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 12, subjectId = 4 },
new ExerciseModel { exerciseId = 5, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 10, subjectId = 1 },
};
I was able to successfully group each of them by date and by subject.
var allQuizzes = quizzes.GroupBy(qz => qz.quizDate, (q, values) =>
new
{
Date = q,
Quizzes = values.GroupBy(v => v.subjectId, (c, values2) =>
new {
SubjectId = c,
QuizSum = values2.Sum(v2 => v2.quizScore)
})
});
var allExercises = exercises.GroupBy(ex => ex.exerciseDate, (e, values) =>
new {
Date = e,
Exercises = values.GroupBy(x => x.subjectId, (z, values2) =>
new {
SubjectId = z,
ExerSum = values2.Sum(r => r.exerciseScore)
})
});
var allExams = exams.GroupBy(ex => ex.examDate, (e, values) =>
new
{
Date = e,
Exercises = values.GroupBy(x => x.subjectId, (z, values2) =>
new
{
SubjectId = z,
ExamSum = values2.Sum(r => r.examScore)
})
});
However, I need to join all three of them to get the sum of all scores. The final table should display like this.
-----------------------------------------------------------------
| Date | Math | Science | History | Language |
| 11/25/2016 | 132 | 111 | 0 | 92 |
| 11/26/2016 | 122 | 20 | 0 | 12 |
-----------------------------------------------------------------
I tried to join them, but it can't seem to join by multiple columns.
I select from all 3 collections results in form of the same anonymous class (the same Idea had Andrei in first answer), that allows me just to collect all results together in all list, without mapping and converting.
var allQuiz = quizzes.GroupBy(x => new { x.subjectId, x.quizDate })
.Select(x => new {
Date = x.Key.quizDate,
Subj = x.Key.subjectId,
Sum = x.Sum(r=>r.quizScore)});
var allExam= exams.GroupBy(x => new { x.subjectId, x.examDate })
.Select(x => new {
Date = x.Key.examDate,
Subj = x.Key.subjectId,
Sum = x.Sum(r=>r.examScore)});
var allExc = exercises.GroupBy(x => new { x.subjectId, x.exerciseDate })
.Select(x => new {
Date = x.Key.exerciseDate,
Subj = x.Key.subjectId,
Sum = x.Sum(r=>r.exerciseScore)});
Combining of all results together:
var all = allQuiz.ToList();
all.AddRange(allExam.ToList());
all.AddRange(allExc.ToList());
var result = all.GroupBy(x => new { x.Date, x.Subj })
.Select(x => new { x.Key.Date, x.Key.Subj, Sum = x.Sum(s => s.Sum)});
var list = result.GroupBy(r => r.Date).Select(x => new {
Date = x.Key,
Math = x.SingleOrDefault(t=>t.Subj==1)?.Sum ?? 0,
Science = x.SingleOrDefault(t=>t.Subj==2)?.Sum ?? 0,
History = x.SingleOrDefault(t=>t.Subj==3)?.Sum ?? 0,
Language = x.SingleOrDefault(t=>t.Subj==4)?.Sum ?? 0,
});
Output in LinqPad:
Here is an idea. Instead of keeping the distinction while grouping, you could convert all three to the same structure. For instance:
var allQuizzes = quizzes.GroupBy(qz => qz.quizDate, (q, values) =>
new
{
Date = q,
Results = values.GroupBy(v => v.subjectId, (c, values2) =>
new {
SubjectId = c,
Sum = values2.Sum(v2 => v2.quizScore)
})
});
Notice names "Results" and "Sum" - you can use the same for the other two objects. And now you have three collections, all of the same structure:
{
Date:
Results: [
{SubjectId, Sum}
{SubjectId, Sum}
...
]
}
Since they are all the same now, you can stop treating them differently, use UNION to merge all three, group them by date and within that by subject. Then you could probably iterate through subject list to get necessary info, depends on what you mean by "final table".
This is what i came up with.
It may not be best optimized, but might be enough for you.
I rendered the results into a StringBuilder in my test.
var result =
quizzes.Select(q => new {SubjectId = q.subjectId, Date = q.quizDate, Score = q.quizScore})
.Union(exams.Select(e => new {SubjectId = e.subjectId, Date = e.examDate, Score = e.examScore}))
.Union(exercises.Select(e => new {SubjectId = e.subjectId, Date = e.exerciseDate, Score = e.exerciseScore}))
.GroupBy(arg => arg.Date,
(key, values)=>
new
{
Key = key,
Scores = values.GroupBy(v => v.SubjectId, (s, values2) => new { SubjectId = s, SumScore = values2.Sum(v2 => v2.Score) })
});
StringBuilder sb = new StringBuilder("Date\t\t");
foreach (SubjectModel subject in subjects)
{
sb.Append($"{subject.subjName}\t");
}
sb.AppendLine();
foreach (var record in result)
{
sb.Append($"{record.Key.ToShortDateString()}\t");
foreach (SubjectModel subject in subjects)
{
int sum = record.Scores.Where(s => s.SubjectId == subject.subjId).Select(s => s.SumScore).DefaultIfEmpty(0).Single();
sb.Append($"{sum}\t");
}
sb.AppendLine();
}
string finalTable = sb.ToString();
Instead of using three different anonymous objects to hold the results, make your own class:
public enum TestType
{
Quiz,
Exam,
Exercise,
}
public class TestScore
{
public TestType Type { get; set; }
public DateTime Date { get; set; }
public int Score { get; set; }
public int SubjectId { get; set; }
// Constructors - make a TestScore object
public TestScore(QuizModel q)
{
Type = TestType.Quiz;
Date = q.quizDate;
Score = q.quizScore;
SubjectId = q.SubjectId;
}
public TestScore(ExamModel e)
{
Type = TestType.Exam;
Date = e.examDate;
Score = e.examScore;
SubjectId = e.SubjectId;
}
public TestScore(ExerciseModel e)
{
Type = TestType.Exercise;
Date = e.exerciseDate;
Score = e.exerciseScore;
SubjectId = e.SubjectId;
}
}
Convert to TestScore:
List<TestScore> scores = new List<TestScore>();
scores.AddRange(quizzes.Select(q => new TestScore(q));
scores.AddRange(exams.Select(e => new TestScore(e));
scores.AddRange(exercises.Select(e => new TestScore(e));
Now you have one datasource instead of three, displaying the results becomes easy.

LINQ group by select with collection

I have a datatable e.g.
ID Agent Date
1 A1 2016-02-19
2 A1 2016-02-20
3 A2 2016-02-19
4 A3 2016-02-20
i want to group these records by Date and return the ID and Agent is list like:
Date:2016-02-19 ,{(1,A1),(3,A2)}
Date:2016-02-20 ,{(2,A1),(4,A3)}
A collection object of ID and Agent Group by Date.
Please suggect how to achieve it using LINQ.
I think that something like the below would do that you want.
var result = datatable.AsEnumerable()
.GroupBy(row=>row.Field<DateTime>("Date"))
.Select(gr=>new
{
Date = gr.Key,
Agents = gr.Select(x => new
{
Id = x.Field<int>("ID"),
Agent = x.Field<string>("Agent")
})
});
Update
If you need for each date the agents to be a comma separated list of the agents,
like this {(2,A1),(4,A3)}, you could try the following approach.
var result = datatable.AsEnumerable()
.GroupBy(row=>row.Field<DateTime>("Date"))
.Select(gr=>new
{
Date = gr.Key,
Agents = "{"+ string.Join(",",
gr.Select(x => new
string.Format("({0},{1})",
x.Field<int>("ID"),
x.Field<string>("Agent"))+"}"
})
});
Here is my example class:
public class Log
{
public int ID { get; set; }
public string Agent { get; set; }
public DateTime Date { get; set; }
public Log(int id, string agent, DateTime date)
{
ID = id;
Agent = agent;
Date = date;
}
}
And here the LINQ statement with some test data:
List<Log> list = new List<Log>()
{
new Log(1, "A", new DateTime(2016, 01, 01, 0, 0, 0)),
new Log(2, "B", new DateTime(2016, 01, 01, 0, 0, 0)),
new Log(3, "C", new DateTime(2016, 01, 01, 0, 0, 0)),
new Log(4, "A", new DateTime(2016, 01, 02, 0, 0, 0)),
new Log(5, "A", new DateTime(2016, 01, 03, 0, 0, 0))
};
var result = from entry in list
group entry by entry.Date
into g
select g;
This will group all your data into groups with a Key based on Log.Date, where each group consits of multiple entries, each with an ID, Agent and Date property.
Here some example code how to access result:
result.ToList().ForEach(group =>
{
Console.WriteLine(group.Key); // The date
group.ToList().ForEach(entry => Console.WriteLine(entry.ID + " - " + entry.Agent)); // Print out each entry per group
});

Translate foreach to linq to solve running balance

I'm new in linq and read some stuff on the web about them.
Now, below is a query works fine which is to calculate the project 12-month running balance from the current date. Is it possible to translate this to linq?
It would help me understand the linq better.
var firstDayMonth = new DateTimeOffset(new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1));
var months = Enumerable.Range(0, 12)
.Select(m => firstDayMonth.AddMonths(m));
List<SomeDate> SomeDates = new List<SomeDate>()
{
new SomeDate { Id = 7, Month = firstDayMonth.AddMonths(0), Balance = 1m },
new SomeDate { Id = 7, Month = firstDayMonth.AddMonths(0), Balance = 3m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 6m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 5m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 3m },
new SomeDate { Id = 9, Month = firstDayMonth.AddMonths(2), Balance = 5m },
new SomeDate { Id = 10, Month = firstDayMonth.AddMonths(3), Balance = 3m },
new SomeDate { Id = 12, Month = firstDayMonth.AddMonths(5), Balance = 15m },
new SomeDate { Id = 13, Month = firstDayMonth.AddMonths(6), Balance = 16m },
new SomeDate { Id = 13, Month = firstDayMonth.AddMonths(6), Balance = 12m },
};
var projected12MonthsBalance = new List<SomeDate>();
foreach(var month in months)
{
projected12MonthsBalance.Add(new SomeDate { Month = month, Balance = SomeDates.TakeWhile(s => s.Month <= month).Sum(s => s.Balance) });
}
Console.WriteLine(projected12MonthsBalance);
public class SomeDate
{
public int Id { get; set; }
public DateTimeOffset Month { get; set; }
public decimal Balance { get; set; }
}
Try this:
var projected12MonthsBalance = months.Select(x => new SomeDate
{
Month = x,
Balance = SomeDates.TakeWhile(s => s.Month <= x).Sum(s => s.Balance)
}).ToList();

Categories

Resources