.NET Back to Basics: The Array Class

One of the fundamental things you need to be able to do when managing data in your application is to use arrays. As you've seen in the previous posts in this series, .NET has fantastic capabilities, making manipulating individual data items a breeze.

Managing strings and ints is only part of what we need to handle, however. We also need to be able to handle strings, integers, and other data types as collections, and for that task, the array class has your back covered.

Providing facilities to search, copy, extract segments, and more, the array class can handle pretty much any task you need to perform with ease.

Searching in an Array

All arrays support the "BinarySearch" method, making finding an element within your collection fast and efficient.

The basic invocation of Binary Search takes two parameters: an Array to search and an object representing the "Thing to search for."

The return value from this is an integer that contains a positive or negative integer. Positive means the thing searched for was found and exists at that index in the array. A negative number means the thing searched for was not found.

The following snippet of code shows how this works in practice:

using System;

namespace dotnetnb
{
   class Program
   {
      static void Main()
      {
         // Create an array of integers to experiment with
         int[] myNumbers = new int[] { 1, 100, 10, 1000, 2,
            5, 50, 60, 10000};

         Console.WriteLine("100 was found at {0}",
            Array.BinarySearch(myNumbers, 100));
         Console.WriteLine("999 was found at {0}",
            Array.BinarySearch(myNumbers, 999));

      }
   }
}

If you run this, you should see the following output:

Array1
Figure 1: Output from our first Binary Search test

You should notice pretty much straight away that something just doesn't add up. 100 is definitely in your array, yet it gives you the same result as searching for the value 999. What gives here?

Well, for a binary search to work correctly, your Array MUST be sorted correctly, and as you can see from our preceding snippet, that's absolutely not the case.

If, however, you add an extra line, so that your code now looks like this:

using System;

namespace dotnetnb
{
   class Program
   {
      static void Main()
      {
         // Create an array of integers to experiment with
         int[] myNumbers = new int[] { 1, 100, 10, 1000, 2,
            5, 50, 60, 10000};
         Array.Sort(myNumbers);

         Console.WriteLine("100 was found at {0}",
            Array.BinarySearch(myNumbers, 100));
         Console.WriteLine("999 was found at {0}",
            Array.BinarySearch(myNumbers, 999));

      }
   }
}

And then run it again, you now should get:

Array2
Figure 2: Once the array is sorted, we get correct results

So, the value 100 was now found at index 6 in the Array (which will be different from its initial position because the Array should now be in a sorted order), but what's with the -8 value?

-8 is the 2's compliment of the next largest value you requested to search for, which in our case is 1000. If you consider the order the Array will now be in after sorting, this will make a lot more sense:

1, 2, 5, 10, 50, 60, 100, 1000, 10000

The next biggest value that's found after 999 is 1000, and that's at index 7.

If you perform a binary 2's compliment on -8 (2's compliment means to flip the bit's and subtract 1) you'll get 7, and if you count the 0-based index for 1000, you'll see that it is indeed 7.

We've used integers here for simplicity, but you can search strings, chars, even entire objects if you want. The important thing to remember is that the array to be searched must either be in chronological order already, or be sorted before you use it.

What, however, if you don't want to or can't sort your array? You can use the 'FindIndex' call, which does the same as 'BinarySearch', but only returns -1 if not found, rather than the next largest match.

There is, however, one major difference in using 'FindIndex' and that is you search using a 'Predicate' (Lambda) rather than a discrete value. The first parameter, however, is still the same as previous, and is the Array to search.

There's no space in this post to go into the details of Lambdas, but you've likely used them before so you'll recognise them when you see them. For example, take the following code:

using System;

namespace dotnetnb
{
   class Program
   {
      static void Main()
      {
         // Create an array of integers to experiment with
         int[] myNumbers = new int[] { 1, 100, 10, 1000, 2,
            5, 50, 60, 10000};

         Console.WriteLine("100 was found at {0}",
            Array.FindIndex(myNumbers, n => n == 100));
         Console.WriteLine("999 was found at {0}",
            Array.FindIndex(myNumbers, n => n == 999));

      }
   }
}

This is the same code as used before, but now converted to use a lambda which, as you can see, states 'n' is to be checked against each element in the array and is to be reported if it matched the number wanted. Running it on our unsorted array gives us:

Array3
Figure 3: FindIndex in action

As you can see, 100 is correctly located at index 1 (Arrays are always 0 based) and 999 is -1 because it's not found.

Because you're using a Lambda to find your value(s), you can use Linq operations, string extensions, and even individual properties; for example:

using System;

namespace dotnetnb
{
   class Program
   {
      static void Main()
      {
         // Create an array of integers to experiment with
         string[] myNames = new string[] { "peter", "brad",
            "susan", "emma", "samantha", "fred", "paul",
            "julie", "rufus"};

         Console.WriteLine("'fred' was found at {0}",
            Array.FindIndex(myNames, n => n.Equals("fred")));
         Console.WriteLine("the first occurance of a name with
            'man' in it was found at {0}", Array.FindIndex(myNames,
            n => n.Contains("man")));

      }
   }
}

Which gives us the following:

Array4
Figure 4: FindIndex can do more than just simple straight searches

As you can see, 'fred' was at zero based index 5, and 4 was reported for 'samantha' because that was the first name that contained the partial string 'man'.

'FindLastIndex' works the same way with the same parameters, but works from the END of the array backwards, and there are also overrides allowing you to set the index to start from, and the number of indexes to search, meaning you can search only a small portion of a much larger array if you want.

Finally, there is also 'Find' and 'FindLast', which take exactly the same parameters as their index counterparts, but instead of returning an index, they return the actual found object.

Moving on, you've already seen how to Sort an Array, but there are also a few other handy, quick utility methods such as 'Reverse', which as, the name suggests, reverses the entries in an array.

using System;

namespace dotnetnb
{
   class Program
   {
      static void Main()
      {
         // Create an array of integers to experiment with
         string[] myNames = new string[] { "peter", "brad",
            "susan", "emma", "samantha", "fred", "paul",
            "julie", "rufus"};

         Console.WriteLine("Before reverse");
         foreach(var name in myNames)
         {
            Console.WriteLine("NAME: {0}", name);
         }
         Console.WriteLine("-------------------------------------");


         Array.Reverse(myNames);

         Console.WriteLine("After reverse");
         foreach (var name in myNames)
         {
            Console.WriteLine("NAME: {0}", name);
         }
      }
   }
}

Immediately, the output shows exactly what's happened:

Array5
Figure 5: Reversing an array using "Array.Reverse"

Another useful method is 'TrueForAll', which applies a check to every element in a given array and returns true if everything matches.

The following code shows where this could come in useful:

using System;

namespace dotnetnb
{
   class Program
   {
      static void Main()
      {
         string[] myNames = new string[] { "peter", "brad",
            "susan", "emma", "samantha", "fred", "paul",
            "julie", "rufus"};
         bool[] checkList = new bool[myNames.Length];

         int checkIndex = 0;
         foreach (var name in myNames)
         {
            checkList[checkIndex] = false;
         }

         // At this point we start some multi threaded operation
         // that set's each boolean in checkList to true, such
         // that when all 9 bools are true then we consider that
         // ALL of our names have been checked off

         // Now we can do a while loop that uses 'TrueForAll'
         // to decide when to terminate...
         while (!Array.TrueForAll(checkList, flag => flag == true))
         {
            // Do something in here, EG: update a spinner or
            // progress or such like.
         }

         Console.WriteLine("All names have been checked off");

      }
   }
}

Warning: don't just copy and paste the preceding code and run it; it'll just hang in an infinite loop without ever ending.

All I'm trying to show here is one possible way you might want to use 'TrueForAll', but there ARE a lot of blanks you'll need to fill in yourself to do anything useful with the example.

A better example might be to have an object that looks something similar to the following:

public class Person
{
   public string Name { get; set; }
   public bool isPresent { get; set; }
}

You then could have something along the lines of this:

bool allPeoplePresent = Array.TrueForAll(checkList,
   person => person.isPresent == true);

Which would be true if every 'Person' object in the array had their 'isPresent' flag set to true. Otherwise, it would be false.

The current official page for the .NET Array class is here.

I seriously recommend swatting up on the methods available; we've only scratched the surface here in this post. You can check to see if an array contains "things", convert entire arrays to different data types, and let's also not forget that you can easily use 'List<T>.ToArray()' and 'Array.ToList<T>' to instantly get access to these tools from within the Systems.Collections data types, giving you immensely powerful operations on many different types of data collections.



About the Author

Peter Shaw

As an early adopter of IT back in the late 1970s to early 1980s, I started out with a humble little 1KB Sinclair ZX81 home computer. Within a very short space of time, this small 1KB machine became a 16KB Tandy TRS-80, followed by an Acorn Electron and, eventually, after going through many other different machines, a 4MB, ARM-powered Acorn A5000. After leaving school and getting involved with DOS-based PCs, I went on to train in many different disciplines in the computer networking and communications industries. After returning to university in the mid-1990s and gaining a Bachelor of Science in Computing for Industry, I now run my own consulting business in the northeast of England called Digital Solutions Computer Software, Ltd. I advise clients at both a hardware and software level in many different IT disciplines, covering a wide range of domain-specific knowledge—from mobile communications and networks right through to geographic information systems and banking and finance.

Related Articles

Comments

  • Senior Software Engineer

    Posted by Bryan Lee Whatley on 05/16/2016 06:18am

    Great basics article to read. Very nicely written. Thanks!

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date