Enumerable.Zip more than 2 collections? - c#

var productNameTags = document.DocumentNode.SelectNodes(textBox3.Text);
var priceTags = document.DocumentNode.SelectNodes(textBox2.Text);
var codeNameAndPrice = productNameTags.Zip(priceTags, (n, w) => new { productNameTags = n, priceTags = w });
int counter = 1;
if (codeNameAndPrice != null)
{
foreach (var nw in codeNameAndPrice)
{
label1.Visible = true;
label1.Text += counter + ". " + nw.productNameTags.InnerHtml + " - " + nw.priceTags.InnerHtml + "\n";
}
}
I have this code which looks at html tags and prints out a product name and a price from a website and prints out like this using .Zip:
Baseball - £5.00
Football - £10.00
Toy Car - £15.00
Is there a simple way of adding three or more variables to zip together using a different method?
e.g.
Baseball - £5.00 - 1123
Football - £10.00 - 1124
Toy Car - £15.00 - 1125
Thanks in advance!

I don't think you can do it easily with .Zip (bar multiple levels of zip?), but you could do it dynamically:
var names = new[] { "Baseball", "Football", "Toy Car" };
var prices = new[] { 5.0M, 10.0M, 15.0M };
var ids = new[] { 1123, 1124, 1125 };
var products = names
.Select((name, index) => new { Name = name, Price = prices[index], Id = ids[index] });

No.
You can chain Zip calls, or you can write (or find) extension/helper method that would walk multiple sources in parallel similar to Zip to provide functionality you are looking for.
In your particular case consider iterating over some parent nodes and selecting individual child nodes relative to parent (assuming all nodes for particular item are children of single "parent" node).

Related

generate a trendline from files in a chart

I want to be able to fetch .csv files from a folder and plot them up in a chart.
Currently, I'm just saving the files and displaying an individual curve like this:
RunTest function:
public List<Tuple<double,double>> runTest()
{
_dpg = new Root(this, "english", false);
_dpg.Init();
var filename = "Dpg10Export_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".csv";
List<Tuple<double, double>> results = new List<Tuple<double, double>>();
var measurement = new Measurement();
var resultCode = RunMeasurement(60, 1000, 2200, ref measurement, null /* TODO: ref ProgressBar? */);
using (var fileStream = new StreamWriter(filename))
{
var count = measurement.I_inc_Values.Count;
for (var i = 0; i < count; i++)
{
var item = measurement.I_inc_Values[i + 1];
var current = (float)Convert.ToDouble(item);
item = measurement.LI_inc_Values[i + 1];
var inductance = (float)Convert.ToDouble(item);
var line = current.ToString() + ";" + inductance.ToString() + ";";
fileStream.WriteLine(line);
currentList.Add(current);
inductanceList.Add(inductance);
results.Add(new Tuple<double, double>(current,inductance));
if (i == 0 || (i + 1) % 32 == 0)
{
Console.WriteLine((i + 1) + ": " + line);
}
}
}
return results;
}
This code produces a csv-file that looks something like this:
0,22 | 0,44
0,32 | 0,54
0,44 | 0,65
And those values produce a curve that looks like this:
When you click on the "get Waveform" button, the curve above is generated. However, I want to display all the curves that has been generated, and a trendline as well. How would I achieve this?
void BindData(List<Tuple<double,double>> results)
{
chart.Series.Clear();
var series1 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = "curr/induc",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = true,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
foreach (var i in results)
{
series1.Points.AddXY(i.Item2,i.Item1);
}
chart.Invalidate();
chart.Series.Add(series1);
}
private void getWaveformBtn_Click(object sender, EventArgs e)
{
Dpg10Client.Dpg10Settings settings = new Dpg10Client.Dpg10Settings();
Dpg10Instrument hej = new Dpg10Instrument(settings);
List<Tuple<double,double>> results = hej.runTest();
double current, inductance;
foreach(var i in results)
{
current = i.Item1;
inductance = i.Item2;
textBoxWaveformInput.Text += current.ToString() + inductance.ToString();
}
BindData(results);
}
TL;DR
Parse the information from the CSV files to generate curves, and create a trendline based on those files.
Marked as duplicate: That answer is regarding a straight line, these values can fluctuate in a curve.
There are many ways to solve most of the various problems in your question.
Let me tackle only the one that actually has to do with calculating an average from multiple series, i.e. will not deal with creating a Series with data from a CSV file.
There is a built-in class that can do all sorts of advanced math, both financial and statistical, but I didn't find one that will help in creating an average line/curve over more than one series.
So let's do it ourselves..
The first issue is that to calculate averages we need not just data but the data, that is their x-values, must be grouped into 'bins' from which we want to get the averages.
Unless the data already are grouped like that, e.g. because they have one value per series per day, we need to create such groups.
Let's first collect all the points from all series we want to handle; you may need to adapt the loop to include just your set of series..:
var allPoints = new List <DataPoint>();
for (int s = 0; s < 3; s++) // I know I have created these three series
{
Series ser = chartGraphic.Series["S" + (s+1)];
allPoints.AddRange(ser.Points);
}
Next we need to decide on a bin range/size, that is a value that determines which x-values shall fall into the same bin/group. I chose to have 10 bins.
So we best get the total range of the x-values and divide by the number of bins we want..:
double xmax = allPoints.Max(x => x.XValue);
double xmin = allPoints.Min(x => x.XValue);
int bins = 10;
double groupSize = (xmax - xmin) / (bins - 1);
Next we do the actual math; for this we order our points, group them and select the average for each grouped set..:
var grouped = allPoints
.OrderBy(x => x.XValue)
.GroupBy(x => groupSize * (int)(x.XValue /groupSize ))
.Select(x => new { xval = x.Key, yavg = x.Average(y => y.YValues[0]) })
.ToList();
Now we can add them to a new series to display the averages in the bins:
foreach (var kv in grouped) avgSeries.Points.AddXY(kv.xval + groupSize/2f, kv.yavg);
I center the average point in the bins.
Here is an example:
A few notes on the example:
The average line doesn't show a real 'trend' because my data are pretty much random.
I have added Markers to all series to make the DataPoints stand out from the lines. Here it is how I did it for the averages series:
avgSeries.MarkerStyle = MarkerStyle.Cross;
avgSeries.MarkerSize = 7;
avgSeries.BorderWidth = 2;
I have added a StripLine to show the bins. Here is how:
StripLine sl = new StripLine();
sl.BackColor = Color.FromArgb(44,155,155,222);
sl.StripWidth = groupSize;
sl.Interval = groupSize * 2;
sl.IntervalOffset = chartGraphic.ChartAreas[0].AxisX.IntervalOffset;
chartGraphic.ChartAreas[0].AxisX.StripLines.Add(sl);
I have also set one point to have a really low value of -300 to demonstrate how this will pull down the average in its bin. To keep the chart still nicely centered on the normal range of data I have added a ScaleBreakStyle to the y-axis:
chartGraphic.ChartAreas[0].AxisY.ScaleBreakStyle.BreakLineStyle = BreakLineStyle.Wave;
chartGraphic.ChartAreas[0].AxisY.ScaleBreakStyle.Enabled = true;

c# creating multiple lists within for statement

Is there a way I can create multiple lists under a "for" statement, with an incrimental naming system (eg monster1, monster2, monster3). I've flagged it up under this is where I want an incrimental name within the code.
The number of lists needed to be generated is defined earlier on and I'm having no issues with that, but I'm getting "error CS0128: A local variable named `...' is already defined in this scope" if I attempt to assign the list name based on a variable with an inbuilt counter to incriment the list names (monsterList in this case).
Is there another way I can do this, so that no matter how many lists I need to be generated, it will create them and name them following this set pattern?
The below code is what I'm looking to have iterate a nuber of times (I'm new, so it's probably horribly inefficient, I'm just trying to get it doing what I want for now!):
for (int monstCounter = 2; monstCounter < totalTimes; monstCounter++)
{
Console.WriteLine();
string monsterLists = "Monster " + monstCounter;
string monsterList = "monsters"+monstCounter;
Console.WriteLine(monsterLists);
p = 0;
foreach (Monster aMonster in monsters)
{
if (monsters[p].MonsterName == monsterLists)
y = p;
p++;
}
y = monsters[y].MonsterPopu;
//Create a list of individuals.
List<MonsterStatistics> **This is where I want an incrimental name** = new List<MonsterStatistics>();
totalTimes1 = y;
counter1 = 1;
for (int b=0; b < totalTimes1; b++)
{
// Add monsters to the list.
monsterList.Add(new MonsterStatistics() {oldAge = rndNum.Next(1,100), name = "Name"+counter1, coupled = false, genderGen = rndNum.Next(0,2)});
counter1++;
}
foreach (MonsterStatistics aMonster in monsterList){
if(aMonster.genderGen == 0)
aMonster.gender = "Male";
else if (aMonster.genderGen == 1)
aMonster.gender = "Female";
else
aMonster.gender = "Genderless";
aMonster.age = rndNum.Next(0,aMonster.oldAge);
Console.WriteLine(aMonster.name + " Age: " + aMonster.age + "/" + aMonster.oldAge + " " + aMonster.gender);
}
}
You can't create variables from a string, its not how variables work in C#.
What you cand o is use a Dictionary and assign for each monsters list a unique key (string), something like:
Dictionary<string, List<MonsterStatistics>> monstersStatistics = new Dictionary<string, List<MonsterStatistics>>();
// Add a monster (here you can name monster with an incrementing variable)
monstersStatistics.Add("monster1", new List<MonsterStatistics>());
// Then fill the monster's statistics
monstersStatistics["monster1"].Add(new MonsterStatistics() { oldAge = rndNum.Next(1, 100), name = "Name" + counter1, coupled = false, genderGen = rndNum.Next(0, 2) });

Differentiating between different values with a Line.Contains()

Something I was curious about, I'm coding a utility for a old game that I play and this allows for custom NPC's. Long story short, I'm coding a reader for these custom NPC files. I've gotten most of the reading down with a line.contains() method (all code will be shown later) but there's a problem. The file can contain either just "height" or "gfxheight" which both do different things. Using line.contains("width") will make it output both width and gfxwidth twice. I don't really know any good way to explain it so here's the file:
width=32
height=32
gfxwidth=64
gfxheight=32
nofireball=1
noiceball=1
noyoshi=1
grabside=0
The Console output when I read it in and do what I need to split the lines and such:
And here's the code I use for height and gfxheight (of course there are others but these are the only problems I have when reading):
if (line.Contains("height"))
{
var split = line.Split(new char[] { '=' }, 2);
decimal dc;
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
npcHeight.Value = Decimal.Parse(split[1].ToString());
npcHeight.Enabled = true;
npcHCb.Checked = true;
}
if (line.Contains("gfxheight"))
{
var split = line.Split(new char[] { '=' }, 2);
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
}
Of course there's also the code for width and gfxwidth and the other various codes but I'm not going to bother posting those because I can apply what I get for the height to those.
So what would I have to do to differentiate between them? Suggestions?
Thanks in advanced,
Mike
Read the file into a string array, then parse it into a dictionary.
var file = File.ReadAllLines(yourFile);
var config = (from line in file
let s = line.Split('=')
select new { Key = s[0], Value = s[1] })
.ToDictionary(x => x.Key, x => x.Value);
Now you can access anything you want by referencing the key:
var gfxHeight = config["gfxheight"]; // gfxHeight is a string containing "32"
If you know the value after the = is always a number, you could parse it:
var config = (from line in file
let s = line.Split('=')
select new { Key = s[0], Value = int.Parse(s[1]) })
.ToDictionary(x => x.Key, x => x.Value);
var gfxHeight = config["gfxheight"]; // gfxHeight is an int containing 32
Instead of trying to figure out what each line is before splitting it, try splitting it first. This parsing approach leverages the format of the file and has a much-reduced dependency on its data:
foreach (var line in lines) {
var data = line.Split('=', 2);
if (data.Length != 2) {
continue;
}
var attrib = data[0];
var value = data[1];
Console.WriteLine(attrib + " is equal to " + value);
switch (attrib) {
case "height":
// ...
break;
case "gfxheight":
// ...
break;
}
}
I figured out a solution actually! Be warned: I haven't refreshed the page yet to see any proposed answers.
if (line.Contains("width"))
{
if (line.Contains("gfx"))
{
var split = line.Split(new char[] { '=' }, 2);
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
npcWidth.Value = Decimal.Parse(split[1].ToString());
npcWidth.Enabled = true;
npcWCb.Checked = true;
}
else
{
var split = line.Split(new char[] { '=' }, 2);
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
pNpcWidth.Value = Decimal.Parse(split[1].ToString());
pNpcWidth.Enabled = true;
pNpcWidthCb.Checked = true;
}
}
Basically that ^
What it does is checks if the line is width with that line.Contains method. And if it does, it then checks to see if it contains gfx in it (as in gfxheight, gfxwidth, etc) and if it does, then that's the gfxheight or gfxwidth value. If not, it's the regular height/width.

How to merge values of two lists together

For example I have:
public static List<int> actorList = new List<int>();
public static List<string> ipList = new List<string>();
They both have various items in.
So I tried joining the values (string and int) together using a foreach loop:
foreach (string ip in ipList)
{
foreach (int actor in actorList)
{
string temp = ip + " " + actor;
finalList.Add(temp);
}
}
foreach (string final in finalList)
{
Console.WriteLine(finalList);
}
Although looking back at this, this was pretty stupid and obviously will not work, as the first forloop is nested.
My expected values for finalList list:
actorListItem1 ipListItem1
actorListItem2 ipListItem2
actorListItem3 ipListItem3
and so on..
So the values from the two lists are concatenated with each other - corresponding of their position in the lists order.
Use ZIP function of LINQ
List<string> finalList = actorList.Zip(ipList, (x,y) => x + " " + y).ToList();
finalList.ForEach(x=> Console.WriteLine(x)); // For Displaying
OR combine them in one line
actorList.Zip(ipList,(x,y)=>x+" "+y).ToList().ForEach(x=>Console.WriteLine(x));
What about some functional goodness?
listA.Zip(listB, (a, b) => a + " " + b)
Assuming you can use .NET 4, you want to look at the Zip extension method and the provided example:
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
// The following example concatenates corresponding elements of the
// two input sequences.
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
foreach (var item in numbersAndWords)
Console.WriteLine(item);
Console.WriteLine();
In this example, because there is no corresponding entry for "4" in words, it is omitted from the output. You would need to do some checking to make sure the collections are the same length before you start.
Loop over the indexes:
for (int i = 0; i < ipList.Count; ++i)
{
string temp = ipList[i] + " " + actorList[i];
finalList.Add(temp);
}
You may also want to add code before this to verify that the lists are the same length:
if (ipList.Count != actorList.Count)
{
// throw some suitable exception
}
for(int i=0; i<actorList.Count; i++)
{
finalList.Add(actorList[i] + " " + ipList[i]);
}

Help with Shannon–Fano coding

Hello I hope some one will help me with this=):
I have a set of some numbers, I need to divide them in two groups with approximately equal sum and assigning the first group with "1", second with "0", then divide each group the same way in to subgroups until subgroups will be one of number from set!
Picture explaining this crazy things):
pict
Here is the basic algorithm, if you know C# it's pretty simple to follow. And the program print the partitions as it explore the tree.
Note that there are some possible bugs and the code is light years away from the quality that a teacher would expect from an homework. (As i guess that it is an homework... if it's for work it's even worse my code would be pretty much Daily-WTF quality)
But it should allow you to understand the basic algorithm structure knowing that :
totalCount does the sum of the Count elements of the collection passed as parameter if you don't know Aggregate.
The other Aggregate usage is there for display, just ignore it.
I commented the sort as multiple elements have the same count and the C# sort function don't preserve order (And i wanted to get the same result as the wikipedia article)
The code:
var symbols = new[] {
new Symbol { Text = "A", Count=15, Probability=double.NaN, Code=""},
new Symbol { Text = "B", Count=7, Probability=double.NaN, Code="" },
new Symbol { Text = "C", Count=6, Probability=double.NaN, Code="" },
new Symbol { Text = "D", Count=6, Probability=double.NaN, Code="" },
new Symbol { Text = "E", Count=5, Probability=double.NaN, Code="" },
}.ToList();
Func<IEnumerable<Symbol>, int> totalCount =
symbols_ => symbols_.Aggregate(0, (a, s) => a + s.Count);
var total = totalCount(symbols);
foreach(var symbol in symbols)
{
symbol.Probability = total / symbol.Count;
}
//symbols.Sort((a, b) => b.Count.CompareTo(a.Count));
// Where is the Y-Combinator when you need it ?
Action<IEnumerable<Symbol>, string, int> recurse = null;
recurse = (symbols_, str, depth) => {
if (symbols_.Count() == 1)
{
symbols_.Single().Code = str;
return;
}
var bestDiff = int.MaxValue;
int i;
for(i = 1; i < symbols_.Count(); i++)
{
var firstPartCount = totalCount(symbols_.Take(i));
var secondPartCount = totalCount(symbols_.Skip(i));
var diff = Math.Abs(firstPartCount - secondPartCount);
if (diff < bestDiff) bestDiff = diff;
else break;
}
i = i - 1;
Console.WriteLine("{0}{1}|{2}", new String('\t', depth),
symbols_.Take(i).Aggregate("", (a, s) => a + s.Text + " "),
symbols_.Skip(i).Aggregate("", (a, s) => a + s.Text + " "));
recurse(symbols_.Take(i), str + "0", depth+1);
recurse(symbols_.Skip(i), str + "1", depth+1);
};
recurse(symbols, "", 0);
Console.WriteLine(new string('-', 78));
foreach (var symbol in symbols)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}", symbol.Code, symbol.Text,
symbol.Count, symbol.Probability);
}
Console.ReadLine();

Categories