Style and code review
Some comments on your original code:
- Good indentation – You are consistent, and the code looks generally good
- Documentation, not so good – This is a small code base, but it is always good to add some documentation to your code. Although, do not comment or document the obvious.
- Method naming – As commented in another option, it is common in c# code to use
PascalCase()
for methods
- Variable naming – In many of the environment we used the following rules for variable naming:
_privateVariable
, PropertyVariable
and localVariable
. Distinguishing the scope using naming, helps reading of the code as you easily see whether it is a private, public or local variable directly. Code like the following is quite readable using this scheme:
public class SomeClass
{
private int _someVariable;
public SomeVariable { get; private set; }
public SomeClass(someVariable)
{
_someVariable = someVariable;
}
}
- Use clear and informative names – According to previous point, and increase readability I would've changed
s
to _stack
- Always look at edge cases – In your case: What will happen if you push beyond the requested capacity? What will happen if initialising with a negative size? What will happen if you
pop
from an empty stack?
- Negative initialisation – Not handled, should most likely thrown an exception
- Push beyond requested capacity – Is handled by doubling array size. This can be dangerous if it happens a few times... Generally I would rather add 10% or 20%, which will grow somewhat better without requiring all memory in due time. Possibly you should/could have some fail safe mechanism if/when the
Array.Resize()
fails
- Pop from empty stack – Not handled, will most likely cause an exception as you return
s[-1]
- Consider going generic – This datastructure is quite useful for a lot of other cases than merely a stack of
int
's
Your questions
Pushing 0
is not possible
You are correct, and this is a good reason why you shouldn't use magic numbers like 0
. Some options exists to fix this issue:
- Use a nullable type internally, and check for
nul
instead of 0
- Use a reference to indicate where your stack is currently (as suggested in some of the other answers). This could/would also reduce complexity seriously, as you could drop the loops and use the reference directly
- Change to another magic number, which would just shift the problem
Realise your own resize
Why would you want do this? You are already relying on the native array, so I don't see the issue in using the native approach for resizing.
However some options to handle resizing issues, or implementing your own version here:
- You could maintain multiple internal lists, i.e. an array of references to other
Stack
's. Not recommended though.
- You could switch your storage to using a linked list, which wouldn't have the same issues regarding memory usage.
- You could disallow memory usage outside the intially requested amount. If someone tries to
push
when it's full you could throw an exception.
Code refactoring
My refactored code is a combination of some of the other answers, with my names and error handling, and an example section to show how to use the generic version of this.
public class Stack<T>
{
private T[] _stack;
private int _nextFreeStackIndex;
public int StackSize { get { return _stack.Length; } }
public Stack(int stackSize)
{
if(stackSize < 0)
throw new ArgumentOutOfRangeException("Requires a positive stack size");
_stack = new T[stackSize];
_nextFreeStackIndex = 0;
}
public void Push(T value)
{
// Don't reallocate before we actually want to push to it
if (_nextFreeStackIndex == _stack.Length)
{
// Double for small stacks, and increase by 20% for larger stacks
Array.Resize(ref _stack, _stack.Length < 100
? 2*_stack.Length
: (int) (_stack.Length * 1.2));
}
// Store the value, and increase reference afterwards
_stack[_nextFreeStackIndex++] = value;
}
public T Pop()
{
if(_nextFreeStackIndex == 0)
throw new InvalidOperationException("The stack is empty");
// Decrease the reference before fetching the value as
// the reference points to the next free place
T returnValue = _stack[--_nextFreeStackIndex];
// As a safety/security measure, reset value to a default value
_stack[_nextFreeStackIndex] = default(T);
return returnValue;
}
}
And here is some code showing how to instantiate it when using generics:
public static void TestStack() {
try {
new Stack<int>(-500);
} catch (ArgumentOutOfRangeException) {
Console.WriteLine("Hooray, couldn't create a negative sized stack");
}
var myStack = new Stack<int>(3);
try {
myStack.Pop();
} catch (InvalidOperationException) {
Console.WriteLine("Hooray, it was empty and failed. :-)");
}
myStack.Push(1);
myStack.Push(2);
var popped = myStack.Pop();
if (popped == 2) {
Console.WriteLine("Yuhu... Found the value I pushed! :-D ");
}
myStack.Push(0);
myStack.Push(3);
myStack.Push(4);
myStack.Push(5);
myStack.Pop(); myStack.Pop(); myStack.Pop();
Console.WriteLine(String.Format("My final write: pop={0}, size={1}", myStack.Pop(), myStack.StackSize));
}
Which gave the following expected output:
Hooray, couldn't create a negative sized stack
Hooray, it was empty and failed. :-)
Yuhu... Found the value I pushed! :-D
My final write: pop=0, size=6
...
on all the summary pages.) \$\endgroup\$System.Collections.Generic
\$\endgroup\$