DataGridView, how to skip duplicates - c#

I have a DataGridView and im trying to check if row already exist if yes I want to skip a loop from outer "foreach" statment.
I figured something like this, but maybe there is some more optimized way to do this.
(This function is deleting current row, and inserting new one, to not duplicating)
(outer foreach)
for (int i = 0; i < dataGridViewFarm.RowCount; i++)
{
if (villageParams[3] == dataGridViewFarm.Rows[i].Cells[3].Value.ToString())
{
dataGridViewFarm.Rows.Remove(dataGridViewFarm.Rows[i]);
}
}

You can remove duplicated rows from the source of dataGridView using Linq to objects, and set the new object as the new DataSource
Method1:
Example: suppose that cell[3] is mycell3
var noDuplicationList = myList.GroupBy(x => x.mycell3)
.Select(g => g.First())
.ToList();
Method2: use DistinctBy() in the MoreLinq project
Install-Package morelinq
var noDuplicationList = myList.DistinctBy( x => x.mycell3).ToList();

It seams like you are using Windows Forms. Adding this tag might help, in order to get answers.
Never the less. How are those rows created? User-input or form a collection?
In case of a collection: I would just check for duplicates before adding the to the dataGridView.
In case of user-input: Also check for duplicates before creating a row.

Related

How to remove zero elements from List<string>?

Read couple of examples, but seems i`m doing something wrong.
Need to remove all "0" strings from a HUGE List without using hashtables.
Tryed using lambda samples from Stack Owerflow and MSDN examples, but looks i`m messing something up.
DataTable book = SQL.SqlGetTable(BookList.SelectedItem.ToString());
List<string> pagesExist = new List<string>();
for (int i = 0; i < book.Rows.Count; i++)
{
pagesExist.Add(book.Rows[i][0].ToString());
}
var found = pagesExist.Find(x => x == "0");
if (found != null)
pagesExist.Remove(found);
I have a pagesExist list of 4000 string elements.
Supposed that
var found = pagesExist.Find(x => x == "0");
will accumulate all zeroes in list and will remove them next string. But somehow found results in 0 elements
No need to create the pagesExist list. Just filter out all non zero rows using a simple linq query over the DataTable. This way your entire code is reduced to only:
DataTable book = SQL.SqlGetTable(BookList.SelectedItem.ToString());
var result = book.AsEnumerable().Where(r => r.Field<int>("FieldName") != 0);
I am assuming that the column contains integers only. If not then keep the use Field<string> and filter for != "0".
As a side note I would recommend looking into SqlGetTable. If it returns a DataTable it already brings all this data into memory from database, something that can be avoided with using linq directly over the DBMS using tools like linq-2-sql or linq-2-entities
You can use RemoveAll :
pagesExist.RemoveAll(p => p == "0");

Performance between check exists before add to list and distinct in linq

In the foreach loop, I want to add the Products to a List, but I want this List to not contain duplicate Products, currently I have two ideas solved.
1/ In the loop, before adding the Product to the List, I will check whether the Product already exists in the List, otherwise I will add it to the List.
foreach (var product in products)
{
// code logic
if(!listProduct.Any(x => x.Id == product.Id))
{
listProduct.Add(product);
}
}
2/. In the loop, I will add all the Products to the List even if there are duplicate products. Then outside of the loop, I would use Distinct to remove duplicate records.
foreach (var product in products)
{
// code logic
listProduct.Add(product);
}
listProduct = listProduct.Distinct().ToList();
I wonder in these two ways is the most effective way. Or have any other ideas to be able to add records to the List to avoid duplication ??
I'd go for a third approach: the HashSet. It has a constructor overload that accepts an IEnumerable. This constructor removes duplicates:
If the input collection contains duplicates, the set will contain one
of each unique element. No exception will be thrown.
Source: HashSet<T> Constructor
usage:
List<Product> myProducts = ...;
var setOfProducts = new HashSet<Product>(myProducts);
After removing duplicates there is no proper meaning of setOfProducts[4].
Therefore a HashSet is not a IList<Product>, but an ICollection<Product>, you can Count / Add / Remove, etc, everything you can do with a List. The only thing you can't do is fetch by index
You first take which elements are not already in the collection:
var newProducts = products.Where(x => !listProduct.Any(y => x.Id == y.Id));
And then just add them using AddRang
listProduct.AddRagne(newItems)
Or you can use foreach loop too
foreach (var product in newProducts)
{
listProduct.Add(product);
}
1 more easy solution could be there no need to use Distint
var newProductList = products.Union(listProduct).ToList();
But Union has not good performance.
From what you have included, you are storing everything in memory. If this is the case, or you are persisting only after you have it ready you can consider using BinarySearch:
https://msdn.microsoft.com/en-us/library/w4e7fxsh(v=vs.110).aspx and you also get an ordered list at the end. If ordering is not important, you can use HashSet, which is very fast, and meant specially for this purpose.
Check also: https://www.dotnetperls.com/hashset
This should be pretty fast and take care of any ordering:
// build a HashSet of your primary keys type (I'm assuming integers here) containing all your list elements' keys
var hashSet = new HashSet<int>(listProduct.Select(p => p.Id));
// add all items from the products list whose Id can be added to the hashSet (so it's not a duplicate)
listProduct.AddRange(products.Where(p => hashSet.Add(p.Id)));
What you might want to consider doing instead, though, is implementing IEquatable<Product> and overriding GetHashCode() on your Product type which would make the above code a little easier and put the equality checks where they should be (inside the respective type):
var hashSet = new HashSet<int>(listProduct);
listProduct.AddRange(products.Where(hashSet.Add));

Is this loop possible to do using simple LINQ?

I got a class called BG which has a property called Name Code.
I instantiate an object called bgList.
Now I am trying to get all the Code of the objects which have their 'Crop' property set to cropName.
I would like to convert the following working code to linq but for the life of me am unable to do that - am quite sure that I am missing something:
List<string> breedingGroupsAndRoles = new List<string>();
for (int i = 0; i < bgList.Count; i++)
{
if (bgList[i].Crop == cropName)
breedingGroupsAndRoles.Add(bgList.[i].Code);
}
The closest I came was this but it only nets me the first item:
breedingGroupsAndRoles.Add(bgrList.Find(c => c.Crop == cropName).Role);
List<string> breedingGroupsAndRoles = bgList
.Where(bg => bg.Crop == cropName)
.Select(bg => bg.Code)
.ToList();
Just for the sake of completeness, the Find method you tried calling on bgList is not part of LINQ, it's a member of the generic List class itself. It only returns the first element matched by the predicate you provide, which is why you were only getting one result. You probably wanted the FindAll method, which returns a list of all matching elements:
List<BG> breedingGroups = bgList.FindAll(c => c.Crop == cropName);
Note that this produces a list of matching BG instances rather than just their Role properties. Depending on how you're processing the results this may be sufficient, otherwise you'll still need LINQ or a loop and a second list to extract the Role values. In any case, an all-LINQ solution such #Tim Schmelter's is likely the better way to go.

A better way to loop through lists

So I have a couple of different lists that I'm trying to process and merge into 1 list.
Below is a snipet of code that I want to see if there was a better way of doing.
The reason why I'm asking is that some of these lists are rather large. I want to see if there is a more efficient way of doing this.
As you can see I'm looping through a list, and the first thing I'm doing is to check to see if the CompanyId exists in the list. If it does, then I find item in the list that I'm going to process.
pList is my processign list. I'm adding the values from my different lists into this list.
I'm wondering if there is a "better way" of accomplishing the Exist and Find.
boolean tstFind = false;
foreach (parseAC item in pACList)
{
tstFind = pList.Exists(x => (x.CompanyId == item.key.ToString()));
if (tstFind == true)
{
pItem = pList.Find(x => (x.CompanyId == item.key.ToString()));
//Processing done here. pItem gets updated here
...
}
Just as a side note, I'm going to be researching a way to use joins to see if that is faster. But I haven't gotten there yet. The above code is my first cut at solving this issue and it appears to work. However, since I have the time I want to see if there is a better way still.
Any input is greatly appreciated.
Time Findings:
My current Find and Exists code takes about 84 minutes to loop through the 5.5M items in the pACList.
Using pList.firstOrDefault(x=> x.CompanyId == item.key.ToString()); takes 54 minutes to loop through 5.5M items in the pACList
You can retrieve item with FirstOrDefault instead of searching for item two times (first time to define if item exists, and second time to get existing item):
var tstFind = pList.FirstOrDefault(x => x.CompanyId == item.key.ToString());
if (tstFind != null)
{
//Processing done here. pItem gets updated here
}
Yes, use a hashtable so that your algorithm is O(n) instead of O(n*m) which it is right now.
var pListByCompanyId = pList.ToDictionary(x => x.CompanyId);
foreach (parseAC item in pACList)
{
if (pListByCompanyId.ContainsKey(item.key.ToString()))
{
pItem = pListByCompanyId[item.key.ToString()];
//Processing done here. pItem gets updated here
...
}
You can iterate though filtered list using linq
foreach (parseAC item in pACList.Where(i=>pList.Any(x => (x.CompanyId == i.key.ToString()))))
{
pItem = pList.Find(x => (x.CompanyId == item.key.ToString()));
//Processing done here. pItem gets updated here
...
}
Using lists for this type of operation is O(MxN) (M is the count of pACList, N is the count of pList). Additionally, you are searching pACList twice. To avoid that issue, use pList.FirstOrDefault as recommended by #lazyberezovsky.
However, if possible I would avoid using lists. A Dictionary indexed by the key you're searching on would greatly improve the lookup time.
Doing a linear search on the list for each item in another list is not efficient for large data sets. What is preferable is to put the keys into a Table or Dictionary that can be much more efficiently searched to allow you to join the two tables. You don't even need to code this yourself, what you want is a Join operation. You want to get all of the pairs of items from each sequence that each map to the same key.
Either pull out the implementation of the method below, or change Foo and Bar to the appropriate types and use it as a method.
public static IEnumerable<Tuple<Bar, Foo>> Merge(IEnumerable<Bar> pACList
, IEnumerable<Foo> pList)
{
return pACList.Join(pList, item => item.Key.ToString()
, item => item.CompanyID.ToString()
, (a, b) => Tuple.Create(a, b));
}
You can use the results of this call to merge the two items together, as they will have the same key.
Internally the method will create a lookup table that allows for efficient searching before actually doing the searching.
Convert pList to HashSet then query pHashSet.Contains(). Complexity O(N) + O(n)
Sort pList on CompanyId and do Array.BinarySearch() = O(N Log N) + O(n * Log N )
If Max company id is not prohibitively large, simply create and array of them where item with company id i exists at i-th position. Nothing can be more fast.
where N is size of pList and n is size of pACList

Using Linq how I have to add list items

IEnumerable<ReportFavorite> list = reportService.GetReportFavorites(userId);
ddlReportFavorite.Items.Add()
I don't know how to add the lists to the dropdown using Linq. Thanks.
You an use AddRange method:
var list = reportService.GetReportFavorites(userId);
ddlReportFavorite.Items.AddRange(list.ToArray());
Depending on the dropdown control you are using, either of these could work:
If it allows its Items to be set to an IEnumerabe<ReportFavourite>:
ddlReportFavorite.Items = reportService.GetReportFavorites(userId);
If Items implements the AddRange method:
ddlReportFavorite.Items.AddRange(reportService.GetReportFavorites(userId));
Or, if these fail
foreach(var reportFavourite in reportService.GetReportFavorites(userId))
ddlReportFavorite.Items.Add(reportFavourite);
Neither of these methods is really "using LINQ", because LINQ is not a good tool to do this. LINQ is meant to be side-effect free.
Edit:
Your comment suggests that you are using a System.Web.UI.WebControls.DropDownList. In this case, the Items collection only accepts instances ListItem, so you need to create these from your ReportFavourites. Try
foreach(var listItem in reportService.GetReportFavorites(userId)
.Select(r => new ListItem(r.Id, r.Name))
ddlReportFavorite.Items.Add(listItem);
Here, I assume the combo box should display ReportFavourite.Name and have a value of ReportFavourite.Id. Use your own properties, of course
Or if you've already checked the data's integrity in the method, you could just simply say:
ddlReportFavorite.Items.AddRange(reportService.GetReportFavorites(userId));
Previously I put as IEnumerable. Now I changed to IList. It is working fine now. Thanks to all.
int userId = workContext.CurrentUser.UserID;
var reportFavoriteList = reportService.GetReportFavorites(userId);
int count = reportFavoriteList.Count;
for (int i = 0; i < count; i++)
{
ddlReportFavorite.Items.Add(reportFavoriteList[i].FavoriteName);
}
Since ddlReprotFavorite is an UI control and itsItemsproperty represent a set of controls as well you can not add directly your business entities instead of use DataSource property which automatically create Items collection from the underlying business entities.
IEnumerable<ReportFavorite> list = reportService.GetReportFavorites(userId);
ddlReportFavorite.DataSource = list;
The IEnumerable<T> is extended by the method Union<T> which unions two IEnumerable<T>'s. This is the more pretty way, without casting it ToList().
var reportFavoriteList = reportService.GetReportFavorites(userId);
ddlReportFavorite.Items = ddlReportFavorite.Items.Union(reportFavoriteList);

Categories

Resources