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 Basics

Definition:

In general, a Hash Table, or Hash Array, or Associative array, or shortly Hash is an array- like data structure, to store some data with an associated key for each; so, ‘atom’ of a hash is a pair of a ‘key’ with a ‘value’. A hash system needs to perform at least three operations:

–      add a new pair,

–      access to value via key

–      the search and delete operations on a key pair

In Harbour, a hash is simply a special array, or more precisely a “keyed” array with special syntax with a set of functions.

Building:

The “=>” operator can be used to indicate literally the relation between <key> <value> pair: <key> => <value>

 We can define and initialize a hash by this “literal” way :

 hDigits_1 := { 1 => 1, 2  => 2, 3  => 3, 4  => 4 }

 or by a special function call:

 hDigits_1 := HB_HASH( 1, 1, 2, 2, 3, 3, 4, 4 )

 Using “add” method may be another way :

hDigits_1 := { => } // Build an empty hash
hDigits_1[ 1] := 1

hDigits_1[ 2] := 2

hDigits_1[ 3] := 3

hDigits_1[ 4] := 4

In this method while evaluating each of above assignments, if given key exits in hash, will be replaced its value; else add a new pair to the hash.

In addition, data can be added to a hash by extended “+=” operator:

   hCountries := { 'Argentina' => "Buenos Aires" }
   hCountries += { 'Brasil'    => "Brasilia" }
   hCountries += { 'Chile'     => "Santiago" }
   hCountries += { 'Mexico'    => "Mexico City" }

Hashs may add ( concatenate ) each other by extended “+” sign :

   hFruits := { "fruits" => { "apple", "chery", "apricot" } }
   hDays   := { "days"   => { "sunday", "monday" } } 
   hDoris := hFruits + hDays

Note:  This “+” and “+=” operators depends xHB lib and needs to xHB lib and xHB.ch.

Typing :

<key> part of a hash may be any legal scalar type : C, D, L, N; and <value> part may be in addition scalar types, any complex type ( array or hash ).

Correction : This definition is wrong ! The correct is :

<key> entry key; can be of type: number, date, datetime, string, pointer.

Corrected at : 2015.12.08; thanks to Marek.

hDigits_2 := {  1  => “One”,  2  => “Two”,  3  => “Three”,  4  => “Four” }

hDigits_3 := { "1" => "One", "2" => "Two", "3" => "Three", "4" => "Four" }
hDigits_4 := { "1" => "One",  2  => "Two",  3  => "Three", "4" => "Four" }
hDigits_5 := {  1  => "One",  1  => "Two",  3  => "Three",  4  => "Four"

All of these examples are legal. As a result, a pair record of a hash may be:

–      Numeric key, numeric value ( hDigits_1 )

–      Numeric key, character value ( hDigits_2 )

–      Character key, character value ( hDigits_3 )

–      Mixed type key ( hDigits_4 )

Duplicate keys (as seen in hDigits_5) is permitted to assign, but not give a result such as double keyed values: LEN( hDigits_5 ) is 3, not 4; because first pair replaced by second due to has same key.

Consider a table-like data 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

We can build a hash with this data :

  hCustomers := { "CC001" => "Pierce Firth",;
 "CC002" => "Stellan Taylor",;
 "CC003" => "Chris Cherry",;
 "CC004" => "Amanda Baranski" }

and list it:

   ?
   ? "Listing a hash :"
   ?
   h1Record := NIL
   FOR EACH h1Record IN hCustomers
      ? cLMarj, h1Record:__ENUMKEY(), h1Record:__ENUMVALUE()
   NEXT

 Accessing a specific record is easy :

 hCustomers[ "CC003" ] // Chris Cherry
*-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.-._.
/*
Hash Basics

*/
#include "xhb.ch"
#define NTrim( n ) LTRIM( STR( n ) )
PROCEDURE Main()
 SET DATE GERM
 SET CENT ON
 SET COLO TO "W/B"

 cLMarj := SPACE( 3 )

 CLS

 hDigits_1 := { => } // Build an empty hash

 hDigits_1[ 1 ] := 1
 hDigits_1[ 2 ] := 2
 hDigits_1[ 3 ] := 3
 hDigits_1[ 4 ] := 4

 ListHash( hDigits_1, "Digits_1" )

 hDigits_2 := HB_HASH( 1, 1, 2, 2, 3, 3, 4, 4 )

 ListHash( hDigits_2, "Digits_2" )

 hDigits_3 := { 1 => 1,;
 2 => 2,;
 3 => 3,;
 4 => 4 }
 ListHash( hDigits_3, "Digits_3" )

 hDigits_4 := { 1 => "One",;
 2 => "Two",;
 3 => "Three",;
 4 => "Four" }
ListHash( hDigits_4, "Digits_4" )

 hDigits_5 := { "1" => "One",;
 "2" => "Two",;
 "3" => "Three",;
 "4" => "Four" }
 ListHash( hDigits_5, "Digits_5" )

 hDigits_6 := { "1" => "One",;
 2 => "Two",;
 3 => "Three",;
 "4" => "Four" }
 ListHash( hDigits_6, "Digits_6" )

 hDigits_7 := { 1 => "One",;
 1 => "Two",; // This line replace to previous due to same key 
 3 => "Three",;
 4 => "Four" }
 ListHash( hDigits_7, "Digits_7" )

 * WAIT "EOF digits"

 hCustomers := { "CC001" => "Pierce Firth",;
 "CC002" => "Stellan Taylor",;
 "CC003" => "Chris Cherry",;
 "CC004" => "Amanda Baranski" }
 ListHash( hCustomers, "A hash defined and initialized literally" )
 ?
 ? "Hash value with a specific key (CC003) :", hCustomers[ "CC003" ] // Chris Cherry
 ?
 cKey := "CC003" 
 ?
 ? "Locating a specific record in an hash by key (", cKey, ":"
 ?
 c1Data := hCustomers[ cKey ]
 ? cLMarj, c1Data

 hCountries := { 'Argentina' => "Buenos Aires" }
 hCountries += { 'Brasil' => "Brasilia" }
 hCountries += { 'Chile' => "Santiago" }
 hCountries += { 'Mexico' => "Mexico City" }

 ListHash( hCountries, "A hash defined and initialized by adding with '+=' operator:" )

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

 hDoris := hFruits + hDays

 ListHash( hDoris, "A hash defined and initialized by concataned two hash with '+' operator:" )

 ?
 @ MAXROW(), 0
 WAIT "EOF HashBasics.prg"

RETURN // HashBasics.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 )
    ?? '', x1Key, "=>"
*   ?? '', VALTYPE( x1Key ) 
*   ?? VALTYPE( x1Value ) 
    IF HB_ISARRAY( x1Value ) 
       AEVAL( x1Value, { | x1 | QQOUT( '', x1 ) } )
    ELSE 
       ?? '', x1Value
    ENDIF 
 NEXT

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

HashBass