Wednesday, January 3, 2018

Max Column Sum by Key (part 5)

JavaScript

Since JavaScript is being reported as one of the current popular languages, I thought it time to try the Learn to Code GR application that I previously wrote in Ada, C#, Python, Java, C# again, C, Kotlin, and Pascal using JavaScript.

Getting started I had to use the internet once again as my classroom.  Took most a day to get started since the advice I found didn't work.  Finally I found someone who knew what they were talking about so was able to do a simple demo of html invoking an external function.  Then, a similar simple demo of html invoking an internal function.  The working samples are:

External function – script.index.js
function go(){
  document.write("javascript is working");
}
– script.html
<html>
  <head>
    <script type="text/javascript" src="script.index.js"></script>
  </head>
  <body>
    <input type="button" onclick="go()" value="Display JS"/>
  </body>
</html>
which displayed a browser page with a button with a label of Display JS.  When clicked javascript is working was displayed.

Internal function – script-internal.html
<html>
  <head>
    <script>
      function go(){
        document.write("internal javascript is working");
      }
    </script>
  </head>
  <body>
    <input type="button" onclick="go()" value="Display Internal"/>
  </body>
</html>
with similar results when script-internal.html was run.

Since reading the max-col-sum-by-key.tsv text file is a necessary first step in reworking the Learn to Code GR application I next tried various html files in an attempt to open and read the file.  I found various results with internet searches but was unable to get them to work until I found one via a Bing search for "javascript open named file" that provided an option from stackoverflow of htm15 - How to read text file in JavaScript - Stack Overflow.

This solution worked out-of-the-box where the html is
<!DOCTYPE html>
<html>
  <head>
    <title>Read File (via User Input selection)</title>
    <script type="text/javascript">
    var reader; //GLOBAL File Reader object for demo purpose only

    /**
     * Check for the various File API support.
     */
    function checkFileAPI() {
        if (window.File && window.FileReader && window.FileList && window.Blob) {
            reader = new FileReader();
            return true;
        } else {
            alert('The File APIs are not fully supported by your browser. Fallback required.');
            return false;
        }
    }

    /**
     * read text input
     */
    function readText(filePath) {
        var output = ""; //placeholder for text output
        if(filePath.files && filePath.files[0]) {          
            reader.onload = function (e) {
                output = e.target.result;
                displayContents(output);
            };//end onload()
            reader.readAsText(filePath.files[0]);
        }//end if html5 filelist support
        else if(ActiveXObject && filePath) { //fallback to IE 6-8 support via ActiveX
            try {
                reader = new ActiveXObject("Scripting.FileSystemObject");
                var file = reader.OpenTextFile(filePath, 1); //ActiveX File Object
                output = file.ReadAll(); //text contents of file
                file.Close(); //close file "input stream"
                displayContents(output);
            } catch (e) {
                if (e.number == -2146827859) {
                    alert('Unable to access local files due to browser security settings. ' +
                     'To overcome this, go to Tools->Internet Options->Security->Custom Level. ' +
                     'Find the setting for "Initialize and script ActiveX controls not marked as safe" and change it to "Enable" or "Prompt"');
                }
            }      
        }
        else { //this is where you could fallback to Java Applet, Flash or similar
            return false;
        }      
        return true;
    }  

    /**
     * display content using a basic HTML replacement
     */
    function displayContents(txt) {
        var el = document.getElementById('main');
        el.innerHTML = txt; //display output in DOM
    }  
</script>
</head>
<body onload="checkFileAPI();">
    <div id="container">   
        <input type="file" onchange='readText(this)' />
        <br/>
        <hr/>  
        <h3>Contents of the Text file:</h3>
        <div id="main">
            ...
        </div>
    </div>
</body>
</html>

This html opens a browser window with a Choose File button with "No file chosen" to the right of it and then a dividing line across the screen followed by

Contents of the Text file:

below the line.  When the Choose File button is clicked I was able to select the max-col-sum-by-key.tsv that I had copied to the folder with the various html files.  This resulted in all the records of the file being displayed in the browser window as
0,912_NUM 1000 1 1 0,912_NUM 1000 2 1 0,912_NUM 1000 1 1 0,912_NUM 1000 2 2 0,912_NUM 1000 1 1 0,912_NUM 1000 2 2 0,912_NUM 1000 1 1 0,912_NUM 1000 1 1 0,912_NUM 1000 2 2 0,912_NUM 1000 1 1 0,912_NUM 2000 1 1 0,912_NUM 2000 1 1
etc.

I expect that this will be the final difficult problem standing in the way of producing another version of the Learn to Code GR problem.  (This proved to be true.  I started on 12/29 and finished the morning of Jan 3 with bowl games and NFL reducing the available time.)

After opening and reading the tsv file I moved the parse function to a .js file and did the functions that it calls in the .js file so I could use node to catch some of the errors in advance.  That is, I didn't see a way to do so for the .html file.  Even so, there were errors that running node on the .js file didn't catch.  For instance, references to an undefined variable.

Also, I reverted to how I kept track of the data in the original C# solution since everything I found says that there isn't a class in JavaScript.  Although I modified the update function from the original C# to use multiple functions as I had done in the C solution.

I couldn't find anything to output to the browser window from the .js file so I used the alert function for debugging.

Also, because I couldn't do output from the .js file, I passed the results back to the .html file and displayed the results there. 

This resulted in a browser window of

Before the file was chosen the display had the Choose File button with the horizontal line and No file chosen to the right of the button.  In both cases "Learn to Code GR" is the text shown on the browser tab.

The two files to do the JavaScript version of the solution are as follows.

LearntoCode.html

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="learntocode.js"></script>

        <title>Learn to Code GR</title>

        <script type="text/javascript">
        var reader; /*GLOBAL File Reader object*/

        /*
         * Code to select, open, and read the text file.
         */

        /* Check for the various File API support. */
        function checkFileAPI() {
            if (window.File && window.FileReader && window.FileList && window.Blob) {
                reader = new FileReader();
                return true;
            } else {
                alert('The File APIs are not fully supported by your browser. Fallback required.');
                return false;
            }
        } // end checkFileAPI


        /* Read text input */
        function readText(filePath) {
            var buffer = ""; /* to contain the file text */
            if(filePath.files && filePath.files[0]) {          
                reader.onload = function (e) {
                    buffer = e.target.result;
                    treatFile(buffer);
                }; // end reader/onload function
                reader.readAsText(filePath.files[0]);
            } // end if html5 filelist support
            else if(ActiveXObject && filePath) { // fallback to IE 6-8 support via ActiveX
                try {
                    reader = new ActiveXObject("Scripting.FileSystemObject");
                    var file = reader.OpenTextFile(filePath, 1); // ActiveX File Object
                    buffer = file.ReadAll(); // text contents of file
                    file.Close(); // close file "input stream"
                    treatFile(buffer);
                } catch (e) {
                    if (e.number == -2146827859) {
                        alert('Unable to access local files due to browser security ' +
                               'settings. ' + 'To overcome this, go to ' +
                               'Tools->Internet Options->Security->Custom Level. ' +
                               'Find the setting for "Initialize and script ActiveX ' +
                               'controls not marked as safe" and change it ' +
                               'to "Enable" or "Prompt"');
                    }
                }      
            }
            else { // this is where you could fallback to Java Applet, Flash or similar
                return false;
            }      
            return true;
        } // end readText

        function display(results) {
            var myDiv = document.getElementById("main");
            var message = "<br><b><u>Results</u></b>";
            message += "<ul><li><b>KEY: </b>" + results[0];
            message += "<li><b>VALUE: </b>" + results[1];
            message += "<li><b>SUM: </b>" + results[2];
            myDiv.innerHTML = message;
        } // end display

        function treatFile(buffer)
        {
            var results = [];
            results = parse(buffer);
            display(results);
        } // end treatFile

        </script>
    </head>

    <body onload="checkFileAPI();">
        <input type="file" onchange='readText(this)' />
        <hr/>
        <div id="main">
        </div>
    </body>

</html>

learntocode.js

// Global data

// The following would be the KeyData and KeyTable structures if there were
// something like struct or class in JavaScript.
var keyCount = 0;
var valueKey = [];
var valueCount = [];
var valueValues = [,]; // different values of the key
var valueSums = [,];   // sum of values of particular key

// Data to be retained as the key and its associated value with the maximum
// number of references.
var maxKey = 0;
var maxValue = 0;
var maxSum = 0;

// Saved data as parse each line.
var savedData = [];
savedData[0] = 0;
savedData[1] = 0;
savedData[2] = 0;

// Functions

// Parse data lines of the text file.
function parse(data) {

    var CR = "\r";
    var HT = "\t";
    var LF = "\n";

    /* Parse each line of data in the buffer to obtain the three fields of
     * interest, converting those fields to integers into an array, and
     * then updating a data structure to retain the data for evaluation
     * when the complete buffer has been parsed.
     */

    var nextField = 0;   // index of the beginning of next field
    var startField;      // range of indexes of
    var numFields = 0;   // index into dataFields array
    var dataFields = []; // Integer values of the three fields of interest
    dataFields[0] = 0;
    dataFields[1] = 0;
    dataFields[2] = 0;

    var bufSize = data.length;
    var index = 0;

    for (index = 0; index < bufSize; index++ )
    {
        // Parse the buffer line
        if (data[index] == HT) // beginning of a field
        {   startField = nextField; // save starting index
            nextField = index + 1;  // the next byte will contain part of next field
            if (numFields > 0)
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, startField, index - 1);
            } // end if numFields > 0
            numFields++;
        } // end if HT

        // Do the update when find CR or LF of reach the end of the buffer.
        // The last option since the file has no terminators for the last line.
        if ((data[index] == CR) || (data[index] == LF) || (index == (bufSize-1)))
        {
            if (numFields < 4) // only do the update once for each line
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, nextField, index - 1);
                // Save the data to determine the key, value combination with
                // maximum number of references
                update(dataFields);

                // Finished with the line in the data array, initialize
                // for next line.
                nextField = 0;
                startField;
                numFields = 0;
                dataFields[0] = 0;
                dataFields[1] = 0;
                dataFields[2] = 0;
                if ((data[index] == CR) && (data[index+1] == LF))
                { index++ } // bypass the extra char at end of line
            } // end if
        } // end if

    } // end for loop

    // Report the number of references with a particular Key and Value.
    var results = [];
    results = report();
    return results;

}; // end parse
   
// Convert character array to integer
function toInt(data, iS, iE)
{
    var NINE = "9" //57 // ASCII character for digit 9
    var ZERO = "0" //48 // ASCII character for digit 0

    var index = iE; // loop in reverse
    var digit = 0;
    var m = 1;      // multiplier for shift
    var number = 0; // Numeric result

    while (index >= iS)
    {
        if ((data[index] >= ZERO) && (data[index] <= NINE))
        {
            digit = data[index] - ZERO; // convert ASCII to digit
            number = number + (m * digit);
            m = m * 10;
            index--;
        }
    }

    return number;

} // end toInt

// Update the tables for a particular file line.
function update(data)
{
    // This function first checks if the key is new and, if so, adds it to the
    // keys array.  It then does similar for the values associated with it.
    // Note: data[0] is the key while data[1] and data[2] are the two values
    //       associated with the key.

    // Save data to be updated for use by updateTotals since needed by
    // multiple functions
    savedData[0] = data[0];
    savedData[1] = data[1];
    savedData[2] = data[2];

    // Check whether the key is already in the key table
    var keyIndex = -1;
    for (var k = 0; k < keyCount; k++)
    {
        if (valueKey[k] == data[0])
        {
            keyIndex = k; // key already in the table
            break; // exit loop      
        }
    } // end for

    if (keyIndex < 0) // key not in the table
    { // add the key
        keyIndex = keyCount;
        valueKey[keyIndex] = data[0];
        valueCount[keyIndex] = 0;
        keyCount++;
    } // end if

    // Add the values for the key
    addValues(keyIndex, savedData[1], savedData[2]);

} // end update

// Add the values or increment their sums
function addValues(keyIndex, value1, value2)
{
    // Check whether the first value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value1)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,v]++; // increment its number of references
            if (value1 == value2) // 2nd value the same
            {
                valueSums[keyIndex,v]++; // increment again
            }
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,v]);
            break; // exit loop
        }
    } // end loop
 
    if (valueIndex < 0) // value not yet in table
    { // add value to the table - index points to last value checked
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value1;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        if (value1 == value2) // dupicated value
        {
            valueSums[keyIndex,index]++; // increment to 2
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,index]);
        }
        else
        {
            add2ndValue(keyIndex, value2);
        }
    } // end outer if

} // end addValues

function add2ndValue(keyIndex, value2)
{
    // Check whether the second value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value2)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,valueIndex]++; // increment number of references
            // check if new max
            updateTotals(savedData[0],value2,valueSums[keyIndex,valueIndex]);
            break; // exit loop
        }
    } // end loop
    if (valueIndex < 0) // value not yet in table
    { // add value to the table
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value2;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        updateTotals(savedData[0],value2,valueSums[keyIndex,index]);
    }

} // end add2ndValue

function updateTotals(key, value, sum)
{
    if (sum > maxSum)
    {
        maxKey = key;
        maxValue = value;
        maxSum = sum;
    }

} // end updateTotals

// Report the key and value with the most references
function report()
{
    var results = [];
    results[0] = maxKey;
    results[1] = maxValue;
    results[2] = maxSum;
    return results;
} // end report

Thursday, October 5, 2017

Max Column Sum by Key (part 4)


Further 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 Pascal - a language that I first (and last) used in 1990 at SCI in Huntsville Alabama.  At that time I had liked it for the brief time that I was part of a small project.  Maybe since after a couple of weeks I was able to guide others who had been on the project longer about how to program in it.

I had seen that GNAT GPS had Pascal as one of its options as a language.  However, in trying to use it GPS would give me an error that it didn't recognize a compiler for Pascal.  So I did an internet search and found Free Pascal and downloaded its Win32 version.  It installs to C:\FPC\3.0.2 and the install did put the path to its bin subfolder into the Windows system path. 

However running it was extremely ugly.  I tried GNAT GPS again but even with the path to it in the system path, GNAT GPS didn't find it to use as the Pascal compiler.

So I went searching for something else and in doing so I came across Lazarus.  I downloaded lazarus-1.6.4-fpc-3.0.2-win32.exe (128.8 MB) into C:\lazarus.  This proved to be a Windows IDE for the previously installed Free Pascal compiler.  A much, much better solution.  So I was off and running.  (Up and running?)  I sure had no memories of what the language looked like.

It assumed that the source code would be in C:\lazarus\fpc\3.0.2\source.  Multiple kinds of projects can be selected.  I forget which I used at the beginning that created .lpr Lazarus Project Main Source file.  After I had problems with the debugger I tried a Console application project that is a .pas Pascal file for the source.

Since Pascal has both a record structure and the ability to declare and implement a class I have included examples of both.  The record structure for the KeyData record type and the KeyTable class that includes the number of different keys and an array of the KeyData record type.  Unlike in C# and Java the implementation of a procedure or function is not included within the class declaration.  Instead a declaration of the procedure/function is included within the class and separate code has to be provided to implement the procedure or function like happens for an Ada package.  Although, with Ada, the private procedures and functions don't need a declaration in the package specification or even in the package body if the implementation is done prior to any invocation of it.

It took me a while to get the use of a class worked out.  As I found problems with the code that caused exceptions I don't know whether this was because I wasn't storing values within the bounds of an array or because I hadn't created the KeyTable class as associated with a Pascal class; in my code TCustomApplication with a constructor and destructor.

In any case, the example of a record is
// Global types
type

  fieldArray = array[1..3] of integer;
  valueArray = array[1..20] of integer;

  // Data about a particular key
  KeyData = record
    key : integer;        // numeric key
    valueCount : integer; // number of different values associated with key
    values : valueArray;  // different values associated with key
    sums : valueArray;    // number of references to a value
  end;
  keyArray = array[1..30] of KeyData;
where keyArray is an array of the KeyData record while the record has two fields of the valueArray.

The KeyTable class is
// KeyTable class declaration
  KeyTable = class(TCustomApplication)

    public
    keyCount : integer; // number of different keys
    keys : keyArray;    // value of key - one in each array position

    // Data to be retained as the key and its associated value with the
    // maximum number of references
    maxKey : Integer;
    maxValue : Integer;
    maxSum : Integer;


    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;

    // Initialize
    procedure Clear();
    // Update keyTable with data from a parsed line
    procedure Update(data : fieldArray);
    // Add the values or increment their sums
    procedure addValues(keyIndex : Integer; value1 : Integer; value2 : Integer);
    procedure add2ndValue(keyIndex : Integer; value2 : Integer);
    procedure updateTotals(key : Integer; value : Integer; sum : Integer);
    // Report the results
    procedure Report();

  end; // KeyTable class

Each of the procedures are separately implemented as with
procedure KeyTable.Clear();

var
  k : integer;

begin // Clear
  keyCount := 0;
  for k := 1 to 30 do
  begin
    keys[k].valueCount := 0;
  end;
  maxKey := 0;
  maxValue := 0;
  maxSum := 0;
  savedData[1] := 0;
  savedData[2] := 0;
  savedData[3] := 0;
end; // Clear
where the name of the class precedes the name of the procedure.  Each has to name its local variables first following the var keyword.  This is also similar to Ada where the variables are named prior to the begin statement.  However, unlike Ada where a loop index type can frequently be determined by the context without needing to be declared, it has to be included after the var keyword for Pascal.  In C kinds of languages it is specified in the loop statement.

Instances of the class are named following a var keyword visible to the none class procedure calls that invoke one of the procedures of the class.  Such as
// Static variables
var
  Table : KeyTable;  // instance of class

// Entry point from operating system
begin // LearnToCodeGR program

  // Initialize
  Table := KeyTable.Create(nil);

  . . .

  // Report the results
  Table.Report();
Here, of course, the name of the class is used in setting the instance of the class while the second dotted notation has been used to specify that that instance of the class is to be invoked.

It should be noted here that, like Ada and unlike C, case makes no difference.  Therefore I named the instance of the class as Table rather than keyTable as I would have in a language where case mattered.

However, initially (as with the other languages) I first determined how to read the max-col-sum-by-Key.tsv file.  In doing so I found that the
Readln(FileIn,buffer);
statement, where buffer is a string, inputs up to the end-of-line LF and/or CR but doesn't include these characters in the data string.  Therefore, the Parse procedure (when it was added) had to change in recognizing the end of the final field - the second value and the third field of interest.

Note that, like Ada, Pascal has both functions, that return a value, and procedures that don't.   Also, in many other ways creating this Pascal application there were reminders of the syntax of Ada.  Which must have been the reason that I liked Pascal on that long ago occasion when I had reason to use it.  I did find the need to surround the code following an if or loop statement with begin … end; blocks a little excessive.  Ada doesn't need the begin but does have the end statement (end if; and end loop;).  C and the like have { } brackets of course.  Whereas { } brackets can be used to surround a comment like /* */ in C.

There is some required sequence of keywords.  For instance, the internal procedures and functions have to be included after the variables (that follow the 'var' keyword) of the program.

The arrays can start anywhere so I have switched to begin some arrays with an index of 1 while leaving others to start at 0 to correspond to the C code.

The code in Pascal is
program LearnToCodeGR;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, SysUtils, CustApp;

// Global types
type

  fieldArray = array[1..3] of integer;
  valueArray = array[1..20] of integer;

  // Data about a particular key
  KeyData = record
    key : integer;        // numeric key
    valueCount : integer; // number of different values associated with key
    values : valueArray;  // different values associated with key
    sums : valueArray;    // number of references to a value
  end;
  keyArray = array[1..30] of KeyData;

// KeyTable class declaration
  KeyTable = class(TCustomApplication)

    public
    keyCount : integer; // number of different keys
    keys : keyArray;    // value of key - one in each array position

    // Data to be retained as the key and its associated value with the
    // maximum number of references
    maxKey : Integer;
    maxValue : Integer;
    maxSum : Integer;


    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;

    // Initialize
    procedure Clear();
    // Update keyTable with data from a parsed line
    procedure Update(data : fieldArray);
    // Add the values or increment their sums
    procedure addValues(keyIndex : Integer; value1 : Integer; value2 : Integer);
    procedure add2ndValue(keyIndex : Integer; value2 : Integer);
    procedure updateTotals(key : Integer; value : Integer; sum : Integer);
    // Report the results
    procedure Report();

  end; // KeyTable class

// Static variables
var
  Table : KeyTable;  // instance of class

  savedData : fieldArray; // data to be visible between multiple procedures

// KeyTable constructor and destructor
constructor KeyTable.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  StopOnException:=True;
  Initialize();
end; // Create

destructor KeyTable.Destroy;
begin
  inherited Destroy;
end;


// KeyTable procedures
procedure KeyTable.Clear();

var
  k : integer;

begin // Clear
  keyCount := 0;
  for k := 1 to 30 do
  begin
    keys[k].valueCount := 0;
  end;
  maxKey := 0;
  maxValue := 0;
  maxSum := 0;
  savedData[1] := 0;
  savedData[2] := 0;
  savedData[3] := 0;
end; // Clear

procedure KeyTable.Update(data : fieldArray);
// This procedure first checks if the key is new and, if so, adds it to
// the keys array.  It then does similar for the values associated with it.
// Note: data[1] is the key while data[2] and data[3] are the two values
//       associated with the key.

var
  keyIndex : integer;
  k : integer;

begin // Update

  // Save data to be updated for use by updateTotals since needed by multiple functions
  savedData[1] := data[1];
  savedData[2] := data[2];
  savedData[3] := data[3];

  // Check whether the key is already in the table
  keyIndex := -1;
  for k := 1 to Table.keyCount do
  begin
    if (keys[k].key = data[1]) then
    begin
      keyIndex := k; // key already in the table
      break; // exit loop
    end;
  end; // for loop

  if keyIndex < 0 then // key not in the table
  begin // add the key
    keyCount := keyCount + 1;
    keyIndex := keyCount;
    keys[keyIndex].key := data[1];
  end; // end if

  // Add the values for the key
  addValues(keyIndex, savedData[2], savedData[3]);

end; // Update

procedure KeyTable.addValues(keyIndex : Integer; value1 : Integer; value2 : Integer);

var
  valueIndex : integer;
  v : integer;

begin // addValues

  // Check whether the first value is already in the table
  valueIndex := -1;
  for v := 1 to keys[keyIndex].valueCount do
  begin
    if (keys[keyIndex].values[v] = value1) then
    begin // value already in the table
      valueIndex := v;
      keys[keyIndex].sums[v] :=  // increment its number of references
      keys[keyIndex].sums[v] + 1;
      if (value1 = value2) then // 2nd value the same
      begin
        keys[keyIndex].sums[v] := // increment again
          keys[keyIndex].sums[v] + 1;
      end;
      // check if new max
      Table.updateTotals(savedData[1],value1,keys[keyIndex].sums[v]);
      break; // exit loop
    end;
  end; // end loop

  if (valueIndex < 0) then // value not yet in table
  begin // add value to the table - index points to last value checked
    keys[keyIndex].valueCount := keys[keyIndex].valueCount + 1;
    v := keys[keyIndex].valueCount;
    keys[keyIndex].values[v] := value1;
    keys[keyIndex].sums[v] := 1;
    if (value1 = value2) then
    begin
      keys[keyIndex].sums[v] := // increment
        keys[keyIndex].sums[v] + 1;
      // check if new max
      Table.updateTotals(savedData[1],value1,keys[keyIndex].sums[v])
    end;
//  else
    if (value1 <> value2) then
    begin
      Table.add2ndValue(keyIndex, value2);
    end;
  end; // end outer if

end; // addValues

// Add the second value or increment its sum
procedure KeyTable.add2ndValue(keyIndex : integer; value2 : integer);

var
  valueIndex : integer;
  v : integer;

begin
  // Check whether the second value is already in the table
  valueIndex := -1;
  for v := 1 to keys[keyIndex].valueCount do
  begin
    if (keys[keyIndex].values[v] = value2) then
    begin // value already in the table
      valueIndex := v;
      keys[keyIndex].sums[valueIndex] := // increment its number of references
      keys[keyIndex].sums[valueIndex] + 1;
      // check if new max
      updateTotals(savedData[1],value2,keys[keyIndex].sums[valueIndex]);
      break; // exit loop
    end;
  end; // end loop

  if (valueIndex < 0) then // value not yet in table
  begin // add value to the table
    v := keys[keyIndex].valueCount;
    keys[keyIndex].values[v] := value2;
    keys[keyIndex].sums[v] := 1;
    keys[keyIndex].valueCount := keys[keyIndex].valueCount + 1;
  end;

end; // add2ndValue

procedure KeyTable.updateTotals(key : Integer; value : Integer; sum : Integer);
begin
  if (sum > maxSum) then
  begin
    maxKey := key;
    maxValue := value;
    maxSum := sum;
  end;
end; // updateTotals

procedure KeyTable.Report();
begin
  WriteLn( 'Key ', maxKey, ' with Value ', maxValue, ' has maximum Sum of ', maxSum );
end; // Report


// General functions and procedures

function toInt(data : string; iStart : integer; iEnd : integer) : integer;
  const
    NINE = '9';
    ZERO = '0';
    numZero = integer('0');

  var
    index : integer;
    digit : integer;
    m : integer = 1;      // multiplier for shift
    number : integer = 0; // Numeric result

begin // toInt
  index := iEnd; // loop in reverse
  while (index >= iStart) do
  begin
    if ((data[index] >= ZERO) and (data[index] <= NINE)) then
    begin
      digit := integer(data[index]) - numZero; // convert ASCII to digit
      number := number + (m * digit);
      m := m * 10;
      index := index - 1;
    end;
  end;
  toInt := number; // return converted value
end; // toInt

// Parse each line of data in the buffer to obtain the three fields of
// interest, converting those fields to integers into an array, and
// then updating a data structure to retain the data for evaluation
// when the complete buffer has been parsed.
procedure Parse(count : Integer; data : string);
  const
    HT : char = #9; // horizontal tab

  var
    convertedValue : integer;
    nextField : integer = 0;  // index of the beginning of next field
    startField : integer;     // range of indexes of
    numFields : integer = 0;  // index into dataFields array
    dataFields : fieldArray;  // Integer values of the three fields of interest

    index : integer = 0;

begin // Parse
  while index < count do
  begin
    // Parse the buffer line
    if (data[index] = HT) then // beginning of a field
    begin
      startField := nextField; // save starting index
      nextField := index + 1;  // the next byte will contain part of next field
      if (numFields > 0) then
      begin
        //convert and store in dataFields
        dataFields[numFields] := toInt(data, startField, index - 1);
      end; // end if numFields > 0
      numFields := numFields + 1;
    end; // end if HT
    index := index + 1;
  end; // while loop
  // Convert and store last field since it ends at the last char of data
  convertedValue := toInt(data, nextField, count);
  dataFields[numFields] := convertedValue; //toInt(data, nextField, count);
  // Save the data to determine the key, value combination with
  // maximum number of references
  Table.Update(dataFields);
end; // Parse

// Variables needed by the main entry point program
var
  FileIn : Text;
  buffer : string;   // line of data from the Text file

// Entry point from operating system
begin // LearnToCodeGR program

  // Initialize
  Table := KeyTable.Create(nil);
  Table.Title:='LearnToCodeGR';

  // Open file
  AssignFile(FileIn, 'C:\\Source\\LearnToCodeGR\\max-col-sum-by-Key.tsv');
  Reset(FileIn); // Ensure that at the start of the file

  // Read and Parse each line
  while not Eof(FileIn) do
  begin
    Readln(FileIn,buffer); // input the string of the file line
    WriteLn(Length(buffer));
    WriteLn(buffer);

    // Parse the line and update to retain all the necessary data.
    Parse(Length(buffer), buffer);
  end;

  Close(FileIn);

  // Report the results
  Table.Report();

  // Terminate
  Table.Free;

end. // LearnToCodeGR program

Note that in KeyTable.addValues I wanted to use an else statement but had trouble getting the compiler to accept it.  So the else is commented out and is followed by a second if statement to obtain the opposite result.  That is, the two values being equal prior to the //else and being unequal following it.