Tuesday, September 19, 2017

Max Column Sum by Key (part 2)

Continuing on from my previous explorations that followed Learn to Code Grand Rapids “Building a Real World App in VS 2017, Part I” of August 10 I decided to try Java.  Java, like Python, is a programming language that I hadn’t tried before so provided something for me to do.

In my previous Max Column Sum by Key document I described my initial C# attempt before I had the file containing the data that was aborted for that reason, then my application using Ada as the language after getting the data file, then with C# once again using the data file, and finally with Python as a new language for me.  The different languages to show the similarities and the differences since I followed the same general outline in each language. 

Continuing from that, as something to do, I downloaded Java to do the same application in that language.  These notes will provide some comments about Java along with the code.

Then, based upon how I was able to use a Java class as a record structure, I revisited C# to determine whether I could do the same with it since I had previously had a problem as described in the initial document.  A brief discussion of the C# application follows that of Java.

Discussion of the Java Application


As described in the discussion of the Ada application in the previous write up, the Java application reads the file supplied by Jeffrey Fuller.  As I had discovered it had records with extraneous text prior to the Key and two Value fields (or what I assume are both Value fields).  Examining the data I found that each record only had a trailing NL (new line) so not a DOS formatted file that would also have a carriage return (CR).  And that the final record was without the trailing NL.  Preceding the Key and each of the two Value fields was a HT (horizontal tab).  That is, the first two records look like
49 44 57 49 50 95 78 85 77 9 49 48 48 48 9 49 9 49 10
49 44 57 49 50 95 78 85 77 9 49 48 48 48 9 50 9 49 10
which translate to ASCII as below.
 1  ,  9  1  2  _  N  U  M HT 1  0  0  0 HT 1 HT  1 NL
 1  ,  9  1  2  _  N  U  M HT 1  0  0  0 HT 2 HT  1 NL
where the numbers below are the byte positions.
 1  2  3  4  5  6  7  8  9 10 11 12 3 14 15 6 17 18 19

Therefore I decided to find the Key with the greatest number of combined value fields of a particular Value.  In the two record sample above this would be a value of 1 since there are three instances of 1 and only one instance of 2 for the Key of 1000.

As with the use of the other languages, I first opened and read the file into a buffer.  As mentioned in the previous document, this would require a different method if the file had been too large to completely read at the start.  Perhaps I’ll do another version to show a way how this could be accomplished.  In any case, starting with a language new to me, this provided the a necessary starting point for how data files might be accessed in Java.  The code to accomplish this is as follows where the initial few lines of code were eventually followed by other necessary code that will be shown later.

Note:  Indentation of four columns was used because that was the “standard” for Python and not due to any necessity for indentation in Java.  Also, print statements that were included for debugging have been commented out to avoid extraneous output in the completed application.

import java.io.*;
import java.io.IOException;

public class LearnToCodeGR
{
  . . . code added later

    public static void main(String args[]) throws IOException
    {
        // Open the file.
        File file = new File( "C:\\Source\\LearnToCodeGR\\max-col-sum-by-Key.tsv" );
        FileInputStream inputFile = null;
        try
        {
            // create FileInputStream object
            inputFile = new FileInputStream(file);

            byte buffer[] = new byte[(int)file.length()];

            // Read from this input stream into an array of bytes
            inputFile.read(buffer);

            // Create string from byte array
//            String s = new String(buffer);
//            System.out.println("File content: " + s);
           
            // Parse the buffer and build data structure
            Parse( buffer.length, buffer );
           
            // Report the results
            Report();
        }
        catch (FileNotFoundException e)
        {
            System.out.println("File not found" + e);
        }
        catch (IOException ioe)
        {
            System.out.println("Exception while reading file " + ioe);
        }
        finally
        {
            // Close the stream using close method
            try
            {
                if (inputFile != null)
                {
                    inputFile.close();
                }
            }
            catch (IOException ioe)
            {
                System.out.println("Error while closing stream: " + ioe);
            }
        }

    } // end method main


} // end class LearnToCodeGR

The invocations of the Parse and Report functions were added later since the initial attempt was only to discover how to open and read a data file.  (I started on September 7 and it took a couple of days to download the Java compiler for Windows and discover that I needed to add the Windows system path so as to run the compiler executable without needing to point to it in the Command Prompt (e.g., DOS) window, to be able to run a Helloworld application found on the internet, and then do the initial code shown above.

Java, like Python, doesn’t seem to have a compiler that runs in a Windows window.  So it has to be run in a Command Prompt window.  I, as usual, changed the folder in the window to that where I was developing the application (c:\Source\Java) so the command was just
> javac LearnToCodeGR.java
since LearnToCodeGR.java was the file name that I used and javac.exe is the java compiler.

During this time I also discovered that to attempt to run the application I had to use the command
> java –classpath C:\source\java LearnToCodeGR
since the LearnToCodeGR class as shown in the above code sample was also in the same Windows folder.  If there had been classes located elsewhere they also would have had to have been named following the classpath parameter.  (In Ada, for instance, a project is first defined that specifies where the various source and user libraries are to be found.)

After quickly getting this far the addition of the Parse function was added (the Report function was done last).  The Parse function was done for only a few data records of the buffer to start with to determine how to implement it in Java while following the method developed in the previous languages.  (The completed application was done in parts of three days following a long weekend.)  Of course, the only thing to learn was how to implement already working code in Java.  And, how to do a class that represented the data structure that I wanted to use to keep track of the parsed key and its values as I had wanted to do in C#.

The Parse function is similar to that of the application in the other languages and is shown below with its debug print statements commented out.
    // Parse array of data as previously read from file to
    // obtain Key and two Values from each record of file
    private static void Parse(int count, byte[] data)
    {

        Byte NINE = 57;
        Byte ZERO = 48;
        Byte TAB = 9;
        Byte CR = 13;
        Byte LF = 10;
      
        String keyString = "";
        String value1String = new String("");
        String value2String = new String("");

        int offset = 0; // offset into data
        Scan scanPhase = Scan.BYPASS;

        // Parse all bytes previously read from the file
        while (offset < count)
        {
//            System.out.println(" " + offset);
            switch (scanPhase)
            {
                case BYPASS:
//                    String xyz = "bypass ";
//                    xyz = xyz.concat(Integer.toString(offset));
//                    xyz = xyz.concat(" ");
//                    xyz = xyz.concat(Byte.toString(data[offset]));
//                    System.out.println(xyz); //"bypass");
                    if (data[offset] == TAB)
                    {
                        scanPhase = Scan.FKEY;
//                        System.out.print("new phase of Key");
                        // Initialize for next set of data
                        keyString = "";
                        value1String = "";
                        value2String = "";
                    } 
                    break;
                       
                  case FKEY:
//                    System.out.println("Key");
                    if (data[offset] != TAB)
                    {
                        if ((data[offset] >= ZERO) & (data[offset] <= NINE))
                        {
                            char digit = (char)data[offset];
                            String dataDigit =  Character.toString(digit);
                            keyString = keyString.concat(dataDigit);
                        }
                    }
                    else
                    {
                        scanPhase = Scan.VALUE1;
//                        System.out.print("new phase of Value1");
                    }
                    break;

                case VALUE1:
//                    System.out.println("Value1");
                    if (data[offset] != TAB)
                    {
                        if ((data[offset] >= ZERO) & (data[offset] <= NINE))
                        {
                            char digit = (char)data[offset];
                            String dataDigit =  Character.toString(digit);
                            value1String = value1String.concat(dataDigit);
                        }
                    }
                    else
                    {
                        scanPhase = Scan.VALUE2;
//                        System.out.print("new phase of Value2");
                    }
                    break;

                case VALUE2:
//                    System.out.println("Value2");
                    if ((data[offset] == LF) | (data[offset] == CR))
                    {
                        // Update tables with the data from the record.
                        Update( keyString, value1String, value2String );

                        // Initialize for next record
                        keyString = "";
                        value1String = "";
                        value2String = "";
                        scanPhase = Scan.BYPASS;
//                        System.out.print("new phase of bypass");
                    }
                    else
                    {
                        if ((data[offset] >= ZERO) & (data[offset] <= NINE))
                        {
                            char digit = (char)data[offset];
                            String dataDigit =  Character.toString(digit);
                            value2String = value2String.concat(dataDigit);
                        }
                    }
                    break;
            } // end switch

            // Complete processing of final record when not terminated by New Line
            offset++; // increment to next data buffer position
            if (offset >= count) // no more data
            {
                if (value2String.length() > 0) // last record fully parsed without trailing NL
                {  Update( keyString, value1String, value2String ); }
                break; // exit loop
            }

        } // end loop

        return;

    } // end Parse

A curiosity in the above is in the use of enumerated Scan type
    public enum Scan
    { BYPASS, FKEY, VALUE1, VALUE2 }
that was declared outside of all the declared classes (except the all encompassing LearnToCodeGR class).  It took me a while to learn that the case statements of the switch statement had to only use the particular enumerated name whereas in setting a scanPhase the dot notation had to be used.  That is,
            switch (scanPhase)
            {
                case BYPASS:
versus scanPhase = Scan.BYPASS;

As can be seen, in this version of the application, the possibility of a non-numeric Key or Value is prevented by only adding numeric characters to the string object that is being created.  The other implementations instead did this checking in a conversion function that converted a byte array to a numeric value.  In this application, since there can be no non-digits in the string, the Java supplied function to convert a string to a number can be invoked as is done in the Update function.


As done in the other applications, the Update function in invoked when the second value has been parsed.  The Update code, with its debug statements commented out, is as follows.
    private static void Update(String key, String value1, String value2)
    {
      // Convert strings to integers.  Already have avoided any non-digits
      // in the strings.
        int nKey = Integer.parseInt(key);
        int nValue1 = Integer.parseInt(value1);
        int nValue2 = Integer.parseInt(value2);
//String yyy = "Update ";
//yyy = yyy.concat(key);
//yyy = yyy.concat(" ");
//yyy = yyy.concat(value1);
//yyy = yyy.concat(" ");
//yyy = yyy.concat(value2);
//yyy = yyy.concat(" ");
//yyy = yyy.concat(Integer.toString(nKey));
//yyy = yyy.concat(" ");
//yyy = yyy.concat(Integer.toString(nValue1));
//yyy = yyy.concat(" ");
//yyy = yyy.concat(Integer.toString(nValue2));
//System.out.println(yyy);

        int keyIndex = -1;
        int valueIndex = -1;
//int numKeys = keyTable.getKeyCount();
//String yaa = "Key Count ";
//yaa = yaa.concat(Integer.toString(numKeys));
//System.out.println(yaa);
        for (int i = 0; i < keyTable.getKeyCount(); i++)
        {
            if (keyTable.getKey(i) == nKey)
            {
                keyIndex = i;  // key already in table
                break; // exit loop
            }
        } // end loop
        if (keyIndex < 0) // key not in the table
        {
            keyIndex = keyTable.setKey(nKey); // add key
            valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
            if (nValue1 != nValue2)
            { keyTable.setValue(keyIndex,nValue2); }
            else
            { keyTable.incSum(keyIndex,valueIndex); }
        }
        else // key in the table
        {
            int valueCount = keyTable.getValueCount(keyIndex);
//String yab = "Value Count ";
//yab = yab.concat(Integer.toString(valueCount));
//System.out.println(yab);
            valueIndex = keyTable.setValue(keyIndex,nValue1); // add value for key
            if (nValue1 != nValue2)
            { keyTable.setValue(keyIndex,nValue2); }
            else
            { keyTable.incSum(keyIndex,valueIndex); }
        }

        return;
    } // end Update

It first converts the strings for the key and the two values to integers and then uses the KeyTable class, as instantiated to the keyTable object, to save the data.  Like the enumerated type, the instatiation is done only within the applications main LearnToCodeGR class via the
    static public KeyTable keyTable = new KeyTable(); // visible instance of KeyTable class
statement.  As will be seen below, the KeyTable class has a constructor that is executed by the above statement.

    // Key class
    static class Key
    {
        int key;        // value of key
        int valueCount; // number of different values associated with key
        int[] values = new int[20]; // up to 20 different values associated with
        int[] sums = new int[20];   //  key and number of instances of value
    }

    // KeyTable class
    static class KeyTable
    {
        public int keyCount; // number of different keys
        public Key[] keys = new Key[30]; // instances of "struct" class

        public KeyTable() // constructor
        {
            this.keyCount = 0;

            // Instantiate pointer for each instance of "struct" class
            for (int i=0; i<30; i++)
            {
                keys[i] = new Key();
            }
        } // end constructor

        // getters \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   
        // Return key at index
        public int getKey(int index)
        {
//String xxx = "getKey index ";
//xxx = xxx.concat(Integer.toString(index));
//xxx = xxx.concat(" keyCount ");
//xxx = xxx.concat(Integer.toString(keyCount));
//xxx = xxx.concat(" key ");
//xxx = xxx.concat(Integer.toString(this.keys[index].key));
//System.out.println(xxx);
            return this.keys[index].key;
        } // end getKey

        // Return number of keys in table
        public int getKeyCount()
        {
            return keyCount;
        } // end getKeyCount

        // Return number of different values for key at index
        public int getValueCount(int index)
        {
            return this.keys[index].valueCount; //[index];
        } // end getValueCount

        // setters \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   
        // Store new key into table and return index of key in the table
        public int setKey(int key)
        {
            for (int i = 0; i < keyCount; i++)
            {
//String xx = "setKey search ";
//xx = xx.concat(Integer.toString(i));
//xx = xx.concat(" ");
//xx = xx.concat(Integer.toString(this.keys[i].key));
//System.out.println(xx);
                if (this.keys[i].key == key) // key already in table
                {
                      return i;
                }
            } // end for loop
            if (this.keyCount < 30) // add key to table if room
            {
                  Key cKey = new Key();
                this.keys[keyCount].key = key;
                int index = keyCount;
                keyCount++;
//String xxx = "setKey add ";
//xxx = xxx.concat(Integer.toString(index));
//xxx = xxx.concat(" ");
//xxx = xxx.concat(Integer.toString(key));
//System.out.println(xxx);
                return index;
            }
            else
            {
                System.out.println("Error: Too many different keys");
                return -1;
            }      

        } // end setKey

        // Add value for key at index, return value index
        public int setValue(int index, int value)
        {
//String bbb = "setValue index ";
//bbb = bbb.concat(Integer.toString(index));
//bbb = bbb.concat(" value ");
//bbb = bbb.concat(Integer.toString(value));
//bbb = bbb.concat(" valueCount ");
//bbb = bbb.concat(Integer.toString(this.keys[index].valueCount));
//bbb = bbb.concat(" key ");
//bbb = bbb.concat(Integer.toString(this.keys[index].key));
//System.out.println(bbb);
            for (int i = 0; i < this.keys[index].valueCount; i++)
            {
                if ( this.keys[index].values[i] == value )
                {
                    // Increment sum
                    this.keys[index].sums[i]++;
//String xxx = "setValue already exists ";
//xxx = xxx.concat(Integer.toString(keyCount));
//xxx = xxx.concat(" ");
//xxx = xxx.concat(Integer.toString(this.keys[index].key));
//xxx = xxx.concat(" ");
//xxx = xxx.concat(Integer.toString(i));
//xxx = xxx.concat(" Value ");
//xxx = xxx.concat(Integer.toString(this.keys[index].values[i]));
//xxx = xxx.concat(" Sum ");
//xxx = xxx.concat(Integer.toString(this.keys[index].sums[i]));
//System.out.println(xxx);
                  return i;
                }
            } // end for loop

            // Add value to array for key since didn't return for existing value
            this.keys[index].values[this.keys[index].valueCount] = value;
            this.keys[index].sums[this.keys[index].valueCount]++;
//String xxx = "setValue add to table";
//xxx = xxx.concat(Integer.toString(index));
//xxx = xxx.concat(" key ");
//xxx = xxx.concat(Integer.toString(this.keys[index].key));
//xxx = xxx.concat(" valueCount ");
//xxx = xxx.concat(Integer.toString(this.keys[index].valueCount));
//xxx = xxx.concat(" Sum ");
//xxx = xxx.concat(Integer.toString(this.keys[index].sums[this.keys[index].valueCount]));
//System.out.println(xxx);
            int valueIndex = this.keys[index].valueCount;
            this.keys[index].valueCount++; // increment number of values for key
            return valueIndex;

        } // end setValue

        // Get value for key at indexes, return value
        public int getValue(int indexKey, int indexValue)
        {
            return this.keys[indexKey].values[indexValue];
        } // end getValue

        // Increment Sum for value of key at indexKey and value at indexValue
        public void incSum(int indexKey, int indexValue)
        {
//String bbb = "incSum indexes ";
//bbb = bbb.concat(Integer.toString(indexKey));
//bbb = bbb.concat(" ");
//bbb = bbb.concat(Integer.toString(indexValue));
//System.out.println(bbb);
            // Increment sum
            this.keys[indexKey].sums[indexValue]++;
//String xxx = "incSum after update key ";
//xxx = xxx.concat(Integer.toString(this.keys[indexKey].key));
//xxx = xxx.concat(" Value ");
//xxx = xxx.concat(Integer.toString(this.keys[indexKey].values[indexValue]));
//xxx = xxx.concat(" Sum ");
//xxx = xxx.concat(Integer.toString(this.keys[indexKey].sums[indexValue]));
//System.out.println(xxx);

            return;

        } // end incSum

        // Get sum for key at indexes, return sum
        public int getSum(int indexKey, int indexValue)
        {
            return this.keys[indexKey].sums[indexValue];
        } // end getSum

    } // end class KeyTable

    static public KeyTable keyTable = new KeyTable(); // visible instance of KeyTable class

where the last statement is the instantiation of the KeyTable class as mentioned above.


Here are two classes:  the KeyTable class and the Key class where the Key class is a Java version of a C struct type.  In Ada it would be
type Key_Type
is record
  key        : Integer; // value of key
  valueCount : Integer; // number of different values associated with key
  values     : array(1..20) of Integer;
  sums       : array(1..20) of Integer;
end record;
where the type name of Key_Type is used since in Ada Key and key refer to the same thing.  That is, case doesn’t matter in a name.  The valueCount is the number of locations in both values and sums that have been supplied an actual value.

Actually, in Ada the array would first be declared similar to
type Array_Type
is array(1..20) of Integer;
and then used to declare the type of values and sums in the record type.  Spreading the declaration across multiple lines is just a matter of preference as is
type Key_Type
is record
versus
type Key_Type is record

Note that the Key_Table class was filled in as I went along first getting the getKey and getKeyCount functions to work and adding other functions as needed using a different set of array items to save the data as in the C# application.  I first tried a triple dimension array to keep track of the key, the values of the key and the number of instances of the particular value.  I was having trouble with how to correctly implement that and was suspicious that I was doing so correctly which use of debug output verified.  I wasted quite a bit of time trying to figure out how make the triple dimensioned array work for what I wanted.

Then I thought that maybe I could do something similar to the namedtuple of the Python application.  This was when I decided to do the Key class to see if I could use it as a C struct and found that Java didn’t complaint.  However, although I added the
        public Key[] keys = new Key[30]; // instances of "struct" class
statement to go with the
        public int keyCount; // number of different keys
statement I forgot to include the code
            // Instantiate pointer for each instance of "struct" class
            for (int i=0; i<30; i++)
            {
                keys[i] = new Key();
            }
to instantiate of the Key class for each instance of keys in the array.  This compiled ok after I changed the KeyTable class and Update code to start using the new structure.  However, when I then attempted to execute the newly built LearnToCodeGR.class (that is, .class not .exe) it threw an exception of
Exception in thread “main” java.lang.NullPointerException
        at LearnToCodeGR$KeyTable.setKey<LearnToCodeGR.java:91>
        at LearnToCodeGR.KeyTable.Update<LearnToCodeGR.java:218>
        ... Parse
        ... main
where the statement at line 91 was
                this.keys[keyCount].key = key;

This is where the power of the subconscious came into play.  After puzzling about the exception a little bit I decided to put the matter away for another day.  Almost immediately while doing something else it occurred to me that the reason for a null pointer was that this.keys[keyCount] didn’t point to anything and that its constructor needed to be executed.  So I noted that down and the next morning implemented it in the for loop of the KeyTable class constructor.  Problem solved.

The only problem left was to implement the incSum function that had been left null.  Then running the application with the debug output showed that the correct results were being obtained. 

So the Report function was added – a simple matter. 
    // Report the results
    private static void Report()
    {
        // Report individual results
        System.out.println( "Report" );
        for (int i = 0; i < keyTable.getKeyCount(); i++)
        {
            for (int j = 0; j < keyTable.getValueCount(i); j++)
            {
                String report = "Key ";
                report = report.concat(Integer.toString(keyTable.getKey(i)));
                report = report.concat(" Value ");
                report = report.concat(Integer.toString(keyTable.getValue(i,j)));
                report = report.concat(" Sum ");
                report = report.concat(Integer.toString(keyTable.getSum(i,j)));
                System.out.println(report);
            } // end for loop
        } // end for loop

        // Items identifying the key with the largest number of a particular value
        int key = 0;
        int value = 0;
        int sum = 0;

        for (int i = 0; i < keyTable.getKeyCount(); i++)
        {
            for (int j = 0; j < keyTable.getValueCount(i); j++)
            {
                if (keyTable.getSum(i,j) > sum) // save key and value with greater sum
                {
                    key = keyTable.getKey(i);
                    value = keyTable.getValue(i,j);
                    sum = keyTable.getSum(i,j);
                }
            } // end for loop
        } // end for loop

        String report = "Key and Value with greatest Sum ";
        report = report.concat(Integer.toString(key));
        report = report.concat(" ");
        report = report.concat(Integer.toString(value));
        report = report.concat(" ");
        report = report.concat(Integer.toString(sum));
        System.out.println( report );

        return;

    } // end Report

The second part of this is the same code as in the Python version except that I realized that the initialize of its if statement wasn’t necessary since the j = 0 of the inner loop for i = 0 of the outer loop would store the initial values into key, value, and sum.

The debug output was then commented out so only
Report
Key 1000 Value 1 Sum 13
Key 1000 Value 2 Sum 7
Key 2000 Value 1 Sum 16
Key 2000 Value 2 Sum 4
Key 3000 Value 1 Sum 20
Key 4000 Value 1 Sum 15
Key 4000 Value 3 Sum 2
Key 4000 Value 2 Sum 2
Key 4000 Value 4 Sum 1
Key 5000 Value 2 Sum 8
Key 5000 Value 1 Sum 7
Key 5000 Value 3 Sum 4
Key 5000 Value 4 Sum 1
Key and Value with greatest Sum 3000 1 20
was displayed in the Command Prompt window.


Discussion of the modified C# Application

 Taking a cue from how I could create the kinds of classes that I wanted in Java to have a C record struct kind of class and a class to keep track of the data that referenced it, I modified the C# application of the initial Max Column Sum by Key report. 

I also declared the enum type outside of any function as I found that I had to do in the Java application.  This worked as well without the curiosity that I mentioned above for Java.

Using the Key and KeyTable classes (in their separate .cs files), the Update was changed similar to in Java while the Parse was modified to use the Scan enum type in the switch statement.

Otherwise, the C# code is pretty much as it was before. 

The complete code is as follows.  Note, that since Visual C# has a debugger, no debug output is in the code.

Key.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MaxColSumbyKey
{
    // Key class
    public class Key
    {
        public int key;        // value of key
        public int valueCount; // number of different values associated with key
        public int[] values = new int[20]; // up to 20 different values associated with
        public int[] sums = new int[20];   //  key and number of instances of value
    } // end Key class

} // end MaxColSumbyKey

KeyTable.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MaxColSumbyKey
{
    // KeyTable class
    public class KeyTable
    {
        public int keyCount; // number of different keys
        public Key[] keys = new Key[30]; // instances of "struct" class

        public KeyTable() // constructor
        {
            this.keyCount = 0;

            // Instantiate pointer for each instance of "struct" class
            for (int i = 0; i < 30; i++)
            {
                keys[i] = new Key();
            }
        } // end constructor

        // getters \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

        // Return key at index
        public int getKey(int index)
        {
            return this.keys[index].key;
        } // end getKey

        // Return number of keys in table
        public int getKeyCount()
        {
            return keyCount;
        } // end getKeyCount

        // Return number of different values for key at index
        public int getValueCount(int index)
        {
            return this.keys[index].valueCount; //[index];
        } // end getValueCount

        // setters \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

        // Store new key into table and return index of key in the table
        public int setKey(int key)
        {
            for (int i = 0; i < keyCount; i++)
            {
                if (this.keys[i].key == key) // key already in table
                {
                    return i;
                }
            } // end for loop
            if (this.keyCount < 30) // add key to table if room
            {
                Key cKey = new Key();
                this.keys[keyCount].key = key;
                int index = keyCount;
                keyCount++;
                return index;
            }
            else
            {
                MessageBox.Show("Error: Too many different keys");
                return -1;
            }

        } // end setKey

        // Add value for key at index, return value index
        public int setValue(int index, int value)
        {

            for (int i = 0; i < this.keys[index].valueCount; i++)
            {
                if (this.keys[index].values[i] == value)
                {
                    // Increment sum
                    this.keys[index].sums[i]++;
                    return i;
                }
            } // end for loop

            // Add value to array for key since didn't return for existing value
            this.keys[index].values[this.keys[index].valueCount] = value;
            this.keys[index].sums[this.keys[index].valueCount]++;

            int valueIndex = this.keys[index].valueCount;
            this.keys[index].valueCount++; // increment number of values for key
            return valueIndex;

        } // end setValue

        // Get value for key at indexes, return value
        public int getValue(int indexKey, int indexValue)
        {
            return this.keys[indexKey].values[indexValue];
        } // end getValue

        // Increment Sum for value of key at indexKey and value at indexValue
        public void incSum(int indexKey, int indexValue)
        {
            // Increment sum
            this.keys[indexKey].sums[indexValue]++;

            return;

        } // end incSum

        // Get sum for key at indexes, return sum
        public int getSum(int indexKey, int indexValue)
        {
            return this.keys[indexKey].sums[indexValue];
        } // end getSum

    } // end class KeyTable

} // MaxColSumbyKey

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MaxColSumbyKey
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // Instantiate the KeyTable class
        KeyTable keyTable = new KeyTable(); // visible instance of KeyTable class

        // Declare an enumerated type to use in Parse
        public enum Scan
        { BYPASS, KEY, VALUE1, VALUE2 }

        // Constants.  That is, not to be changed.
        const Byte NINE = 57;
        const Byte ZERO = 48;

        private void toolStripMenuItem1_Click_1(object sender, EventArgs e)
        {
            // Click on File -- not needed
        }

        private void ReportResults()
        {
            int keyMostCount = 0;
            int keyMostValues = 0;
            int keyMostSums = 0;

            // Search the keys
            for (int keyIndex = 0; keyIndex < keyTable.keyCount; keyIndex++)
            {
                int valuesCount = keyTable.getValueCount(keyIndex);
                if (valuesCount >= keyMostValues) // can be tie for most instances of key
                {
                    // search for greatest number of values
                    for (int valueIndex = 0; valueIndex < valuesCount; valueIndex++)
                    {
                        if (keyTable.getSum(keyIndex, valueIndex) > keyMostSums) // save key and value with greater sum
                        {
                            keyMostCount = keyTable.getKey(keyIndex);
                            keyMostValues = keyTable.getValue(keyIndex, valueIndex);
                            keyMostSums = keyTable.getSum(keyIndex, valueIndex);
                        }
                    } // end for loop
                } // end if

            } // end for loop

            keyTextBox.Text = keyMostCount.ToString(); 
            valueTextBox.Text = keyMostValues.ToString();
            sumTextBox.Text = keyMostSums.ToString();
            this.Refresh();

        } // end ReportResults

        // Update tables with 'key' and 'value'
        public void Update(int key, int[] value)
        {
            // Search keyTable
            int keyIndex = -1;
            int valueIndex = -1;

            for (int i = 0; i < keyTable.keyCount; i++) // getKeyCount()
            {
                if (key == keyTable.getKey(i))
                {
                    keyIndex = i;
                    break; // exit loop
                }
            } // end loop

            if (keyIndex < 0) // key not in the table
            {
                keyIndex = keyTable.setKey(key); // add key
                valueIndex = keyTable.setValue(keyIndex, value[0]); // add value for key
                if (value[0] != value[1])
                { keyTable.setValue(keyIndex, value[1]); }
                else
                { keyTable.incSum(keyIndex, valueIndex); }
            }
            else // key in the table
            {
                int valueCount = keyTable.getValueCount(keyIndex);
                valueIndex = keyTable.setValue(keyIndex, value[0]); // add value for key
                if (value[0] != value[1])
                { keyTable.setValue(keyIndex, value[1]); }
                else
                { keyTable.incSum(keyIndex, valueIndex); }
            }

         } // end Update

        private int ConvertToNumeric(byte[] keyField, int index)
        {
            // Convert the byte string to an integer.
            // Check the StructClasses to find the Key or add a new one.
            // Remember the index into the KeyValueRec to use for the field Value.
            int start;
            start = 0;
            int finish;
            finish = index - 1;
            if (finish < 0) // debug
            { MessageBox.Show("ConvertToNumeric error 1"); }
            int keyInt = 0;
            if ((keyField[finish] >= ZERO) & (keyField[finish] <= NINE))
            {
                keyInt = keyField[finish] - ZERO; // convert ASCII to digit
            }
            finish--;
            int m = 10;
            while (finish >= start)
            {
                if (finish < 0) // debug
                { MessageBox.Show("ConvertToNumeric error 2"); }
                if ((keyField[finish] >= ZERO) & (keyField[finish] <= NINE))
                {
                    keyInt = keyInt + (m * (keyField[finish] - ZERO));
                }
                m = m * 10;
                finish--;
            }
            return keyInt;
        } // ConvertToNumeric

        private void Parse(int count, Byte[] data)
        {
            // Build a table of keys (second field of a record) and, for each key,
            // the number of value items (third and fourth fields) of the key and 
            // the number of instances of the particular value.

            // The first field ends with a TAB.  The second field (Key) ends the
            // same way as does both the Value fields or at the end of record
            // following the second Value field (in this case NL but for a different
            // file - a DOS file - in the CR LF pair).
            // All other data is ignored until end-of-file (EOF) or end of record.

            const Byte HT = 9; // horizontal tab
            const Byte CR = 13;
            const Byte LF = 10;
            const Byte NL = 10; // new line

            Scan scanPhase = Scan.BYPASS;

            int keyCount = 0; // number of actual bytes in keyField
            Byte[] keyField = new Byte[16]; // max of 16 bytes for a key
            int value1Count = 0;
            Byte[] value1Field = new Byte[10];
            int value2Count = 0;
            Byte[] value2Field = new Byte[10];

            int index = 0; // index into keyField, etc for next Byte

            int keyValue = 0; // keyField as converted
            int[] valueValues = new int[2];
            valueValues[0] = 0;
            valueValues[1] = 0;

            for (int i = 0; i < count; i++) // examine each byte of data
            {
                Byte debugxx = data[i]; // examine byte with debugger

                // Parse key
                switch (scanPhase)
                 {
                    case Scan.BYPASS: // ignore text until after first HT found
                        if (data[i] == HT)
                        {
                            scanPhase = Scan.KEY;
                            // Initialize for next set of data
                            keyCount = 0;
                            value1Count = 0;
                            value2Count = 0;
                        }
                        break;
                    case Scan.KEY:  // capture the Key
                        if (data[i] != HT)
                        {
                            keyField[index] = data[i];
                            index++;
                        }
                        else
                        {
                            keyCount = index;
                            keyValue = ConvertToNumeric(keyField, keyCount);
                            scanPhase = Scan.VALUE1; // Value immediately follows HT
                            index = 0;
                        }
                        break;
                    case Scan.VALUE1: // capture first value
                        if (data[i] != HT)
                        {
                            value1Field[index] = data[i];
                            index++;
                        }
                        else
                        {
                            value1Count = index;
                            valueValues[0] = ConvertToNumeric(value1Field, value1Count);
                            scanPhase = Scan.VALUE2; // Value immediately follows HT
                            index = 0; // Capture the first value
                        }
                        break;
                    case Scan.VALUE2: // capture 2nd value
                        if ((data[i] != HT) && (data[i] != NL) && (data[i] != CR) && (data[i] != LF))
                        {
                            value2Field[index] = data[i];
                            index++;
                        }
                        else
                        {
                            value2Count = index;
                            valueValues[1] = ConvertToNumeric(value2Field, value2Count);
                            index = 0;

                            // Update tables with the data from the record.
                            Update(keyValue, valueValues);

                            // Initialize for next record
                            keyCount = 0;
                            value1Count = 0;
                            value2Count = 0;
                            scanPhase = Scan.BYPASS;
                        }
                        break;
                }
                if (i >= count)
                {
                    return;
                }
            } // end for
            return;

        } // Parse

        private void ReadAndParse(Stream file)
        {
            System.Byte[] buffer = new byte[file.Length]; // buffer to contain file

            int numBytesRead = 0;
            try
            {
                // Read everything in file.
                numBytesRead = file.Read(buffer, 0, (int)file.Length);
                file.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error reading file");
            }

            Parse(numBytesRead, buffer); // Parse the bytes read
            return;

        } // ReadAndParse

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        { // to open the file
            Stream myStream = null;

            // Get an instance of a FileDialog.
            OpenFileDialog openFileDialog = new OpenFileDialog();

            // Use a filter to allow only certain file extensions.
            openFileDialog.InitialDirectory = "c:\\";
            openFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            openFileDialog.FilterIndex = 2;
            openFileDialog.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    if ((myStream = openFileDialog.OpenFile()) != null)
                    {
                        using (myStream)
                        {
                            // Read the file and build tables from the data.
                            ReadAndParse(myStream);
                            ReportResults();
                            return;
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
                }
            }
        } // openToolStripMenuItem_Click

        private void keyTextBox_TextChanged(object sender, EventArgs e)
        {
            // don't care if has changed
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void valueTextBox_TextChanged(object sender, EventArgs e)
        {

        }

    } // class Form1

} // namespace MaxColSumbyKey

Form1.Designer.cs
namespace MaxColSumbyKey
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.directorySearcher1 = new System.DirectoryServices.DirectorySearcher();
            this.directorySearcher2 = new System.DirectoryServices.DirectorySearcher();
            this.menuStrip1 = new System.Windows.Forms.MenuStrip();
            this.menuStrip2 = new System.Windows.Forms.MenuStrip();
            this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
            this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
            this.keyTextBox = new System.Windows.Forms.TextBox();
            this.valueTextBox = new System.Windows.Forms.TextBox();
            this.sumTextBox = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            this.menuStrip2.SuspendLayout();
            this.SuspendLayout();
            //
            // directorySearcher1
            //
            this.directorySearcher1.ClientTimeout = System.TimeSpan.Parse("-00:00:01");
            this.directorySearcher1.ServerPageTimeLimit = System.TimeSpan.Parse("-00:00:01");
            this.directorySearcher1.ServerTimeLimit = System.TimeSpan.Parse("-00:00:01");
            //
            // directorySearcher2
            //
            this.directorySearcher2.ClientTimeout = System.TimeSpan.Parse("-00:00:01");
            this.directorySearcher2.ServerPageTimeLimit = System.TimeSpan.Parse("-00:00:01");
            this.directorySearcher2.ServerTimeLimit = System.TimeSpan.Parse("-00:00:01");
            //
            // menuStrip1
            //
            this.menuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
            this.menuStrip1.Location = new System.Drawing.Point(0, 24);
            this.menuStrip1.Name = "menuStrip1";
            this.menuStrip1.Padding = new System.Windows.Forms.Padding(4, 2, 0, 2);
            this.menuStrip1.Size = new System.Drawing.Size(212, 24);
            this.menuStrip1.TabIndex = 0;
            this.menuStrip1.Text = "menuStrip1";
            //
            // menuStrip2
            //
            this.menuStrip2.ImageScalingSize = new System.Drawing.Size(20, 20);
            this.menuStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.toolStripMenuItem1});
            this.menuStrip2.Location = new System.Drawing.Point(0, 0);
            this.menuStrip2.Name = "menuStrip2";
            this.menuStrip2.Padding = new System.Windows.Forms.Padding(4, 2, 0, 2);
            this.menuStrip2.Size = new System.Drawing.Size(212, 24);
            this.menuStrip2.TabIndex = 1;
            this.menuStrip2.Text = "menuStrip2";
            //
            // toolStripMenuItem1
            //
            this.toolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.toolStripMenuItem2});
            this.toolStripMenuItem1.Name = "toolStripMenuItem1";
            this.toolStripMenuItem1.Size = new System.Drawing.Size(37, 20);
            this.toolStripMenuItem1.Text = "File";
            this.toolStripMenuItem1.Click += new System.EventHandler(this.toolStripMenuItem1_Click_1);
            //
            // toolStripMenuItem2
            //
            this.toolStripMenuItem2.Name = "toolStripMenuItem2";
            this.toolStripMenuItem2.Size = new System.Drawing.Size(103, 22);
            this.toolStripMenuItem2.Text = "Open";
            this.toolStripMenuItem2.Click += new System.EventHandler(this.openToolStripMenuItem_Click);
            //
            // keyTextBox
            //
            this.keyTextBox.Location = new System.Drawing.Point(9, 50);
            this.keyTextBox.Margin = new System.Windows.Forms.Padding(2);
            this.keyTextBox.Name = "keyTextBox";
            this.keyTextBox.Size = new System.Drawing.Size(99, 20);
            this.keyTextBox.TabIndex = 2;
            this.keyTextBox.TextChanged += new System.EventHandler(this.keyTextBox_TextChanged);
            //
            // valueTextBox
            //
            this.valueTextBox.Location = new System.Drawing.Point(9, 90);
            this.valueTextBox.Margin = new System.Windows.Forms.Padding(2);
            this.valueTextBox.Name = "valueTextBox";
            this.valueTextBox.Size = new System.Drawing.Size(99, 20);
            this.valueTextBox.TabIndex = 3;
            this.valueTextBox.TextChanged += new System.EventHandler(this.valueTextBox_TextChanged);
            //
            // sumTextBox
            //
            this.sumTextBox.Location = new System.Drawing.Point(9, 132);
            this.sumTextBox.Name = "sumTextBox";
            this.sumTextBox.Size = new System.Drawing.Size(99, 20);
            this.sumTextBox.TabIndex = 4;
            //
            // label1
            //
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(6, 116);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(28, 13);
            this.label1.TabIndex = 5;
            this.label1.Text = "Sum";
            //
            // label2
            //
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(9, 76);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(32, 13);
            this.label2.TabIndex = 6;
            this.label2.Text = "Vaue";
            //
            // label3
            //
            this.label3.AutoSize = true;
            this.label3.Location = new System.Drawing.Point(12, 34);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(25, 13);
            this.label3.TabIndex = 7;
            this.label3.Text = "Key";
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(212, 206);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.sumTextBox);
            this.Controls.Add(this.valueTextBox);
            this.Controls.Add(this.keyTextBox);
            this.Controls.Add(this.menuStrip1);
            this.Controls.Add(this.menuStrip2);
            this.MainMenuStrip = this.menuStrip1;
            this.Margin = new System.Windows.Forms.Padding(2);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.menuStrip2.ResumeLayout(false);
            this.menuStrip2.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.DirectoryServices.DirectorySearcher directorySearcher1;
        private System.DirectoryServices.DirectorySearcher directorySearcher2;
        private System.Windows.Forms.MenuStrip menuStrip1;
        private System.Windows.Forms.MenuStrip menuStrip2;
        private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1;
        private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem2;
        private System.Windows.Forms.TextBox keyTextBox;
        private System.Windows.Forms.TextBox valueTextBox;
        private System.Windows.Forms.TextBox sumTextBox;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
    }
}

Note:  Most of Form1.Designer.cs was generated by C#.  Small changes had to be made when one of text boxes had its name changed after it had been created.

The result of running the application is

No comments: