Hash vs Table

Consider a table for customers records with two character fields : Customer ID and customer name:

Cust_ID Cust_Name
CC001 Pierce Firth
CC002 Stellan Taylor
CC003 Chris Cherry
CC004 Amanda Baranski

 It’s known all possible and necessary operations on a table: APPEND, DELETE, SEEK and so on; by the way, for SEEK we need an index file also.

Listing this table is quite simple:

USE CUSTOMER
WHILE .NOT. EOF()
   ? CUST_ID, CUST_NAME
   DBSKIP()
ENDDO

 If our table is sufficiently small, we can find a customer record without index and SEEK :

LOCATE FOR CUST_ID = “CC003”
? CUST_ID, CUST_NAME

If we want all our data will stand in memory and we could manage it more simple and quick way, we would use an array ( with some considerations about size of table; if it is too big, this method will be problematic ) :

aCustomer := {}    // Declare / define an empty array
USE CUSTOMER
WHILE .NOT. EOF()
   AADD(aCustomer, { CUST_ID, CUST_NAME } )
   DBSKIP()
ENDDO
Traversing this array is quite simple :

FOR nRecord := 1 TO LEN( aCustomer )

    ? aCustomer[ nRecord, 1 ], aCustomer[ nRecord, 2 ]
NEXT
or :

a1Record := {}

FOR EACH a1Record IN aCustomer
   ? a1Record[ 1 ], a1Record[ 2 ]
NEXT

And locating a specific record too:

nRecord := ASCAN( aCustomer, { | a1Record | a1Record[ 1 ] == “CC003” } )

? aCustomer[ nRecord, 1 ], aCustomer[ nRecord, 2 ]

A lot of array functions are ready to use for maintain this array : ADEL(), AADD(), AINS() etc …

Now, let’s see how we could use a hash for achieve this job :

hCustomer := { => } // Declare / define an empty hash

USE CUSTOMER
WHILE .NOT. EOF()
   hCustomer[ CUST_ID ] := CUST_NAME
   DBSKIP()
ENDDO
Let’s traversing :

h1Record := NIL

FOR EACH h1Record IN hCustomer
   ? h1Record: __ENUMKEY(),h1Record:__ENUMVALUE()
NEXT

Now, we have a bit complicate our job; a few field addition to the table :

No: Field Name Type Width  Dec Decription

1

 CUST_ID

C

 5

0

Id ( Code )

2

 CUST_NAME

C

10

0

Name

3

 CUST_SNAM

C

10

0

Surname

4

 CUST_FDAT

D

 8

0

First date

5

 CUST_ACTV

L

 1

0

Is active ?

6

 CUST_BLNCE

N

11

2

Balance

 While <key> part of an element of a hash may be C / D / N / L type; <xValue> part of hash too may be ANY type of data, exactly same as arrays.

So, we can make fields values other than first ( ID) elements of an array:

hCustomer := { => } // Declare / define an empty hash
USE CUSTOMER
WHILE .NOT. EOF()
   a1Data:= { CUST_NAME, CUST_SNAM, CUST_FDAT, CUST_ACTV, CUST_BLNCE }
   hCustomer[ CUST_ID ] := a1Data
   DBSKIP()
ENDDO
Let’s traversing :

h1Record := NIL

FOR EACH h1Record IN hCustomer
   a1Key  := h1Record:__ENUMKEY()
   a1Data := h1Record:__ENUMVALUE()
   ? a1Key
   AEVAL( a1Data, { | x1 | QQOUT( x1 ) } )
NEXT
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._
/*
Hash vs Tables
 
*/
#define NTrim( n ) LTRIM( STR( n ) )
#define cLMarj SPACE( 3 )
PROCEDURE Main()

  SET DATE GERM
  SET CENT ON
  SET COLO TO "W/B"
  SetMode( 40, 120 )
 
  CLS
 
  hCustomers := { => } // Declare / define an empty PRIVATE hash
 
  IF MakUseTable() 
 
     Table2Hash()
 
     * Here the hash hCustomers may be altered in any way
 
     ZAP
 
     Hash2Table()
 
  ELSE
      ? "Couldn't make / USE table"
  ENDIF
 
  ?
  @ MAXROW(), 0
  WAIT "EOF HashVsTable.prg"
 
RETURN // HashVsTable.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
PROCEDURE Table2Hash()
   hCustomers := { => } 
   WHILE .NOT. EOF()
     hCustomers[ CUST_ID ] := CUST_SNAM
     DBSKIP()
   ENDDO
 
   ListHash( hCustomers, "A hash transferred from a table (single value)" )
 
   hCustomers := { => } // Declare / define an empty hash
   DBGOTOP()
   WHILE .NOT. EOF()
      hCustomers[ CUST_ID ] := { CUST_NAME, CUST_SNAM, CUST_FDAT, CUST_ACTV, CUST_BLNCE }
      DBSKIP()
   ENDDO
 
   ListHash( hCustomers, "A hash transferred from a table (multiple values)" )
 
RETURN // Table2Hash()

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

PROCEDURE Hash2Table()
   LOCAL h1Record,;
         c1Key,;
         a1Record,;
         n1Field
 
   FOR EACH h1Record IN hCustomers
      c1Key := h1Record:__ENUMKEY()
      a1Record := h1Record:__ENUMVALUE()
      DBAPPEND()
      FIELDPUT( 1, c1Key )
      AEVAL( a1Record, { | x1, n1 | FIELDPUT( n1 + 1 , x1 ) } )
   NEXT h1Record
   DBGOTOP()
 
   ?
   ? "Data trasferred from hash to table :"
   ?
   WHILE ! EOF()
      ? STR( RECN(), 5), ''
      FOR n1Field := 1 TO FCOUNT()
         ?? FIELDGET( n1Field ), ''
      NEXT n1Field
      DBSKIP()
   ENDDO 
 
RETURN // Hash2Table()

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

PROCEDURE ListHash( hHash, cComment )
 
  LOCAL x1Pair
 
  cComment := IF( HB_ISNIL( cComment ), '', cComment )
 
  ? 
  ? cComment // , "-- Type :", VALTYPE( hHash ), "size:", LEN( hHash )
  ?
  IF HB_ISHASH( hHash ) 
     FOR EACH x1Pair IN hHash
        nIndex := x1Pair:__ENUMINDEX()
        x1Key := x1Pair:__ENUMKEY()
        x1Value := x1Pair:__ENUMVALUE()
        ? cLMarj, NTrim( nIndex ) 
*       ?? '', VALTYPE( x1Pair )
        ?? '', x1Key, "=>"
*       ?? '', VALTYPE( x1Key ) 
*       ?? VALTYPE( x1Value ) 
        IF HB_ISARRAY( x1Value ) 
           AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
        ELSE 
           ?? '', x1Value
        ENDIF 
     NEXT
  ELSE
    ? "Data type error; Expected hash, came", VALTYPE( hHash ) 
  ENDIF HB_ISHASH( hHash )
RETURN // ListHash()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

FUNCTION MakUseTable() // Make / USE table
 
 LOCAL cTablName := "CUSTOMER.DBF"
 LOCAL lRetval, aStru, aData, a1Record 
 
 IF FILE( cTablName ) 
    USE (cTablName)
 ELSE
    aStru := { { "CUST_ID", "C", 5, 0 },;
               { "CUST_NAME", "C", 10, 0 },;
               { "CUST_SNAM", "C", 10, 0 },;
               { "CUST_FDAT", "D", 8, 0 },;
               { "CUST_ACTV", "L", 1, 0 },;
               { "CUST_BLNCE", "N", 11, 2 } }
    * 
    * 5-th parameter of DBCREATE() is alias - 
    * if not given then WA is open without alias 
    *                              ^^^^^^^^^^^^^ 
    DBCREATE( cTablName, aStru, , .F., "CUSTOMER" ) 
 
    aData := { { "CC001", "Pierce", "Firth", 0d20120131, .T., 150.00 },; 
               { "CC002", "Stellan", "Taylor", 0d20050505, .T., 0.15 },;
               { "CC003", "Chris", "Cherry", 0d19950302, .F., 0 },;
               { "CC004", "Amanda", "Baranski", 0d20011112, .T., 12345.00 } }
 
    FOR EACH a1Record IN aData
        CUSTOMER->(DBAPPEND())
        AEVAL( a1Record, { | x1, nI1 | FIELDPUT( nI1, X1 ) } )
    NEXT a1Record 
    DBGOTOP()
 
 ENDIF 
 
 lRetval := ( ALIAS() == "CUSTOMER" )
 
RETURN lRetval // MakUseTable()

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._
 
HashVsTable

Hash Details – 2


( GET / SET Functions )

HB_HGET() : Returns a hash value

Syntax :   HB_HGET( <hsTable>, <Key> ) -> <Value>

HB_HPOS() : Locates the index of a key within a hash table

Syntax :      HB_HPOS( <hsTable>, <Key> ) -> nPosition

HB_HVALUEAT() : Gets/sets a hash value at a given position

Syntax :      HB_HVALUEAT( <hsTable>, <nPosition>, [<NewValue>] ) -> <Value>

HB_HVALUES() : Returns an array of the values of a hash table

Syntax :      HB_HVALUES( <hsTable> ) -> <aValues>

HB_HKEYAT() : Gets a hash table key at a given position

Syntax :      HB_HKEYAT( <hsTable>, <nPosition> ) -> <Key>

HB_HKEYS() : Returns an array of the keys of a hash table

Syntax :      HB_HKEYS( <hsTable> ) -> <aKeys>

HB_HPAIRAT() : Returns a two-dimensional array of a hash table entry key/value pair

Syntax :      HB_HPAIRAT( <hsTable>, <nPosition> ) -> <aKeyValue>

HB_HSCAN() : Scans a hash table for a value

Syntax :  HB_HSCAN( <hsTable>, <Value>, [<nStart>], [<nCount>, [<lExact>] ) -> nPosition

HB_HSET() : Sets a hash value

Syntax :      HB_HSET( <hsTable>, <Key>, <Value> ) -> <hsTable>

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

/*

Hash Details - 2

 ( GET / SET Funtions )

*/
#define xTrim( x ) IF( HB_ISNUMERIC( x ), LTRIM( STR( x ) ), ALLTRIM( x ) )
PROCEDURE Main()
   SetMode( 60, 120 )

   SET COLO TO "W/B"

   cLMarj := SPACE( 3 )

   CLS

   hFruits := { "apple" => "green",;
                "chery" => "pink",; 
                "apricot" => "yellow",;
                "orange" => "orange" }

   ListHash( hFruits, "Fruits" )

   * HB_HGET( <hsTable>, <Key> ) -> <Value>

   ? "Value found by literal of 'apple' : ",; 
      hFruits[ "apple" ] // green
   ? "Value found by function of 'apple' : ",; 
      HB_HGET( hFruits, "apple" ) // green
   ?

   * HB_HSET( <hsTable>, <Key>, <Value> ) -> <hsTable>
   hFruits[ "apple" ] := "red"
   ? "New (assigned literally) Value of 'apple' : ",; 
     hFruits[ "apple" ] // red

   HB_HSET( hFruits, "apple", "yellow" )
   ? "Newer (assigned by function) Value of 'apple' : ",; 
     hFruits[ "apple" ] // yellow
   ? 

   * HB_HPOS( <hsTable>, <Key> ) -> nPosition
   ? "Position of 'apple' : ", ;
     xTrim( HB_HPOS( hFruits, "apple" ) ) // 1
   * HB_HVALUEAT( <hsTable>, <nPosition>, [<NewValue>] ) -> <Value>
   ? "Position of 'melon' : ", ;
      xTrim( HB_HPOS( hFruits, "melon" ) ) // 0
   nPos := HB_HPOS( hFruits, "apricot" )
   ? "Position of 'apricot' : ",; 
     xTrim( HB_HPOS( hFruits, "apricot" ) ) // 4
   ?
   ? "Value of ", xTrim( nPos ) + ".th position :", ;
   HB_HVALUEAT( hFruits, nPos ) // yellow
   ?
   HB_HVALUEAT( hFruits, nPos, "fushia" ) 

   ? "Changed value in this position :",; 
   HB_HVALUEAT( hFruits, nPos ) // fushia
   ? 

   * HB_HKEYAT( <hsTable>, <nPosition> ) -> <Key>
   ? "Key in this position :" ,; 
     HB_HKEYAT( hFruits, nPos ) // apricot
   ?

   * HB_HKEYS( <hsTable> ) -> <aKeys>

   aKeys := HB_HKEYS( hFruits ) 

   ? "All keys in 'hFruits' : "
   AEVAL( aKeys, { | cKey | QQOUT( cKey, '' ) } ) 
                    // apple chery orange apricot
   ?

   * HB_HVALUES( <hsTable> ) -> <aValues>
   aValues := HB_HVALUES( hFruits ) 

   ? "All values in 'hFruits' : "
   AEVAL( aValues, { | cValue | QQOUT( cValue, '' ) } ) 
                     // green pink orange yellow
   ?

   * HB_HPAIRAT( <hsTable>, <nPosition> ) -> <aKeyValue>

   ? "All pairs in 'hFruits' : "

   Note : Compare this loop with below ListHash()

   FOR nPos := 1 TO LEN( hFruits )
      aKey_Value := HB_HPAIRAT( hFruits, nPos ) 
      ? xTrim( nPos ), aKey_Value[ 1 ], "=>", aKey_Value[ 2 ]
   NEXT
   ?

   * HB_HSCAN( <hsTable>, <Value>, [<nStart>], [<nCount>, [<lExact>] ) 
   *                                                     -> nPosition
   ? "Position found by scanning value 'pink' : ",;
   xTrim( HB_HSCAN( hFruits, "pink" ) ) // 2
   ?
   @ MAXROW(), 0
   WAIT "EOF HashDetails-2.prg"

RETURN // HashDetails-2.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
PROCEDURE ListHash( hHash, cComment )

 LOCAL x1Pair

   cComment := IF( HB_ISNIL( cComment ), '', cComment )

   ? cComment, '' 
   * ?? "-- Type :", VALTYPE( hHash ),'' 
   * ?? "size:", NTrim ( LEN( hHash ) ) 
   ?
   FOR EACH x1Pair IN hHash
      nIndex := x1Pair:__ENUMINDEX()
      x1Key := x1Pair:__ENUMKEY()
      x1Value := x1Pair:__ENUMVALUE()
      ? cLMarj, xTrim( nIndex ) 
      * ?? '', VALTYPE( x1Pair )
      ?? '', xTrim( x1Key ), "=>"
      * ?? '', VALTYPE( x1Key ) 
      * ?? VALTYPE( x1Value ) 
      IF HB_ISARRAY( x1Value ) 
        AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
      ELSE 
        ?? '', xTrim( x1Value )
      ENDIF 
   NEXT

   ? REPL( "~", 32 ) 

RETURN // ListHash()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
hashdets-2

Hash Details – 1


Some details of hash manipulations:

HB_HCOPY() function copies a hash to another.

Syntax :

   HB_HCOPY( <hsDestination>, <hsSource>, [<nStart>], [<nCount>] ) -> 
             <hsDestination>

 As noticed in syntax, copy operation may be limited by  [<nStart>], [<nCount>]  arguments.

 But a hash may NOT built by “COPY” method; because  <hsDestination> argument  isn’t optional.

   hFruits  := { "fruits" => { "apple", "cherry", "apricot" } }
   hFruits2 := HB_HCOPY( hFruits )   // Argument error !

Though it’s possible in two steps:

   hFruits2 := HB_HASH()   // Built first an empty hash
   hFruits2 := HB_HCOPY(hFruits2, hFruits ) // copy second onto first

As a result, for hash copy process two hashes should be exist:

   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } }
   hFruits := HB_HCOPY( hFruits, hDays )

or 

   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } }
   hTarget := HB_HASH()
   hTarget := HB_HCOPY( hTarget, hFruits )
   hTarget := HB_HCOPY( hTarget, hDays )

HB_HMERGE() function merge two hashes.

Syntax:

   HB_HMERGE( <hsDestination>, <hsSource>, <bBlock>|<nPosition> ) -> 
              <hsDestination>
   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } }
   hMerged := HB_HMERGE( hFruits, hDays )

hFruits :

    1 days => sunday monday

    2 fruits => apple cherry apricot

hTarget :

    1 days => sunday monday

    2 fruits => apple cherry apricot

AADD( hFruits[ "fruits" ], "melon" )

hFruits and hTarget :

  1 days => sunday monday

  2 fruits => apple cherry apricot melon

Result of above tests :

HB_HCOPY() and HB_HMERGE() doesn’t “physically” copy / merge hashes data; instead, copy / merge only by reference(s).

HB_HCLONE() function :  Cloning (exact copy of) hashes.

Syntax:

   HB_HCLONE( <hsTable> ) -> <hsDestination>
   hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
   hClone := HB_HCLONE( hFruits )

hClone : fruits => apple cherry apricot

   AADD( hFruits[ "fruits" ], "melon" )  // Source changed

    hClone : fruits => apple cherry apricot

*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._./*

Hash Details - 1

 Copy, Merge & Clone Hashes. 

*/
#define NTrim( x ) IF( HB_ISNUMERIC( x ), LTRIM( STR( x ) ), x )
PROCEDURE Main()
  SET COLO TO "W/B"

  cLMarj := SPACE( 3 )

  CLS
  ? "Copy one hash to another : HB_HCOPY() function : " 
  ? "Syntax :",;
  "HB_HCOPY( <hsDestination>, <hsSource>, [<nStart>]," 
  ? " [<nCount>] ) -> <hsDestination>"
  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hDays := { "days" => { "sunday", "monday" } } 

  * hFruits2 := HB_HCOPY( hFruits ) // Argument error !
  hFruits := HB_HCOPY( hFruits, hDays )
  ListHash( hFruits, "Copied-1 (Fruits)" )

  hDays[ "days" ] := "friday"
  ListHash( hFruits, "copied or referenced ?" )
  hTarget := HB_HASH()
  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hDays := { "days" => { "sunday", "monday" } } 

  hTarget := HB_HCOPY( hTarget, hFruits )
  hTarget := HB_HCOPY( hTarget, hDays )

  ListHash( hTarget, "Copied-2 ( Target )" )

  AADD( hFruits[ "fruits" ], "melon" )
  ListHash( hTarget, "copied or referenced ?" )

  ? "Merge two hashes : HB_HMERGE() function : " 
  ? "Syntax :",;
  "HB_HMERGE( <hsDestination>, <hsSource>, <bBlock>|"
  ? " <nPosition> ) -> <hsDestination>"

  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hDays := { "days" => { "sunday", "monday" } } 

  hMerged := HB_HMERGE( hFruits, hDays )

  ListHash( hFruits, "Merged (hFruits)" )
  ListHash( hMerged, "Merged (hTarget)" )
  AADD( hFruits[ "fruits" ], "melon" )
  ListHash( hFruits, "Merged or referenced ? ( hFruits) " )
  ListHash( hMerged, "Merged or referenced ? ( hMerged) " )

  * 
  * Result of above tests :
  * 
  * HB_HCOPY() and HB_HMERGE() doesn't "physically" copy / merge hashes data;
  * 
  * instead copy / merge only by reference(s).
  *
  ? "Cloning (exact copy of) hashes : HB_HCLONE() function : " 
  ? "Syntax :",;
  "HB_HCLONE( <hsTable> ) -> <hsDestination>"

  hFruits := { "fruits" => { "apple", "cherry", "apricot" } }
  hClone := HB_HCLONE( hFruits )
  ListHash( hClone, "Cloned" )
  AADD( hFruits[ "fruits" ], "melon" )
  ListHash( hClone, "Source changed" )

  ?
  @ MAXROW(), 0
  WAIT "EOF HashDetails-1.prg"

RETURN // HashDetails-1.Main()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

PROCEDURE ListHash( hHash, cComment )

  LOCAL x1Pair := NIL

  cComment := IF( HB_ISNIL( cComment ), '', cComment )

  ? cComment, '' 
  * ?? "-- Type :", VALTYPE( hHash ),'' 
  * ?? "size:", NTrim ( LEN( hHash ) ) 
  ?
  FOR EACH x1Pair IN hHash
     nIndex := x1Pair:__ENUMINDEX()
     x1Key := x1Pair:__ENUMKEY()
     x1Value := x1Pair:__ENUMVALUE()
     ? cLMarj, NTrim( nIndex ) 
     * ?? '', VALTYPE( x1Pair )
     ?? '', NTrim( x1Key ), "=>"
     * ?? '', VALTYPE( x1Key ) 
     * ?? VALTYPE( x1Value ) 
     IF HB_ISARRAY( x1Value ) 
        AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
     ELSE 
        ?? '', NTrim( x1Value )
     ENDIF 
  NEXT

  ? REPL( "~", 32 ) 

RETURN // ListHash()
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.

HashDets1