I've seen code like the following in the Silverlight toolkit and can't understand how it is safe to do:
private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Update the cache
if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
{
for (int index = 0; index < e.OldItems.Count; index++)
{
_items.RemoveAt(e.OldStartingIndex);
}
}
}
If you remove an item from say index 5 doesn't that change the current index of every item in the _items collection after index 5 to be one less than before? So then how is it safe to continually remove items using their "old" indexes like this code does?
I'd really like to understand why this works.
Any ideas?
This code looks like it is removing a contiguous set of items from a particular starting index. If you read the remove call carefully:
_items.RemoveAt( e.OldStartingIndex );
you will notice that it is iteratively removing items from a constant index. This means it is collapsing the list by removing items in a contiguous range. This may very well be correct - assuming that is the intent of the code.
The loop executes as many times as e.OldItems.Count indicates. So (presumably) it is told how many items to remove starting from a given index.
As a general practice, you have to be careful about how you remove items from collections for a couple reasons:
Item indexes do indeed change as you remove items. So care must be taken to avoid errors resulting from item index positions shifting.
Mutating a list while iterating over it (as in a foreach loop or with an explicit IEnumerator) results in exceptions. You cannot mutate a list (either add or delete) while iterating - it invalidates the iterator.
Related
I'm designing an inventory system. right now, I need to test whether or not an item is in the inventory in order for the stage to know whether to instantiate that item in the particular level or not.
I add the items to the levels in groups, so this code is located within an array loop which "unloads" the "pack" of items corresponding to each level.
if (inv.indexOf(group[i]) == -1) {
//add item + item functionality
}
This method works when I add the item to the inventory such as this:
inv.push(group[i]);
if (inv.indexOf(group[i]) == -1) {
//add item + item functionality
}
But that doesn't work, because why would I add an item to the inventory without the user collecting it first? so the code is actually structured as so:
if (inv.indexOf(group[i]) == -1) {
//if item is not in inventory, add to stage
addChild(group[i]);
//when a user clicks this (any) item,
group[i].addEventListener(MouseEvent.CLICK, function itemFunctionality(e:MouseEvent){
//target item clicked
var item = e.target;
//add the item to the inventory
inv.push(item);
//sidenote: if i were to check inv.indexOf(item) here, i
//would get a positive index. unfortunately,
//i cant check whether the item is in the inventory
//after its already been added to the level...
item.removeEventListener(MouseEvent.CLICK, itemFunctionality);
});
}
The problem is when you leave and come back to the level, the items you already collected re-instantiate. If you collect an item again, the inventory adds a copy of the item you already collected.
The inv.indexOf(group[i]) checker doesn't understand that when the array loop reaches the corresponding, item group[i] == the object added to the inventory through inv.push(item) or in other words inv.push(e.target) (which, of course, I couldn't write directly into the code)...
When I trace whats inside of static array inv, what group[i] is within the array loop, or what e.target is, they all output the same type of item, "[object itemName]", signifying that the indexOf check SHOULD match up.
Update :
It appears if I make the items static as well as the array group they belong to this method works within the mouse event callback:
inv.push(item);
group.splice(group.indexOf(item), 1);
Though I had to remove the items and the item groups from their own class and put them inside of the level class itself... I feel this method kind of sucks because everything is getting disorganized and grouped into the same class.
Any helpful suggestions?
Objects are matched with their references. It means two objects created from the same class are not identical, they are different objects.
Assign unique IDs to your items and use them in your inventory. Like;
inv.push(item.id);
if (inv.indexOf(item.id) == -1) {
//add item + item functionality
}
Working with IDs is also better for serializing / deserializing.
There's your problem:
The problem is when you leave and come back to the level, the items you already collected re-instantiate.
Why would coming back to a level cause reinstatiation of any kind?
You should only ever once create each level object and part of that process should be creating all objects contained in that level. Visiting a level merely is an interaction with that object, which may include removing objects and adding them to the inventory. Once the objects are gone from the level, they are gone.
There's no reason to reinstantiate a level (or any of the objects within it) when revisiting it. If you are running into this problem because you are using a time line based approach with frames and gotoAndStop() to switch between levels then this is the core of your problem and you should stop doing that.
I have a HashSet containing tens of thousands of rectangles, when the Y is less than 0 I want to remove it from the HashSet
right now my code looks like the following
for (Rectangle p : point) {
if(p.y<0){
point.remove(p);
System.out.println("removing p");
continue;
}
Here is the code for my HashSet
public HashSet<Rectangle> point;
it never removes the Rectangle that has a Y less than 0 but the System.out.println("removing p"); runs.
The problem is HashSet does not allow you to remove items that had their hash changed. So it is pretty much useless for changing items. Instead use the Array class built in libGDX and it will work fine. Not sure of the performance though maybe someone can post an answer with faster performance.
I've been creating a system for storing objects that persist for a specified duration, then remove themselves. However, I'm having some trouble figuring out how to remove expired items from the array while the for loop is running (to minimize extra iterations through the array)
Here is what I've made so far. Terms in the list are flagged with a Boolean value indicating that they're done.
For i = 0 To VisualEffects.Count - 1 Step 1
VisualEffects(i).Update(gt)
VisualEffects(i).Draw(sb, Pos, Cam)
If VisualEffects(i).CD.isExpired() Then
VisualEffects.RemoveAt(i)
i = -1
End If
Next
Why does this produce an error? How can I remove a term from an array and continue iterating through the remainder of the loop?
Regards
Ares
The line
i = -1
sets i to -1, causing the For loop to terminate.
If you can, reverse the order of the loop so that you start with the last array element and count down. That makes the remove logic much more straightforward.
.NET arrays are fixed-size so you can't actually remove an element from an array at all. You can change the value of an element, so you can set an element to Nothing to remove the object that it referred to, but the element is still there. If you read the MSDN documentation for the Array class, you'll see that the RemoveAt method throws a NotSupportedException for this reason.
If you're actually using a collection rather than an array then you can call RemoveAt but you need to loop from the last index to the first rather than first to last. That way, removing an item will not affect the indexes of those items that you are yet to visit.
I have a winforms TreeView control with the Sorted property set to true. I also override the default sorter by assigning and instance of IComparer to the TreeViewNodeSorter property.
Unfortunately adding a few thousand nodes using the AddRange function takes perhaps 10 seconds. If I set Sorted to false the AddRange function is < 1/2 second. (please no discussions about the validity of adding so many nodes)
Aha I hear you say.. there is a problem in my IComparer object. Not according to the profiler. Barely any time is spent in the sorting object and yet the AddRange function is right at the top of the list of slow functions.
The problem is easy to replicate in a test project. Simply create a list of TreeNodes and add it to the an existing expanded tree node using the AddRange function. This will use the default sort on the tree text - again it is disproportionately slow.
To demonstrate how disproportionately slow it is if I disable the Sorted property in the test probject and use the List<T>.Sort function (with a delegate that compares the Text of the nodes) on my list of nodes before adding them to the tree there is virtually no delay.
This leads to the workaround of sorting the nodes manually before using AddRange. That's OK but it means a lot of work to find the correct insertion point when adding nodes to an existing set of child nodes - rather less convenient than simply setting Sorted to true.
Is there anyway to speed up the behaviour?
EDIT - it seems the only way is to sort before adding.. it's a bit of a hassle but I came up with the following extension method:
public static void AddSortedRange(this TreeNodeCollection existingNodes, IList<TreeNode> nodes, TreeView treeView, IComparer sorter)
{
TreeNode[] array = new TreeNode[nodes.Count + existingNodes.Count];
existingNodes.CopyTo(array, 0);
nodes.CopyTo(array, existingNodes.Count);
Array.Sort(array, sorter);
treeView.BeginUpdate();
existingNodes.Clear();
existingNodes.AddRange(array);
treeView.EndUpdate();
}
It is quicker to copy the existing nodes to an array, append the new nodes, sort the array and then replace that trying to manipulate nodes inline in the tree view - the slowest operation in the above code is the existingNodes.Clear() call
The performance problems you have are related to the fact that you are adding items to a sorted TreeView. What happens behind the scenes when you add to a sorted list is that for each item that you are adding, it tries to find it's place, which means that it needs to go through the whole list for each item, now imagine how many iteration that makes for each new item :)
What you can do is this:
TreeView tv = new TreeView(); // Just so I have a TreeView variable
TreeNode[] nodes = ... // Well, your list of nodes that you want to add
tv.SuspendLayout();
tv.Sorted = false;
tv.Nodes.Clear();
tv.Nodes.AddRange( nodes );
tv.Sorted = true;
tv.ResumeLayout();
For performance reasons we are using the SuspendLayout/ResumeLayout methods to disable the painting process used by the TreeView when manipulating it's items, which we would cause by removing the items and then adding them as well, since it would need to repaint to add the new item that you are adding (for each of the items).
Right before we are doing any changes to the Nodes Collection we have to call Sorted = false; to disable the sorting (this is just temporary - the user will not see any changes because of SuspendLayout).
Then just add the items to the collection (since the TreeView is not sorted for the time being it should be really quick).
Then we enable the sorting again by calling Sorted = true; setting the Sorted Property to true will cause for the collection to do a sort.
This way, the sort will be performed only once (and therefore the TreeView will just go once through the items).
One more thing, if you have a custom sorter defined for the ListView (tv.ListViewItemSorter), set it to null before adding the items as well, just temporary of course, re-enable it again before the ResumeLayout call.
I experienced a locking situation using the Sort() method.
It worked fine for weeks, then once, it stucks, stucking my application with 25% CPU in the task manager.
var allTags = _TagEngine.GetTags(1, force);
try
{
TagTree.BeginUpdate();
TagTree.Nodes.Clear();
foreach (var rec in allTags)
{
... adding nodes in the tree
}
TagTree.Sort(); // <= stuck here !
}
finally
{
TagTree.EndUpdate();
}
So I watch inside the Sort() method using a decompiler, and I noticed it handles already the BeginUpdate/EndUpdate feature internally.
Then I moved the TagTree.Sort() outside the BeginUpdate/EndUpdate, and it works fine since.
var allTags = _TagEngine.GetTags(1, force);
try
{
TagTree.BeginUpdate();
TagTree.Nodes.Clear();
foreach (var rec in allTags)
{
... adding nodes in the tree
}
}
finally
{
TagTree.EndUpdate();
}
TagTree.Sort();
I hardly understood what happened here. Why it worked in the past, and suddenly stoped. Frankly, I did not had time enough to dig further and anyway, the most important is here : it works again.
I made a simple extension to the TreeView control. It is very fast. It moves internal storage to a Dictionary which makes a huge difference. In my real world example I have 100000 records that I need to load. It took 37 minutes before, but now it takes 2.2 seconds!!
You can find example and code on CodeProject: http://www.codeproject.com/Articles/679563/Fast-TreeView
So far I've found 2 ways to delete selected items from a listbox (winform):
1.
ListBox.SelectedObjectCollection tempCollection = new ListBox.SelectedObjectCollection(myListBox);
for (int i = tempCollection.Count - 1; i >= 0; i--)
{
myListBox.Items.Remove(tempCollection[i]);
}
2.
while (myListBox.SelectedItems.Count > 0)
{
myListBox.Items.Remove(myListBox.SelectedItem);
// or
//myListBox.Items.Remove(myListBox.SelectedItems[0]);
}
The 2nd way is easy to understand, but the 1st one is strange for me. They're both work for me, I just want to know the difference?
The first way is written really strangely. It looks strange because it goes backwards. This is so that it doesn't upset the collection of selected items. The selected items collection isn't fully created, it's an Enumerable collection, and items come off it only when you ask for them. You can't use a foreach, because the collection of selected items keeps changing as you remove items.
I actually prefer the second way. Besides, whatever reads easier is easier to maintain. The first one reads: get the selected items, go through them backwards, removing each from the list.
The second one reads: while there are selected items, remove the first available selected item.
Much better the second way.