c# creating multiple lists within for statement - c#

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

Related

How can I access multi-element List data stored in a public class?

My first question on SO:
I created this public class, so that I can store three elements in a list:
public class myMultiElementList
{
public string Role {get;set;}
public string Country {get;set;}
public int Commonality {get;set;}
}
In my main class, I then created a new list using this process:
var EmployeeRolesCountry = new List<myMultiElementList>();
var rc1 = new myMultiElementList();
rc1.Role = token.Trim();
rc1.Country = country.Trim();
rc1.Commonality = 1;
EmployeeRolesCountry.Add(rc1);
I've added data to EmployeeRolesCountry and have validated that has 472 lines. However, when I try to retrieve it as below, my ForEach loop only retrieves the final line added to the list, 472 times...
foreach (myMultiElementList tmpClass in EmployeeRolesCountry)
{
string d1Value = tmpClass.Role;
Console.WriteLine(d1Value);
string d2Value = tmpClass.Role;
Console.WriteLine(d2Value);
int d3Value = tmpClass.Commonality;
Console.WriteLine(d3Value);
}
This was the most promising of the potential solutions I found on here, so any pointers greatly appreciated.
EDIT: adding data to EmployeeRolesCountry
/*
Before this starts, data is taken in via a csvReader function and parsed
All of the process below is concerned with two fields in the csv
One is simply the Country. No processing necessary
The other is bio, and this itself needs to be parsed and cleansed several times to take roles out
To keep things making sense, I've taken much of the cleansing out
*/
private void File_upload_Click(object sender, EventArgs e)
{
int pos = 0;
var EmployeeRolesCountry = new List<myMultiElementList>();
var rc1 = new myMultiElementList();
int a = 0;
delimiter = ".";
string token;
foreach (var line in records.Take(100))
{
var fields = line.ToList();
string bio = fields[5];
string country = fields[4];
int role_count = Regex.Matches(bio, delimiter).Count;
a = bio.Length;
for (var i = 0; i < role_count; i++)
{
//here I take first role, by parsing on delimiter, then push back EmployeeRolesCountry with result
pos = bio.IndexOf('.');
if (pos != -1)
{
token = bio.Substring(0, pos);
string original_token = token;
rc1.Role = token.Trim();
rc1.Country = country.Trim();
rc1.Commonality = 1;
EmployeeRolesCountry.Add(rc1);
a = original_token.Length;
bio = bio.Remove(0, a + 1);
}
}
}
}
EDIT:
When grouped by multiple properties, this is how we iterate through the grouped items:
var employeesGroupedByRolwAndCountry = EmployeeRolesCountry.GroupBy(x => new { x.Role, x.Country });
employeesGroupedByRolwAndCountry.ToList().ForEach
(
(countryAndRole) =>
{
Console.WriteLine("Group {0}/{1}", countryAndRole.Key.Country, countryAndRole.Key.Role);
countryAndRole.ToList().ForEach
(
(multiElement) => Console.WriteLine(" : {0}", multiElement.Commonality)
);
}
);
__ ORIGINAL POST __
You are instantiating rc1 only once (outside the loop) and add the same instance to the list.
Please make sure that you do
var rc1 = new myMultiElementList();
inside the loop where you are adding the elements, and not outside.
All references are the same in your case:
var obj = new myObj();
for(i = 0; i < 5; i++)
{
obj.Prop1 = "Prop" + i;
list.Add(obj);
}
now the list has 5 elements, all pointing to the obj (the same instance, the same object in memory), and when you do
obj.Prop1 = "Prop" + 5
you update the same memory address, and all the pointers in the list points to the same instance so, you are not getting 472 copies of the LAST item, but getting the same instance 472 times.
The solution is simple. Create a new instance every time you add to your list:
for(i = 0; i < 5; i++)
{
var obj = new myObj();
obj.Prop1 = "Prop" + i;
list.Add(obj);
}
Hope this helps.

Why is the .Clear() function clearing the wrong list?

public void ConvertMoves()
{
for (int i = 0; i < maxDirections; i++)
{
Debug.Log("gimme tsMoves "+tSpossibleMoves[i].Count + " from " + this);
possibleAttacks[i] = tSpossibleAttacks[i];
possibleAttacksInactive[i] = tSpossibleAttacksInactive[i];
possibleAttackIndicators[i] = tSpossibleAttackIndicators[i];
possibleMoves[i] = tSpossibleMoves[i];
Debug.Log("Gimme moves(1) " + possibleMoves[i].Count + " from " + this);
}
for (int i = 0; i < maxDirections; i++)
{
tSpossibleAttacks[i].Clear();
tSpossibleAttacksInactive[i].Clear();
tSpossibleAttackIndicators[i].Clear();
tSpossibleMoves[i].Clear();
Debug.Log("Gimme moves(2) " + possibleMoves[i].Count + " from " + this);
}
}
so the Debug Log reports the following:
gimme tsMoves 2 from JeanArc(Clone) (JeanArc)
Gimme moves(1) 2 from JeanArc(Clone) (JeanArc)
sofar everything is doing fine but then...
Gimme moves(2) 0 from JeanArc(Clone) (JeanArc)
why does it clear the moves of whole different List variable ?
This doesn't create a copy of the list item:
possibleAttacks[i] = tSpossibleAttacks[i]
It simply copies the reference to the same object into a second variable, so possibleAttacks[i] and tSpossibleAttacks[i] now both point to the same item in memory. Think of it like having two credit cards to access one bank account.
You can read more about reference types here in Microsoft's docs.
As Heinzi pointed out in the comment below, you can copy your item (as it's a list) by calling:
possibleAttacks[i] = tSpossibleAttacks[i].ToList();
By the way, if you just want to assign tSpossibleAttacks[i] and then reset it, you could also just do this:
possibleAttacks[i] = tSpossibleAttacks[i];
tSpossibleAttacks[i] = new List<your_type_name_here>(); // this will overwrite the reference held by `tSpossibleAttacks[i]`.
Note that if your list contains reference types, you have a similar problem within the list, for example:
public class Test
{
public string Name { get; set; }
}
List<Test> list1 = new List<Test>();
list1.Add(new Test() { Name = "John" });
List<Test> list2 = list1.ToList();
Console.WriteLine(list1[0].Name); // John
Console.WriteLine(list2[0].Name); // John
list2[0].Name = "Fred";
Console.WriteLine(list1[0].Name); // Fred
Console.WriteLine(list2[0].Name); // Fred
So I'd recommend reading up on value types vs reference types and how references work in C#.
What #John said. You need to copy the lists.
for (int i = 0; i < maxDirections; i++)
{
Debug.Log("gimme tsMoves "+tSpossibleMoves[i].Count + " from " + this);
possibleAttacks[i] = tSpossibleAttacks[i];
tSpossibleAttacks[i] = new List<T>;
possibleAttacksInactive[i] = tSpossibleAttacksInactive[i];
tSpossibleAttacksInactive[i] = new List<U>();
possibleAttackIndicators[i] = tSpossibleAttackIndicators[i];
tSpossibleAttackIndicators[i] = new List<V>();
possibleMoves[i] = tSpossibleMoves[i];
tSpossibleMoves[i] = new List<Z>();
Debug.Log($"Gimme moves(1), i={i}: {possibleMoves[i].Count} from {this}");
Debug.Log($"Gimme moves(2) i={i}: {tpossibleMoves[i].Count} from {this}");
}
Example:
var l1 = new List<string>();
List<string> l2;
l1.Add("One");
l1.Add("Two");
l2 = l1;
l1 = new List<string>();
l1.Add("Three");
Console.WriteLine("L1:");
foreach (var elem in l1)
{
Console.WriteLine(elem);
}
Console.WriteLine("L2:");
foreach (var elem in l2)
{
Console.WriteLine(elem);
}
This prints:
L1:
Three
L2:
One
Two

Enumerable.Zip more than 2 collections?

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).

How do i use class array so that it does not rewrite the first array that is stored?

Umm, i'm creating some subscription form and the max is 5 subscriptions.
So, i have a class Member and in the class i'm using the Get&Set method thingy(i've finally sorta learnt this get&set thing haha &i hope i'm using it correctly).
private string name;
private string address;
public string Name { get { return name; } set { name = value; } }
public string Address { get { return address; } set { address = value; } }
In the class form
Member[] memberArray = new Member[5];
Member memberSub = new Member();
BtnAdd_Click
memberSub.Name = TbName.Text;
memberSub.Address = TbAddress.Text;
for (int i = 0; i < memberArray.Length; i++)
{
if (memberArray[i] == null)
{
memberArray[i] = memberSub;
MessageBox.Show(TbName.Text + " has been added as a subscriber.");
break;
}
}
When i click display, it'll show the last input, and the front inputs are lost, idk why it's happening and there is an error? i think it's cause of the null array data, but i'm not sure.
Unless there is something wrong with my display button codes..?
for (int i = 0; i < memberArray.Length; i++)
{
rtbDisplay.Text += "Name: " + memberArray[i].Name + Environment.NewLine
+ "Address: " + memberArray[i].Address + Environment.NewLine
"----------------------------------------------------"
+ Environment.NewLine;
}
you are changing one object state and passing it to all of the array items,so at last you will only have the last entered input in all array item because they are all referring to single object memberSub and other array items also have memberSub reference so they will display same result,you need to instantiate each object of array separate and then set their properties like this:
for (int i = 0; i < memberArray.Length; i++)
{
if (memberArray[i] == null)
{
memberArray[i] = new Member();
memberArray[i].Name = TbName.Text;
memberArray[i].Address = TbAddress.Text;
MessageBox.Show(TbName.Text + " has been added as a subscriber.");
break;
}
}
You are only using one Member instance, so you will end up with an array full of references to the same instance. When you assign the object to the array, it's just the reference that is stored, it doesn't create a copy of the object to store in the array.
Instead of using a single Member instance, create a new instance each time that you want to store it in the array.
Just declare a reference for the object:
Member memberSub;
Then when you need to use one, create a new one:
memberSub = new Member();
memberSub.Name = TbName.Text;
memberSub.Address = TbAddress.Text;
You can consider using a list instead of an array:
List<Member> memberList = new List<Member>(5);
It's easier to add items to a list. You just have to keep track of the length of the list. Something like:
if (memberList.Count < 5) {
memberList.Add(memberSub);
} else {
MessageBox.Show("No more members allowed.");
}
When you display the members, just loop through all the items in the list. As there are no empty items in the list you don't need to skip any items. You can access the items in the list just as you access the items in an array. The only change is that the size of the list is in the Count property where you use Length for the array:
for (int i = 0; i < memberArray.Count; i++) {
...

Using one array to track 3 pieces of data

I need your help with using one array to track 3 pieces of data. I have o use one array because it is school assignment and I am required to do so.
The array has to track Province ID, Region ID and the population for each region. There is 13 province and in each province there is 48 regions. Since the array is fixed size, I used a 624 by 3 multi dimensional array. The first column is for the provinceID, the second is for the regionID, and the third one is for the population.. I set the rows to 624 because there is 13 province and 48 regions in each province so 13*48 = 624.
I was able to insert my data and display them like this
ProvinceID # 1 RegionID # 1: 125000
Instead I would like to display the regions and population by province.
Something like this:
ProvinceID #1
RegionID # 1: 12000
RegionID # 2: 30000
ProvinceID #2
RegionID #1: 14000
RegionID #: 145000
Here is what I did
I declare a global array
int[,] census;
I initialize it on form initialize
census = new int[624,3];
Here is my insert
try
{
// declare variables
int prov, region, population;
prov = Convert.ToInt32(txtProvinceID.Text);
region = Convert.ToInt32(txtRegionID.Text);
population = Convert.ToInt32(txtPopulation.Text);
census[counter, 0] = prov;
census[counter, 1] = region;
census[counter, 2] = population;
counter++;
MessageBox.Show("Population " + txtPopulation.Text.ToString() + " saved for Province #" + txtProvinceID.Text.ToString()
+ " , Region #" + txtRegionID.Text.ToString(), "Success!");
txtRegionID.Clear();
txtProvinceID.Clear();
txtPopulation.Clear();
txtProvinceID.Focus();
}
catch (Exception ex)
{
if (ex.Message == "Input string was not in a correct format.")
{
MessageBox.Show("Please enter a numeric value", "Error");
}
else
{
MessageBox.Show(ex.Message, "Error");
}
}
This is the code to retrieve the data and save them to a file
string output = "";
try
{
for (int rows = 0; rows < census.GetLength(0); rows++)
{
for (int columns = 0; columns < census.GetLength(1); columns++)
{
if (census[rows, columns] != 0)
{
if (columns == 0)
{
output += "Province ID #" + census[rows, columns];
}
else if (columns == 1)
{
output += "Region ID #" + census[rows, columns] + ": ";
}
else if (columns == 2)
{
output += census[rows, columns] + "\n";
}
}// END if census[rows, coloumns]!=0
}// END for coloumns
}//END for(int row =0
// save the data to a text file
SaveFileDialog sfd = new SaveFileDialog();
sfd.FileName = "untitled";
sfd.Filter = "Text (*.txt)|*.txt|Word Doc (*.doc)|*.doc";
sfd.DefaultExt = "txt";
sfd.AddExtension = true;
sfd.ShowDialog();
FileStream sf = new FileStream(sfd.FileName, FileMode.Create);
StreamWriter sw = new StreamWriter(sf);
sw.Write(output);
sw.Close();
}
catch (Exception)
{
}
Think of a table in which the rows are provinces and the columns are regions. And in each table cell is the population for that province/region. It would look like this
Region 1 Region 2 Region 3
Province 1 12000 13000 14000
Province 2 11320 9876 1234
Province 3 19723 32767 5038
Province 4 263 1284 1961
This exactly corresponds to a two-dimensional array:
int[,] census = new int[4,3];
And the values in the array would be, for example:
census[0,0] = 12000; // Province 1, Region 1
census[0,1] = 13000; // Province 1, Region 2
census[2,2] = 5038; // Province 3, Region 3
Modifying your code, you'd create your census array as:
int[,] census = new int[13, 48];
And to populate it:
prov = Convert.ToInt32(txtProvinceID.Text);
region = Convert.ToInt32(txtRegionID.Text);
population = Convert.ToInt32(txtPopulation.Text);
census[prov-1, region-1] = population;
Now, if you want to output by province and region:
StringBuilder output = new StringBuilder();
for (int prov = 0; prov < census.GetLength(0); prov++)
{
output.AppendLine("Province ID " + prov+1);
for (int region = 0; region < census.GetLength(1); ++region)
{
output.AppendLine(" Region #" + region+1 + " " +
census[prov, region]);
}
}
And then to output it, you open the file as you showed and Write(output.ToString()).
I used StringBuilder to construct the output rather than appending to a string. Appending strings is very expensive.
Also, you can simplify your file writing you using File.WriteAllText, like this:
// Show the Save File dialog box. And then,
File.WriteAllText(sfd.Filename, output.ToString());
There's no need to open a FileStream and then a StreamWriter. File.WriteAllText takes care of all that for you.
Update
To skip provinces that have no data, you write to a temporary StringBuilder and then append that to the final output only if there is data. For example:
StringBuilder output = new StringBuilder();
for (int prov = 0; prov < census.GetLength(0); prov++)
{
StringBuilder sbTemp = new StringBuilder();
for (int region = 0; region < census.GetLength(1); ++region)
{
if (census[prov, region] != 0)
{
sbTemp.AppendLine(" Region #" + region+1 + " " +
census[prov, region]);
}
}
if (sbTemp.Length > 0)
{
// At least one region had population, so add that
// and the province to the output.
output.AppendLine("Province ID " + prov+1);
output.Append(sbTemp);
}
}
The array has to track Province ID, Region ID and the population for each region.
This indicates that you should create a class to hold the three pieces of data. This way, it will be much easier to work with the array. Something like:
class Region
{
public int ProvinceId { get; set; }
public int RegionId { get; set; }
public long Population { get; set; }
}
Notice that I chose a different type for population, you wouldn't be able to do this with a simple array and you're certainly going to need something like that sooner or later.
When you have that, you can use LINQ to create groups of regions from the same province:
Region[] regions = …; // or maybe List<Region>
var regionGroups = regions.GroupBy(r => r.ProvinceId);
var result = new StringBuilder();
foreach (regionGroup in regionGroups)
{
result.AppendFormat("Province ID #{0}\n\n", regionGroup.Key);
foreach (var region in regionGroup)
{
result.AppendFormat(
"Region ID #{0}: {1}\n", region.RegionId, region.Population);
}
}
return result.ToString();
Also, you should never ever do this:
catch (Exception)
{
}
If you need to handle some specific exceptions, handle those, but only those.
I think it is really difficult to do this with your current program design
i suggest you to use jagged arrays for storing the values since LINQ can not be used on 2D arrays
A jagged array is an "array of arrays"
string[][] census=new string[624][];
for(int i=0;i<624;i++)
{
census[i]=new string[3];
}
Now you can use LINQ to get the required data from the array
var match=from c in census
where c[0]=="Province ID Text"
from details in c
where details!="Province ID Text"
select details;
and print the values as follows
foreach(var i in match)
Console.WriteLine(i);
If you are really particular on NOT using objects or database tables to store the values and
also don't want to use LINQ, the following solution might help you
void Select(string key)
{
for(int i=0;i<624;i++)
{
if(census[i][0]==key)
{
Console.Write(census[i][1]);
Console.Write(census[i][2]);
Console.WriteLine();
}
}
}
To eliminate duplicate data, we use a List to check whether that Province Id was previously checked or not
string done = "";
for (int i = 0; i < 624; i++)
{
if (!done.Contains(census[i][0]))
{
Console.WriteLine(census[i][0]);
Select(census[i][0]);
}
done=done+census[i][0];
}
I hope this code helps you but this is really NOT a good way to program.

Categories