How to remove contents from list - c#

I'm very new to coding. Is there any other way through which I can write this code to remove contents from list in c#?
public void RemoveItem(string itemDescription)
{
MenuItem found = new MenuItem(false, 0, "", ""); // must be a better way - to fix
foreach (MenuItem item in MenuItems)
{
if (item.Description == itemDescription)
{
found = item;
}
}
MenuItems.Remove(found);
}

There are several ways how to improve this code. Some of them were already mentioned.
As commented by #tim-schmelter
// removes the last matching occurrence from the menu and avoids the "dummy" object
public void RemoveItem(string itemDescription)
{
MenuItem found = null; // this is better
foreach (MenuItem item in MenuItems)
{
if (item.Description == itemDescription)
{
found = item;
}
}
if (found != null)
MenuItems.Remove(found);
}
As originally answered by #SomeBody
// removes the first matching occurrence from the menu
public void RemoveItem(string itemDescription)
{
foreach (MenuItem item in MenuItems)
{
if (item.Description == itemDescription)
{
MenuItems.Remove(found);
break; // or return;
}
}
}
// same but replacing the loop with a LINQ approach
public void RemoveItem(string itemDescription)
{
MenuItem found = MenuItems.FirstOrDefault(item => item.Description == itemDescription);
if (found != null)
MenuItems.Remove(found);
}
// removing all occurences (assumes that MenuItems is from type `List<MenuItem>`
public void RemoveItem(string itemDescription)
{
MenuItems.RemoveAll(item => item.Description == itemDescription);
}
// assuming List<MenuItem>, removing the first occurrence only
public void RemoveItem(string itemDescription)
{
int index = MenuItems.Find(item => item.Description == itemDescription);
if (index >= 0)
MenuItems.RemoveAt(index);
}

I'm assuming that MenuItems is a List<MenuItem>.
In which case, as Johnathan says in the comments MenuItems.RemoveAll(item => item.Description == itemDescription); will remove all elements in the list that match your criteria and is probably the most efficient option:
public void RemoveItem(string itemDescription)
{
MenuItems.RemoveAll(item => item.Description == itemDescription);
}
Ideally, though, I'd use an ID field to make sure you're definitely removing the right element.

From a performance perspective there is another solution which especially makes sense if a list grows very huge or is traversed very often (which is hopefully not the case for a menu list).
Most of the so far given answers require two lookups (one for finding the item, one for removing the item), or have some overhead by calling a predicate function.
The following is probably the fastest method and works with every collection implementing IList<T>:
// remove last matching item
public void RemoveItem(string itemDescription)
{
for (int i = MenuItems.Count - 1; i >= 0; i--) // reverse for-loop, to avoid Count property call on each iteration
{
if (MenuItems[i].Description == itemDescription)
{
MenuItems.RemoveAt(i);
return;
}
}

#geraldmayr answer is great.
Only thing I would change is the approach of the original answer in LINQ to make it a clean one liner.
public static void RemoveItem(string itemDescription)
{
menuItems.Remove(menuItems.First(menuItem => menuItem.ItemDescription == itemDescription));
}

Related

Algorithm to implement a JOIN with some limitation

Let me explain my matching problem with a real example (the problem is generic). Assume having 2 lists: of "selections" loaded from different sources. The list don't have duplicates.
Let's say mkTPL.Selections and mkDB.Selections come from SQL Tables each with an unique index on the id and the selection's name. The problem is that sometimes IdSelectionType is null (in the selection from mkTPL.Selections)
foreach (var selTPL in mkTPL.Selections)
{
foreach (var selDB in mkDB.Selections)
{
if (selTPL.IsTheSame(selDB))
selTPL.OddOrResultValue = selDB.OddOrResultValue;
}
}
public bool IsStessaSelezione(SelectionPrints selDb)
{
if (selDb.IdSelectionType == this.IdSelectionType)
return true;
else
{
bool isSameName = selDb.Name == this.Name;
bool isSimilarName = false;
if (!isSameName)
{
isSimilarName = RegexReplace(selDb.Name, #"\([\d.]+\)") == RegexReplace(this.Name, #"\([\d.]+\)");
}
return isSameName || isSimilarName;
}
}
The match alghtoritm that I have implemented is not efficient. Once a selection is matched I shouldn't try to match it further with others (because of the unique index on the id and on the selection name).
Linq could provide me an easy solution?
First of all, you should break when you found a match:
foreach (var selTPL in mkTPL.Selections)
{
foreach (var selDB in mkDB.Selections)
{
if (selTPL.IsTheSame(selDB))
{
selTPL.OddOrResultValue = selDB.OddOrResultValue;
break; // <--
}
}
}
Second, I would make a dictionary of mkDB.Selections, where you store the regexed value so you don't have to make that calculation over and over again, on every iteration.
Something like:
var mkDBDictionary = mkDB.Selections.ToDictionary(s => RegexReplace(s.Name, #"\([\d.]+\)"), s => s);
foreach (var selTPL in mkTPL.Selections)
{
string selTPLName = RegexReplace(selTPL.Name, #"\([\d.]+\)");
if (mkDBDictionary.TryGetValue(selTPLName, out var selDB))
{
selTPL.OddOrResultValue = selDB.OddOrResultValue;
}
}

Set a field in all the children of an item

I have an Item in sitecore lets say "AddressItem" that has some children. I want to edit a Field "IsMain" in all the child items.
I am using Foreach loop. Is there some better way to achieve this.
foreach (Sitecore.Data.Items.Item child in AddressItem.Children)
{
using (new SecurityDisabler())
{
child.Editing.BeginEdit();
child.Fields["IsMain"].Value = "0";
child.Editing.EndEdit();
}
}
Its probably faster to set the IsMain fields standard value to 0 then reset all items to that standard value. There is no function for that out of the box but the below code will do it.
This function is a little more robust then your requirement but its the code I have as is.
First you need a user with the correct permissions to replace: ElevatedUserAccount
Next get the list of items you would like to reset the values for then create a list of fields you wish to reset. In your case AddressItem.Children and IsMain.
Finally pass them into the below methods.
public void ResetFields(List<Item> items, List<string> fields)
{
if (items.Any() && fields.Any())
{
foreach (Item item in items)
{
ResetFieldsOnItem(item, fields);
}
}
}
public void ResetFieldsOnItem(Item item, List<string> fields)
{
if (item != null && fields.Any())
{
using (new Sitecore.Security.Accounts.UserSwitcher(ElevatedUserAccount, true))
{
item.Editing.BeginEdit();
foreach (string field in fields)
{
item.Fields[field].Reset();
}
item.Editing.EndEdit();
}
}
}

Remove duplicates and put the list into a listbox

This is an uni assignment and I am having problem with part of it. This is the code;
namespace Assignment_1
{
public partial class Classifier : System.Web.UI.Page // We are using a web form as stated
{
protected void Page_Load(object sender, EventArgs e) // No variables are initiated for the beginning
{
}
protected void ButtonClassify_Click(object sender, EventArgs e)
{
if (this.TextBox1.Text != "")
{
List<string> numbersText = this.TextBox1.Text.Split(',').ToList<string>();
foreach (var item in numbersText)
{
int num = int.Parse(item);
if (RadioButtonList1.SelectedValue == "Both")
{
if (num % 2 == 0)
{
if (CheckBoxDuplicate.Checked == true)
{
List<int> evenNumbers = new List<int>();
evenNumbers.Add(num);
List<int> distinctEvenNumbers = evenNumbers.Distinct().ToList();
ListBoxEvenNumbers.DataSource = distinctEvenNumbers;
}
else
{
//Put the results into the respective boxes
ListBoxEvenNumbers.Items.Add(num.ToString());
}
}
else
{
//Put the results into the respective boxes
ListBoxOddNumbers.Items.Add(num.ToString());
}
}
if (RadioButtonList1.SelectedValue == "Even")
{
if (num % 2 == 0)
{
//Put the results into the respective boxes
ListBoxEvenNumbers.Items.Add(num.ToString());
}
}
if (RadioButtonList1.SelectedValue == "Odd")
{
if (num % 2 == 1)
{
//Put the results into the respective boxes
ListBoxOddNumbers.Items.Add(num.ToString());
}
}
Let me explain the question and what I have done. User inserts list of numbers into a text box and then has 3 options (radiolistbutton). He can list even, odd or both type of numbers. They display in even and odd listboxes(2 listboxes). I have done up to this part.
There is a checkbox to remove duplicates and the user can check it if he wishes to. If the button is checked, the code should remove the duplicates. I tried to do this part in the 4th "if-else" "if (CheckBoxDuplicate.Checked == true)". The way I understand it, I check if the number is even and then check the CheckboxDuplicate button. if it is checked I put the values in a new list and then delete repeated values. Then put into EvenNumbers listbox. For some reason, this part doesn't work.
If you would like to help me, please don't post just your answer. This is my first project in C# and it is difficult for me to understand an elegant solution yet. If you have time, please check my code and let me know where I made a mistake. Thanks for your time in advance.
Sprinkle a bit of linq magic on it, and you're done.
var my_list = new List<int>{1,2,3,4,5,5,6,7};
var pair = my_list.Where (n => n%2 ==0); // 2,4,6
var odd = my_list.Where (n => n%2 ==1); // 1,3,5,5,7
var unique_pair = pair.Distinct(); // stays the same
var unique_odd = odd.Distinct(); // 1,3,5,7
From here is just adding it to your appropriate calls and GUI containers
From your comment, here are a couple of things:
Change the if to if - else if, since only one will apply.
you can do it the way you do, but it's not the most efficient. If you go that way, you'll have to figure out which numbers you've added in order to not have duplicates.
alternatively, you can simply create the lists like I've done in the code above, and then assign them at the end. It'll save you time and code.
Here's some more help, with no code, since I believe i covered it.
Step 1: get the user input, and create a list of ints. (call it: input_list).
Step 2: According to what he chose (even, odd, both), you want to assign to each listbox, a list of numbers. Look at my above code, it'll do that bit for you.
Step 3: If user choses unique, you pass to those listboxes the Distinct list, again, look at my above code for that.
You can apply the unique on the event of the checkbox being selected if you prefer.
Notes:
Keep the list of ints (the input_list) as a variable, so you don't need to parse it whenever he changes his selection.
public enum PairOddEnum
{
Evens,
Odds,
Both
}
public void BindControl(PairOddEnum type)
{
if (this.textBox1.Text != "")
{
List<string> numbersText = this.textBox1.Text.Split(',').ToList<string>();
var evens = numbersText.Where(t => int.Parse(t) % 2 == 0).Distinct();
var odds = numbersText.Where(t => int.Parse(t) % 2 == 1).Distinct();
if (type == PairOddEnum.Evens)
{
ListBoxEvenNumbers.DataSource = evens.ToList();
}
else if (type == PairOddEnum.Odds)
{
ListBoxOddNumbers.DataSource = odds.ToList();
}
else
{
ListBoxEvenNumbers.DataSource = evens.ToList();
ListBoxOddNumbers.DataSource = odds.ToList();
}
}
}
protected void ButtonClassify_Click(object sender, EventArgs e)
{
if (RadioButtonList1.SelectedValue == "Both")
{
BindControl(PairOddEnum.Both);
}
if (RadioButtonList1.SelectedValue == "Even")
{
BindControl(PairOddEnum.Evens);
}
if (RadioButtonList1.SelectedValue == "Odd")
{
BindControl(PairOddEnum.Odds);
}
}

Changing collection inside foreach

Here is my code:
foreach (OrderItem item in OrderInfo.order)
{
orderItemViews.Single(i => i.numericUpDown.Name == item.id.ToString()).numericUpDown.Value = item.count;
}
It gives an exception.
I know that I can't change the collection inside foreach
How can I change this code to make it work? Best of all if it would be LINQ code.
exception says that "The collection was modified". sorry can't provide real message of exception because it in non-english
sorry guys. I've found where collection is changing. It was inside *numericUpDown_ValueChanged* handler. anyway I've got an answer. thank you
You can use ToList(), Like this :
foreach (OrderItem item in OrderInfo.order.ToList())
{
orderItemViews.Single(i => i.numericUpDown.Name == item.id.ToString()).numericUpDown.Value = item.count;
}
Or use normal for loop :
for (int i = 0 ; i < OrderInfo.order.Count; i++)
{
OrderItem item = OrderInfo.order[i];
orderItemViews.Single(i => i.numericUpDown.Name == item.id.ToString()).numericUpDown.Value = item.count;
}
Tip : Performance wise, It's better to use the second way.
This is what I do, when I need to modify the collection.
foreach (OrderItem item in OrderInfo.order.ToList())
{
...
}
Create a copy. Enumerate the copy, but update the original one.
You can use an extension ToEach static method:
public delegate void ToEachTransformAction<T>(ref T Telement);
[Extension()]
public static void ToEach<T>(Generic.IList<T> thislist, ToEachTransformAction<T> Transform)
{
for (var n = 0; n < thislist.Count; n++)
{
Transform.Invoke(thislist(n));
}
}

Performing a boolean AND string search on sub-collections of a collection (non-LINQ)

I hope the title makes sense.
I have a set of items that I want to search and select a subset of, based on a set of keywords that must all appear at least once in any of the SubItems of the Items. I believe this could easily be achieved using LINQ, but I'm using .NET 2.0 for this project.
The code below should achieve pretty much what I want to do, assuming AllBitsAreSet is implemented, but I'm wondering if I'm missing an alternative, simpler way of doing this?
Since there doesn't appear to be a good way of checking if all the bits in a BitArray are set, besides looping through them all (please tell me if there is!), I'm wondering about "nicer" alternatives. Not necessarily more CPU efficient, because I doubt the below code will be too slow for the data sets I'm working with, but ones with less code.
public List<Item> Search(Item[] items, List<string> keywords)
{
List<Item> results = new List<Item>();
BitArray flags = new BitArray(keywords.Count);
foreach (Item item in items)
{
flags.SetAll(false);
foreach (SubItem subItem in item.SubItems)
{
for (int i = 0; i < keywords.Count; i++)
{
if (subItem.StringValue.IndexOf(keywords[i]) >= 0)
flags[i] = true;
}
}
if (AllBitsAreSet(flags)) results.Add(item);
}
return results;
}
You can use LINQ Bridge to get LINQ support on .NET 2.0 and use the following LINQ query.
items.Where(i =>
keywords.All(k =>
i.SubItems.Any(s =>
s.StringValue.Contains(k))));
You can avoid using the bit set if you swap the two inner loops - the performance impact depends on thenumber of sub items vs the number of keywords.
foreach (Item item in items)
{
Boolean found = false;
foreach (String keyword in keywords)
{
found = false;
foreach (SubItem subItem in item.SubItems)
{
if (subItem.StringValue.Contains(keyword))
{
found = true;
break;
}
}
if (!found)
{
break;
}
}
if (found)
{
result.Add(item);
}
}
I would write it as follows. Of course this is very similar to Daniel's solution, but I believe it is better.
public List<Item> Search(Item[] items, List<string> keywords)
{
List<Item> results = new List<Item>();
foreach (Item item in items)
if(ContainsAllKeywords(item, keywords))
results.Add(item);
return results;
}
bool ContainsAllKeywords(Item item, List<string> keywords)
{
foreach (string keyword in keywords)
if (!ContainsKey(item.SubItems, keyword))
return false;
return true;
}
bool ContainsKey(IEnumerable<SubItem> subItems, string key)
{
foreach (SubItem subItem in subItems)
if (subItem.StringValue.Contains(key))
return true;
return false;
}
edit: changed == to .Contains() as per comment

Categories

Resources