How to delete duplicates from structure array with int and string components - c#

In my project I have
struct Cities{public int Name;public int n}
int n represents the population of a city.
I also have cities[] c;, that array will be filled with names and number of citizens in city.
Example:
c[0].Name="New York";c[0].n=845698;
I need to write a method that will erase all cities from the array which have same names (if there is some) and to add their population to first one.

This should do it:
struct Cities
{
public string name;
public int n;
}
[Test]
public void SomeMethod()
{
Cities[] c = new Cities[7];
c[0].name = "new york";
c[0].n = 10;
c[1].name = "detroit";
c[1].n = 20;
c[2].name = "las vegas";
c[2].n = 30;
c[3].name = "new york";
c[3].n = 40;
c[4].name = "detroit";
c[4].n = 50;
c[5].name = "chicago";
c[5].n = 60;
c[6].name = "chicago";
c[6].n = 70;
c = c.GroupBy(ct => ct.name)
.Select(cl => new Cities
{
name = cl.First().name,
n = cl.Sum(ct => ct.n)
}).ToArray();
foreach (var city in c)
{
Console.WriteLine($"city={city.name}, pop={city.n}");
}
}

If the situation is just as simple as you describe, you might want to consider a
Dictionary<string, int>
You would then never add a duplicate city, rather if the city already existed (use trygetvalue(key, out value) or something) you would simply add the population.
Otherwise you are stuck with a simple search to find duplicates, and a routine which combines the duplicates and removes one of them.

Related

Adding a new line in "string.join()" when formatting arrays?

Hello I am a newbie programmer, I am trying to format arrays in a way where there is a line break at the end of a specific set of arrays. I currently have 4 separate arrays in which I want to arrange each item in the array to a specific pattern. I have accomplished this task but now I am stumped because they are all in one line. Let me give you an example: (I am doing this on a datagridview by the way)
(This is what I want to happen)
Binder Clips Small pc 12 1260
Selleys All Clear pc 12 2400
(This is what I am getting)
Binder Clips Small pc 12 1260 Selleys All Clear pc 12 2400
This is my code:
//I get these from a datagridview
var items = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[1].Value.ToString().Trim())
.ToArray();
var units = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[2].Value.ToString().Trim())
.ToArray();
var quantity = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[6].Value.ToString().Trim())
.ToArray();
var prices = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[8].Value.ToString().Trim())
.ToArray();
//this is what I use to sort out the pattern that I want the arrays to be in
string[] concat = new string[items.Length * 4];
int index = 0;
for (int i = 0; i < items.Length; i++)
{
concat[index++] = items[i];
concat[index++] = units[i];
concat[index++] = quantity[i];
concat[index++] = prices[i];
}
// and this is where I am stuck because I can just put \n, it would ruin the format even more
cartitems.Text = string.Join(" ", concat);
I also tried doing something like this:
int j = 0;
string str = "";
foreach (var item in concat)
{
str += item;
if (j <= concat.Length - 1)
{
if (j % 3 == 0)
str += " ";
else
str += "\n";
}
j++;
}
It kinda gets the job done but the line breaks are all over the place.
This is what my projects look like so you can get a better gist on where am I getting the data from the 4 arrays:
basically the product name, unit, quantity and line total
and lastly I am storing in on a label so I can see how it the formatting looks like:
that about sums up my problem, I really hope you can help a newbie like me out, I have a feeling the answer is quite simple and I am just un-experienced.
As a general rule, you should keep your data structures (how the data is stored, implemented here as an array of values) separate from the data representation (in this case, written to a list box).
C# is an object-oriented language, so we might as well take advantage of that, right?
Create a class for your items.
class Item
{
public string Name { get; set; }
public string Unit { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
override public string ToString() {
return $"{Name} {Unit} {Quantity} {Price}";
}
}
This is how you load your array.
Item[] concat = new Item[items.Length];
int index = 0;
for (int i = 0; i < items.Length; i++) {
concat[index++] = new Item {
Name = items[i],
Unit = units[i],
Quantity = quantity[i],
Price = prices[i]
};
}
and this is how you can add the list of items to a listbox.
foreach(Item item in concat) {
listBox.Items.Add(item);
}

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.

Skip and take method in List

I have a class Players. And I want to create Hyperlink with Skip and Take methods. But gives me System.Linq.Enumerable error. My goal is make a pyramid user list. here is my codes
public class Players
{
public string Name{ get; set; }
public int Order{ get; set; }
public int ID { get; set; }
}
List<Players> playerlist= new List<Players>();
playerlist= (from DataRow dr in dt.Rows
select new Players()
{
Name= (dr["name"].ToString()),
Order= int.Parse(dr["order"].ToString()),
ID = int.Parse(dr["Id"].ToString())
}).ToList();
playerlist= playerlist.OrderBy(x => x.Order).ToList();
int skip = 0;
int take = 1;
int addedCount = 0;
do
{
HyperLink links= new HyperLink();
links.Text = "" + playerlist.Skip(skip ).Take(take).Select(x => x.Name);
links.NavigateUrl = "playerdetails.aspx?id=" + oyunculistesi.Skip(skip).Take(take).Select(x => x.ID);
Page.Controls.Add(links);
addedCount += take ;
skip+= take ;
take += +1;
}
while (addedCount < playerlist.Count);
It is working with StringBuilder but with HyperLink not.
sb.AppendLine(string.Join(" ", players.Skip(skip).Take(take).Select(x => $"{x.Order}) {x.Name}")));
Your Select is returning an IEnumerable of char and you need to build a string from them by using string.Join like what you did in the StringBuilder:
linkuret.Text = string.Join("" , playerlist.Skip(skip).Take(take).Select(x => x.Name));
I would rewrite your loop in this way
int skip = 0;
while (skip < playerlist.Count)
{
HyperLink links= new HyperLink();
Players p = playerlist.Skip(skip).FirstOrDefault();
links.Text = $"{p.Name}"
links.NavigateUrl = $"playerdetails.aspx?id={p.Id}"
Page.Controls.Add(links);
skip++;
}
First I have removed the Take part from your code and used FirstOrDefault to get always the first element after the skip. Finally the Players elements is loaded just one time and then I used the properties of the class with more readable code.

List Re-Order is mixing up property values across list items

I have a strange situation happening or maybe it is my misunderstanding of lists, or it is one of those cases where you look at it too long and am missing something simple.
When I re-order a list in this example, it is mixing up column values across rows in the list. Here are some pics as an example as captured during debugging.
In the below pic, first note the list ordering done on 783. Next note the ItemId value of position [0] in the list. It is 1121.
Now after just advancing from line 793 to 798, note the values of inventoryNeed after it has been assigned the value from lstInventoryNeeds[i]. Note that its ItemId value is 1336! And further, the same list entry in position [0] also changed JUST the ItemId value from 1121 to 1336! What just happened?!?!
Now if I comment out the original list re-order code on line 783 and run it again, see this pic, captured just after the inventoryNeed assignment when i = 0 just as before, but there is no mix up in the ItemId value.
Any idea why this is happening?
Here is code for this section, but not sure how useful it is taken out of context:
private ActionConfirmation<int> AllocateContainersForNeedList(List<AllocationNeedViewModel> lstInventoryNeedsOriginal, int intUserId, bool failOnShortage)
{
//Sort by this so if first alloc attempt fails for the remnant,
//then add that qty to next Need in list (if exists) and attempt allocate for full sheets
var lstInventoryNeeds = lstInventoryNeedsOriginal;
//.OrderBy(x => x.ItemId)
//.ThenByDescending(x => x.IsRemnant)
//.ToList();
ActionConfirmation<int> allocateResult = ActionConfirmation<int>.CreateSuccessConfirmation("Allocation of Containers for Needs List Successful.",1);
bool firstOfTwoNeedsForSameItem = false;
AllocationNeedViewModel inventoryNeed;
for (int i = 0; i < lstInventoryNeeds.Count; i++)
{
inventoryNeed = lstInventoryNeeds[i];
//If this is an inventory tracked item, allocate it
if (boolIsInvenTrackedItem(inventoryNeed.ItemId))
{
//Allocate the required inventory for the need just created
if (lstInventoryNeeds.Count() > i + 1)
{
firstOfTwoNeedsForSameItem = inventoryNeed.IsRemnant && (lstInventoryNeeds[i + 1].ItemId == inventoryNeed.ItemId);
}
else
{
firstOfTwoNeedsForSameItem = false;
}
allocateResult =
AllocInvenForNeed(
inventoryNeed.FacilityId,
inventoryNeed,
intUserId,
0,
0,
!firstOfTwoNeedsForSameItem
);
//Create Allocation Error
if (!allocateResult.WasSuccessful)
{
//Check if original attempt was for a Remnant
if (firstOfTwoNeedsForSameItem)
{
//Add current remnant need to the next need for this item, which will then get allocated on the next loop
lstInventoryNeeds[i + 1].QtyNeeded = lstInventoryNeeds[i + 1].QtyNeeded + inventoryNeed.QtyNeeded;
}
else
{
return allocateResult;
}
}
} //if inventory tracked item
}//for loop
return allocateResult;
}
Here is a MCVE, but I can't replicate the problem...but maybe I'm missing some key element that would have an impact
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var lstInventoryNeedsOriginal = new List<AllocNeedVM>();
int b = 5;
for (int a = 0; a <= b; a++)
{
int c = b - a;
var allocNeed = new AllocNeedVM()
{
ItemId = c,
ItemDesc = "Item " + c.ToString(),
IsRemnant = false
};
lstInventoryNeedsOriginal.Add(allocNeed);
}
AllocNeedVM inventoryNeed;
Console.WriteLine("List before sort.");
for (int i = 0; i < lstInventoryNeedsOriginal.Count; i++)
{
inventoryNeed = lstInventoryNeedsOriginal[i];
Console.WriteLine("{0}, {1}, {2}", inventoryNeed.ItemId, inventoryNeed.ItemDesc, inventoryNeed.IsRemnant);
}
var lstInventoryNeeds = lstInventoryNeedsOriginal
.OrderBy(x => x.ItemId)
.ThenByDescending(x => x.IsRemnant)
.ToList();
Console.WriteLine("List after sort.");
for (int i = 0; i < lstInventoryNeeds.Count; i++)
{
inventoryNeed = lstInventoryNeeds[i];
Console.WriteLine("{0}, {1}, {2}", inventoryNeed.ItemId, inventoryNeed.ItemDesc, inventoryNeed.IsRemnant);
}
Console.ReadLine();
}
}
public class AllocNeedVM
{
public int ItemId { get; set; }
public string ItemDesc { get; set; }
public bool IsRemnant { get; set; }
}
}

remove list-items with Linq when List.property = myValue

I have the following code:
List<ProductGroupProductData> productGroupProductDataList = FillMyList();
string[] excludeProductIDs = { "871236", "283462", "897264" };
int count = productGroupProductDataList.Count;
for (int removeItemIndex = 0; removeItemIndex < count; removeItemIndex++)
{
if (excludeProductIDs.Contains(productGroupProductDataList[removeItemIndex].ProductId))
{
productGroupProductDataList.RemoveAt(removeItemIndex);
count--;
}
}
Now i want to do the same with linq. Is there any way for this?
The second thing would be, to edit each List-Item property with linq.
you could use RemoveAll.
Example:
//create a list of 5 products with ids from 1 to 5
List<Product> products = Enumerable.Range(1,5)
.Select(c => new Product(c, c.ToString()))
.ToList();
//remove products 1, 2, 3
products.RemoveAll(p => p.id <=3);
where
// our product class
public sealed class Product {
public int id {get;private set;}
public string name {get; private set;}
public Product(int id, string name)
{
this.id=id;
this.name=name;
}
}
Firstly corrected version of your current code that won't skip entries
List<ProductGroupProductData> productGroupProductDataList = FillMyList();
string[] excludeProductIDs = { "871236", "283462", "897264" };
int count = productGroupProductDataList.Count;
for (int removeItemIndex = 0; removeItemIndex < count; removeItemIndex++)
{
while (removeItemIndex < count && excludeProductIDs.Contains(productGroupProductDataList[removeItemIndex].ProductId)) {
productGroupProductDataList.RemoveAt(removeItemIndex);
count--;
}
}
}
This linq code would do the job.
List<ProductGroupProductData> productGroupProductDataList = FillMyList();
string[] excludeProductIDs = { "871236", "283462", "897264" };
productGroupProductDataList=productGroupProductDataList.Where(x=>!excludedProductIDs.Contains(x.ProductId)).ToList();
Alternatively using paolo's answer of remove all the last line would be would be
productGroupProductDataList.RemoveAll(p=>excludedProductIDs.Contains(p=>p.ProductId));
What you mean by "The second thing would be, to edit each List-Item property with linq."?
As per your comment here's a version that creates a set that excludes the elements rather than removing them from the original list.
var newSet = from p in productGroupProductDataList
where !excludeProductIDs.Contains(p.ProductId))
select p;
The type of newSet is IEnumerable if you need (I)List you can easily get that:
var newList = newSet.ToList();

Categories