Scott
Scott

Reputation: 13

Why is Reset function setting wrong value?

I'm supposed to write a LIFO (last in first out) class for chars, which can be edited by Pop and Add functions and seen by Peek function or foreach. Class is working on array to be more optimalized, but foreach for some reason's not working. I tried to make function GetEnumerator based on return value of _arr.GetEnumerator() function, but it was not working, because when I printed item, in console was shown TestApp.LIFO, so I made this, but now foreach won't print a single item and by debuging _i value on Reset function is 0. Can someone say why is it happening and suggest solution?

using System;
using System.Collections;
using System.Collections.Generic;

namespace TestApp {
    internal class LIFO : IEnumerator, IEnumerable {
        public LIFO(int size) {
            _arr = new char[size];
            _index = 0;
        }

        public LIFO(char[] arr) {
            _arr = arr.Clone() as char[];
            _index = 0;
        }

        public char Peek() => _index == 0 ? '\0' : _arr[_index - 1];

        public bool Add(char c) {
            if (_index == _arr.Length)
                return false;

            try {
                _arr[_index] = c;
            } catch (Exception) {
                return false;
            }

            ++_index;

            return true;
        }

        public void Pop() {
            if (_index == 0)
                return;

            _arr[--_index] = '\0';
        }

        private int _i;

        public IEnumerator GetEnumerator() => this;
        public bool MoveNext() => --_i > -1;

        public void Reset() => _i = _index - 1;
        public object Current => _arr[_i];

        private int _index;

        private readonly char[] _arr;

    }
}

In Program.cs:

using System;

namespace TestApp {
    internal static class Program {
        private static void Main() {
            LIFO l = new(17);

            l.Add('k');
            l.Add('h');
            l.Add('c');

            foreach (var item in l)
                Console.WriteLine(l);
        }
    }
}

Upvotes: 0

Views: 105

Answers (2)

Harun Cetin
Harun Cetin

Reputation: 109

Your index counter variable using get values in _arr array (it is _i) and index varible that doing increase and decrease operations on it (it is _index) are different. Because of that your for loop never iterate your collection. I fix the code with some addition here. I hope it's helpful.

LIFO.cs

using System;
using System.Collections;
using System.Collections.Generic;

namespace TestApp
{
    internal class LIFO : IEnumerator, IEnumerable
    {
        public LIFO(int size)
        {
            _arr = new char[size];
            _index = 0;
        }

        public LIFO(char[] arr)
        {
            _arr = arr.Clone() as char[];
            _index = 0;
        }
        public int Count() => _index;
        public char Peek() => _index == 0 ? '\0' : _arr[_index - 1];

        public bool Add(char c)
        {
            if (_index == _arr.Length)
                return false;

            try
            {
                _arr[_index] = c;
            }
            catch (Exception)
            {
                return false;
            }

            ++_index;

            _i = _index;

            return true;
        }

        public void Pop()
        {
            if (_index == 0)
                return;

            _arr[--_index] = '\0';
            _i = _index;
        }

        public IEnumerator GetEnumerator() => (IEnumerator)this;
        public bool MoveNext() => --_index > -1;
        public void Reset() => _index = _i;
        public object Current
        {
            get => _arr[_index];
        }

        private int _index;
        private int _i;
        private readonly char[] _arr;

    }
}

Program.cs

using System;

namespace TestApp
{
    internal static class Program
    {
        private static void Main()
        {
            LIFO l = new LIFO(17);

            l.Add('k');
            l.Add('h');
            l.Add('c');
            Console.WriteLine("Count: " + l.Count());

            foreach (var i in l)
                Console.WriteLine(i);

            l.Reset();
            foreach (var i in l)
                Console.WriteLine(i);

        }
    }
}

Upvotes: 0

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391406

The issue is that Reset is not called. It is no longer needed but the interface is not changed due to backwards compatibility. Since new iterators implemented using yield return is actually required to throw an exception if Reset is called, no code is expected to call this method anymore.

As such, your iterator index variable, _i, is never initialized and stays at 0. The first call to MoveNext steps it below 0 and then returns false, ending the foreach loop before it even started.

You should always decouple the iterators from your actual collection as it should be safe to enumerate the same collection twice in a nested manner, storing the index variable as an instance variable in your collection prevents this.

You can, however, simplify the enumerator implementation vastly by using yield return like this:

public class LIFO : IEnumerable
{
    ...

    public IEnumerator GetEnumerator()
    {
        for (int i = _index - 1; i >= 0; i--)
            yield return _arr[i];
    }
}

You can then remove the _i variable, the MoveNext and Reset methods, as well as the Current property.

If you first want to make your existing code working, with the above note I made about nesting enumerators, you can change your GetEnumerator and Reset methods as follows:

public IEnumerator GetEnumerator()
{
    Reset();
    return this;
}

public void Reset() => _i = _index;

Note that you have to reset the _i variable to one step past the last (first) value as you're decrementing it inside MoveNext. If you don't, you'll ignore the last item added to the LIFO stack.

Upvotes: 1

Related Questions