Alternative to nested loop for updating a collection - c#

I am getting a list of products from my database as a BindingList. I would like to update the quantities some of the products in that list using another list of items already selected by the user.
The idea is when the user brings up a fresh list of products from the database. The list will show the quantities of products that have already been selected from a previous search.
I have come up with the following nested loop. It works but wouldn't scale well as a search in the database could yield a large list that has to be traversed. How do you guys think I could improve this?
Also, I skimmed through the class where they taught the Big-O notation. What's the complexity of the below solution?
Thanks.
for (int i = 0; i < dbProducts.Count; i++ )
{
for (int j = 0; j < GlobalVars.productList.Count; j++)
{
EposProduct selectedProduct = GlobalVars.productList.ElementAt(j);
EposProduct dbProduct = dbProducts.ElementAt(i);
if(selectedProduct.ProductID == dbProduct.ProductID)
{
dbProduct.Quantity = selectedProduct.Quantity;
}
}
}

Your current approach with two nested loops is at best O(n^2) not counting the inner ElementAt method calls. Use a dictionary instead to do this in O(n):
var gbMap = GlobalVars.productList.ToDictionary(x => x.ProductId,
x => x.Quantity);
foreach(var product in dbProducts)
{
if(gbMap.ContainsKey(product.ProductId))
product.Quantity = gbMap[product.ProductId];
}

Related

C# & SQL Server : query takes long time

I have an ASP.NET MVC project and getting categories list and adding subcategories list to each category. My query takes long time. Thanks for answers.
// Create Sub Cat List
List<CategoryVM> catlist = (List<CategoryVM>)catServices.GetAll();
for (int i = 0; i < catlist.Count(); i++)
{
List<SubCategoryVM> subCatById = subCatServices.GetAll().Where(x => x.Category_Id == catlist[i].Id).ToList();
foreach (SubCategoryVM item in subCatById)
{
catlist[i].SubCategoryVM.Add(item);
}
}
ViewData["CatAndSubcatList"] = catlist;
And my service code is:
public IEnumerable<CategoryVM> GetAll()
{
var data = ProjectMapper.ConvertToVMList<IEnumerable<CategoryVM>>(_CategoryRepository.GetAll());
return (IEnumerable<CategoryVM>)data;
}
Instead of getting Categories and then adding the subcategories in a loop, use LINQ and get them in a single call. We don't have your model, so this sample is by guess:
var catlist = dbContext.CategoryVM.Include("SubCategoryVM").ToList();
This is your code edited. Instead of getting all Categories and then getting all subcategories again and again in a loop, it gets the lists from database just once and does the rest locally:
//Create Sub Cat List
List<CategoryVM> catlist = (List<CategoryVM>)catServices.GetAll();
List<SubCategoryVM> subCats = subCatServices.GetAll();
for (int i = 0; i < catlist.Count(); i++)
{
foreach (SubCategoryVM item in subCatById)
{
catlist[i].SubCategoryVM.AddRange(subCats.Where(x => x.Category_Id == catlist[i].Id));
}
}
You run sql inside loop so it may run 1000 times. That is why it is slow. I call it 1+N issue. Network connection (read Input/Output (IO) operations) is usually slow.
You need to change your code to get what you want from SQL Server in a 1 or 2 query (not N). Then you can have a loop to process your in memory data.
This is loop issue. GetAll(): i think this method is getting all record then you filter from In-memory collection. Create generic method to execute query on server side.

Matching lisboxes items and creating result

I am creating an Exam system in c#. I am creating result, i have answers in a listbox1 and correct answers in another listbox2, my issue is values in the listboxes should be compared and result should be generated on its base. If half the values match student is pass otherwise fail.
My code for this is following but it does not work.
for(int intCount = 0; intCount < listBoxSanswers.Items.Count;intCount++)
{
for (int intSubCount = 0; intSubCount < listBoxActAnswers.Items.Count; intSubCount++)
{
if (listBoxActAnswers.Items[intCount].ToString() == listBoxActAnswers.Items[intSubCount].ToString())
{
listBox3.Items.Add(listBoxActAnswers.Items[intCount].ToString());
}
}
}
If you want to use your approach, than you have to change one of the two lists to listBoxSanswers
If you want a shorter way, without the loops, you can try this line:
listBox3.Items.AddRange(listBoxActAnswers.Items.Cast<string>().ToList().Intersect(listBoxSanswers.Items.Cast<string>().ToList()).ToArray());
EDIT:
Oh okay, so you have a DataTable as a DataSource.
Than you can do it this way:
listBox3.Items.AddRange(listBoxActAnswers.Items.Cast<DataRowView>().Select(r => r[0]).ToList().Intersect(listBoxSanswers.Items.Cast<DataRowView>().Select(r => r[0]).ToList()).ToArray());
Maybe you should adapt Select(r => r[0]) to the right column which is your DisplayMember.

c# collections and re-numbering not working as expected

Hi i'm trying to setup simple test data.
I simply want to take a collection which is smallish and make it bigger by add itself multiple times.
After I;ve added them together i want to re-number the property LineNumber
so that there are no duplicates and that it goes in order. 1,2,3,4....
no matter what i try it doesn't seem to work and i cant see the mistake.
var sampleTemplateLine = dataContext.TemplateFileLines.ToList();
*//tired this doesnt work either*
//List<TemplateFileLine> lineRange = new List<TemplateFileLine>();
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
var allProducts = sampleTemplateLine
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.ToList();
int i = 1;
foreach (var item in allProducts)
{
item.LineNumber = i;
i++;
}
this doesnt seem to work either
//re-number the line number
var total = allProducts.Count();
for (int i =0; i < total; i++)
{
allProducts[i].LineNumber = i+1;
}
PROBLEM: below RETURN 4 when i'm expecting 1
var itemthing = allProducts.Where(x => x.LineNumber == 17312).ToList();
You are adding the same objects multiple times. You wold have to add new objects or clone the ones you have.
The problem is they are pointing the same object. So if you change a property it changes all the pointed objects at the same
You can use Clone method if it exist, if not you can create your own Clone method like in this question.

filtering large DataTable in for loop

I have a DataTable with Row.Count=2.000.000 and two columns containing integer values.
So what i need is filtering the datatable in a loop, efficiently.
I'm doing it with;
for (int i= 0; i< HugeDataTable.Rows.Count; i++)
{
tempIp= int.Parse(HugeDataTable.Rows[i]["col1"].ToString());
var filteredUsers = tumu.Select("col1= " + tempIp.ToString()).Select(dr => dr.Field<int>("col2")).ToList();
HashSet<int> filtered = new HashSet<int>(filteredUsersByJob2);
Boolean[] userVector2 = userVectorBase
.Select(item => filtered.Contains(item))
.ToArray();
...
}
What should I do to improve performance. I need every little trick. Datatable index, linq search are what i came up with google search. I d like hear your suggestions.
Thank you.
You may use Parallel.For
Parallel.For(0, table.Rows.Count, rowIndex => {
var row = table.Rows[rowIndex];
// put your per-row calculation here});
Please have a look at this post
You're using a double for loop. If your tumu contains a lot of rows it will be very slow.
Fix: make a dictionary with all users before your for loop. In your for loop check the dictionary.
Something like this:
Dictionary<string, id> usersByCode;//Init + fill it in
for (int i= 0; i< HugeDataTable.Rows.Count; i++)
{
tempIp= int.Parse(HugeDataTable.Rows[i]["col1"].ToString());
if(usersByCode.Contains(tempId)
{
//Do something
}
}

Is there a more efficent way to randomise a set of LINQ results?

I've produced a function to get back a random set of submissions depending on the amount passed to it, but I worry that even though it works now with a small amount of data when the large amount is passed through, it would become efficent and cause problems.
Is there a more efficent way of doing the following?
public List<Submission> GetRandomWinners(int id)
{
List<Submission> submissions = new List<Submission>();
int amount = (DbContext().Competitions
.Where(s => s.CompetitionId == id).FirstOrDefault()).NumberWinners;
for (int i = 1 ; i <= amount; i++)
{
bool added = false;
while (!added)
{
bool found = false;
var randSubmissions = DbContext().Submissions
.Where(s => s.CompetitionId == id && s.CorrectAnswer).ToList();
int count = randSubmissions.Count();
int index = new Random().Next(count);
foreach (var sub in submissions)
{
if (sub == randSubmissions.Skip(index).FirstOrDefault())
found = true;
}
if (!found)
{
submissions.Add(randSubmissions.Skip(index).FirstOrDefault());
added = true;
}
}
}
return submissions;
}
As I say, I have this fully working and bringing back the wanted result. It is just that I'm not liking the foreach and while checks in there and my head has just turned to mush now trying to come up with the above solution.
(Please read all the way through, as there are different aspects of efficiency to consider.)
There are definitely simpler ways of doing this - and in particular, you really don't need to perform the query for correct answers repeatedly. Why are you fetching randSubmissions inside the loop? You should also look at ElementAt to avoid the Skip and FirstOrDefault - and bear in mind that as randSubmissions is a list, you can use normal list operations, like the Count property and the indexer!
The option which comes to mind first is to perform a partial shuffle. There are loads of examples on Stack Overflow of a modified Fisher-Yates shuffle. You can modify that code very easily to avoid shuffling the whole list - just shuffle it until you've got as many random elements as you need. In fact, these days I'd probably implement that shuffle slightly differently to you could just call:
return correctSubmissions.Shuffle(random).Take(amount).ToList();
For example:
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
for (int i = 0; i < elements.Length; i++)
{
// Find an item we haven't returned yet
int swapIndex = i + rng.Next(elements.Length - i);
T tmp = elements[i];
yield return elements[swapIndex];
elements[swapIndex] = tmp;
// Note that we don't need to copy the value into elements[i],
// as we'll never use that value again.
}
}
Given the above method, your GetRandomWinners method would look like this:
public List<Submission> GetRandomWinners(int competitionId, Random rng)
{
List<Submission> submissions = new List<Submission>();
int winnerCount = DbContext().Competitions
.Single(s => s.CompetitionId == competitionId)
.NumberWinners;
var correctEntries = DbContext().Submissions
.Where(s => s.CompetitionId == id &&
s.CorrectAnswer)
.ToList();
return correctEntries.Shuffle(rng).Take(winnerCount).ToList();
}
I would advise against creating a new instance of Random in your method. I have an article on preferred ways of using Random which you may find useful.
One alternative you may want to consider is working out the count of the correct entries without fetching them all, then work out winning entries by computing a random selection of "row IDs" and then using ElementAt repeatedly (with a consistent order). Alternatively, instead of pulling the complete submissions, pull just their IDs. Shuffle the IDs to pick n random ones (which you put into a List<T>, then use something like:
return DbContext().Submissions
.Where(s => winningIds.Contains(s.Id))
.ToList();
I believe this will use an "IN" clause in the SQL, although there are limits as to how many entries can be retrieved like this.
That way even if you have 100,000 correct entries and 3 winners, you'll only fetch 100,000 IDs, but 3 complete records. Hope that makes sense!

Categories