I do a lot of different iterations. I have learned all sorts of tricks and patterns. For one, i have found no need for “for each” iterations, as i generally just get the count or length, and use standard i++ iterator.
I have looked curiously at queues and stacks over the years,but cant really find a need for them. What are some hypothetical uses for them in game dev, that an iterator cant do, or that would be significantly simpler/more readable than an iterator?
Foreach is rather convenient unless you need the iterator variable itself.
Never mind the “x is faster” theoretical observations that are marginal at best in real world scenarios.
Queues are for collections that can grow at a different rate than they are removed. A command queue comes to mind where the user may issue various cloud save calls eg “moved inventory item from slot A to slot B” but behind the scenes these changes are only pushed to the cloud every couple seconds. And these pushes may be limited to at most 20 items. So you send out (remove) 20 items from the queue every couple seconds until the queue is empty. By using a queue you can make sure you’re pushing the oldest (longest in cache) commands first.
Stacks would be quite useful for a card game where you can have stack of cards and you can only add to / remove from the top of the stack.
I have just found i usually do, or upon refactoring i often come upon a for each loop that i need to change to an iteration, so as of about a week ago, i just decided no more for each loops. Just dont see the point, all bench tests ive seen show very little performance difference between the two. Its kind of like my “no var” preference. I just feel the iterator is more explicit.
Queues and Stacks don’t really have anything to do with iterators, honestly. They’re just types of collections that have been designed with a certain idiomatic pattern in mind.
Naturally you use a queue where you need something work in a first in, first out basis. And a stack where you need something in a last in, first out basis. There’s not much else to say about it. Same reason you may use a HashSet (don’t want duplicates), or want a Dictionary (fast lookup). They have particular usages in mind.
Both of which come up a lot less than using a regular array or List. Particularly as neither are natively serializable by Unity.
I hope you do make that change with a refactoring, not manually. You can flip between both variants with a single refactoring, at least in good IDEs.
Well, there is a point in that it makes the code more readable. Consider:
for (int i = 0; i < transform.ChildCount; i++)
transform[i].Translate(transform[i].position + moveDir);
vs
foreach (Transform child in transform)
child.Translate(child.position + moveDir);
Not only does it remove all the various symbols that make for hard reading, it also gives transform a speaking name (provided you care for naming variables, which you should).
Which means you prefer to duplicate the type even though it is implicit. This goes against the DRY principle. It again makes code harder to read, but on top it makes code harder to WRITE too:
Dictionary<MyTypeForKeys, Dictionary<string, object>> whatever = new Dictionary<MyTypeForKeys, Dictionary<string, object>>();
vs
var whatever = new Dictionary<MyTypeForKeys, Dictionary<string, object>>();
Note that any good IDE will fine-print the actual type of a “var” variable next to it.
Also note that I do not consider Microsoft’s IDE to adhere to my notion of “good”.
First, there is nothing preventing you from using just for loops. Especially if you want to rely on counts and you need an index.
foreach is a specific feature of C#, belonging to what’s known as enumerators, which allows you to loop over a generalized set. It’s not really there to replace the other loops, it’s more of a convenient, generalized approach of iterating through some elements, which happens all the time in programming. However, with foreach it’s more about the object-oriented approach, encapsulation, and effective use of memory, than it is about iterating through arrays and other index-based solutions. Also, we all need ways to iterate through non-indexable collections such as the dictionary.
While for and while (or do) loops are there for legacy and performance reasons, they are much more mathematical and low-level in what they’re doing, they are essentially parts of the fundamental language grammar.
foreach is a more modern, high level construct, that can be applied to arrays and lists, but we occasionally don’t because in that case it mostly just adds an overhead (there is a link in the end where you can learn more about it). So we tend to avoid it when working with tight-performing low-level code, but not when your code is sufficiently high-level (which is typically the main application logic) or sufficiently complex (in terms of maintenance).
The whole thing of enumerators boils down to what collections are supposed to be. Basically ICollection is an interface that defines Count, Add, Remove, Contains, and Clear, the most typical operations. But it also inherits from IEnumerable, which means the collection must support a simple iteration over its elements. To enable this the interface defines GetEnumerator method that returns IEnumerator. Implementor of IEnumerator is a type that implements iteration logic via Current property, MoveNext, and Reset. The way you use this is conveniently hidden by foreach, it’s practically a syntactic sugar feature.
In other words, there is nothing particularly special about foreach, it’s merely a part of the extended language grammar.
Here you can see all kinds of experiments which not only push the performance of foreach (with arrays) to be in line with the classic for loops, but also illustrates how the foreach is actually rewritten by Roslyn (and why collections that contain reference types or implement IDisposable must have a try..catch block included, which is something we normally avoid in game dev if we can help it).
All of this knowledge combines into better understanding of foreach and subsequently you’ll learn how to maintain a healthy balance of different iteration techniques for the best outcome (typically a product or a sweet spot between performance and maintainability).
It is not that the iterator can’t do something, it is mostly signaling what you’re doing. For example if you develop an Undo-stack, you will use, you guessed it, a stack. The last command goes in, the last command comes out first.
If you develop a tactical RPG, you probably familiar with the order of actions like character images on the top of the screen and represent in what order the characters perform their turns… the clue is that in order, so it’s usually a queue.
Or the timeline in “For The King” is also a queue. You add to the end and take the next from the front.
For one example, Stack could be useful example for Undo/Redo systems. Queue is useful for deterministic simulations where you have a queue of operations in the order that they arrived (whenever they arrived).
You can also use a List, an Array, or 20 fixed variables definitions. A lot of what you write is syntatic sugar and convenience for the task at hand. Just use what you want, no one is forcing you to do anything. Technically all math can be solved with only addition - but subtraction, division and multiplication are preferred for many situations to avoid stupid formulas.
Just remember that it is definitely beneficial to understand different patterns and usages because you can do a variety of things in a variety of ways using a variety of tools. If you don’t know, you’re just a one trick pony that will eventually be too inflexible to be relevant. Whether you like the option or not is somewhat irrelevant - the value add to the design is what is important.
You can do it with a foreach too if you don’t mind relying on LINQ.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var items = new List<string> { "apple", "banana", "cherry" };
foreach (var i in Enumerable.Range(0, items.Count))
{
Console.WriteLine($"Index: {i}, Item: {items[i]}");
}
}
}
Very much! Nowadays it’s really more of a personal preference, though it used to be that foreach wasn’t as optimal as it is now (for that particular use case).
“Also, we all need ways to iterate through non-indexable collections such as the dictionary.”
Right, and hashsets.
So, as analogy, when does queue or stack become the best option, or perhaps only option, is what I am getting at. And by best i mean easy to read, and not a lot of shifting around or reordering of lists etc.
Perhaps they are good for, say an update that catches only some of the data providers that frame, assuring that even with the fickleness of object update, everybody gets their update data change once per frame, be it this frame or the next?
hmm.
Asked another way: is there a difference between a stack, and an iterator running backwards? (Count to zero, i- -)…
There seems to be some kind of “limiter” on sizes of queues and stacks?
But anyways, in my reading last night i came upon sortedsets, which i did not know existed, and seem pretty fast… …and i can see great use for those for what i am doing…
…i once read a nuts and bolts on hashsets, and it was way above my head at the time, would like to find that again, as well as a nuts and bolts on how sortedsets are done so fast, harder to find now, i think, than it was six years ago.
As has been already mentioned, when you have a situation where their particular idiomatic pattern is the best choice. There have already been examples posted in this thread.
All of them boil down to Jump and Branch statements reading registers, so the philosophical question is really moot. You are choosing some arbitrary midpoint between “express the problem as succinctly as possible” and “command the hardware as discretely as possible” that happens to have a for (int i ...) syntax.
If you want FIFO, you use Queue. The low-level implementation of Queue manages the internal complexity of allocating or reallocating or shifting elements in the underlying arrays for you, so that adding and fetching from opposite ends minimizes the actual copying around of data. Often this is with a ring-consumption scheme. If someone else reads your code and sees the word ‘queue’, they answered a ton of their own questions before reading anything else.
If you want LIFO, you use Stack. There’s not much internal difference from List, so if you’re happier with List, use it. Similar to Queue, it manages the capacity versus the actual growth, minimizing the number of times it has to allocate more without much in the way of hints to go by. A reader immediately understands a ‘stack’ to have a business end and an uninteresting end.
There are MANY situations where the set of data is completely unordered or arbitrarily ordered, and treating an iterator as a plain integer is just orthodoxy for the sake of orthodoxy. Almost dogma, as we’re down to post #17. Walking a HashSet, for example. The only thing you really want to know is that a single trip through the data will not revisit the same data multiple times accidentally.
Multithreading too. I remember a past work project that ran heavy calculations on other threads, pushed the results into a ConcurrentQueue, and then in an Update method they were pulled out and passed to the appropriate Unity APIs.
class UndoHandler
{
readonly Stack<IUndoable> history = new();
public void RecordAction(IUndoable undoable) => history.Push(undoable);
public void UndoLastAction()
{
if(history.Count > 0)
{
history.Pop().Undo();
}
}
}
List:
class UndoHandler
{
readonly List<IUndoable> history = new();
public void RecordAction(IUndoable undoable) => history.Add(undoable);
public void UndoLastAction()
{
if(history.Count > 0)
{
int lastIndex = history.Count - 1;
var lastAction = history[lastIndex];
history.RemoveAt(lastIndex);
lastAction.Undo();
}
}
}
It’s not like it’s an enormous difference in terms of readability, but it’s definitely there.
And being able to completely get rid of the possibility of off-by-one errors, and other errors that using indexes can quite easily result in, is a big plus in my book.