Outputting linq getting overflow exception - c#

I'm trying to output a certain answer with matching data using linq. Here's the code,
public string[] netoilVar(string[] final)
{
var items = netOil.Zip(seqNum, (oil, seq) => new {Oil = oil, Seq = seq });
var items2 = netOil2.Zip(seqNum2, (oil, seq) => new { Oil = oil, Seq = seq });
List<string> vars = new List<string>();
foreach (var item in items2.Join(items, i => i.Seq, i => i.Seq, (a, b) => new
{
x = a.Seq,
y = this.GetTheAnswer(Convert.ToDouble(a.Oil), Convert.ToDouble(b.Oil)),
oilnum1 = a.Oil,
oilnum2 = b.Oil,
}))
{
vars.Add(item.y + "," + item.oilnum1 + "," + item.oilnum2);
final = vars.ToArray();
}
return final;
}
//BEGINS THE EXECUTE BUTTON METHOD
private void executeBtn_Click(object sender, EventArgs e)
{
//NET OIL VARIANCE MATHEMATICS
if (netOilRadBtn.Checked)
{
int i = listHead;
string[] x = new String[1000];
StreamWriter sw = new StreamWriter("testNetOil.csv");
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, Net Oil Variance., Current Year's Net Oil, Last Year's Net Oil");
//Loops until the end of the list, printing out info
while (i != -1)
{
sw.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, {18}, {19}, {20}, {21}, {22}, {23}, {24}, {25}, {26}, {27}, {28}, {29}, {30}, {31}, {32}, {33}, {34}",
QuoteString(leaseName[i]), fieldName[i], QuoteString2(reservoir[i]), operator1[i], county[i], state[i], majo[i], resvCatgory[i], disRate[i], netOil2Int[i], netGas2Int[i], workingInt[i], grossWells[i]
, ultOil[i], ultGas[i], grossOil[i], grossNGL[i], grossGas[i], netOil[i], netGas[i], netNGL[i], revToInt[i], operExpense[i], totInvest[i], revOil[i], revGas[i], operatingProfit[i],
revNGL[i], discNetIncome[i], seqNum[i], wellID[i], incASN[i], lifeYears[i], ownQual[i], netoilVar(x)[i]);
i = pointers[i];
}
sw.Close();
}
I'm getting an IndexOutOfRangeException on the while loop that is printing out all the data, most specifically on the netoilVar(x)[I] part. Is there any way I can get the correct index there so I don't get the exception?

While loops can be dangerous as you have seen here. I would highly recommend abstracting out your data to make things easier to work with.
Model your data like this:
public class MyData
{
public string LeaseName { get; set; }
public int UltOil { get; set; }
public int UltGas { get; set; }
public static string GetHeaders()
{
return "LeaseName, UltOil, UltGas";
}
public override string ToString()
{
return string.Join(",", LeaseName, UltOil, UltGas);
}
}
Then when writing out your csv file use the model like this:
private void Foo()
{
//NET OIL VARIANCE MATHEMATICS
if (netOilRadBtn.Checked)
{
var input = new List<MyData>(); // <-- Fill in your data here.
StreamWriter sw = new StreamWriter("testNetOil.csv");
sw.WriteLine(MyData.GetHeaders());
//Loops until the end of the list, printing out info
foreach (var item in input)
{
sw.WriteLine(item);
}
sw.Close();
}
}
Obviously this is abbreviated, you will want to include all your fields, name and type them appropriately.

Related

a static class method not returning the right value

The idea of the program is to output the department which has the biggest salary combined by every person working in that department.
so I have my program.cs:
string print = string.Empty;
int n = int.Parse(Console.ReadLine());
for(int a = 0; a < n; a++)
{
string input = Console.ReadLine();
List<string> inputs = input.Split(" ").ToList();
if(inputs[4].Contains("#"))
{
Employee info = new Employee(inputs[0], double.Parse(inputs[1]), inputs[2], inputs[3], inputs[4], int.Parse(inputs[5]));
print = info.ToString();
}
else
{
Employee info = new Employee(inputs[0], double.Parse(inputs[1]), inputs[2], inputs[3], "n/a", int.Parse(inputs[4]));
print = info.ToString();
}
Employee.Calculation(inputs[3], double.Parse(inputs[1]));
}
Console.WriteLine(print);
and part of my Employee.cs, which is the inportant one:
public static void Calculation(string department, double salary)
{
Dictionary<string, double> data = new Dictionary<string, double>();
if (data.ContainsKey(department))
{
data[department] += salary;
}
else
{
data.Add(department, salary);
}
foreach (KeyValuePair<string, double> info in data)
{
if (info.Value > biggestSalary)
{
biggestSalary = info.Value;
toReturn = info.Key;
}
}
}
public override string ToString()
{
string line1 = "Highest average salary: " + toReturn;
return line1;
}
with this input:
4
Pesho 120000 Dev Daskalo pesho#abv.bg 28
Toncho 333333.33 Manager Marketing 33
Ivan 15000 ProjectLeader Development ivan#ivan.com 40
Gosho 130033333 Freeloader Nowhere 18
the last line is ignored for some reason when I debugged it and it returns the 2nd biggest salary - in department "Marketing".
with this input:
6
Stanimir 496.37 Temp Coding stancho#yahoo.com 50
Yovcho 610.13 Manager Sales 33
Toshko 609.99 Manager Sales toshko#abv.bg 44
Venci 0.02 Director BeerDrinking beer#beer.br 23
Andrei 700.00 Director Coding 45
Popeye 13.3333 Sailor SpinachGroup popeye#pop.ey 67
i get "Coding" instead of "Sales". When you combine the 2 people working "Coding" you get 700 + 496 = 1196. When you combine the 2 people working in "Sales" you get 609 + 610 = 1219 and then the output should be "Highest average salary: Sales", but instead the output is "Highest average salary: Coding";
You are creating a new dictionary every time the Calculation method is called.
Dictionary<string, double> data = new Dictionary<string, double>();
// The first block is never called as the Dictionary never contains anything at this point. The else block always runs.
if (data.ContainsKey(department))
{
data[department] += salary;
}
else
{
data.Add(department, salary);
}
There is therefore only ever one value in the dictionary for the one employee that that is meant to be added.
Since an employee with the Coding department has the highest individual value, that is the department that is returned.
Without commenting on the other aspects of the code the way to avoid this issue is to create the Dictionary first outside of the Calculation method.
Assuming that you would add Employee to the List<Employee>, finding this value using LINQ would look like
public class Employee
{
public string Name { get; set; }
public string Dept { get; set; }
public decimal Salary { get; set; }
}
// preload
var empList = new List<Employee>();
empList.Add(new Employee(){Name = "A", Salary = 10, Dept = "Sales" });
empList.Add(new Employee(){Name = "B", Salary = 10, Dept = "Coding" });
empList.Add(new Employee(){Name = "C", Salary = 30, Dept = "Sales" });
empList.Add(new Employee(){Name = "D", Salary = 20, Dept = "Coding" });
// execute
var topItem = empList.GroupBy(_ => _.Dept)
.Select(g => new { D = g.First().Dept, TS = g.Sum(s => s.Salary)})
.OrderByDescending(item => item.TS)
.First();
Console.WriteLine($"The department '{topItem.D}' has biggest salary: '{topItem.TS}'");
The department 'Sales' has biggest salary: '40'
Returning to the point that your code structure has a role in your problem. Even if you calculate this in the loop, you still want to accumulate your employees on the class/application scope variable, so you have continuous access to it.
As applicable to your case, your Employee info = new Employee seem not added to any list. And Employee.Calculation does not use any program-lever variable, which would keep the state.
If I wanted to keep this structure, the one you have, I could declare your class like
public class Employee
{
private static List<Employee> _empList = new List<Employee>();
// your constructor
public Employee (........)
{
// Assign your properties here
_empList.Add(this);
}
// And your `Employee.Calculation` would lose any parameters and look like this
public static void Calculation()
{
var topItem = _empList.GroupBy(_ => _.Dept).......
Console.WriteLine($"The department '{topItem.D}' has biggest salary: '{topItem.TS}'");
}
}
^^^ that is if I really wanted to fix the issue but keep the structure you have already

Sort list by price c#

I'm trying to sort a list based on the price for each item in the list.
Here's what I want my output to look like:
ROLLS_ROYCE1 -- 6.608 €
ROLLS_ROYCE3 -- 4.956 €
ROLLS_ROYCE2 -- 0.826 €
However, here's what the current output actually is:
ROLLS_ROYCE1 -- 6.608 €
ROLLS_ROYCE2 -- 0.82 €
ROLLS_ROYCE3 -- 4.956 €
Here's my code:
public void MyFunction()
{
List<string> mylist = new List<string>(new string[]
{
"ROLLS_ROYCE1 -- 0,826 € -- 8 PCS -- 14:02:53.876",
"ROLLS_ROYCE2 -- 0,826 € -- 1 PCS -- 17:02:53.888",
"ROLLS_ROYCE3 -- 0,826 € -- 6 PCS -- 18:09:55.888"
});
foreach (string f in mylist)
{
decimal b = Convert.ToDecimal(GetPrice(f), CultureInfo.GetCultureInfo("de-DE")) * Convert.ToDecimal(GetPieces(f));
tradesforbigbuyslist += GetName(f) + " -- " + b.ToString() + " €" +
Environment.NewLine;
}
string[] splittedt2 = tradesforbigbuyslist.Split(new string[] {
System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
listBox3.DataSource = splittedt2;
}
public string GetPrice (string sourceline)
{
string newstring = sourceline;
string test1 = newstring.Replace(FetchThemAll.SubstringExtensions.Before(newstring, "--"), "");
string textIWant = test1.Replace("--", "");
string finalPrice = FetchThemAll.SubstringExtensions.Before(textIWant, "€");
return finalPrice;
}
public string GetPieces(string sourceline)
{
string ertzu = sourceline;
string ertzu1 = FetchThemAll.SubstringExtensions.Between(ertzu, "€", "PCS");
string ertzu2 = ertzu1.Replace("--", "");
return ertzu2;
}
public string GetName(string sourceline)
{
string barno = FetchThemAll.SubstringExtensions.Before(sourceline, "--");
return barno;
}
How can I sort these strings correctly?
You could simplify a lot of this work by representing each line of input as a class with relevant properties like this. If accuracy is super important like with dealing real money then fixed precision data type should represent the price. However I am using double below for simplicity.
public class Car {
public string Name;
public short Pieces;
public double Price;
}
Then you would parse them at the beginning and have a list of these Car items. Assuming the Price above represents the desired value you wish to sort by the list you seek would be obtained by the following linq query.
var cars = new List<Cars>(); //Assumed definition
var frenchCulture = CultureInfo.CreateSpecificCulture("fr-FR"); //For Euros symbol usage later
//Parse Logic in Between
var sortedCars = cars.OrderByDescending(c => c.Price); //Linq Query yielding IEnumerable. If you must have a list simply append toList()
Then your output might be set like this.
foreach (var car in sortedCars)
// output with string.format("{0} -- {1}", car.Name, car.Price.ToString("C3", frenchCulture))
Warning that this code was not tested but should be approximately correct. I did do some research for the string format.
Well, the format of your strings in mylist looks consistent enough that something like this might work (without using extension methods or Regex at all):
var parsed = mylist.Select(line => line.Split(new[] { " -- " }, StringSplitOptions.None)).Select(parts => new
{
Name = parts[0],
Price = Convert.ToDecimal(parts[1].Substring(0, parts[1].IndexOf(' '))),
Pieces = Convert.ToInt32(parts[2].Substring(0, parts[2].IndexOf(' ')))
});
var sorted = parsed.OrderByDescending(x => x.Price * x.Pieces);
Then you can do whatever you want with sorted - e.g. convert the items back to strings and display them in listBox3.
Here is what I did: I have tested this and it seems to work.
public void MyFunction()
{
List<string> mylist = new List<string>(new string[]
{
"ROLLS_ROYCE1 -- 0,826 € -- 8 PCS -- 14:02:53.876",
"ROLLS_ROYCE2 -- 0,826 € -- 1 PCS -- 17:02:53.888",
"ROLLS_ROYCE3 -- 0,826 € -- 6 PCS -- 18:09:55.888"
});
var map = new Dictionary<string, double>();
foreach (string f in mylist)
{
var inputs = f.Split(" -- "); //Creates a list of strings
var unitPrice = Convert.ToDouble(inputs[1].Split(' ')[0]);
var numUnits = Convert.ToDouble(inputs[2].Split(' ')[0]);
var key = inputs[0];
if(map.ContainsKey(key)) map[key] = numUnits*unitPrice;
else map.Add(key, numUnits*unitPrice);
}
var sortedMap = map.OrderByDescending(x=>x.Value);
foreach(var item in sortedMap){
Console.WriteLine($"{item.Key} -- {item.Value} €");
}
}
It may be overkill for you, but what I usually do in cases like this is create a class that knows how to parse one of those lines into strongly typed properties, like Name, Price, Quantity, etc. Usually I create a static method named Price that takes in an input string and returns an instance of the class.
In this case it would look something like:
public class Item
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public TimeSpan Time { get; set; }
public decimal Total => Price * Quantity;
public static Item Parse(string input)
{
if (input==null) throw new ArgumentNullException();
var parts = input
.Split(new[] {"--", " ", "€", "PCS"},
StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 4)
throw new ArgumentException(
"Input must contain 4 sections separated by \"--\"");
decimal price;
if (!decimal.TryParse(parts[1], out price))
throw new ArgumentException(
"Price must be a valid decimal in the second position");
int quantity;
if (!int.TryParse(parts[2], out quantity))
throw new ArgumentException(
"Quantity must be a valid integer in the third position");
TimeSpan time;
if (!TimeSpan.TryParse(parts[3], out time))
throw new ArgumentException(
"Time must be a valid TimeSpan in the fourth position");
return new Item
{
Name = parts[0],
Price = price,
Quantity = quantity,
Time = time
};
}
}
With the work being done in the class, our main code is simplified tremendously:
List<string> mylist = new List<string>(new string[]
{
"ROLLS_ROYCE1 -- 0,826 € -- 8 PCS -- 14:02:53.876",
"ROLLS_ROYCE2 -- 0,826 € -- 1 PCS -- 17:02:53.888",
"ROLLS_ROYCE3 -- 0,826 € -- 6 PCS -- 18:09:55.888"
});
List<Item> orderedItems = mylist
.Select(Item.Parse)
.OrderByDescending(item => item.Total)
.ToList();
And then displaying the items would be as simple as:
orderedItems.ForEach(item => Console.WriteLine($"{item.Name} -- {item.Total} €"));
Output

Getting matching data to output with Linq

I have two files that are being read into separate arrays such as:
String[] leaseName2 = new String[1000];
String[] fieldName2 = new String[1000];
String[] reservoir2 = new String[1000];
String[] operator2 = new String[1000];
String[] county2 = new String[1000];
String[] state2 = new String[1000];
String[] majo2 = new String[1000];
String[] resvCatgory2 = new String[1000];
String[] netOil2 = new String[1000];
String[] netGas2 = new String[1000];
String[] netNGL2 = new String[1000];
String[] leaseName = new String[1000];
String[] fieldName = new String[1000];
String[] reservoir = new String[1000];
String[] operator1 = new String[1000];
String[] county = new String[1000];
String[] state = new String[1000];
String[] majo = new String[1000];
String[] resvCatgory = new String[1000];
String[] netOil = new String[1000];
String[] netGas = new String[1000];
String[] netNGL = new String[1000];
I then merge the two files using Linq, to merge two of the matching arrays such as netOil and netOil2 that give me a double answer. There are a bunch of different rows of data that are matched by the same seqNum arrays.
String[] seqNum2 = new String[1000]; //This will be the identifier
String[] seqNum = new String[1000]; //This will be the identifier
The problem I am having is outputting all of the corresponding data such as leaseName[] and fieldname[] and reservoir[] with the identifying seqNum array. Here is my Linq 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 items2.Join(items, i => i.Seq, i => i.Seq, (a, b) => new
{
SeqID = a.Seq,
Answer = this.GetTheAnswer(Convert.ToDouble(a.Oil), Convert.ToDouble(b.Oil)),
//OilNum1 = a.Oil, GIVES FIRST OIL FROM NET OIL ARRAY 1, item.oilnum1 will print out first oil #
//OilNum2 = b.Oil, GIVES SECOND OIL FROM NET OIL ARRAY 2, item.OilNum2 ********
}))
{
sw.WriteLine(item.SeqID + "," + item.Answer); //this prints out the seqNum and Answer that I want to match with all of the other data in the arrays
/* commented out to see what I've tried
int x = listHead;
x.Equals(item.SeqID);
while (x != -1)
{
sw.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, {18}, {19}, {20}, {21}, {22}, {23}, {24}, {25}, {26}, {27}, {28}, {29}, {30}, {31}, {32}, {33}, {34}, {35}, {36}",
QuoteString(leaseName[x]), fieldName[x], QuoteString2(reservoir[x]), operator1[x], county[x], state[x], majo[x], resvCatgory[x], disRate[x], netOil2Int[x], netGas2Int[x], workingInt[x], grossWells[x]
, ultOil[x], ultGas[x], grossOil[x], grossNGL[x], grossGas[x], netOil[x], netGas[x], netNGL[x], revToInt[x], operExpense[x], totInvest[x], revOil[x], revGas[x], operatingProfit[x],
revNGL[x], discNetIncome[x], seqNum[x], wellID[x], incASN[x], lifeYears[x], ownQual[x], prodTax[x], item.SeqID, item.Answer);
x = pointers[x];
//sw.WriteLine(item);
}*/
}
sw.Close();
}
}
I am not able to print out all of the data with the matching seqNum in the foreach loop, so I tried to create another loop but that was printing out a lot of extra data that was not useful. If anyone has any idea on how to print out all of the data with the seqNum after I get the Answer with the Linq code, I would appreciate it if you could let me know. Any help would be greatly appreciated.
I would suggest that you learn and practice some basic intro programming and OOP before attempting to use C# and LinQ.
First of all, the sole idea of having 11 arrays of strings is disgusting. You should learn how to create a proper data model and do so.
public class MyRecord
{
public int SeqNum {get;set;} // Notice the proper case in property names
public string LeaseName {get;set;}
public string FieldName {get;set;}
public string Reservoir {get;set;}
//... Etc.
}
Then, take a second to analyze whether it is a good idea that ALL your properties be string. For example, if a property can only have numeric values, it would be better that you strongly type that and use int or double. If a property can only have "true" or "false" values, that would be a bool, and so on.
The problem I am having is outputting all of the corresponding data
such as leaseName[] and fieldname[] and reservoir[] with the
identifying seqNum array.
That's because your data is modeled in the worst possible way. If you create a class like above, you will not have that problem, because all data pertaining to the same record will be in the same instance of this class. Then:
List<MyRecord> File1 {get;set;}
List<MyRecord> File2 {get;set;}
var myrecord = File1.FirstOrDefault(x => x.SeqNum == someSeqNum);
var leasename = myrecord.LeaseName;
var reservoir = myrecords.Reservoir;
//... etc
See how easy life actually is?
To top it all, PLEASE, for the sake of mankind, REMOVE your business logic from the code behind the UI. Create proper SERVICES to do that, with relevant methods with relevant parameters, in order to isolate this functionality and be able to REUSE it.
private void executeBtn_Click(object sender, EventArgs e)
{
MyService.ProcessRecords(relevant,parameters,from,the,UI);
}

Count all duplicates items and determine average time of each duplicate ocurrance of array of strings

I have an array that contains data as follows (string and time data separated by a comma):
array[0]= Channel 1, 01:05:36
array[1]= Channel 2, 02:25:36
array[2]= Group 1, 22:25:36
array[3]= Netwk, 41:40:09
array[4]= LossOf, 03:21:17
array[5]= LossOf, 01:13:28
array[6]= Channel 1, 04:25:36
array[7]= Channel 2, 00:25:36
.
.
.
array[xxx]= xxx, xxx
I would like to count all duplicates items and determine average time for each duplicate found as follows:
Item1, Channel 1, 2 occurrences, average time for each occurrence is about xx minutes
Item2, Channel 2, 2 occurrences, average time for each occurrence is about xx minutes
Item3, LossOf, 2 occurrences, average time for each occurrence is about xx minutes
The time format is hh:mm:ss
This is what I have done so far, which only gives me total times of duplicates:
public void CountDuplicates(string[] myStringArray)
{
//count duplicates
ArrayList list = new ArrayList();
int loopCnt=0;
foreach (string item in myStringArray)
{
if (!String.IsNullOrEmpty(myStringArray[loopCnt]) == true)
list.Add(item);
loopCnt++;
}
loopCnt = 0;
Dictionary<string, int> distinctItems = new Dictionary<string, int>();
foreach (string item in list)
{
if (!distinctItems.ContainsKey(item))
{
distinctItems.Add(item, 0);
loopCnt++;
}
distinctItems[item] += 1;
}
foreach (KeyValuePair<string, int> distinctItem in distinctItems)
{
txtDisplayResults.AppendText("Alarm Error: " + distinctItem.Key + ", How many times: " + distinctItem.Value + "\r\n");
}
}
Maybe this:
a channel class to hold your values
class Channel
{
public String Name { get; set; }
public TimeSpan Duration { get; set; }
}
your sample data
var array = new[]{
"Channel 1, 01:05:36",
"Channel 2, 02:25:36",
"Group 1, 22:25:36",
"Network, 41:40:09",
"Loss of, 03:21:17",
"Loss of, 01:13:28",
"Channel 1, 04:25:36",
"Channel 2, 00:25:36",
};
the query
var channelGroups = array.Select(s =>
{
var tokens = s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var tsTokens = tokens[1].Split(':');
return new Channel()
{
Name = tokens[0],
Duration = new TimeSpan(
int.Parse(tsTokens[0]), // hours
int.Parse(tsTokens[1]), // minutes
int.Parse(tsTokens[2])) // seconds
};
})
.GroupBy(c => c.Name)
.Select(g => new
{
Channel = g.Key,
Count = g.Count(),
Average = g.Average(c => c.Duration.TotalMinutes)
});
output:
foreach(var group in channelGroups)
{
Console.WriteLine("Channel:[{0}] Count:[{1}] Average:[{2}]"
, group.Channel, group.Count, group.Average);
}
demo: http://ideone.com/6dF6s
Channel:[Channel 1] Count:[2] Average:[165.6]
Channel:[Channel 2] Count:[2] Average:[85.6]
Channel:[Group 1] Count:[1] Average:[1345.6]
Channel:[Network] Count:[1] Average:[2500.15]
Channel:[Loss of] Count:[2] Average:[137.375]
something like to following help?
var regex=new Regex(#"^(?<item>.*), (?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})$");
var result=array.Select(a=> regex.Match(a)).Where(a=>a.Success)
.Select (a => new {
item=a.Groups["item"].Value,
time=decimal.Parse(a.Groups["hours"].Value)*60 +
decimal.Parse(a.Groups["minutes"].Value) +
decimal.Parse(a.Groups["seconds"].Value)/60
})
.GroupBy (a => a.item)
.Select (a =>new {item=a.Key, duplicates=a.Count(),time=a.Average (b => b.time)} );
to format as a string you can then do:
var resultString=result.Aggregate(new StringBuilder(),(sb,a)=>sb.AppendFormat("Item: {0}, # of occurences: {1}, Average Time: {2:0.00}\r\n",a.item,a.duplicates,a.time)).ToString();
resultString will now be the string you are looking for.
EDIT -- After request to use a dictionary, as tuple is unavailable you need to define a class to hold the temporary data. I've used pulbic fields but you could easily expand to use properties round private fields
public class Data {
public int Occurrences;
public decimal Time;
public Data(int occurrences, decimal time) {
this.Occurrences=occurrences;
this.Time=time;
}
}
var regex=new Regex(#"^(?<item>.*), (?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})$");
var dict = new Dictionary<string,Data>();
foreach (var entry in array) {
if (regex.IsMatch(entry)) {
var match=regex.Match(entry);
var item=match.Groups["item"].Value;
var time=decimal.Parse(match.Groups["hours"].Value)*60 +
decimal.Parse(match.Groups["minutes"].Value) +
decimal.Parse(match.Groups["seconds"].Value)/60;
if (dict.ContainsKey(item)) {
dict[item].Occurrences++;
dict[item].Time+=time);
} else {
dict[item]=new Data(1,time);
}
}
}
StringBuilder sb=new StringBuilder();
foreach (var key in dict.Keys) {
sb.AppendFormat("Item: {0}, # of occurences: {1}, Average Time: {2:0.00}\r\n", key, dict[key].Occurrences, dict[key].Time / dict[key].Occurrences);
}
var resultString=sb.ToString();

Linq Aggregate on object and List

I do this query with NHibernate:
var test = _session.CreateCriteria(typeof(Estimation))
.SetFetchMode("EstimationItems", FetchMode.Eager)
.List();
An "Estimation" can have several "EstimationItems" (Quantity, Price and ProductId)
I'd like a list of "Estimation" with these constraints :
One line by "Estimation" code on the picture (ex : 2011/0001 and 2011/0003)
By estimation (means on each line) the number of "EstimationItems"
By Estimation (means on each line) the total price (Quantity * Price) for each "EstimationItems"
I hope the structure will be clearer with the picture below.
Thanks,
Here's a proposition:
var stats =
from estimation in test
group estimation by estimation.code into gestimation
let allItems = gestimation.SelectMany(x => x.EstimationItems)
select new
{
Code = gestimation.Key,
ItemNumber = allItems.Count(),
TotalPrice = allItems.Sum(item => item.Price * item.Quantity)
};
Now this creates an anonymous type with the three properties you wanted (code of the estimation, number of items for this estimation code, and total price of the items for this estimation code).
You can adapt it to specific needs. Just bear in mind that allItems is a IEnumerable<EtimationItem> containing all the EstimationItem belonging to a Estimation with the same code.
If you want to use this object outside the scope of the method creating it, which you can't do with anonymous types, then you should create a class to hold these values.
Corrected proposition:
proposition:
var stats =
(from est in test.Cast<Estimation>()
group est by est.code into gEst
let allItems = gEst.SelectMany(est => est.EstimationItems).Cast<EstimationItem>()
select new TestingUI
{
Code = gEst.Key,
Quantity = gEst.Count(),
Total = gEst.Sum(item => item.Price * item.Quantity)
}).ToList();
Dictionary<string, Tuple<int,decimal>> dico = new Dictionary<string, Tuple<int,decimal>>();
foreach (var itemEstimation in test)
{
Estimation estimation = (Estimation)itemEstimation;
if (dico.ContainsKey(estimation.Code) == false)
{
decimal total = 0;
foreach (var item in estimation.EstimationItems)
{
EstimationItem estimationItem = (EstimationItem)item;
total += item.Price * item.Quantity;
}
dico.Add(estimation.Code, new Tuple<int, decimal>(estimation.EstimationItems.Sum(x => x.Quantity), total));
}
}
List<TestingUI> finalResult = new List<TestingUI>();
foreach (var item in dico)
{
Tuple<int, decimal> result;
dico.TryGetValue(item.Key, out result);
finalResult.Add(new TestingUI() { Code = item.Key, Quantity = result.Item1, Total = result.Item2 });
}

Categories

Resources