How can I use C#/LINQ to calculate weighted averages - c#

This is to process stock data; the data is in this format:
public class A
{
public int Price;
public int Available;
}
let's take this data for example:
var items = new List<A>
{
new A { Price = 10, Available = 1000 },
new A { Price = 15, Available = 500 },
new A { Price = 20, Available = 2000 },
};
my query returns the average price for a specific volume, so for example:
if I have a requested volume of 100, my average price is 10
if I have a requested volume of 1200, I take the first 1000 at a price of 10, then the next 200 at a price of 15
etc
I have implemented that in C#, but I am trying to find if this could be done with LINQ directly with the database iterator.
I get data that is already sorted by price, but I don't see how to solve this without iteration.
Edit:
this is the code:
public static double PriceAtVolume(IEnumerable<A> Data, long Volume)
{
var PriceSum = 0.0;
var VolumeSum = 0L;
foreach (var D in Data)
{
if (D.Volume < Volume)
{
PriceSum += D.Price * D.Volume;
VolumeSum += D.Volume;
Volume -= D.Volume;
}
else
{
PriceSum += D.Price * Volume;
VolumeSum += Volume;
Volume = 0;
}
if (Volume == 0) break;
}
return PriceSum / VolumeSum;
}
and the test code:
var a = new List<A>
{
new A { Price = 10, Volume = 1000 },
new A { Price = 15, Volume = 500 },
new A { Price = 20, Volume = 2000 }
};
var P0 = PriceAtVolume(a, 100);
var P1 = PriceAtVolume(a, 1200);
Clarification:
Above I said I'd like to move it to LINQ to use the database iterator, so I'd like to avoid scanning the entire data and stop iterating when the answer is calculated. The data is already sorted by price in the database.

This is probably the most Linqy you can get. It uses the Aggregate method, and specifically the most complex of the three overloaded versions of Aggregate, that accepts three arguments. The first argument is the seed, and it is initialized with a zeroed ValueTuple<long, decimal>. The second argument is the accumulator function, with the logic to combine the seed and the current element into a new seed. The third argument takes the final accumulated values and projects them to the desirable average.
public static decimal PriceAtVolume(IEnumerable<A> data, long requestedVolume)
{
return data.Aggregate(
(Volume: 0L, Price: 0M), // Seed
(sum, item) => // Accumulator function
{
if (sum.Volume == requestedVolume)
return sum; // Goal reached, quick return
if (item.Available < requestedVolume - sum.Volume)
return // Consume all of it
(
sum.Volume + item.Available,
sum.Price + item.Price * item.Available
);
return // Consume part of it (and we are done)
(
requestedVolume,
sum.Price + item.Price * (requestedVolume - sum.Volume)
);
},
sum => sum.Volume == 0M ? 0M : sum.Price / sum.Volume // Result selector
);
}
Update: I changed the return type from double to decimal, because a decimal is the preferred type for currency values.
Btw in case that this function is called very often with the same data, and the list of data is huge, it could be optimized by storing the accumulated summaries in a List<(long, decimal)>, and applying BinarySearch to quickly find the desirable entry. It becomes complex though, and I don't expect that the prerequisites for the optimization will come up very often.

This is working as well (although not a one-liner):
private static decimal CalculateWeighedAverage(List<A> amountsAndPrices, int requestedVolume)
{
int originalRequestedVolume = requestedVolume;
return (decimal)amountsAndPrices.Sum(amountAndPrice =>
{
int partialResult = Math.Min(amountAndPrice.Available, requestedVolume) * amountAndPrice.Price;
requestedVolume = Math.Max(requestedVolume - amountAndPrice.Available, 0);
return partialResult;
}) / originalRequestedVolume;
}
Take the sum of price * available as long as the requested volume is bigger than 0 and subtracting the amount of every item in the list in each "sum iteration". Finally divide by the original requested volume.

You could do something to generate the items' prices as a sequence. e.g.
public class A
{
public int Price;
public int Available;
public IEnumerable<int> Inv => Enumerable.Repeat(Price, Available);
}
var avg1 = items.SelectMany(i => i.Inv).Take(100).Average(); // 10
var avg2 = items.SelectMany(i => i.Inv).Take(1200).Average(); // 10.8333333333333

I think the best you can do with LINQ is minimize the running total computation done on the server and compute most of it on the client, but minimize the amount downloaded from the server.
I assume the items are already projected down to the two minimum columns (Price, Availability). If not, a Select can be added before pulling the data from the database into orderedItems.
// find price of last item needed; worst case there won't be one
var lastPriceItem = items.Select(i => new { i.Price, RT = items.Where(it => it.Price <= i.Price).Sum(it => it.Available) }).FirstOrDefault(irt => irt.RT > origReqVol);
// bring over items below that price
var orderedItems = items.OrderBy(i => i.Price).Where(i => i.Price <= lastPriceItem.Price).ToList();
// compute running total on client
var rtItems = orderedItems.Select(i => new {
Item = i,
RT = orderedItems.Where(i2 => i2.Price <= i.Price).Sum(i2 => i2.Available)
});
// computer average price
var reqVol = origReqVol;
var ans = rtItems.Select(irt => new { Price = irt.Item.Price, Quantity = Math.Min((reqVol -= irt.Item.Available)+irt.Item.Available, irt.Item.Available) })
.Sum(pq => pq.Price * pq.Quantity) / (double)origReqVol;

Related

Calculate Percent Change From IGrouping Count in Linq/C#

I would like to have a "percent change for 'Investigations' and 'Breaches' by each quarter. I'm currently grouping by quarter and getting counts but I cannot figure out how to add percent change.
This is what I want to have a IEnumerable/List of:
public class StatusCountDto
{
public string Quarter { get; set; }
public int Investigations { get; set; }
public double InvestigationsChange { get; set; }
public int Breaches { get; set; }
public double BreachesChange { get; set; }
}
Currently I am grouping by the Quarter and getting counts but I cannot figure out how to get the percent change of Investigation counts and Breaches counts from the previous quarter.
The data is already sorted by Quarter. If the previous value doesn't exit (first index) then it should be 0.
This is what I have so far.
Metrics.GroupBy(m => m.Quarter )
.Select((g, index) => new StatusCountDto
{
Quarter = g.Key,
Investigations = g.Count(),
Breaches = g.Where(a => a.Breach == "Yes").Count()
})
.ToList();
Is there a way to use the index to calculate the percent change?
Using an extension method based on the APL scan operator, which is like Aggregate but returns the intermediate results, you can run through the data and refer back to previous counts.
// TRes combineFn(TRes PrevResult, T CurItem)
// First PrevResult is TRes seedFn(T FirstItem)
// FirstItem = items.First()
// First CurItem = items.Skip(1).First()
// output is seedFn(items.First()), combineFn(PrevResult, CurItem), ...
public static IEnumerable<TRes> Scan<T, TRes>(this IEnumerable<T> items, Func<T, TRes> seedFn, Func<TRes, T, TRes> combineFn) {
using (var itemsEnum = items.GetEnumerator()) {
if (itemsEnum.MoveNext()) {
var prev = seedFn(itemsEnum.Current);
for (; ; ) {
yield return prev;
if (!itemsEnum.MoveNext())
yield break;
prev = combineFn(prev, itemsEnum.Current);
}
}
}
}
Given this variation of Scan that uses a lambda to seed the result stream, you can use it to compute the whole stream:
var ans = Metrics
.GroupBy(m => m.Quarter)
.Select(g => new {
Quarter = g.Key,
Investigations = g.Count(),
Breaches = g.Count(a => a.Breach == "Yes")
})
.Scan(f => new StatusCountDto { // first result
Quarter = f.Quarter,
Investigations = f.Investigations,
Breaches = f.Breaches
},
(prev, cur) => new StatusCountDto { // subsequent results
Quarter = cur.Quarter,
Investigations = cur.Investigations,
InvestigationsChange = 100.0 * (cur.Investigations - prev.Investigations) / prev.Investigations,
Breaches = cur.Breaches,
BreachesChange = 100.0 * (cur.Breaches - prev.Breaches) / prev.Breaches
}
)
.ToList();

Find Range of Most Profitable Products

I have over 1,000 records and I am using this to find the highest value of (profit * volume).
In this case its "DEF" but then I have to open excel and sort by volume and find the range that produces the highest profit.. say excel column 200 to column 800 and then I'm left with say from volume 13450 to volume 85120 is the best range for profits.. how can I code something like that in C# so that I can stop using excel.
public class Stock {
public string StockSymbol { get; set; }
public double Profit { get; set; }
public int Volume { get; set; }
public Stock(string Symbol, double p, int v) {
StockSymbol = Symbol;
Profit = p;
Volume = v;
}
}
private ConcurrentDictionary<string, Stock> StockData = new();
private void button1_Click(object sender, EventArgs e) {
StockData["ABC"] = new Stock("ABC", 50, 14000);
StockData["DEF"] = new Stock("DEF", 50, 105000);
StockData["GHI"] = new Stock("GHI", -70, 123080);
StockData["JKL"] = new Stock("JKL", -70, 56500);
StockData["MNO"] = new Stock("MNO", 50, 23500);
var DictionaryItem = StockData.OrderByDescending((u) => u.Value.Profit * u.Value.Volume).First();
MessageBox.Show( DictionaryItem.Value.StockSymbol + " " + DictionaryItem.Value.Profit);
}
I wrote up something that may or may not meet your requirements. It uses random to seed a set of test data (you can ignore all of that).
private void GetStockRange()
{
var stocks = new Stock[200];
var stockChars = Enumerable.Range(0, 26).Select(n => ((char)n + 64).ToString()).ToArray();
var random = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < stocks.Length; i++)
{
stocks[i] = new Stock(stockChars[random.Next(0, 26)], random.NextDouble() * random.Next(-250, 250), random.Next(1, 2000));
}
var highestPerformaceSelectionCount = 3;
var highestPerformanceIndices = stocks
.OrderByDescending(stock => stock.Performance)
.Take(Math.Max(2, highestPerformaceSelectionCount))
.Select(stock => Array.IndexOf(stocks, stock))
.OrderBy(i => i);
var startOfRange = highestPerformanceIndices.First();
var endOfRange = highestPerformanceIndices.Last();
var rangeCount = endOfRange - startOfRange + 1;
var stocksRange = stocks
.Skip(startOfRange)
.Take(rangeCount);
var totalPerformance = stocks.Sum(stock => stock.Performance);
var rangedPerformance = stocksRange.Sum(stock => stock.Performance);
MessageBox.Show(
"Range Start: " + startOfRange + "\r\n" +
"Range End: " + endOfRange + "\r\n" +
"Range Cnt: " + rangeCount + "\r\n" +
"Total P: " + totalPerformance + "\r\n" +
"Range P: " + rangedPerformance
);
}
The basics of this algorithm to get some of the highest performance points (configured using highestPerformanceSelectionCount, min of 2), and using those indices, construct a range which contains those items. Then take a sum of that range to get the total for that range.
Not sure if I am way off from your question. This may also not be the best way to handle the range. I wanted to post what I had before heading home.
I also added a Performance property to the stock class, which is simply Profit * Volume
EDIT
There is a mistake in the use of the selected indices. The indices selected should be used against the ordered set in order to produce correct ranged results.
Rather than taking the stocksRange from the original unsorted array, instead create the range from the ordered set.
var stocksRange = stocks
.OrderByDescending(stock => stock.Performance)
.Skip(startOfRange)
.Take(rangeCount);
The indices should be gathered from the ordered set as well. Caching the ordered set is probably the easiest route to go.
As is generally the case, there are any number of ways you can go about this.
First, your sorting code above (the OrderByDescending call). It does what you appear to want, more or less, in that it produces an ordered sequence of KeyValuePair<string, Stock> that you can then choose from. Personally I'd just have sorted StockData.Values to avoid all that .Value indirection. Once sorted you can take the top performer as you're doing, or use the .Take() method to grab the top n items:
var byPerformance = StockData.Values.OrderByDescending(s => s.Profit * s.Volume);
var topPerformer = byPerformance.First();
var top10 = byPerformance.Take(10).ToArray();
If you want to filter by a particular performance value or range then it helps to pre-calculate the number and do your filtering on that. Either store (or calculate) the Performance value in the class, calculate it in the class with a computed property, or tag the Stock records with a calculated performance using an intermediate type:
Store in the class
public class Stock {
// marking these 'init' makes them read-only after creation
public string StockSymbol { get; init; }
public double Profit { get; init; }
public int Volume { get; init; }
public double Performance { get; init; }
public Stock(string symbol, double profit, int volume)
{
StockSymbol = symbol;
Profit = profit;
Volume = volume;
Performance = profit * volume;
}
}
Calculate in class
public class Stock
{
public string StockSymbol { get; set; }
public double Profit { get; set; }
public int Volume { get; set; }
public double Performance => Profit * Volume;
// you know what the constructor looks like
}
Intermediate Type (with range filtering)
// let's look for those million-dollar items
var minPerformance = 1000000d;
var highPerformance = StockData.Values
// create a stream of intermediate types with the performance
.Select(s => new { Performance = s.Profit * s.Volume, Stock = s })
// sort them
.OrderByDescending(i => i.Performance)
// filter by our criteria
.Where(i => i.Performance >= minPerformance)
// pull out the stocks themselves
.Select(i => i.Value)
// and fix into an array so we don't have to do this repeatedly
.ToArray();
Ultimately though you'll probably end up looking for ways to store the data between runs, update the values and so forth. I strongly suggest looking at starting with a database and learning how to do things there. It's basically the same, you just end up with a lot more flexibility in the way you handle the data. The code to do the actual queries looks basically the same.
Once you have the data in your program, there are very few limits on how you can manipulate it. Anything you can do in Excel with the data, you can do in C#. Usually easily, sometimes with a little work.
LINQ (Language-Integrated Native Query) makes a lot of those manipulations trivial, with extensions for all sorts of things. You can take the average performance (with .Average()) and then filter on those that perform 10% above it with some simple math. If the data follows some sort of Normal Distribution you can add your own extension (or use this one) to figure out the standard deviation... and now we're doing statistics!
The basic concepts of LINQ, and the database languages it was roughly based on, give you plenty of expressive power. And Stack Overflow is full of people who can help you figure out how to get there.
try following :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
List<Stock> stocks = null;
public Form1()
{
InitializeComponent();
stocks = new List<Stock>() {
new Stock("ABC", 50, 14000),
new Stock("DEF", 50, 105000),
new Stock("GHI", -70, 123080),
new Stock("JKL", -70, 56500),
new Stock("MNO", 50, 23500)
};
}
private void button1_Click(object sender, EventArgs e)
{
DataTable dt = Stock.GetTable(stocks);
dataGridView1.DataSource = dt;
}
}
public class Stock {
public string StockSymbol { get; set; }
public double Profit { get; set; }
public int Volume { get; set; }
public Stock(string Symbol, double p, int v) {
StockSymbol = Symbol;
Profit = p;
Volume = v;
}
public static DataTable GetTable(List<Stock> stocks)
{
DataTable dt = new DataTable();
dt.Columns.Add("Symbol", typeof(string));
dt.Columns.Add("Profit", typeof(int));
dt.Columns.Add("Volume", typeof(int));
dt.Columns.Add("Volume x Profit", typeof(int));
foreach(Stock stock in stocks)
{
dt.Rows.Add(new object[] { stock.StockSymbol, stock.Profit, stock.Volume, stock.Profit * stock.Volume });
}
dt = dt.AsEnumerable().OrderByDescending(x => x.Field<int>("Volume x Profit")).CopyToDataTable();
return dt;
}
}
}

Fill the first missing null elements after shifting and rolling window

I'm recreating a strategy made in python with pandas. I think my code works, even tho I haven't compared the values yet, because I'm getting an exception. Basically, the problem is that .Shift(20) removes the first 20 elements and .Window(12 * 60 / 15) removes 47 elements. The typical prices are 10180 by default. They become 10113 after the shifting and rolling window. I tried using .FillMissing(), but it doesn't seem to append the first null values to the series.
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if not {'buy', 'sell'}.issubset(dataframe.columns):
dataframe.loc[:, 'buy'] = 0
dataframe.loc[:, 'sell'] = 0
dataframe['typical'] = qtpylib.typical_price(dataframe)
dataframe['typical_sma'] = qtpylib.sma(dataframe['typical'], window=10)
min = dataframe['typical'].shift(20).rolling(int(12 * 60 / 15)).min()
max = dataframe['typical'].shift(20).rolling(int(12 * 60 / 15)).max()
dataframe['daily_mean'] = (max+min)/2
return dataframe
My code (C#)
public override List<TradeAdvice> Prepare(List<OHLCV> candles)
{
var result = new List<TradeAdvice>();
var typicalPrice = candles.TypPrice().Select(e => e ?? 0).ToList();
var typicalSma = typicalPrice.Sma(10);
var series = typicalPrice.ToOrdinalSeries();
var min = series.Shift(20).Window(12 * 60 / 15).Select(kvp => kvp.Value.Min()).FillMissing(); // 10113 elements / 10180 expected
var max = series.Shift(20).Window(12 * 60 / 15).Select(kvp => kvp.Value.Max()).FillMissing(); // 10113 elements / 10180 expected
var dailyMean = (max + min) / 2;
var asd = dailyMean.SelectValues(e => Convert.ToDecimal(e)).Values.ToList();
var crossedBelow = asd.CrossedBelow(typicalPrice);
var crossedAbove = asd.CrossedAbove(typicalPrice);
for (int i = 0; i < candles.Count; i++)
{
if (i < StartupCandleCount - 1)
result.Add(TradeAdvice.WarmupData);
else if (crossedBelow[i]) // crossBelow is 10113 elements instead of 10180...
result.Add(TradeAdvice.Buy);
else if (crossedAbove[i]) // crossBelow is 10113 elements instead of 10180...
result.Add(TradeAdvice.Sell);
else
result.Add(TradeAdvice.NoAction);
}
return result;
}
public class OHLCV
{
public DateTime Timestamp { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Volume { get; set; }
}
If you have an ordinal series that you create with ToOrdinalSeries, it means that the index of the series will be automatically generated numerical value from 0 to length of your series - 1. However, this is still a real index and Deedle keeps the mapping when you use operations like Shift.
If your index was a date, say 01/01 => a, 02/01 => b, 03/01 => c, then Shift would shift the values and drop the keys that are no longer needed, i.e. you may get 02/01 => a, 03/01 => b.
It works the same with ordinal indices, so if you have 0 => a, 1 => b, 2 => c and shift the data, you will get something like 1 => a, 2 => b.
If you then want to get 0 => <default>, 1 => a, 2 => b, then you can do this using Realign which takes the new list of keys that you want to have followed by FillMissing. For example:
var ts = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }.ToOrdinalSeries();
var mins = ts.Shift(2).Window(2).Select(kvp => kvp.Value.Min());
var realigned = mins.Realign(Enumerable.Range(0, 10)).FillMissing(-1);
ts.Print(); // Starts from key '0'
mins.Print(); // Starts from key '3' because of Shift & Window
realigned.Print(); // Starts from key '0' with three -1 values at the start

Constructing a list of strings following a statistical breakdown

I'm building a function that will construct a list of strings by using a statistical breakdown provided as an argument. I'm looking for a more elegant and precise way to accomplish this. Here is a working example of what I have so far. This is working currently for simple cases, but I anticipate more complex cases causing issue (math rounding, etc.)
public static void StatisticalList()
{
List<string> statisticalList = new List<string>();
int totalCount = 500;
// end goal is 30% of our result list has the value "1"
List<string> entry1 = new List<string>() { "30", "1" };
// end goal is 40% of our result list has the value "2"
List<string> entry2 = new List<string>() { "40", "2" };
// end goal is 30% of our result list has the value "3"
List<string> entry3 = new List<string>() { "30", "3" };
List<List<string>> container = new List<List<string>>(){entry1, entry2, entry3};
foreach(List<string> entry in container)
{
double doub = Convert.ToDouble(entry[0]);
double percentage = doub / 100;
double numberOfElements = (double) percentage * totalCount;
for (int i = 0; i < numberOfElements; i++)
{
statisticalList.Add(entry[1]);
}
}
foreach (string i in statisticalList)
{
Console.WriteLine(i);
}
}
There are a couple of things I would do to make this "more elegant". First, I would create a class to represent a rule, which is a "Goal Percent" and a "Value":
public class StatisticalRule
{
public double PercentGoal { get; set; }
public double Value { get; set; }
}
Then, I would create a method that takes in a list of these rules, along with a desired list size, and returns a list populated with the values. I've added some logic to your code to adjust the percentage of each item in case the total is less than or greater than 100 percent (another option would be to just throw an exception if the totals don't add up to 100).
For example, if someone added 3 items where one was 60%, one was 50%, and another was 40%, we have to adjust those amounts (we can't fill 150%). So what I've done is determine the total amount we need to adjust (-50 in this example), and then, for each item, calculate what percent of the total that item's PercentGoal represents, apply it to the adjustment amount, and apply that to the rule's percentGoal (so in this example, 60% = 40%, 50% = 33%, and 40% = 27%), and then populate the list with these adjusted percentages.
I also use Enumerable.Repeat to add items to the list, which is a little more elegant than the loop construct.
public static List<double> GetStatisticalList(List<StatisticalRule> rules, int totalCount)
{
List<double> statisticalList = new List<double>();
// Capture any difference between our total percentages and 100 percent
var totalPct = rules.Sum(r => r.PercentGoal);
var pctDiff = 100 - totalPct;
foreach (var rule in rules)
{
// Calculate the percentage of the total this value represents
var pctOfTotal = rule.PercentGoal / totalPct * 100;
// Calculate the amount we need to adjust this
// percentage by so the totals equal 100
var pctAdjustment = pctDiff * pctOfTotal / 100;
// Determine the number of items to add by adding our adjustment to
// our percentage goal and applying that percentage to the totalCount
var numItems = (int) ((rule.PercentGoal + pctAdjustment) / 100 * totalCount);
// Add the adjusted amount of this value to our list
statisticalList.AddRange(Enumerable.Repeat(rule.Value, numItems));
}
return statisticalList;
}
Notice I'm returning a list of double instead of string. The method itself should work directly with the data type it expects when possible, and leave it to the caller to do any conversions. This makes for cleaner, more intentional code, and creates fewer assumptions.
To use this code with your example above, you would do something like this:
static void Main()
{
// Create our rules
var statRules = new List<StatisticalRule>
{
new StatisticalRule {PercentGoal = 30, Value = 1},
new StatisticalRule {PercentGoal = 40, Value = 2},
new StatisticalRule {PercentGoal = 30, Value = 3},
};
// Get our 500 item stat list with rules applied
var statList = GetStatisticalList(statRules, 500);
// Display the statistics
Console.WriteLine($"Our statistics list contains {statList.Count} items:");
foreach (var uniqueValue in statList.Distinct())
{
var valueCount = statList.Count(i => i == uniqueValue);
Console.WriteLine(" - Value: {0}, Count: {1}, Percent of Total: {2}%",
uniqueValue, valueCount, (double)valueCount / statList.Count * 100);
}
Console.Write("\nDone!\nPress any key to continue...");
Console.ReadKey();
}
Output

Creating a two-dimensional array

I am trying to create a two dimensional array and I am getting so confused. I was told by a coworker that I need to create a dictionary within a dictionary for the array list but he couldn't stick around to help me.
I have been able to create the first array that lists the the programs like this
+ project 1
+ project 2
+ project 3
+ project 4
The code that accomplishes this task is below-
var PGList = from x in db.month_mapping
where x.PG_SUB_PROGRAM == SP
select x;
//select x.PG.Distinct().ToArray();
var PGRow = PGList.Select(x => new { x.PG }).Distinct().ToArray();
So that takes care of my vertical array and now I need to add my horizontal array so that I can see the total amount spent in each accounting period. So the final output would look like this but without the dashes of course.
+ program 1-------100---200---300---400---500---600---700---800---900---1000---1100---1200
+ program 2-------100---200---300---400---500---600---700---800---900---1000---1100---1200
+ program 3-------100---200---300---400---500---600---700---800---900---1000---1100---1200
+ program 4-------100---200---300---400---500---600---700---800---900---1000---1100---1200
I have tried to use a foreach to cycle through the accounting periods but it doesn't work. I think I might be on the right track and I was hoping SO could provide some guidance or at the very least a tutorial for me to follow. I have posted the code that I written so far on the second array below. I am using C# and MVC 3. You might notice that their is no dictionary within a dictionary. If my coworker is correct how would I do something like that, I took a look at this question using dictionary as a key in other dictionary but I don't understand how I would use it in this situation.
Dictionary<string, double[]> MonthRow = new Dictionary<string, double[]>();
double[] PGContent = new double[12];
string lastPG = null;
foreach (var item in PGRow)
{
if (lastPG != item.PG)
{
PGContent = new double[12];
}
var MonthList = from x in db.Month_Web
where x.PG == PG
group x by new { x.ACCOUNTING_PERIOD, x.PG, x.Amount } into pggroup
select new { accounting_period = pggroup.Key.ACCOUNTING_PERIOD, amount = pggroup.Sum(x => x.Amount) };
foreach (var P in MonthList)
{
int accounting_period = int.Parse(P.accounting_period) - 1;
PAContent[accounting_period] = (double)P.amount;
MonthRow[item.PG] = PGContent;
lastPG = item.PG;
}
I hope I have clearly explained my issue, please feel free to ask for any clarification needed as I need to solve this problem and will be checking back often. Thanks for your help!
hope this helps.
// sample data
var data = new Dictionary<string, List<int>>();
data.Add("program-1", new List<int>() { 100, 110, 130 });
data.Add("program-2", new List<int>() { 200, 210, 230 });
data.Add("brogram-3", new List<int>() { 300, 310, 330 });
// query data
var newData = (from x in data
where x.Key.Contains("pro")
select x).ToDictionary(v => v.Key, v=>v.Value);
// display selected data
foreach (var kv in newData)
{
Console.Write(kv.Key);
foreach (var val in kv.Value)
{
Console.Write(" ");
Console.Write(val.ToString());
}
Console.WriteLine();
}
output is:
program-1 100 110 130
program-2 200 210 230
Don't try to use anonymous types or LINQ projection to create new data types, especially if you're a beginner, you will just get confused. If you want a specialized data type, define one; e.g.:
public class Account
{
public string Name { get; private set; }
public decimal[] MonthAmount { get; private set; }
readonly int maxMonths = 12;
public Account(string name, ICollection<decimal> monthAmounts)
{
if (name == null)
throw new ArgumentNullException("name");
if (monthAmounts == null)
throw new ArgumentNullException("monthAmounts");
if (monthAmounts.Count > maxMonths)
throw new ArgumentOutOfRangeException(string.Format(" monthAmounts must be <= {0}", maxMonths));
this.Name = name;
this.MonthAmount = new decimal[maxMonths];
int i = 0;
foreach (decimal d in monthAmounts)
{
this.MonthAmount[i] = d;
i++;
}
}
}
Use instances of this type directly, you do not have to convert them to arrays, dictionaries, lists, or anything else:
var accountPeriods = new List<Account>();
accountPeriods.Add(new Account("program-1", new decimal[] { 1, 2, 3, 4 }));
You can use LINQ or whatever to query or alter instances of your new type:
foreach (Account a in accountPeriods)
foreach (decimal d in a.MonthAmount)
DoSomethingWith(d);
That should be enough to get you started.
I want to thank #Ray Cheng and #Dour High Arch for their help but I have figured out another way to accomplish this task and I wanted to post my code so that the next person that is having the same trouble can figure out their problem faster.
Above I split my code into more managable sections to explain my problem as clearly as I could and the code below has all those parts combined so you can see the big picture. This code returns an array that contains the program and the amounts for every month.
public virtual ActionResult getAjaxPGs(string SP = null)
{
if (SP != null)
{
var PGList = from x in db.month_mapping
where x.PG_SUB_PROGRAM == SP
select x;
var PGRow = PGList.Select(x => new { x.PG }).Distinct().ToArray();
float[] PGContent = new float[12];
Dictionary<string,float[]> MonthRow = new Dictionary<string, float[]>();
foreach (var item in PGRow)
{
PGContent = new float[12];
var MonthList = from x in db.month_Web
where x.PG == item.PG
group x by new { x.ACCOUNTING_PERIOD, x.PG, x.Amount } into pggroup
select new { accounting_period = pggroup.Key.ACCOUNTING_PERIOD, amount = pggroup.Sum(x => x.Amount) };
foreach (var mon in MonthList)
{
int accounting_period = int.Parse(mon.accounting_period) - 1;
PGContent[accounting_period] = (float)mon.amount/1000000;
}
MonthRow[item.PG] = PGContent;
}
return Json(MonthRow, JsonRequestBehavior.AllowGet);
}
return View();
}
This code worked great for me since I am pulling from a Linq to SQL query instead of adding data directly into the code. My problems stemmed from mainly putting the data pulls outside of the foreach loops so it only pulled 1 piece of data from the SQL instead of all twelve months. I hope this helps some one else who is trying to pull data in from SQL data sources into multidimensional arrays.

Categories