Trying to output all information with linq - c#

I've written some code that matches two arrays by the identifiers called seqNum and gets an answer from some math. I'm able to print out the seqNum and the Answer I get but I'm having trouble getting all of the other information that comes along with the seqNum. I want to be able to output something like this:
Name Date `seqNum` Answer...
My code is giving me the answer but then it just spits out the first Name in the file over and over again with different seqNums and Answers.
Here's the code:
private void executeBtn_Click(object sender, EventArgs e)
{
//NET OIL VARIANCE MATHEMATICS
if (netOilRadBtn.Checked)
{
using (var sw = new StreamWriter("testNetOil.csv"))
{
var items = netOil.Zip(seqNum, (oil, seq) => new { Oil = oil, Seq = seq });
var items2 = netOil2.Zip(seqNum2, (oil, seq) => new { Oil = oil, Seq = seq });
sw.WriteLine("Lease Name, Field Name, Reservoir, Operator, County, ST, Majo, Resv Cat, Discount Rate, Net Oil Interest, Net Gas Interest, Working Interest, Gross Wells, Ultimate Oil, Ultimate Gas, Gross Oil, Gross NGL, Gross Gas, Net Oil, Net Gas, Net NGL, Revenue To Int., Oper. Expense, Total Invest., Revenue Oil, Revenue Gas, Operating Profit, Revenue NGL, Disc Net Income, SEQ, Well ID, INC ASN, Life Years, Own Qual, Production Tax, NET OIL VARIANCE");
foreach (var item in items.Join(items2, i => i.Seq, i => i.Seq, (a, b) => new
{SeqID = a.Seq, Answer = this.GetTheAnswer(Convert.ToDouble(a.Oil), Convert.ToDouble(b.Oil))
}))
{
int x = 0;
x.Equals(item.SeqID);
while (x != -1)
{
sw.WriteLine(leaseName[x] + "," + item.SeqID + "," + item.Answer);
x--;
}
So basically I just need to print out the matching name with the correct seqNum and Answer. If anyone has any ideas or comments, that would be greatly appreciated. And my math method is pretty simple if anyone needs to see:
public double GetTheAnswer(double first, double second)
{
double answer = (first - second) / second;
return answer;
}

Well, you should include the data in the anonymous type
foreach (var item in items.Join(items2, i => i.Seq, i => i.Seq, (a, b) => new
{
SeqID = a.Seq,
Answer = this.GetTheAnswer(Convert.ToDouble(a.Oil),
Convert.ToDouble(b.Oil)),
Name = a.Name, // b.Name?
Date = a.Date, // b ?
seqNum = a.seqNum // b ?
}))
What are you trying by (while) looping inside the foreach? You loop through a collection of anonymous types that have no repetitive data in them, item is just a collection of flat data.
So everything after int x = 0; is useless. Just output the data you collect in the anonymous type row by row. Since the header ("Lease Name, Field Name, Reservoir, Operator, ...) is much longer I think there should be much more in the part new { ... }.

You need to change the assignment of x, because you are giving it no value.
int x = Convert.ToInt32(item.SeqID);
Try it and see if you get different results.

Related

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

Group and separate list

I have below entity structure
public class Item
{
public EnumType Type { get; set; }
public int Price { get; set; }
}
public enum EnumType
{
A =1,
B=2,
C =3
}
I have a list of items as follow
var items = new List<Item>
{
new Item{ Price=5, Type= EnumType.B},
new Item{ Price=5, Type= EnumType.B},
new Item{ Price=5, Type= EnumType.B},
new Item{ Price=10, Type= EnumType.B},
new Item{ Price=10, Type= EnumType.B},
new Item{ Price=10, Type= EnumType.B},
new Item{ Price=15, Type= EnumType.C},
new Item{ Price=15, Type= EnumType.C},
new Item{ Price=15, Type= EnumType.C},
new Item{ Price=15, Type= EnumType.C},
new Item{ Price=15, Type= EnumType.C}
};
If the price and type are same, based on type it need to exclude every nth item from the list and then calculate the sum.
i.e type B = 3, Type C = 4
Which means in above sample data, since there are 3 items each in type B once it group by price and type it need to exclude every 3rd item when calculate sum.
So sum for type B will be 5+5+10+10 and sum for type C will be 15+15+15+15
I tried using modular but seems its not the correct direction
I have tried this so far
static int GetFactorByType(EnumType t)
{
switch(t)
{
case EnumType.A:
return 2;
case EnumType.B:
return 3;
case EnumType.C:
return 4;
default:
return 2;
}
}
var grp = items.GroupBy(g => new { g.Type, g.Price }).Select(s => new
{
type= s.Key.Type,
price = s.Key.Price,
count = s.Count()
}).Where(d => d.count % GetFactorByType(d.type) == 0).ToList();
Here's one solve:
//track the type:nth element discard
var dict = new Dictionary<EnumType, int?>();
dict[EnumType.B] = 3;
dict[EnumType.C] = 4;
//groupby turns our list of items into two collections, depending on whether their type is b or c
var x = items.GroupBy(g => new { g.Type })
.Select(g => new //now project a new collection
{
g.Key.Type, //that has the type
SumPriceWithoutNthElement = //and a sum
//the sum is calculated by reducing the list based on index position: in where(v,i), the i is the index of the item.
//We drop every Nth one, N being determined by a dictioary lookup or 2 if the lookup is null
//we only want list items where (index%N != N-1) is true
g.Where((v, i) => (i % (dict[g.Key.Type]??2)) != ((dict[g.Key.Type] ?? 2) - 1))
.Sum(r => r.Price) //sum the price for the remaining
}
).ToList(); //tolist may not be necessary, i just wanted to look at it
It seemed to me like your question words and your example are not aligned. You said (and did in code):
If the price and type are same, based on type it need to exclude every nth item from the list and then calculate the sum. i.e type B = 3, Type C = 4
Which to me means you should group by Type and Price, so B/5 is one list, and B/10 is another list. But you then said:
Which means in above sample data, since there are 3 items each in type B once it group by price and type it need to exclude every 3rd item when calculate sum. So sum for type B will be 5+5+10+10
I couldn't quite understand this. To me there are 3 items in B/5, so B/5 should be a sum of 10 (B/5 + B/5 + excluded). There are 3 items in B/10, again, should be (B/10 + B/10 + excluded) for a total of 20.
The code above does not group by price. It outputs a collection of 2 items, Type=B,SumWithout=30 and Type=C,SumWithout=60. This one groups by price too, it outputs a 3 item collection:
var x = items.GroupBy(g => new { g.Type, g.Price })
.Select(g => new
{
g.Key.Type,
g.Key.Price,
SumPriceWithoutNthElement =
g.Where((v, i) => (i % (dict[g.Key.Type]??2)) != ((dict[g.Key.Type] ?? 2) - 1))
.Sum(r => r.Price) }
).ToList();
The items are Type=B,Price=5,SumWithout=10 and Type=B,Price=10,SumWithout=20 and Type=C,Price=15,SumWithout=60
Maybe you mean group by type&price, remove every 3rd item (from b, 4th item from c etc), then group again by type only and then sum
This means if your type B prices were
1,1,1,1,2,2,2,2,2
^ ^
we would remove one 1 and one 2 (the Ines with arrows under them), then sum for a total of 9. This is different to removing every 3rd for all type b:
1,1,1,1,2,2,2,2,2
^ ^ ^
?
In which case, maybe group by Type/sum again the SumWithout output from my second example
I did consider that there might be a more efficient ways to do this without LINQ.. and it would nearly certainly be easier to understand the code if if were non LINQ - LINQ isn't necessarily a wonderful magic bullet that can kill all ptoblems, and even though it might look like a hammer with which every problem can be beaten, sometimes it's good to avoid
Depending on how you intended the problem to be solved (is price part of the group key or not) building a dictionary and accumulating 0 instead of th price every Nth element might be one way.. The other way, if price is to be part of the key, could be to sum all the prices and then subtract (count/N)*price from the total price
Grouping by a new object, which is always unique, guarantees you that you'll have as many groups as you have items. Try something like this:
var grp = items.GroupBy(g => $"{g.Type}/{g.Price}").Select(s => new
{
type= s.Value.First().Type,
price = s.Value.First().Price,
count = s.Value.Count()
}).Where(d => count % GetFactorByType(d.type) == 0).ToList();
This way, you group by a string composed from the type/price combination, so if the items are equivalent, the strings will be equal.
The $"{g.Type}/{g.Price}"string amounts to "B/5" for your first three item examples, so it's quite readable as well.

Ways to enhance nested Parallel loops

I have a method that contains nested parallel loops to populate one of the properties of the loop.
public static string CalculateFantasyPointsLeagueSettings(ref List<Projections> projection, League league, Teams teamList)
{
string PPR = "";
var stats = (from a in league.Settings.StatCategories.stats
join b in league.Settings.StatModifiers.stats on a.StatId equals b.StatId
select new Tuple<string, double>(a.Name.Replace(" ", string.Empty), b.Value)).ToList();
var props = new Projections().GetType().GetProperties();
double receptionValue = (stats.Where(a => a.Item1 == "Receptions").Select(a => a.Item2).FirstOrDefault());
if (receptionValue == 1.0)
{
PPR = "PPR";
}
else if (receptionValue == .5)
{
PPR = "Half PPR";
}
Parallel.ForEach(projection, proj =>
{
double points = 0;
Parallel.ForEach(props, prop =>
{
var stat = (from a in stats
where a.Item1 == prop.Name
select
Convert.ToDouble(prop.GetValue(proj, null)) * a.Item2
).FirstOrDefault();
points += stat;
});
proj.ProjectedPoints = Math.Round(points, 2);
proj.FantasyTeam = (from a in teamList.TeamList
where a.Players.player.Select(b => b.Name.Full).Contains(proj.Name)
select a.Name).FirstOrDefault();
});
return PPR;
}
What this method does is calculates the points for a fantasy football player based on their league settings (stats object) and their projected stats (projections list). Since stats is not a static list (you can have custom settings), I need to be able to loop through all the items in that list, and calculate each one by hand. In order to do this, I have to get the property in Reflection and than use it's value. Since projection has about 35k records and stats will be well over 30, this method takes a long time to run, probably due to reflection. I am trying to figure out if there is anything I can do to make it run faster. Right now it takes about 2-3 seconds, which is not ideal. I cannot put it in cache as it is fairly dynamic. Any help would be greatly appreciated.

LINQ to Entities - Simple Way to split resultset into multiple objects

I have a table, containing weekly sales data from multiple years for a few hundred products.
Simplified, I have 3 columns: ProductID, Quantity, [and Date (week/year), not relevant for the question]
In order to process the data, i want to fetch everything using LINQ. In the next step I would like create a List of Objects for the sales data, where an Object consists of the ProductId and an array of the corresponding sales data.
EDIT: directly after, I will process all the retrieved data product-by-product in my program by passing the sales as an array to a statistics software (R with R dot NET) in order to get predictions.
Is there a simple (built in) way to accomplish this?
If not, in order to process the sales product by product,
should I just create the mentioned List using a loop?
Or should I, in terms of performance, avoid that all together and:
Fetch the sales data product-by-product from the database as I need it?
Or should I make one big List (with query.toList()) from the resultset and get my sales data product-by-product from there?
erm, something like
var groupedByProductId = query.GroupBy(p => p.ProductId).Select(g => new
{
ProdcutId = g.Key,
Quantity = g.Sum(p => p.Quantity)
});
or perhaps, if you don't want to sum and, instread need the quantities as an array of int ordered by Date.
var groupedByProductId = query.GroupBy(p => p.ProductId).Select(g => new
{
ProdcutId = g.Key,
Quantities = g.OrderBy(p => p.Date).Select(p => p.Quantity).ToArray()
});
or maybe you need to pass the data around and an anonymous type is inappropriate., you could make an IDictionary<int, int[]>.
var salesData = query.GroupBy(p => p.ProductId).ToDictionary(
g => g.Key,
g => g.OrderBy(p => p.Date).Select(p => p.Quantity).ToArray());
so later,
int productId = ...
int[] orderedQuantities = salesData[productId];
would be valid code (less the ellipsis.)
You may create a Product class with id and list of int data. Something as below:
Public class Product{
public List<int> list = new List<int>();
public int Id;
Public Product(int id,params int[] list){
Id = id;
for (int i = 0; i < list.Length; i++)
{
list.Add(list[i]);
}
}
}
Then use:
query.where(x=>new Product(x.ProductId,x.datum1,x.datum2,x.datum3));

Categories

Resources