Sunday 3 May 2015

ArrayList

An ArrayList is an interesting data structure that lets us create a list of different types of objects, that is we can cast classes as objects and box types.

Lets start by first create a simple person class.

   
    public class Person
    {
        public string Name { get; set; }
        public int BirthYear { get; set; }
        public Person(string Name, int BirthYear)
        {
            this.Name = Name;
            this.BirthYear = BirthYear;
        }

        public override string ToString()
        {
            return $"{this.Name} born {this.BirthYear}";
        }
    }


Just as before a very simple class which allows us to instantiate an object with two properties constructing a representation of a person.

Now in our main let's create an arraylist of various types


    using System.Collections;

    var arrayList = new ArrayList() { 1, "two", 3.4, 'a', new char[] { 'a', 'b', 'c' }, new Person("Pawel", 84) };

    var i = 0;
    foreach (var o in arrayList){
        var t = o.ToString().Length < 7 ? "\t\t" : "";
        Console.WriteLine($"{i++}) {o}\t{t} is of type {o.GetType()}");
    }

    //Gets or sets the number of elements in the ArrayList
    Console.WriteLine($"\nCapacity of an arraylist included sub-array counts {arrayList.Capacity}");

    //Gets the number of actual elements in the ArrayList
    Console.WriteLine($"Count of an array list only returns the number of elements in the array list {arrayList.Count}\n");



this lets us hold a cornucopia of types in one collection. In the above we have, Integers, characters, character arrays and even our own Person type.

One caveat to be aware of is that if we try to use the 'sort' method on our array list, each element within the collection must implement the IComparable interface and each element must be comparable with all other in the arraylist which could prove to be challenging. In instances of hierarchy, it is pretty straight forward, just ensure that your base class implements the IComparable interface, and you should be ok, for example if you have an 'Employee' class which inherits from a 'Person' class, then you can add both to an array list and implement the 'sort' method, just like you would in a regular array of Persons.

    
    public class Person : IComparable {
        public DateOnly Birthdate { get; set; }
        public string Name { get; set; }

        public Person(string name, DateOnly birthdate)
        {
                this.Name = name;
                this.Birthdate = birthdate;
        }
        public virtual int Age {
            get{
                // Save today's date.
                var today = DateTime.Today;

                // Calculate the age.
                var age = today.Year - Birthdate.Year;

                // Go back to the year in which the person was born in case of a leap year
                if (Birthdate.Year > today.AddYears(-age).Year) age--;
                    return age;
            }
        }

        public override string ToString()
        {
            return $" {this.Name} is a {this.GetType().Name} and is {this.Age} years old";
        }

        public int CompareTo(object? obj)
        {
            var other = (Person)obj;
            if(other != null)
                if(this.Age - other.Age < 0)
                    return   1;
                else if(this.Age - other.Age > 0)
                    return -1;
            return 0;
        }
    }

    public class Employee : Person
    {
        private static int runningId = 0;
        public int idNumber { get; set; }
   
        public Employee(string name, DateOnly birthdate) : base(name, birthdate)
        {
            this.idNumber = ++runningId;
        }

        public override string ToString()
        {
            return $"{base.ToString()} with id Number: {idNumber}";
        }
    }


Since we implemented the IComparable interface on our base Person class we can easily use the Array 'sort' method.


    using System.Collections;
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("\nArray list:");

            var arrayList = new ArrayList() {
                new Person("Pawel", new DateOnly(1984, 01, 31)),
                new Employee("John", new DateOnly(1988, 2, 12))
            };

            arrayList.Sort();

            foreach (var person in arrayList)
                Console.WriteLine(person);

            Console.WriteLine("\nNormal array:");
            var array = new Person[] {
                new Person("Pawel", new DateOnly(1984, 01, 31)),
                new Employee("John", new DateOnly(1988, 2, 12))
            };
    
            Array.Sort(array);
            foreach (var person in array)
                Console.WriteLine(person);
        }
    }


As you can see since employee inherits from person it leverages persons implementation of the IComparable interface letting us sort our array list, had we added an object that didn't inherit from person and couldn't be compared to a person then we'd get an exception.

However, if as below we really stretched it and implemented the IComparable interface in such a fashion that we could compare different objects to each other that would still be valid as shown below in our redefined Person class followed by our new Dog class.


    public class Person : IComparable {
        public DateOnly Birthdate { get; set; }
        public string Name { get; set; }

        public Person(string name, DateOnly birthdate)
        {
                this.Name = name;
                this.Birthdate = birthdate;
        }
        public virtual int Age {
            get{
                // Save today's date.
                var today = DateTime.Today;

                // Calculate the age.
                var age = today.Year - Birthdate.Year;

                // Go back to the year in which the person was born in case of a leap year
                if (Birthdate.Year > today.AddYears(-age).Year) age--;
                    return age;
            }
        }

        public override string ToString()
        {
            return $" {this.Name} is a {this.GetType().Name} and is {this.Age} years old";
        }

        public int CompareTo(object? obj)
        {
            dynamic otherobj as Person != null ? (Person)obj : obj as Dog != null ? (Dog)obj
                : throw new NullReferenceException();  

            if(other != null)
                if(this.Age - other.Age < 0)
                    return   1;
                else if(this.Age - other.Age > 0)
                    return -1;
            return 0;
        }
    }

    public class Dog : IComparable
    {
        public DateOnly Birthdate { get; set; }
        public string Name { get; set; }
       
        public Dog(string name, DateOnly birthdate)
        {
            this.Name = name;
            this.Birthdate = birthdate;
        }

        public int Age {
            get{
                // Save today's date.
                var today = DateTime.Today;

                // Calculate the age.
                var age = today.Year - Birthdate.Year;

                // Go back to the year in which the person was born in case of a leap year
                if (Birthdate.Year > today.AddYears(-age).Year) age--;
                return age * 7;
            }
        }

        public override string ToString()
        {
            return $" {this.Name} is a {this.GetType().Name} and is {this.Age} years old";
        }

        public int CompareTo(object? obj)
        {
            dynamic other = obj as Person != null ? (Person)obj : obj as Dog != null ? (Dog)obj
                :  throw new NullReferenceException();

            if(other != null)
                if(this.Age - other.Age < 0)
                    return   1;
                else if(this.Age - other.Age > 0)
                    return -1;
            return 0;
        }
    }


Notice that in the above we introduced the dynmaic keyword for our object type, that just means that our other type will be defined at runtime and not compile time. Now if we run our program,


    using System.Collections;

    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("\nArray list:");

            var arrayList = new ArrayList() {
                new Person("Pawel", new DateOnly(1984, 01, 31)),
                new Employee("John", new DateOnly(1988, 2, 12)),
                new Dog("Spot", new DateOnly(2011,2,15)),
                new Dog("Kitcha", new DateOnly(2020,2,15))
            };
   
            arrayList.Sort();

            foreach (var person in arrayList)
                Console.WriteLine(person);

            Console.WriteLine("\nNormal array:");
            var array = new Person[] {
                new Person("Pawel", new DateOnly(1984, 01, 31)),
                new Employee("John", new DateOnly(1988, 2, 12))
            };

            Array.Sort(array);
            foreach (var person in array)
                Console.WriteLine(person);
        }
    }


we see that in our array list, we have two distinct types 


now just because we can, doesn't mean we should, however if you must, rather than implementing the same code in the Dog and person class, we can extract it and create a PersonToDogComparrer, as is shown in the next example.


    using System.Collections;

    public class Person
    {
        public DateOnly Birthdate { get; set; }
        public string Name { get; set; }

        public Person(string name, DateOnly birthdate)
        {
            this.Name = name;
            this.Birthdate = birthdate;
        }
        public virtual int Age
        {
            get
            {
                var today = DateTime.Today;
                var age = today.Year - Birthdate.Year;

                if (Birthdate.Year > today.AddYears(-age).Year) age--;
                    return age;
            }
        }

        public override string ToString()
        {
            return $" {this.Name} is a {this.GetType().Name} and is {this.Age} years old";
        }
    }

    public class Dog
    {
        public DateOnly Birthdate { get; set; }
        public string Name { get; set; }

        public Dog(string name, DateOnly birthdate)
        {
            this.Name = name;
            this.Birthdate = birthdate;
        }

        public int Age
        {
            get
            {
                var today = DateTime.Today;
                var age = today.Year - Birthdate.Year;

                if (Birthdate.Year > today.AddYears(-age).Year) age--;
                return age * 7;
            }
        }

        public override string ToString()
        {
            return $" {this.Name} is a {this.GetType().Name} and is {this.Age} years old";
        }
    }

    public class Employee : Person
    {
        private static int runningId = 0;
        public int idNumber { get; set; }
        public Employee(string name, DateOnly birthdate) : base(name, birthdate)
        {
            this.idNumber = ++runningId;
        }

        public override string ToString()
        {
            return $"{base.ToString()} with id Number: {idNumber}";
        }
    }

    class PersonToDogComparer : IComparer
    {
        public int Compare(object? objX, object? objY)
        {
            dynamic This = objX as Person != null ? (Person)objX : objX as Dog != null ? (Dog)objX :
                throw new NullReferenceException();
            dynamic That = objY as Person != null ? (Person)objY : objY as Dog != null ? (Dog)objY :
                throw new NullReferenceException();

            if (This != null)
                if (This.Age - That.Age < 0)
                    return 1;
                else if (This.Age - That.Age > 0)
                    return -1;
            return 0;
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("\nArray list:");

            var arrayList = new ArrayList() {
                new Person("Pawel", new DateOnly(1984, 01, 31)),
                new Employee("John", new DateOnly(1988, 2, 12)),
                new Dog("Spot", new DateOnly(2011,2,15)),
                new Dog("Kitcha", new DateOnly(2020,2,15))
            };


            arrayList.Sort(new PersonToDogComparer());

            foreach (var person in arrayList)
                Console.WriteLine(person);
        }
    }


In the code above we extracted our compare logic into its own class which implements the 'IComparer' interface, then we pass an instance of that class to our sort method.