Search code examples
delphidelphi-7

Best method to scan a CSV file for a value in Delphi 7


I am looking for advice on how to best handle a programming task using Delphi 7.

I have the need to be able to quickly identify a value from a CSV file (less than 15kb in size). The CSV file exists in format of:

Chapter number, Paragraph number, Total number of words in the paragraph

I want to be able to retrieve this last value, i.e., the number of words, by providing a function the first two values (i.e., the chapter and paragraph number).

The CSV file is sorted numerically, that is:

1,1,30    // first paragraph of first chapter  (line # 1)
1,2,56    // second paragraph of first chapter  (line # 2)
1,3,101
1,4,56
...
2,1,78
2,2,51
...
100,1,87
100,2,101
...
100,23,78    // last paragraph of last chapter (line # 1500)

So in the example above, I'd like to pass 2,2 to a function and have it return "51" (integer)

I'd like to avoid using a database table because: 1) the amount of data isn't that large (1500 lines in the CSV file, i.e., 1500 paragraphs), 2) the extra overhead of a database engine (I only need to read data, not write data), 3) the frequency that this function will be called from within the program.

What would you recommend, and why?


Solution

  • TYPE
      TTwoDimIntArr = ARRAY OF ARRAY OF Cardinal;
    
    PROCEDURE SetValue(VAR ARR : TTwoDimIntArr ; Chapter,Paragraph,Value : Cardinal);
      BEGIN
        IF Chapter>=LENGTH(ARR) THEN SetLength(ARR,SUCC(Chapter));
        IF Paragraph>=LENGTH(ARR[Chapter]) THEN SetLength(ARR[Chapter],SUCC(Paragraph));
        ARR[Chapter,Paragraph]:=Value
      END;
    
    FUNCTION GetValue(CONST ARR : TTwoDimIntArr ; Chapter,Paragraph : Cardinal) : Cardinal;
      BEGIN
        IF Chapter>=LENGTH(ARR) THEN
          Result:=0
        ELSE IF Paragraph>=LENGTH(ARR[Chapter]) THEN
          Result:=0
        ELSE
          Result:=ARR[Chapter,Paragraph]
      END;
    
    FUNCTION ParseFile(CONST FileName : STRING) : TTwoDimIntArr;
      VAR
        SL  : TStrings;
        S   : STRING;
        P,Q : Cardinal;
        {$IFDEF DELPHI7 }
          I : Cardinal;
        {$ENDIF }
    
      BEGIN
        SL:=TStringList.Create;
        TRY
          SL.LoadFromFile(FileName);
          {$IFDEF DELPHI7 }
          FOR I:=1 TO SL.Count DO BEGIN
            S:=SL[PRED(I)];
          {$ELSE }
          FOR S IN SL DO BEGIN
          {$ENDIF }
            P:=POS(',',S);
            Q:=PosEx(',',S,SUCC(P));
            SetValue(Result,StrToInt(COPY(S,1,PRED(P))),StrToInt(COPY(S,SUCC(P),PRED(Q-P))),StrToInt(COPY(S,SUCC(Q),255)))
          END
        FINALLY
          SL.Free
        END
      END;
    

    ParseFile parses the file and returns it in a two-dimensional dynamic array. If you are 100% sure that you don't exceed the boundaries of the array you can access it directly. Otherwise the GetValue function is a safe wrapper to access the contents of the array.

    Use it as:

    USES ... StrUtils ...;
    
    .
    .<My Code>
    .
    VAR ARR : TTwoDimIntArr;
    
    BEGIN
      ARR:=ParseFile(<FileName>);
      .
      .
      .
      Words:=GetValue(ARR, <Chapter>, <Paragraph>);
      .
      .
    END.
    

    If Delphi 7 doesn't have the PosEx function in StrUtils, you can code it as follows:

    FUNCTION PosEx(CONST SearchFor,SearchIn : STRING ; StartPos : Cardinal = 1) : Cardinal;
      BEGIN
        Result:=POS(SearchFor,COPY(SearchIn,StartPos,$7FFFFFFF));
        IF Result>0 THEN INC(Result,PRED(StartPos))
      END;