Removing duplicate objects from a list [duplicate] - c#

This question already has answers here:
Remove duplicates from a List<T> in C#
(32 answers)
Closed 7 years ago.
I am currently working on a project and have hit a snag.
I'm taking in an unformatted string[] of football teams and I'm trying to filter the data and return it in a more orgainised format.
I'm fine with most of it (splitting the string to get the relevant values and sorting the format) except i have created a Team object which holds most of this data and as i loop through the original string i create a new team object each time i see a team name. Then i check to see if i've seen that object before and if i have i don't create a new object. After creating the Team or skipping that part i add the relevant info to the team object and continue.
My issue is the list i'm using to hold the final team info has many duplicates mean my check to see if the object exists or not doesn't work. The code is :
After splitting string,
List<Team> teams = new List<Team>();
for (int i = 1; i <= matches.Length - 1; i++)
{
string fullStr = matches[i];
string[] score = fullStr.Split(',');
string[] team1 = score[0].Split('!');
string team1Name = team1[0];
Team teams1 = new Team(team1Name);
if (teams.Contains(teams1) != true)
{
teams.Add(teams1);
}
string team1Score = team1[1];
int team1ScoreInt = int.Parse(team1Score);
string[] team2 = scores[1].Split('!');
string team2Name = team2[1];
Team teams2 = new Team(team2Name);
if (!teams.Contains(teams2))
{
teams.Add(teams2);
}
When i print the list i get the format i want but multiple Germanys etc. And only the score etc of that 1 game rather than them all adding to 1 Germany Team object.
Any ideas how i can stop the duplicates and maintain using only the 1 Team object every time i see that team name?
Thanks

You can implement IEqualityComparer for Team class and check equality of objects based on its values. Something like below.
using System.Collections.Generic;
public class Team{
public string Name {get;set;}
public int score {get;set;}
}
//Create some dummy data
public List<Team> lstTeam = new List<Team>{
new Team{Name="A", score=1},
new Team{Name="A", score=1},
new Team{Name="B", score=1},
new Team{Name="C", score=2},
new Team{Name="A", score=2},
new Team{Name="C", score=2}
};
List<Team> lstDistictTeams = lstTeam.Distinct<Team>(new DistinctComparer()).ToList();
foreach(Team t in lstDistictTeams)
{
Console.WriteLine("Team {0} has Score {1}",t.Name,t.score);
}
//This class provides a way to compare two objects are equal or not
public class DistinctComparer : IEqualityComparer<Team>
{
public bool Equals(Team x, Team y)
{
return (x.Name == y.Name && x.score == y.score); // Here you compare properties for equality
}
public int GetHashCode(Team obj)
{
return (obj.Name.GetHashCode() + obj.score.GetHashCode());
}
}
Here is running example : http://csharppad.com/gist/6428fc8738629a36163d

Looks like the problem is you're creating a new Team object with the name of the team for each result.
When you then compare against the list to see if it is contained, you're checking to see if there's a reference to that object in the list, and as you've just created it, there won't be, so every Team object you create will be added to the list.
You'll need to check whether the list contains a Team object with the name, rather than just check for the instance of the object.

Related

Split ObservableCollection into limited amount collection [duplicate]

This question already has an answer here:
how to split ObservableCollection
(1 answer)
Closed 2 years ago.
I'm working on a payment report printer for work and have an ObservableCollection that fills when the client's name is selected. So, for example, I have a collection name Clients which can have up to only 60 entries. For printing purposes, I need to separate this into 5 separate lists and I can't think of how to go about it.
This is the class for the items used if helps any
public class payment
{
public string amount { get; set; }
public string date { get; set; }
}
I have a collection name Clients which can have up to only 60 entries. For printing purposes, I need to separate this into 5 separate lists
I guess this might look something like:
// An observable collection of 60 items
var clients = new ObservableCollection<Payment>(
Enumerable.Range(0, 60).Select(i => new Payment {Amount = i.ToString()}));
// The number of lists to create
var numLists = 5;
var itemsPerList = clients.Count / numLists + 1;
// Create a list of new ObservableCollections
var smallerCollections = new List<ObservableCollection<Payment>>();
// Copy the items from 'clients' into smaller collections of
// 'itemsPerList' size, and add those to the smallerCollections list
for (var i = 0; i < clients.Count; i++)
{
// Add a new list every time we add 'itemsPerList' items
if (i % itemsPerList == 0) smallerCollections.Add(new ObservableCollection<Payment>());
// Add this item to the last list
smallerCollections.Last().Add(clients[i]);
}

List-items automatically update while they're in an Object and they shouldn't

I started to make a football game app in C#. I should make 15 Matchdays. Every matchday has a number, date and two lists with teams in it. In every MatchDay the teams with the same index in the lists play against eachother. (TeamList1[0] plays against TeamList2[0], ...). I make 15 matchdays with a for loop and switch the teams in the lists with the SwitchTeams() method. First i make a matchday-object with the two lists in it, then I put this matchday in a MatchDayList and then i change the lists and make the next matchday object. But when i switch the teams in the list in the normal code, the list in my MatchDay object changes as well.
I don't know how this is possible and how to make the normal lists change and the lists that are in the MatchDay object remain the same. Does anyone have a solution for this? Thank you
//variables
List<Team> teamList1 = new List<Team>();
List<Team> teamList2 = new List<Team>();
List<MatchDay> matchDayList = new List<MatchDay>();
//Making the 15 Matchdays
private void ComposeGamesMenuItem_Click(object sender, RoutedEventArgs e)
{
DateTime date = new DateTime(DateTime.Now.Year, 7, 31);
while (date.DayOfWeek != DayOfWeek.Saturday)
{
date = date.AddDays(-1);
}
for (int i = 1; i <= 15; i++) {
List<Team> helpList1 = teamList1;
List<Team> helpList2 = teamList2;
MatchDay helpMatchDay = new MatchDay(i, helpList1, helpList2, date);
matchDayList.Add(helpMatchDay);
SwitchTeams();
date = date.AddDays(7);
}
}
// Switching teams in original lists
private void SwitchTeams()
{
teamList2.Insert(0, teamList1[1]);
teamList1.RemoveAt(1);
teamList1.Add(teamList2[8]);
teamList2.RemoveAt(8);
}
//Matchday class
public class MatchDay
{
private int DayNumber;
private List<Team> TeamsList1;
private List<Team> TeamsList2;
private List<int> ScoresList1;
private List<int> ScoresList2;
private DateTime Date;
public MatchDay(int dayNumber, List<Team> teamsList1, List<Team> teamsList2, DateTime date)
{
DayNumber = dayNumber;
TeamsList1 = teamsList1;
TeamsList2 = teamsList2;
Date = date;
}
}
I expect the List in TeamList1 and TeamList2 in the Matchday Class not the change after the SwitchTeams() method. Only the TeamList1 and TeamList2 that are variables should change and put in the new Matchday Object and also not change ofcourse.
Because it's the same list. In your program you have exactly two lists of teams:
List<Team> teamList1 = new List<Team>();
List<Team> teamList2 = new List<Team>();
You never create any more. When you use them to construct your MatchDay object:
new MatchDay(i, helpList1, helpList2, date)
That object now has a reference to each list. Any changes made to a list will be visible by all references to that list. They haven't been copied.
You can create new lists with the .ToList() extension method. For example:
new MatchDay(i, helpList1.ToList(), helpList2.ToList(), date)
This would create new lists, but they would of course still have references to the same objects. So you can modify one list, and it won't modify the other one. But if you modify a property on one of the objects in that list it will modify the object in the original list as well, and all for the same reason as the original problem you described.
In general, assigning a reference-type object (anything other than primitive types like int, bool, double, etc.) to a variable does not create a copy of that object, it just sets that variable as a reference to that same object.

How do I store the initial state of a list of objects so that I can compare them to an updated list?

I have a list that is constantly being updated throughout my program. I would like to be able to compare the initial count and final count of my list after every update. The following is just a sample code (the original code is too lengthy) but it sufficiently captures the problem.
class Bot
{
public int ID { get; set; }
}
public class Program
{
public void Main()
{
List<Bot> InitialList = new List<Bot>();
List<Bot> FinalList = new List<Bot>();
for (int i = 0; i < 12345; i++)
{
Bot b = new Bot() {ID = i};
InitialList.Add(b);
}
FinalList = InitialList;
for (int i = 0; i < 12345; i++)
{
Bot b = new Bot() {ID = i};
FinalList.Add(b);
}
Console.Write($"Initial list has {InitialList.Count} bots");
Console.Write($"Final list has {FinalList.Count} bots");
}
}
Output:
Initial list has 24690 bots
Final list has 24690 bots
Expected for both lists to have 12345 bots.
What is correct way to copy the initial list so new set is not simply added to original?
To do what you seem to want to do, you want to copy the list rather than assign a new reference to the same list. So instead of
FinalList = InitialList;
Use
FinalList.AddRange(InitialList);
Basically what you had was two variables both referring to the same list. This way you have two different lists, one with the initial values and one with new values.
That said, you could also just store the count if that's all you want to do.
int initialCount = InitialList.Count;
FinalList = InitialList;
Although there's now no longer a reason to copy from one to the other if you already have the data you need.
I get the feeling you actually want to do more than what's stated in the question though, so the correct approach may change depending on what you actually want to do.

Sorting List Array based on an index of array

I want to sort a List Array on the basis of an array item.
I have a List Array of Strings as below:
List<String>[] MyProjects = new List<String>[20];
Through a loop, I have added five strings
(Id, Name, StartDate, EndDate, Status)
to each of the 20 projects from another detailed List source.
for(int i = 0; i<20; i++){
MyProjects[i].Add(DetailedProjectList.Id.ToString());
MyProjects[i].Add(DetailedProjectList.Name);
MyProjects[i].Add(DetailedProjectList.StartDate);
MyProjects[i].Add(DetailedProjectList.EndDate);
MyProjects[i].Add(DetailedProjectList.Status)}
The Status values are
"Slow", "Normal", "Fast", "Suspended" and "" for unknown status.
Based on Status, I want to sort MyProject List Array.
What I have done is that I have created another List as below
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
I tried as below to sort, however unsuccessful.
MyProjects = MyProjects.OrderBy(x => sortProjectsBy.IndexOf(4));
Can anyone hint in the right direction. Thanks.
I suggest you to create class Project and then add all the fields inside it you need. It's much nicer and scalable in the future. Then create a List or an Array of projects and use the OrderBy() function to sort based on the field you want.
List<Project> projects = new List<>();
// Fill the list...
projects.OrderBy(project => project.Status);
The field Status has to be a primitive type or needs to implement the interface IComparable in order for the sorting to work. I suggest you add an enum for Status with int values.
First consider maybe to use Enum for status and put it in a different file lite (utils or something) - better to work like that.
enum Status {"Slow"=1, "Normal", "Fast", "", "Suspend"}
Now about the filtering you want to achieve do it like this (you need to tell which attribute of x you are referring to. In this case is status)
MyProjects = MyProjects.OrderBy(x => x.status == enum.Suspend);
Read about enums :
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
Read about lambda expressions :
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions
First of all, storing project details as List is not adivisable. You need to create a Custom Class to represent them.
For example,
public class DetailedProjectList
{
public string Name {get;set;}
public eStatus Status {get;set;}
// rest of properties
}
Then You can use
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
For example
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status="Fast"},
new DetailedProjectList{Name="abc2", Status="Normal"},
new DetailedProjectList{Name="abc3", Status="Slow"},
};
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
Output
abc3 Slow
abc2 Normal
abc1 Fast
A better approach thought would be to use Enum to represent Status.
public enum eStatus
{
Slow,
Normal,
Fast,
Unknown,
Suspended
}
Then your code can be simplified as
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status=eStatus.Fast},
new DetailedProjectList{Name="abc2", Status=eStatus.Normal},
new DetailedProjectList{Name="abc3", Status=eStatus.Slow},
};
var result = MyProjects.OrderBy(x=> x.Status);
Ok so you have a collection of 20 items. Based on them you need to create a list of strings(20 DetailedProjectList items).
What you can do to solve your problem is to SORT YOUR COLLECTION before you create your list of strings. In this way your list of strings will be sorted.
But your code is not optimal at all. So you should concider optimization on many levels.
Lets say you have ProjectDetail class as follow:
private class ProjectDetail
{
public int Id {get;set;}
public string Name {get;set;}
DateTime StartDate {get;set;} = DateTime.Now;
DateTime EndDate {get;set;} = DateTime.Now;
public string Status {get;set;}
public string toString => $"{Id} - {Name} - {StartDate} - {EndDate} - {Status}";
}
Notice that I have added a toString attribute to make things easier, and I also have added default values.
Then your program could be like:
static void Main(string[] args)
{
var projectDetails = MockProjectItems();
Console.WriteLine("Before sortig:");
foreach (var item in projectDetails)
{
Console.WriteLine(item.toString);
}
var myProjects = projectDetails.OrderBy(p => p.Status).Select(p => p.toString);
Console.WriteLine("\n\nAfter sorting:");
foreach (var item in myProjects)
{
Console.WriteLine(item);
}
}
where the helper method is
private static List<ProjectDetail> MockProjectItems()
{
var items = new List<ProjectDetail>(20);
for(int i = 0; i < 20 ; i += 4){
items.Add(new ProjectDetail{Id = i, Name = "RandomName "+i, Status = "Slow"});
items.Add(new ProjectDetail{Id = i+1, Name = "RandomName "+(i+1), Status = "Normal"});
items.Add(new ProjectDetail{Id = i+2, Name = "RandomName "+(i+2), Status = "Fast"});
items.Add(new ProjectDetail{Id = i+3, Name = "RandomName "+(i+3), Status = "Suspended"});
}
return items;
}
Then your program should print the following:

Deleting an array element while moving others down?

I'm really new to programming, so take this with a grain of salt.
I've made 2 arrays that correspond to eachother; One is a Name array and one is a Phone Number array. The idea is that the spot [1] in NameArray corresponds to spot [1] in the PhoneArray. In other words, I need to keep these 'pairings' in tact.
I'm trying to make a function that deletes one of the spots in the array, and shifts everything down one, as to fill the space left empty by the deleted element.
namearray = namearray.Where(f => f != iNum).ToArray();
is what I've tried, with iNum being the number corresponding to the element marked for deletion in the array.
I've also tried converting it to a list, removing the item, then array-ing it again.
var namelist = namearray.ToList();
var phonelist = phonearray.ToList();
namelist.Remove(txtName.Text);
phonelist.Remove(txtPhone.Text);
namearray = namelist.ToArray();
phonearray = phonelist.ToArray();
lbName.Items.Clear();
lbPhone.Items.Clear();
lbName.Items.AddRange(namearray);
lbPhone.Items.AddRange(phonearray);
with txtName.Text and txtPhone.Text being the strings for deletion in the corresponding list boxes.
Can someone suggest a better way to do it / What I'm doing wrong / How to fix?
Thanks guys
-Zack
A better way would be to have an array of a class that contains a Name and Phone Number object:
public class PersonData
{
public string Name { get; set; }
public string Phone { get; set; }
}
public PersonData[] data;
That way, instead of keeping two arrays in sync, it's one array with all the appropriate data.
Try a loop through both arrays, moving the values of each down an index each time.
Start the loop at the index of the value you want to delete. So you would find the IndexOf(T) the value you want, storing it as deleteIndex and run the loop starting from that index.
When you hit the end of the array, set the last value as null or string.Empty (depending what value type the array holds).
A bit like this:
var deleteIndex = namearray.IndexOf("TheStringYouWantToDelete");
for (int i = deleteIndex; i < namearray.Length; i++)
{
if (i == namearray.Length - 1) // The "last" item in the array.
{
namearray[i] = string.Empty; // Or null, or your chosen "empty" value.
phonearray[i] = string.Empty; // Or null, or your chosen "empty" value.
}
else
{
namearray[i] = namearray[i+1];
phonearray[i] = phonearray[i+1];
}
}
This will work for deleting and moving values 'down' in index.
You could also rewrite the code for moving them the other way, as it would work similarly.
Reordering them completely? Different ball game...
Hope this helps.
If the namearray and phonearray contain strings and you know the index of the element to remove (iNum) then you need to use the overload of the Where extension that takes a second parameter, the index of the current element in the evaluation
namearray = namearray.Where((x, y) => y != iNum).ToArray();
However the suggestion to use classes for your task is the correct one. Namearray and Phonearray (and whatever else you need to handle in future) are to be thought as properties of a Person class and instead of using arrays use a List<Person>
public class Person
{
public string FirstName {get; set;}
public string LastName {get; set;}
public string Phone {get; set;}
}
List<Person> people = new List<Person>()
{
{new Person() {FirstName="Steve", LastName="OHara", Phone="123456"}},
{new Person() {FirstName="Mark", LastName="Noname", Phone="789012"}}
};
In this scenarion removing an item knowing the LastName could be written as
people = people.Where(x => x.LastName != "OHara").ToList();
(or as before using the index in the list of the element to remove)
people = people.Where((x, y) => y != iNum).ToArray();
The other answers provide some better design suggestions, but if you're using ListBoxes and want to stick with arrays, you can do this to synchronize them:
int idx = lbName.Items.IndexOf(txtName.Text);
if (idx > -1)
{
lbName.Items.RemoveAt(idx);
lbPhone.Items.RemoveAt(idx);
}
namearray = lbName.Items.Cast<string>().ToArray<string>();
phonearray = lbPhone.Items.Cast<string>().ToArray<string>();
Use a dictionary instead.
Dictionary<string, string> phoneBook = new Dictionary<string, string>();
phoneBook["Foo"] = "1234567890";
phoneBook["Bar"] = "0987654321";
phoneBook.Remove("Bar");

Categories

Resources