Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 67 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
67
Dung lượng
371,36 KB
Nội dung
The first line that throws an exception makes use of an index that is lower than the base of 0 (1). This is not a common error, because it is obvious that the array's lower bound is not an index value less than 0. But the second error is more common, because it is not difficult to inadvertently reference an element beyond the upper bound of the array, especially when you use magic numbers in your code. The array sAlarms has only five elements (0 to 4), but the index is zero−based, so the upper bound is actually 4 and element 5 does not exist. The NullReferenceException is raised when you try to work with an array object that has been declared without elements. The following code, which escapes detection if Option Explicit is set to Off, will thus not work unless it is properly constructed and initialized: Alarms() To fix it, you need to declare the array with elements (remember, the name and the braces are just a reference variable to the array object itself. If you need to declare the array now and then provide the length (number of elements) later, declare the array with one "zero" element and then ReDim the array later to expand it: ReDim Preserve Alarms(numElements) Here, numElements represents a variable that sets the new length of the array. The two SafeArray exceptions are raised when the rank or data types of unmanaged so−called safe arrays differ from what's expected (the target signatures) in the managed world. Passing Arrays to Methods We can easily pass an array as an argument to a method. To accomplish this, you just have to leave the brackets off the end of the array name when you do the passing. Have a look at the following statement: SortArray(sAlarms) This code passes the array sAlarms to the method SortArray. Why do we not need the brackets and the element Length information? The arrays do not need to schlep such extra baggage when they get passed around because the array object is "aware" of its own size, and the actual array object remains put. The Length property holds the size. So, when we pass the array, we implicitly pass the length data as part of the object's data, because only the reference variable to the array is passed. Obviously, you cannot simply pass an array to any method that has not defined the array parameter. The method that is to expect the array needs to make room for the arriving reference. The receiving method's definition should thus make arrangements for the array in its parameter list, like this: Sub SortArray(ByRef sAlarms() As Integer) 'do something with the array End Sub This code specifies that the SortArray method is to expect an array of type Integer as the parameter. Bear in mind that arrays, like all objects, get passed by reference (or call by reference), so you are not actually sending the entire object to the method, just the reference to it. By passing by reference and not by value, we can "pass" a huge array to the method without issue (refer to Chapter 7 for a discussion on pass by value and pass by reference). Passing Arrays to Methods 388 The latter part of this chapter shows how to pass array references to methods, return array references, and use the various methods and built−in functions to work with arrays. In fact, without the ability to pass arrays to methods and return them, we would not be able to do much with our arrays. Receiving Arrays from Methods You can receive an array of values from a method call (or the array reference variable). Typically, you pass the array to some utility method, which sorts or populates the array or does something exciting with the values in the array. Then the array is returned to your calling method. The following example calls the GetArray method, which delivers a reference to an array of bytes: Public Sub PrintByteArray() Dim bite As Byte = CByte(54) Dim intI As Integer Dim ReturnArray() As Byte ReturnArray = FixArray (bite) For intI = 0 To ReturnArray.GetUpperBound(0) Debug.WriteLine("Byte {0}: {1}" intI, ReturnArray(intI)) Next intI End Sub The following function performs the operation and returns the byte array: Public Function FixArray(ByVal bite As Byte) As Byte() Dim newByteArray(2) As Byte newByteArray(0) = bite newByteArray (1) = bite + bite newByteArray (2) = bite + CByte(50) Return newByteArray End Function You'll find much more information on passing and receiving arrays in the following sections on searching and sorting. Searching and Sorting Arrays The simplest array search algorithm is typically known as a sequential search, because you iterate through each element in the array, one element at a time in sequence, to find the element that matches what you are looking for. The following code does exactly that, using a For loop to "iterate" through the array. The example looks for a specific value in the array and then returns the index value holding the matching variable. Sub WhatIndex(ByRef array() As Integer, ByVal ArrayVal As Integer) Dim intI As Integer For intI = 0 To UBound(array) If ArrayVal = array(intI) Then Console.WriteLine("Found the value {0} at index {1}", _ ArrayVal, intI) End If Next intI End Sub This method receives the reference of the array to iterate through. It uses the ForNext loop to iterate through the array until the variable passed to the ArrayVal parameter matches the value of the element in the array. Receiving Arrays from Methods 389 Here's how you call it: Console.WriteLine(WhatIndex(Alarms, 87)) As an alternative, you can instantiate an iterator over your array and loop through it with a MoveNext method. An iterator that implements IEnumerator is ideal for this job, and since System.Array implements IEnumerable, we can make an iterator with the GetEnumerator method in the same fashion as we did with the Stack and Queue classes. The following code demonstrates the instantiation of an iterator over an array: Sub WhatIndex(ByRef array() As Integer, ByVal ArrayVal As Integer) Try Dim index As Integer Dim myIterator As System.Collections.IEnumerator = _ array.GetEnumerator() While myIterator.MoveNext() index += 1 If CType(myIterator.Current, Integer) = ArrayVal Then Console.WriteLine("Found the value {0} at index {1}", _ ArrayVal, intI) End If End While Catch E As InvalidOperationException Console.WriteLine("An error occurred: {0}", E.Message) End Try End Sub But you do not really need such elaborate code. The Array class provides a similar "ready made" method that can return the indexes of both the first and last encounters of the value (plus several variations in between). Check out the following code: Public Sub FindIndex() Dim IndexFinder() As Integer With IndexFinder Console.WriteLine(.IndexOf(Alarms, 87)) End With End Sub Can you tell what's cooking here? First, we need a reference variable to the array class. Then, we use the reference to invoke the IndexOf method. As for the iterator, you learned earlier that it runs at O(1) so for large arrays it might be a lot more efficient than the IndexOf method. You should also be aware that IndexOf is defined in IList so varying implementations of it exist, both custom implementations and framework implementations. Also, as you'll see exactly in the section "The IndexOf Method" in the next chapter, IndexOf itself may implement an IEnumerator object to iterate over a collection. The BinarySearch Method The BinarySearch method is simple to use. It essentially looks for the first occurrence of the element value you specify and returns an Integer representing the index value of the element holding the first occurrence of the value. If the method is unsuccessful, it will return 1 to the caller. The BinarySearch Method 390 The following code declares an array of type Double and then searches for the occurrence of 4.0: Dim sAlarms() As Double = New Double(3) {1.3, 2.5, 3.0, 4.0} Console.WriteLine("Found at index: {0}", _ sAlarms2.BinarySearch(sAlarms2, 4.0) The method returns 3 to the caller, which just so happens to be the UBound element of this sAlarms array. The BinarySearch algorithm can be used for a variation of array search criteria, but you must remember to sort the array first for the best performance. Here's why: When you perform a binary search, the algorithm bisects the array it searches and then checks the last value in the first part of the array. If the value found is less than the search value we are looking for, the algorithm will only search the second part. If it turns out that the value is more than the search value, then the value we are looking for might be in the first part of the arrayin which case the second part of the array is ignored. This is why the algorithm is called binary search; it has nothing to with the binary representation of the value. The method makes the assumptions just described because it knows that the data in the array is sorted and that if an item is not in one part of the array, it must be in the other. Thus, only part of the array is searched, which is why binary search is so fast. The method is overloaded as follows: BinarySearch(Array, Object) As Integer BinarySearch(Array, Object, IComparer) As Integer BinarySearch(Array, Integer, Integer, Object) As Integer BinarySearch(Array, Integer, Integer, Object, IComparer) As Integer While the Array.BinarySearch method is documented to require you to first sort the array, it does not specify the behavior of the method if it encounters an unsorted array. The following example seeds an array with values and then sets BinarySearch on it before and after sorting. The results are not surprising. The first example, Sub FindMyValue() Dim sArray() As Integer = {12, 82, 23, 44, 25, 65, 27} Debug.WriteLine(Array.BinarySearch(sArray, 27)) End Sub writes 2 to the Output window. However, what happens if we first sort the array? Sub FindMyValue() Dim sArray() As Integer = {12, 82, 23, 44, 25, 65, 27} Array.Sort(sArray) Debug.WriteLine(Array.BinarySearch(sArray, 27)) End Sub We now get 3 written to the Output window, which is correct. Results are not only produced faster by an order of magnitude if the array is first sorted, they can also be relied on. Thus, let's now talk about the important job of sorting arrays. We will return to binary search in the section "Working with Trees" in Chapter 14. The BinarySearch Method 391 The Basics of Sorting Arrays Most algorithms that use arrays will require the array to be searched for one reason or another. The problem with the code in the preceding section is that the array we were searching was at first not sortedand you saw the result. If the value we are looking for turns up at the end of the array, we will have iterated through the entire array before hitting the match, which means we take longer to get results because the binary search cannot perform the n/2 operation. If the array is huge, searching it unsorted might give us more than unpredictable results. Sequential searching like this will suffice when the size of the data set is small. In other words, the amount of work a sequential search does is directly proportional to the amount of data to be searched. If you double the list of items to search, you typically double the amount of time it takes to search the list. To speed up searching of larger data sets, it becomes more efficient to use a binary search algorithmor an O(logn) algorithm. But to do a binary search, we must first sort the array. Search efficiency is greatly increased when the data set we need to search or exploit is sorted. If you have access to a set of data, it can be sorted independently of the application implementing the searching algorithm. If not, the data needs to be sorted at run time. The reason array sorts are so common is that sorting a list of data into ascending or descending order not only is one of the most often performed tasks in everyday life, it is also one of the most frequently required operations on computers (and few other data structures can sort and search data as easy as an array). The Array class provides a simple sorting method, Sort, that you can use to satisfactorily sort an array. The Sort method is static, so you can use it without having to instantiate an array. The following code demonstrates calling the Sort method statically and as an instance: 'with the instance method With sAlarm .Sort(sAlarm) End With 'or with the static method Array.Sort(sAlarm) The sorting method comes from the Array collection of methods. Simply write Array.Sort and pass the array reference variable to the Sort method as an argument. The Sort method is overloaded, so have a look at the enumeration of methods in the class to find what you need. The following code sorts an array (emphasized) before returning the index of Integer value 87, as demonstrated earlier: Public Function GetIndexOfValue(ByRef myArray() As Integer, ByVal _ ArrayVal As Integer) As Integer Array.Sort(myArray) Return .IndexOf(myArray, ArrayVal) End With End Function While the System.Array class provides a number of Sort methods, the following sections demonstrate typical implementations for the various array−sorting algorithms, such as Bubble Sort and Quicksort. These have been around a lot longer than .NET and translate very easily to Visual Basic code. Porting these sorts to Visual Basic provides a terrific opportunity to show off what's possible with .NET, the Array methods, and built−in functions. The Basics of Sorting Arrays 392 As discussed, Array comes preloaded with a version of quicksort, but having access to your own sort code will be invaluable for many occasions. Bubble Sort The bubble sort is widely used to sort small arrays and is very easy to work with. It gets its name from the idea that the smaller values "bubble" to the top, while the heavier ones sink (which is why it's also known as "sinking sort"). And if you watch the values move around in your debugger's Locals windows you see why it's called bubble sort (see the "Debugging With Visual Studio .NET" section in Chapter 17). What you should see this code produce is as follows: The array sAlarms is initialized to capture the alarm IDs and descriptions that are pulled from a database or direct feed (I just initialize the array here). The PrintArray method is called twice to show the array both unsorted and sorted. The bubble sort is called before the second call to PrintArray to display the sorted list out to the console: Public Module BubbleTest Dim Alarms() As Integer = New Integer() {134, 3, 1, 23, 87, 342, 2, 9} Sub Main() PrintArray(Alarms) BubbleSort(Alarms) PrintArray(Alarms) Console.ReadLine() End Sub Public Overloads Sub PrintArray(ByRef Array() As Integer) Dim result As String Dim intI As Integer For intI = 0 To UBound(Array) result = CStr(Array(intI)) Console.WriteLine(result) Next intI Console.WriteLine("−") End Sub Public Sub BubbleSort(ByRef Array() As Integer) Dim outer, inner As Integer For outer = 0 To Array.Length 1 For innerI = 0 To Array.Length 2 If (Array(innerI) > Array(innerI + 1)) Then Transpose(Array, innerI, innerI + 1) End If Next innerI Next pass End Sub Public Overloads Sub Transpose(ByRef Array() As Integer, ByVal first _ As Integer, ByVal second As Integer) Dim keep As Integer keep = Array(first) Array(first) = Array(second) Array(second) = keep End Sub End Module Bubble Sort 393 The output to the console shows the list of elements of the array unsorted, and then sorted after the array reference variable is passed through the BubbleSort method. The output is as follows: 134 3 1 23 87 342 2 9 −−− 1 2 3 9 23 87 134 342 In the initializer for the console, I created the array and initialized it with a collection of numbers (the list of alarms coming out of a queue, popped off a stack or off the back of a serial port). The code used to do this is as follows: Dim sAlarms() As Integer = New Integer() {134, 3, 1, 23, 87, 342, 2, 9} Then the array is passed to the PrintArray method, which prints the values of each element to the console. The PrintArray method is useful and will save you from having to write the For loop every time you want to print out the array, or stream the values out to some place like a screen or a file or a remote location. I have overloaded the method to provide some useful implementations, especially to use an IEnumerator instead of the ForNext: Public Overloads Sub PrintArray(ByRef Array() As Integer) Dim result As String Dim intI As Integer For intI = 0 To UBound(sArray) result = CStr(sArray(intI)) Console.WriteLine(result) Next I Console.WriteLine("−") End Sub In the preceding code, we use the For loop to write the random numbers with which the array has been initialized to the console. This demonstrates that the array is currently unsorted. After generating the array, we then called the BubbleSort method and passed it the reference of the array to sort. This is achieved using the following method: Public Sub BubbleSort(ByRef Array() As Integer) Dim outer, inner As Integer For outer = 1 To Array.Length − 1 For inner = 0 To Array.Length 2 If (Array(inner) > Array(inner + 1)) Then Transpose(Array, inner, inner + 1) End If Next inner Next outer Bubble Sort 394 End Sub How does this bubble sort work? Notice that there are two loops, an outer loop and an inner loop. (By the way, this is pretty standard stuff, and many people use this sort for a small array, or small collections. I adapted it directly from the C version and several Java versions. I have not seen a C# one yet, but it would probably be identical to the latter variation just mentioned.) The outer loop controls the number of iterations or passes through the array. An Integer named outer is declared, and thus the outer For loop is as follows: For outer = 0 To Array.Length − 1 'inner For loop in here Next pass Upon the first iteration, outer is set to start at 0. Then the loop repeats for the length of the array, determined by incrementing outer (Next outer) with each pass of the array until outer is equal to the array's Length 1 property (it does not need the final iteration). For each loop of the outer array, we run the inner loop as follows: For inner = 0 To Array.Length 2 Next inner The variable inner is first initialized to 0. Then, with each iteration of the loop, as long as inner is less than the length of the array minus 2, we do a comparison of each element against the one above it, as shown in the pseudocode here: If Array(inner) is greater than Array(inner + 1) then swap them For example, if Array(inner) is 3 and Array (inner+1) is 2, then we swap them so that 2 comes before 3 in the array. Often, it pays to actually sketch what's happening with the code, as demonstrated in Figure 12−9, a technique used by many gurus who believe that the mind is the model of invention. Figure 12−9: Use a math pad if necessary to "sketch" the actions the sort must take The method that does the swapping is Transpose, which looks like this: Public Overloads Sub Transpose(ByRef Array() As Integer, ByVal first _ As Integer, ByVal second As Integer) Dim keep As Integer keep = Array(first) Array(first) = Array(second) Array(second) = keep End Sub Bubble Sort 395 The Transpose method shown here gets a reference to the array we are sorting. We also pass into Transpose the element positions we are going to swap around. It helps to see this without the placeholders in pseudocode, as follows: Transpose(The Alarms array, the first element, the second element) First, we create an Integer variable called keep to hold the value of the first element passed in: keep = Array(first) Then, we can assign the second element to the first one, as follows: Array(first) = Array(second) Now we give element 2 the value of element 1, as follows: Array(second) = keep When the Transform method completes, it returns us to the inner loop, and the new value in the higher element of the two previous elements is compared to the one above it. Notice that the Transpose method is separated from the BubbleSort method. Why did we do that when we could have just as easily included the swapping routines in the BubbleSort method? Two reasons. First, we are following the rule discussed in Chapter 7 covering the construction of methods: It's better to decompose the algorithm into separate problem−solving methods. While this is a border line casebecause the method is small enough to be included in the one methodexpanding the BubbleSort method later becomes more difficult if the Transpose algorithm is left in (as you will see shortly). Also, the code is more readable and more maintainable like this. Another thing to consider is that the Transpose method can be useful outside of the BubbleSort method and can be defined in a sort interface or a utility class containing sorting methods (loose coupling and high cohesion). There may just be other opportunities to use the method, as the forthcoming code will demonstrate. In the following code, a "slick" alternative uses the Xor operator discussed in Chapter 5 to evaluate ordinal values (Short, Byte, Integer, and Long). This method is an overloaded version of Transpose. Public Overloads Sub Transpose(ByRef Array() As Integer, ByVal first _ As Integer, ByVal second As Integer) Dim keep As Integer Array(first) = Array(first) Xor Array(second) Array(second) = Array(first) Xor Array(second) Array(first) = Array(first) Xor Array(second) End Sub A third variation of Transpose pushes the keep variable onto a stack. I mentioned this technique earlier in the chapter: Public Overloads Sub Transpose(ByRef Array() As Integer, ByVal first _ As Integer, ByVal second As Integer) Dim keep As Stack keep.Push(Array(first)) Array(first) = Array(second) Array(second) = keep.Pop End Sub Bubble Sort 396 I have never found that using a stack in this way has any adverse effect over the running time of the sort. Later, the technique is again used in this book's version of the quicksort algorithm. The BubbleSort method looks very efficient from a distance, but when you strip it down line by line and operation by operation, you can see that for a large array, its running time will explode. For small arrays (like 25 elements), it's great and it's fast because we do not have to do any fancy partitioning with the array object. But we'll need something more efficient for larger data structures, which tend to beg for dissection. Let's see what happens when the array gets much bigger, as discussed in the following section. The next section maintains the simplicity of the bubble sort but attempts to keep the size of the arrays to sort as small as possible. Partition and Merge The divide−and−conquer philosophy discussed in Chapter 7 can be applied to large data structures like a large array. Instead of running BubbleSort for one large array and then running out of "bath water," we can divvy up the array into smaller arrays, or portions, and then sort the portions separately. After that, we need to merge the portions back into one large array. Remember, BubbleSort walks through every element in the array, so BubbleSort on one large array is not efficient. But what if we were to chop the large array into two smaller ones? The result is an n/2 sort, and if the array is small enough, the running time will still be linear. In essence, we can now use BubbleSort on two small arrays instead of one large one. There are two parts to this algorithm. The first part divides a large array into two smaller arrays and then sorts the subarrays or portions. The second part merges the two sorted subarrays back into one large array. So how would you divide the array? Since we have access to its Length property that's actually the easy chore. The following code can be used for the division: bound = Array.Length \ 2 'bound represents the upper bound of the first part 'or bound is the other part's upper bound bound = Array.Length 2 What have we here? Firstly, the array is only logically split into two arrays; we still have one array. But when we make the calls to the BubbleSort method, we first sort up the middle of the array, then we start over again and sort the second part of the array. Once you run the arrays through the BubbleSort method, you end up with the two portions of the same array, both sorted in the same call. We can kick off this idea as demonstrated in the following example: Public Sub BubbleSort(ByRef array() As Integer) Dim outer, inner As Integer For outer = outerStart To array.Length \ 2 For inner = innerStart To array.Length \ 2 If (array(inner) > array(inner + 1)) Then Transpose(array, inner, inner + 1) End If Next inner Next outer Dim outerStart As Integer = outer Dim innerStart As Integer = inner For outer = outerStart To array.Length 2 For inner = innerStart To array.Length 2 If (array(inner) > array(inner + 1)) Then Transpose2(array, inner, inner + 1) Partition and Merge 397 [...]... exchange the two element values The value 23 is moved to the index of the 83 element, and 83 goes to the index of the 23 element In other words, the elements swap their values at their index positions in the array The result is shown in Figure 12−13 Figure 12−13: The positions of the elements after the swap We have to continue with the process, starting from the left and stopping at the element holding 79 ... To recap, the element that sits at the intersection of the partition is called the pivot element Once the pivot is identified, all values in the array less than or equal to the pivot are sent to one side of it, and all values greater are sent to the other The partitions are then sorted recursively, and when complete, the entire array is sorted The recursive sorting of the partitions is not the difficult... write the code to recursively sort each partition So, the algorithm uses the chosen pivot value to partition the array by looping through each array index position from the left and from the right, and comparing each index value to the pivot value If the values meet the "change sides" condition, they are swapped; otherwise, they are left alone and the loop advances the left−side index to the right and the. .. will take all the numbers in the array less than or equal to 43 and move them to the beginning of the array However, because we have chosen the first element as the pivot, we still don't know how far we need to move the less than or equal to elements to the one end of the array or how far we need to move the greater than elements to the other end The way this problem has been solved over the years is... 79 On the right, we keep marching to the left, stopping at 9 We repeat the process, and we note the index positions and exchange the two element values We can perform the interchange because we know the index of each element We keep doing this until the march from the left crosses over the march from the right This point then becomes known as the array partition intersection, the point at which the array... each end of the arrayfrom the element after the pivot (0) to the other end of the arraycomparing the value of each element with the value of 402 Quicksort the pivot element until we find an element value that is less than or equal to the pivot value and one that is greater than the pivot value So, we start at the beginning, skipping the first value because it is the pivot, and stop at the element holding... right−side index to the left It will keep going until both operations intersect the array and overlap As the algorithm loops from left to right and from right to left, the point of the intersection is then used as the partition point and the location to where we move the pivot's value The algorithm swaps the value that is less than or equal to it with the value of the pivot, the value in the first element... us The array ends up as shown in Figure 12−14 Figure 12−14: The array after all elements are swapped If we examine the array, we see that on the left side of the intersection, we have elements less than or equal to the pivot, and on the right of the intersection, we have all the elements greater than the pivot At this point, the array is partitioned and the star (*) represents the position for the. .. positioned the pivot at the intersection of the partitions (its final resting place) So, the last piece of the pivot puzzle is to interchange the last element value of the lesser or equal to values with the pivot The pivot is now at the intersection of both partitions, as shown in Figure 12−15 Figure 12−15: The array after the pivot has been moved to the intersection 403 Quicksort Next, we need to write the. .. for the client to call QuickSort and not have to specify the various parameters other than the reference variable of the array to sort Notice that in the preceding code the method makes use of the CompareTo method to determine if a value in the array is greater than, less than, or equal to the value to the right of it CompareTo returns a value greater than 0 if the left−hand value is greater than the . to the other. The partitions are then sorted recursively, and when complete, the entire array is sorted. The recursive sorting of the partitions is not the difficult part; it's finding the. exchange the two element values. The value 23 is moved to the index of the 83 element, and 83 goes to the index of the 23 element. In other words, the elements swap their values at their index. Bubble Sort 393 The output to the console shows the list of elements of the array unsorted, and then sorted after the array reference variable is passed through the BubbleSort method. The output is