How Add value to Object Property within a List<> - c#

Still a beginner so please bare with them me on this. I would like the ability to add a number value to my property after the list distinguishes that there are duplicate values.
For example, two users have inputted into the system that they have the same employee ID of 001, the List allows them both to go in then applies a condition to check the list for any duplicates so in this case there would be. Now this conditional is true, add 1 to that property so the last employee now has employee ID of 002.
I believe LINQ will more than likely be involved in this process, but not to sure on how to go about this. I understand I will also have to update my List after performing this after to maintain the properties state throughout the program.
Thanks in advance hope everything is clear, question again How to add value to a property within a List<>
Code Snippet below.
Employee = new Employee(employeeFirstName, employeeLastName,001); // Hard coded for sake of example.
EmployeeList.AddEmployee(Employee);
EmployeeList Class
public static void AddEmployee(Employee employee)
{
employees.Add(new Employee(employee.FirstName,employee.LastName,employee.EmployeeID));
employees.Add(new Employee("John", "Jones", 001));
}
public void employeeIdValidation()
{
if (employees.Count() != employees.Select(x => new {staffId = x.EmployeeID }).Distinct().Count())
{
Console.WriteLine("Every Book and Category should be unique");
// employee ID increments by 1
// update List
}
else
{
Console.WriteLine("No duplicate found");
}
}
public static List<Employee> GetEmployeeList()
{
return employees; // With the updated EmployeeID
}

IMHO, you should use a Dictionary<int, Employee> (where the key is the Id) instead of a list. This will force you to update the ID of a duplicate record before adding to the collection and save a whole bunch of trouble and computation.

To find in your list what Employees have the same Id you can use this linq function.
var result=employees.ToLookup(e => e.Id);
At this way you can group the employees that have the same Id. Then, you can iterate for each group of employees with the same id as I show you below:
foreach (IGrouping<int, Employee> group in result)
{
//More than 2 employees with the same Id
if (group.Count()>1)
{
foreach (Employee employee in group)
{
//Change your ids here
}
}
}
[Update 1]:
To change the Ids I proporse you find first the max id before iterate in the groups.
int nextID = result.Max(e => e.Key)+1;
Then, change the second foreach cycle for a for cycle as you can see below. When you find a group with more than two Employees with the same id, don't change the first element, change the rest at this way:
//The first element don't change the id, start for the second element
for (int j = 1; j < group.Count(); j++)
{
var currentEmployee=group.ElementAt(j);
// Change the id and refresh the nextID variable
currentEmployee.Id = nextID++;
}

Related

I try to make a lamda expression in C# for filtering a list and can't make it work when I compare a variable to an item in an array

So I have the gui set up so that I have two main listboxes. I'm still figuring out what kind of gui I want for this application so there is another one but that's not relevant. One listbox is a list of options for what you want to check for a department or an employee. The other is a list of departments. Right now I have the functionality for the name option to view the names of employees for a department. I just need to know how I can filter a list so that the only employees that show up are the ones who are in the chosen department after I click on the submit button. I figured I would use a lambda expression for that and it hasn't been working for me. I really want to know how to use lambda expressions better so please only give me a solution that involves using them. If it's impossible or if it would be more efficient to do something else then let me know.
File where I put reads and set dept array to file contents
//list of employees
public static List<Employee> EmployeeList = new List<Employee>();
//array to hold the options users have for interacting with info
public static string[] OptionsArr;
//array to hold the departments
public static string[] DeptsArr;
//skipping around same file to relevant code
//set the departments array to the contents of the depts file
DeptsArr = File.ReadAllLines("..\\..\\departments.txt");
Not sure if needed
Method for populating DeptListBox
private void UpdateDeptListBox()
{
//set up for new info
DeptListBox.Items.Clear();
//prevent interfence with update
DeptListBox.BeginUpdate();
//set department listbox to depts array
DeptListBox.DataSource = Program.DeptsArr;
DeptListBox.EndUpdate();
}
Problem Method - the submit button method
List<Employee> ResultList = new List<Employee>();
//name
if (OptionsListBox.SelectedIndex == 1)
{
//user selects Marketing department
if (DeptListBox.SelectedIndex == 0)
{
//problem is either with lambda exp or Program.DeptsArr comparison
foreach (Employee empl in Program.EmployeeList.Where(empl => empl.Dept.CompareTo(Program.DeptsArr[0]) == 0).ToList())
{
//this doesn't happen
ResultList.Add(empl);
}
for (int i = 0; i<ResultList.Count; i++)
{
ResultListBox.Items.Add(ResultList[i].Lname + " " + ResultList[i].Fname + " " + ResultList[i].Dept);
}
}
}
}
For me it can be helpful when I am having issues to break stuff down and look at smaller pieces. Are you sure the issue is your lambda function? It may be your options listbox != 1 or that the data is not being read in correctly.
As far as I can tell, this part should work. Although there are some issues with it:
foreach (Employee empl in Program.EmployeeList.Where(empl =>empl.Dept.CompareTo(Program.DeptsArr[0]) == 0).ToList())
{
//this doesn't happen
ResultList.Add(empl);
}
You could start with just the Employee lambda function and hard code the values. Maybe something like this which does indeed produce the correct results (Bob and Brandon)
List<Employee> ResultList = new List<Employee>();
List<Employee> EmployeeList = new List<Employee> {
new Employee{ Name = "Bob", Dept = "Accounting" },
new Employee{ Name = "Larry", Dept = "A" },
new Employee{ Name = "Margret", Dept = "B" },
new Employee{ Name = "Brandon", Dept = "Accounting" }
};
string[] DeptsArr = new string[2];
DeptsArr[0] = "Accounting";
DeptsArr[1] = "A";
//user selects Marketing department
if (departmentIndex == 0)
{
foreach (Employee empl in EmployeeList.Where(empl => empl.Dept.CompareTo(DeptsArr[0]) == 0).ToList())
{
ResultList.Add(empl);
}
}
However your lamda function inside a foreach loop is redundant. You can think of a lambda function as an instruction for running a foreach loop. A foreach loop by itself could look like this:
List<Employee> ResultList = new List<Employee>();
foreach (Employee empl in EmployeeList)
{
if(empl.Dept == DeptsArr[0])
{
ResultList.Add(empl);
}
}
You could get the same result as the foreach loop above, by using the following lamda function:
List<Employee> ResultList = EmployeeList.Where(empl => empl.Dept == DeptsArr[0]).ToList();
A final note is that the "ToList()" on the end of that lambda function is what executes the loop and returns the result as a List. Many times this is not required. Without the "ToList()" part an IEnumerable will be returned which you may be able to use instead. Using an IEnumerable instead of calling ToList() can have better performance in many scenarios.
If you want to test whether a specific value is in an array then you call Contains on that array, e.g.
var allEmployees = new List<Employee>();
// Populate allEmployees here.
var selectedEmployees = allEmployees.Where(e => selectedDepartments.Contains(e.Department)).ToArray();
The selectedEmployees array will contain only the Employee objects from the allEmployees list with a Department property value that is contained in the selectedDepartments array/collection.

C# foreach in List - for removing elements

I feel stupid for asking this but I am struggling to quite understand foreach. Say for example that I am making a book repository app, with a Book class as well as an Inventory class. The Inventory class has a removeBook method that removes a book from the inventory. The parameter for the method would be an int bookID. I am thinking I should use foreach to accomplish this. I understand the most basic use of foreach but I can not figure out how to use it to basically select a specific bookID that is a parameter in the method. Could someone help point me in the right direction?
Here's a code snippet, I know the method is wrong:
List<Book> Books = new List<Book>
{
new Book{ bookID = 5, Name = "Moby Dick", Price = 20.00 },
new Book{ bookID = 2, Name = "50 Shades of Grey", Price = 0.99 }
};
public void removeBook(int bookID)
{
foreach (var bookID in Books)
{
Products.Remove(book);
}
}
Removing a thing from a collection isn't what foreach is for - it's for performing some operation on every value in the collection. If you want to remove a book with a specific id, you could use a regular for loop:
// in Inventory class having List<Book> Books,
// assuming Book has a public int Id property
public void RemoveBook(int bookId) {
for (int i = 0; i < this.Books.Count; i++) {
if (this.Books[i].Id == bookId) {
this.Books.RemoveAt(i);
return;
}
}
}
If there are any duplicates for some reason (there shouldn't be - IDs should be unique) and you wanted to remove all books with given ID, this code should do it:
public void RemoveBooks(int bookId) {
// iterating from the end of the array
// to prevent skipping over items
for (int i = this.Books.Count - 1; i >= 0; i--) {
if (this.Books[i].Id == bookId) {
this.Books.RemoveAt(i);
}
}
}
EDIT: fixed the code, thanks to Gerardo Grignoli
So if you want to remove a book from a list you would not necessarily use a foreach loop. Simplest way would be to use the RemoveAll function on List.
public void RemoveBook(int bookId) =>
Books.RemoveAll(book => book.Id == bookId);
If you have
List<Thing> things = ....
Then in a foreach like this
foreach (Thing theThing in things)
{
// do something with "theThing"
}
the foreach loops through all items in the things list and executes that block of code (between the { }) for each consecutive value, which is stored in the theThing variable (and is of type Thing).
You can replace Thing theThing with var theThing, for the exact same result.
foreach wont work if you are iterating over the same list so use for loop or linq instead
MSDN
The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.
private void RemoveBookFromInventory(int bookID)
{
foreach(Books book in listOfBooks)
{
if(book.bookID == bookID)
{
listOfBooks.Remove(book); //wont work
}
}
for(int i=0;i<listOfBooks.Count();i++)
{
if (listOfBooks[i].bookID == bookID)
listOfBooks.Remove(listOfBooks[i]);
}
}
As others have implied, you can't really change the contents of a list while iterating through it with a for loop, as that would change the data structure you are working on.
There are several other ways to solve this though. I personally like solutions in this style, where you fetch the index of the book first, and then remove it in a separate step afterwards:
var bookID = 2;
int? index = Books
// Generate a list of book- and position- pairs:
.Select((book, pos) => new {book, pos})
// First matching pair (or null):
.FirstOrDefault(set => set.book.bookID == bookID)?
// If NULL was not returned, get the index property:
.pos;
// Removal is only attempted if a matching book was found:
if(index.HasValue){
Books.RemoveAt(index.Value);
}

Why is RemoveAll(x => x.Condition) removing all my records?

I'm working on creating a filter for a collection of employees. In order to do this I initially fetch a raw collection of all employees. I clone this list so I can iterate over the original list but remove items from the second list.
For each filter I have, I build a collection of employee ids that pass the filter. Having gone through all filters I then attempt to remove everything that isn't contained in any of these lists from the cloned list.
However for some reason, whenever I attempt to do this using .RemoveAll(), all records seemed to be removed and I can't figure out why.
Here is a stripped down version of the method I'm using, with only 1 filter applied:
public List<int> GetFilteredEmployeeIds(int? brandId)
{
List<int> employeeIds = GetFilteredEmployeeIdsBySearchTerm();
List<int> filteredEmployeeIds = employeeIds.Clone();
// Now filter the results based on which checkboxes are ticked
foreach (var employeeId in employeeIds)
{
// 3rd party API used to get values - please ignore for this example
Member m = new Member(employeeId);
if (m.IsInGroup("Employees"))
{
int memberBrandId = Convert.ToInt32(m.getProperty("brandID").Value);
// Filter by brand
List<int> filteredEmployeeIdsByBrand = new List<int>();
if (brandId != null)
{
if (brandId == memberBrandId)
filteredEmployeeIdsByBrand.Add(m.Id);
var setToRemove = new HashSet<int>(filteredEmployeeIdsByBrand);
filteredEmployeeIds.RemoveAll(x => !setToRemove.Contains(x));
}
}
}
return filteredEmployeeIds;
}
As you can see, I'm basically attempting to remove all records from the cloned record set, wherever the id doesn't match in the second collection. However for some reason every record seems to be getting removed.
Anybody know why?
P.S: Just to clarify, I have put in logging to check the values throughout the process and there are records appearing in the second list, however for whatever reason they're not getting matched in the RemoveAll()
Thanks
Ok only minutes after posting this I realised what I did wrong: The scoping is incorrect. What it should've been was like so:
public List<int> GetFilteredEmployeeIds(int? brandId)
{
List<int> employeeIds = GetFilteredEmployeeIdsBySearchTerm();
List<int> filteredEmployeeIds = employeeIds.Clone();
List<int> filteredEmployeeIdsByBrand = new List<int>();
// Now filter the results based on which checkboxes are ticked
foreach (var employeeId in employeeIds)
{
Member m = new Member(employeeId);
if (m.IsInGroup("Employees"))
{
int memberBrandId = Convert.ToInt32(m.getProperty("brandID").Value);
// Filter by brand
if (brandId != null)
{
if (brandId == memberBrandId)
filteredEmployeeIdsByBrand.Add(m.Id);
}
}
}
var setToRemove = new HashSet<int>(filteredEmployeeIdsByBrand);
filteredEmployeeIds.RemoveAll(x => !setToRemove.Contains(x));
return filteredEmployeeIds;
}
Essentially the removal of entries needed to be done outside the loop of the employee ids :-)
I know that you said your example was stripped down, so maybe this wouldn't suit, but could you do something like the following:
public List<int> GetFilteredEmployeeIds(int? brandId)
{
List<int> employeeIds = GetFilteredEmployeeIdsBySearchTerm();
return employeeIds.Where(e => MemberIsEmployeeWithBrand(e, brandId)).ToList();
}
private bool MemberIsEmployeeWithBrand(int employeeId, int? brandId)
{
Member m = new Member(employeeId);
if (!m.IsInGroup("Employees"))
{
return false;
}
int memberBrandId = Convert.ToInt32(m.getProperty("brandID").Value);
return brandId == memberBrandId;
}
I've just done that off the top of my head, not tested, but if all you need to do is filter the employee ids, then maybe you don't need to clone the original list, just use the Where function to do the filtering on it directly??
Please someone let me know if i've done something blindingly stupid!!

Update Property in List of objects based from a list of ids

Two lists. One is a object representing a group of people I sent a email to. For brevity lets say the structure is
public class EmailSent
{
public int Id {get;set;}
public string Email {get;set;}
public bool HasResponse {get;set;}
}
That has a backing table of every email I've sent (minus the HasResponse column). I have another table that stores all the responses.
public class EmailResponse
{
public int Id {get;set;}
public string Response {get;set;}
}
I have a test that is currently failing and I can't figure out how to get it to pass. In production I basically query the EmailSent table with something like SELECT Id, Email from EmailSent where Id between #MinId and #MaxId
Nothing fancy there. My test basically does a yield return that does a single EmailSent between each of those numbers... The next part is that I do a select on that list to give me the Id's and make a second query to the EmailResponse. SELECT Id from EmailResponse WHERE Id in (...generate list of id's) In my test I write
public IEnumerable<EmailResponse> GetEmailResponses(IEnumerable<long> Ids, int waveId)
{
foreach (var id in Ids.Take(10))
{
yield return new EmailResponse {Id = id};
}
}
The test that is failling is this
[Test]
public void WhenAnEmailGroupIsSelectedSpecificInformationIsShown()
{
_viewModel.SelectedEmailGroup = _viewModel.EmailGroups[0];
_viewModel.Emails.Count.Should().Be(286);
_viewModel.Emails.Count(x => x.HasMatchingResult).Should().Be(10);
}
it's failling with error message, expected 10 for the count, but found 0. What I have in place right now is (changed var to IEnumerable for clarity)
IEnumerable<EmailGroup> emails = _dao.GetEmailsSent(SelectedEmailGroup);
IEnumerable<EmailResponse> results = _dao.GetEmailResponses(emails.Select(x => x.Id), SelectedEmailGroup.WaveId);
IEnumerable<EmailGroup> matches = emails.Join(results, x => x.Id, y => y.Id, (x, y) => x).ToList();
//matches.ForEach(x => x.HasMatchingResult = true); this is the line that probably needs to change
foreach (var email in emails)
{
Emails.Add(email);
}
it's obvious to me what is wrong, but i can't figure out how to easily update emails based on responses. Please help :)
The most likely problem is that you do not have a ToList() on making emails IEnumerable, meaning that it would be re-generated again when your unit tests ask for it. At this point the HasMatchingResult flag would be lost, so your tests would fail. Fixing this one is easy - simply add ToList to the call that makes emails, and uncomment your ForEach:
IEnumerable<EmailGroup> emails = _dao.GetEmailsSent(SelectedEmailGroup).ToList();
You do not need to perform a join there: all you have to do is picking EmailGroups that have matching Ids among the responces:
ISet<int> emailIdsWithResponses = new HashSet<int>(results.Select(r => r.Id));
IEnumerable<EmailGroup> matches = emails.Where(e => emailIdsWithResponses.Contains(e.Id)).ToList();
At this point you can call your ForEach, or better yet walk through the items in a "plain" foreach loop, setting their HasMatchingResult flags:
foreach (var e in matches) {
e.HasMatchingResult = true;
}

Add List output cumulatively

I have a class, I am using list to get (assigning multiple list entry values in c#).
// This will return me more than 1 records.
List<ClassEmpInfo> employeeDetails =GetEmployeeInformation();
List<ClassEmployee> empInfo = null;
foreach (ClassEmployee employee in employeeDetails)
{
//This needs to show all the ids belonging to employeeDetails.
//If there are 3 different employee ids ,
//the list empInfo should hold the output of all the 3,
//but i am getting the last 3rd one alone.
//How to cumulatively add 3 different employee ids.
empInfo = GetEmployeeDetails(Id, EmpId);
}
I am getting the last employee information rather than all the employee details in the empInfo list.
If the type is string I can do something like:
if (strType.Length > 0)
{
strType = strType + returned values;
}
else
{
strType = strType;
}
How do I add the list values cumulatively?
I think what you want to do is the following:
List empInfo = new List<detail_type>(); // whatever type is returned by GetEmployeeDetails
foreach (ClassEmployee employee in employeeDetails)
{
empInfo.Add(GetEmployeeDetails(id, EmpId));
}
It's rather unclear to me, though, if GetEmployeeDetails returns a single value or a list of values. If it returns a list of values, then change the line in the loop to:
empInfo.AddRange(GetEmployeeDetails(id, EmpId));
You add stuff to a list with .Add() this is per definition cumulative.

Categories

Resources