C# Merging 2 dictionaries and adding values between them - c#

I am learning C# and needed to merge two dictionaries so I could add values found in both.
The first dictionary is payePlusNssf that holds a value for each key (key represents employee ID). So far I have employees 1,2,3,4 and 5
Dictionary<int, decimal> payePlusNssf = new Dictionary<int, decimal>();
paye.ToList().ForEach(x =>
{
var deductNSSF = x.Value + nssfAmount;
payePlusNssf.Add(x.Key, deductNSSF);
});
The 2nd dictionary is nhifRatesDictionary that holds rates to be added to each value per employee in the first dictionary.
Dictionary<int, IEnumerable<NHIFRates>> nhifRatesDictionary =
new Dictionary<int, IEnumerable<NHIFRates>>();
basicPayDictionary.ToList().ForEach(x =>
{
List<NHIFRates> nhifValueList = new List<NHIFRates>();
// Using Employee basic pay
decimal basicPay = x.Value;
bool foundflag = false;
foreach (var item in nhifBracketList)
{
if (basicPay >= item.Min && basicPay <= item.Max)
{
nhifValueList.Add(new NHIFRates { Rate = item.Rate });
foundflag = true;
break;
}
}
if (!foundflag)
{
nhifValueList.Add(new NHIFRates { Rate = 0m });
}
nhifRatesDictionary.Add(x.Key, nhifValueList);
});
struct NHIFRates
{
public decimal Rate { get; set; }
}
In summary I need this after merging and adding:
Dict 1 Dict 2 Result Dict 3
key value key rate key value
1 500 1 80 1 580
2 1000 2 100 2 1100
3 2000 3 220 3 2220
4 800 4 300 4 1100
5 1000 5 100 5 1100
How do I achieve this? I have looked at past similar problems on this site but have not been very helpful to me.

Not tested but try:
payePlusNssf.ToDictionary(
v => v.Key,
v => v.Value + nhifRatesDictionary[v.Key].Sum(nhifr => nhifr.Rate)
);
This assumes that since the value of nhifRatesDictionary is IEnumreable you want to sum over all the values in the enumerable. This should also work if the IEnumerable is empty. If you know that there is exactly one value for each key then you can use:
payePlusNssf.ToDictionary(
v => v.Key,
v => v.Value + nhifRatesDictionary[v.Key].Single(nhifr => nhifr.Rate)
);

What about a simple for cycle?
Dictionary<int,decimal> d3 = new Dictionary<int,decimal>();
for (int i = 1,i<=payePlusNssf.Count,i++)
{
d3.Add (i,payePlusNssf[i]+((nhifRatesDictionary[i])[0]).Rate);
}
If the ID numbers are not guranteed to be this simple you can use
foreach (var x in payePlusNssf)
{
d3.Add(x.Key,x.Value+ ((nhifRatesDictionary[x.Key])[0]).Rate);
}
Or do it completely differently, do not keep three separate dictionaries that are guaranteed to have the same keys and create an employee class like
class Employee
{
public decimal payePlusNssf;
public decimal nhifRate;
public decimal Sum
{
get { return payePlusNssf + nhifRate ;}
}
}
and have one Dictionary with everything - saves you problems with keeping the dictionaries all updated.

Related

Use LINQ to GroupBy two columns and build a dictionary with them?

I am trying to use LINQ in order to build a record that is sorted by it's Store Preference and has a List of applications that are associated with each CompletionStatusFlag. In order to do this my first thought was to have the record hold a StorePreference, and then have a Dictionary that seperates the applications based on the CompletionStatus.
Employee Application
public class EmployeeApplication
{
public int Id { get; set; }
public StorePreference { get; set; }
public CompletionStatusFlag CompletionStatus { get; set; }
}
ApplicationCountRecord
public class ApplicationCountRecord
{
public Store StorePreference { get; set; }
public Dictionary<CompletionStatusFlag, List<EmployeeApplication>> StatusApplicationPairs { get; set; }
= new Dictionary<CompletionStatusFlag, List<EmployeeApplication>>();
public int TotalCount => StatusApplicationPairs.Count();
}
The problem arises when trying to create the dictionary. I have the completion status saved, but how do I get the current applications that match the completion status in order to pass them into the dictionary?
public void ConvertAppsToResults()
{
var applications = _appRepo.Query()
.GroupBy(x => new { x.StorePreference, x.CompletionStatus })
.Select(y => new ApplicationCountRecord()
{
StorePreference = y.Key.StorePreference,
//Somehow create dictionary here.
});
}
Heres is another incorrect attempt that wont even compile, but it might help you see my thought process.
public void ConvertAppsToResults()
{
//get apps in DateFilter range, and creates records seperated by StorePreference, and CompletionStatus
var applications = _applicationRepo.Query()
.GroupBy(x => new { x.StorePreference1, x.CompletionStatus })
.Select(y => new ApplicationCountRecord()
{
StorePreference = y.Key.StorePreference,
StatusApplicationPairs = new Dictionary<CompletionStatusFlag, List<EmployeeApplication>>()
.Add(y.Key.CompletionStatus, y.Where(x => x.CompletionStatus == y.Key.CompletionStatus).ToList());
});
}
There's a double grouping going on here, which is possibly the source of your confusion
employeeApplications
.GroupBy(ea => ea.Store)
.Select(g => new ApplicationCountRecord()
{
StorePreference = g.Key
StatusApplicationPairs = g.GroupBy(ea => ea.CompletionStatus).ToDictionary(g2 => g2.Key, g2 => g2.ToList())
}
)
Suppose you have 100 EmployeeApplications, across 10 Stores, and there are 5 statuses and 2 applications in each status. 2 apps * 5 statuses * 10 stores = 100 applications
GroupBy(Store) takes your list of e.g. 100 EmployeeApplications (10 per store) and groups it into 10 IGrouping that are conceptually each a list of 10 EmployeeApplications. 10 IGrouping for Store1, 10 IGrouping for Store2 etc..
Select runs over the 10 groupings, and on each one g (which, remember, behaves like a list of EmployeeApplications that all have the same Store, given in g.Key) it groups again by calling GroupBy(CompletionStatus) to further subdivide the 10 EAs in g on CompletionStatus. The 10 EAs for e.g. "Store 1" are divvied up into 5 IGroupings (5 statuses) that have 2 EAs in each inner grouping, then the same process is done for Store 2, 3 etc. There are thus 5 g2.Keys, one for each of the 5 statuses and there are 2 EAs in each g2
ToDictionary is called after it's grouped, so you get a dictionary with 5 keys and each key relates to a list of 2 applications. The ToDictionary takes two arguments; what to use for the key (g2.Key is a Status) and what to use for the value (g2.ToList() realizes a List<EmployeeApplication>)
= new Dictionary<CompletionStatusFlag, List<EmployeeApplication>>(); is unnecessary in AppCountRecord, as it will be replaced anyway

Creating key-value pairs from SQL result that have duplicate data

My problem at the moment is I have results coming back from a SQL query that returns a result like this:
125 Month 10.00 Wholesale
125 Year 20.00 Wholesale
126 Month 20.00 Wholesale
126 Year 30.00 Wholesale
127 Month 40.00 Wholesale
127 Year 50.00 Wholesale
where integer column is the ID of the column.
when the data gets returned to the C# calling code, it is placed into an object followering this structure:
PuctName;
}
I am just having issues with how to create the terms without causing an endless amount of loops.
you can use a Dictionary that way you will have a key value pair. the key is ProductID and the value the list of Terms.
var dictionary = new Dictionary<int, List<Terms>>();
foreach (ProductTermAndPricingDataItem item in productInformationItems)
{
if(dictionary.ContainsKey(item.ProductID))
{
dictionary[item.ProductID].Add(new Terms { Term = item.BillingPeriodName, Price = item.PriceAmount});
}
else
{
dictionary.Add(item.ProductID, new List<Terms>() { new Terms() {Term = item.BillingPeriodName, Price = item.PriceAmount } });
}
}
You can use Linq and GroupBy:
List<ProductPricingGetDataItem> grouped = productInformationItems.GroupBy(
p => p.ProductID,
(key, g) => new ProductPricingGetDataItem() { ProductID = key, Terms = g.Select(x => new Terms(x.BillingPeriodName, x.PriceAmount)).ToList() }).ToList();
In order for that code to work, you need to add a constructor to Terms :
public Terms(string term, decimal price)
{
Term = term;
Price = price;
}
Fiddle with working example : https://dotnetfiddle.net/EE2BpP
For LINQ lovers:
//Your initial data list
var productInformationItems = new List<ProductTermAndPricingDataItem>();
var productPricingGetDataItems = productInformationItems.ToLookup(item => item.ProductID)
.Select(grouping => new ProductPricingGetDataItem
{
ProductID = grouping.Key,
Terms = grouping.Select(item => new Terms
{
Price = item.PriceAmount,
Term = item.BillingPeriodName
}).ToList()
}).ToList();
In your exact case the result is :
Feel free to ask if something is not clear.

Buy X Pay for Y Algorithm

I have to write "Buy X Pay for Y" algorithm.
Request that comes to my endpoint is list of Articles
public class Article
{
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
payFor variable comes from db and is defined by package discount id
Here's my algorithm that I wrote so far
if (purchasedQuantity >= minPurchaseQuantity)
{
var c = 0;
foreach (var article in articlesForPackage.OrderByDescending(a => a.UnitPrice))
{
for (var i = 1; i <= article.Quantity; i++)
{
c++;
if (c > payFor)
{
c = 0;
result.Add(new Discount
{
Value = article.UnitPrice
});
}
}
}
}
Unfortunately, this algorithm does not work in some cases.
When the package discount is defined buy 3 and pay for 2 it works, but if buy 3 pay for one doesn't work.
Could somebody help me?
This is how the algorithm should work:
We have 3 articles
1 Art1 - 20$
2 Art2 - 30$
3 Art3 - 40$
If minPurchaseQuantity is 3 and payFor is 2 it means that cost of Art1 should be added to result list (because it is the cheapest one)
If minPurchaseQuantity is 3 and payFor is 1 it means that cost of Art2 and Art1 should be added to result list (Now only the Art2 is adding)
Well, the main issue is, that you reset c as soon as it gets larger than payFor. This works as long as minPurchaseQuantity-payFor=1, but in other cases it won't.
While it's not as easy as the solution I presented in my first answer, I think the actual algorithm can be implemented more concisely. The following code first batches the items in groups eligible for discount. For each of the batches it then skips as many as payFor items and calculates the discount from the rest
// first batch the items in batches eligible for discount (e.g. batches of three in take 3, pay for x)
var batchedItems = BatchItemsEligibleForDiscount(items, minPurchaseQuantity);
var discounts = batchedItems.Select(batch => batch.Skip(payFor))
.SelectMany(batch => batch) // flatten nested IEnumerable to an IEnumerable<Artible>
.Select(article => new Discount() { Value = article.UnitPrice });
The BatchItemsEligibleForDiscount gets the batches that are eligible for discount (i.e. have 3 items each if it's "take 3, pay for X". Articles with a Quantity>1 are "exploded", i.e. if the quantity is 3, 3 distinct objects are created.
IEnumerable<IEnumerable<Article>> BatchItemsEligibleForDiscount(items, minPurchaseQuantity)
{
return items.OrderByDescending(article => article.UnitPrice)
.Select(article => Enumerable.Range(1, article.Quantity).Select(n => new Article() { Quantity = 1, UnitPrice = article.UnitPrice })) // "explode" articles
.SelectMany(item => item) // flatten to an IEnumerable<Article>
.Select((article, index) => new { article, index })
.GroupBy(x => x.index / minPurchaseQuantity)
.Where(group => group.Count() == minPurchaseQuantity) // only take batches elegible for discount
.Select(group => group.Select(x => x.article));
}
See this fiddle for a demonstration.
OLD ANSWER
Calculating the discount is way easier. You can calculate the number of bundles elegible for discount (if its take 3, pay for 2 and 8 items, you have two whole bundles of 3 items each). By calculating the difference between the items to take and the items to pay and multiplying it with the number of bundles and the price per item, you can calculate the discount
var numberOfDiscountableBundles = article.Quantity / amountOfItemsElegibleForDiscount;
var discount = numberOfDiscountableBundles * (amountOfItemsElegibleForDiscount - payFor) * article.UnitPrice;
Example: Take 3, pay for 1 with 8 items:
numberOfDiscountableBundles = 8 / 3 = 2 (integer division!)
discount = 2 * (3 - 1) * p = 2 * 2 * p = 4 * p
It's two discounted bundles of three items each (six items). Four of those items are not payed for (only one per bundle), hence the total price is discounted by four times the price of a unit.
You could encapsule this in a method
Discount CalculateDiscountForArticle(Article article, int amountOfItemsElegibleForDiscount, int payFor)
{
var numberOfDiscountableBundles = article.Quantity / amountOfItemsElegibleForDiscount;
var discount = numberOfDiscountableBundles * (amountOfItemsElegibleForDiscount - payFor) * article.UnitPrice;
return new Discount
{
Value = discount
};
}
And your original function gets as easy as
var discounts = articlesForPackage.OrderByDescending(a => a.UnitPrice)
.Select(a => CalculateDiscountForArticle(a, amountOfItemsElegibleForDiscount, payFor));
EDIT TO OLD ANSWER
If the discount is granted only once per customer and article, the calculation is a bit different
double discount = 0;
if(article.Quantity >= amountOfItemsElegibleForDiscount)
{
var discount = (amountOfItemsElegibleForDiscount - payFor) * article.UnitPrice;
}

How can I fill the gaps in attribute values in the list?

I have a list:
List<BtnCountViews> btnCountViewsList;
The BtnCountViews class looks like this:
public class BtnCountViews
{
public int DayOfYear { get; set; }
public int BtnCount { get; set; }
public int Views { get; set; }
}
I have a rather unusual requirement and I am not sure how to go about starting to implement it.
What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.
To give me a start can anyone tell me how I can find the min and max DayOfYear in the btnCountViewsList. Note I tagged this with LINQ but I'm not sure if this is the best tool to use.
Also would be happy if someone can suggest a way to fill in the missing objects but that's not really the focus of this question as I think I need to find out how to get the min and max first.
You can add missing days without finding min and max explicitly:
Sort the list by DayOfYear in ascending order (how?)
Start a loop index i at the end of the list, and work your way backward; stop when i reaches zero
Compare DayOfYear attribute at i and i-1
If the two days differ by one, move down to the next i
Otherwise insert a new record with DayOfYear set to that of btnCountViewsList[i] minus one.
At the end of this process your list would contain entries for each value of DayOfYear. Here is a sample implementation:
items.Sort((x, y) => x.DayOfYear.CompareTo(y.DayOfYear));
Console.WriteLine("Before: {0}", string.Join(", ", items.Select(x => x.DayOfYear)));
int i = items.Count-1;
while (i > 0) {
if (items[i].DayOfYear == items[i-1].DayOfYear+1) {
i--;
} else {
items.Insert(i, new BtnCountViews { DayOfYear = items[i].DayOfYear-1 });
}
}
Demo.
This is working on linqpad:
Int32 max = 0, min = 0;
btnCountViewsList.ForEach(x => {
min = Math.Min(x.Views, min);
max = Math.Max(x.Views, max);
});
What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.
My suggestion is that we don't try to find the missing days, we create all:
BtnCountViews[] arr = new BtnCountViews[365]; // or 366?
// suppose DayOfYear begins with 0.
for (int i = 0; i < arr.Length; i++)
{
arr[i] = new BtnCountViews { DayOfYear = i };
}
foreach (BtnCountViews item in btnCountViewsList)
{
arr[item.DayOfYear].BtnCount = item.BtnCount;
arr[item.DayOfYear].Views = item.Views;
}
then arr is what you want.
And if the result should be the btnCountViewsList:
btnCountViewsList.Clear();
btnCountViewsList.AddRange(arr);
So the lazy in me says, make a backfill list and use your existing (and gappy) list as a map.
public static IList<BtnCountViews> GetDefaultList()
{
var defaultList = Enumerable.Range(1, 365).Select(e =>
new BtnCountViews
{
DayOfYear = e,
BtnCount = 0,
Views = 0
}
).ToList();
return defaultList;
}
Iterate through the backfill list and consult the map to see if the DayOfYear value exists as a key, and if not, then add it to the map.
public static IList<BtnCountViews> GetBackFilledList(IList<BtnCountViews> incoming)
{
var map = incoming.ToDictionary(k => k.DayOfYear, v => v);
var defaultList = GetDefaultList();
foreach(var itm in defaultList)
{
if (map.ContainsKey(itm.DayOfYear)) continue;
map.Add(itm.DayOfYear, itm);
}
return map.Select(m => m.Value).ToList();
}
Once the iteration is finished, convert the map into a list, which should now consist of the original values + default values for missing DayOfYear entries as well.
return map.Select(m => m.Value).ToList();
Dotnetfiddle of a sample program here: https://dotnetfiddle.net/wSJy56
Is there a more elegant way to do this? Most surely. But this code executes in about 0.011 seconds, which to me is pretty decent so long as you're not calling this functionality over and over again (e.g. you decide to analyze 30 years of data and need to get that done in 0.011 seconds). But then we'd have to be looking more towards parallelism rather than code elegance to solve that can of worms.
Hope this helps...
Try the following
btnCountViewsList = btnCountViewsList.Where(b => b.BtnCount == 0).Where(v => v.Views == 0).ToList();
If I understood what you were asking, you want to get objects where BtnCount = 0 and Views = 0.
This will select all the objects where Views = 0, and then that IEnumarable will be through another LINQ expression where it only selects the other property that equals to 0.
The shortest linq way, using an left outer join (LEFT OUTER JOIN in LINQ) and Range
var result = (from a in Enumerable.Range(0, 365)
join lst in btnCountViewsList on a equals lst.DayOfYear into ps
from p in ps.DefaultIfEmpty()
select (p==null) ? new BtnCountViews() { DayOfYear = a}:p).ToList()
among the lines of some other responses, but without hard coding the total days of the year as leap years will have 366 days
var range = new
{
Start = new DateTime(2017, 1, 1),
End = new DateTime(2017, 12, 31),
};
var days = Enumerable.Range(range.Start.DayOfYear, range.End.DayOfYear);
var query = from day in days
from counter in
(
from temp in btnCountViewsList
where temp.DayOfYear == day
select temp
).DefaultIfEmpty()
select new BtnCountViews
{
DayOfYear = day,
BtnCount = counter == null ? 0 : counter.BtnCount,
Views = counter == null ? 0 : counter.Views,
};
will give you something like

C# Lambda to filter list based on existence on another list

List A: 3,5,5,5,7,9
List B: 3,5
Both of the list are the same type and those values are from a field ID. My objective is to construct a forloop that will return me 7,9 because 7,9 is not existed in List B.
I've tried the following but no luck:
int counter = 0;
foreach(var item in ListA.Where(x=>ListB.Any(b=>x.ID != b.ID)))
{
counter++;
//Here I should perform operation with item that having ID 7 and 9
}
Updates:
Using a except method in the above case, counter will still return me 4 simply because each of the 5 in ListA are different object eventhou they are sharing the same ID. My ultimate objective is to have the counter as 2 irregardless whether the object is the same or not. As long as the ID of object in ListA is 3 or 5, I would wanna exclude it.
Just use the Except extension mtehod
foreach (var item in ListA.Except(ListB)) {
...
}
it should be "ALL", or "Not Any"
foreach(var item in ListA.Where(x=>ListB.All(b=>x.ID != b.ID)))
{
//Here I should perform operation with item that having ID 7 and 9
}
update:
As you actually want to have distinct result from A except B, so, you can do either:
foreach(var item in ListA.GroupBy(m=>m.ID).Where(x=>ListB.All(b=>b.ID != x.Key)))
{
counter ++;
Debug.writeline(item.Key);
}
or
foreach(var id in ListA.Select(x=>x.ID).Distinct().Except(ListB.Select(y=>y.ID)))
{
counter++;
}
note: all untested - i have no compiler with me for the moment.
Change your query like this:
foreach(var item in ListA.Where(x=> !ListB.Any(b => x.ID == b.ID)))
And it should work fine.
Try This:
List<int> listA=new List<int>(new[]{ 3,5,7,9});
List<int> listB=new List<int>(new[]{ 3,5});
var items=(from a in listA
select a).Except(from b in listB
select b);
foreach(var item in items)
{
Console.WriteLine(ll);
}
Output:
7
9
Except method can be used when both List are of same type.
If Type is different. We can use like this.
var outPut = _employees.Where(i => _employeeExtensions.Any(j => i.EmpId == j.EmpId));
I think you want to get the items in a list where the items' IDs are different:
Example that I put together in LinqPad:
void Main()
{
List<Person> a = new List<Person>()
{
new Person { ID = 1 },
new Person { ID = 2 },
new Person { ID = 3 },
};
List<Person> b = new List<Person>()
{
new Person { ID = 1 },
};
var c = a.Where(x => b.Any(bprime => bprime.ID != x.ID));
foreach(var item in c)
{
Console.WriteLine(item.ID);
}
}
class Person
{
public int ID { get; set; }
}
Output:
2
3
This works similar to the Except method but this will check the elements' properties.

Categories

Resources