Skip to content

Instantly share code, notes, and snippets.

@kdorsel
Created May 7, 2021 13:29
Show Gist options
  • Save kdorsel/75617c638a8b35905b6691d8149481c6 to your computer and use it in GitHub Desktop.
Save kdorsel/75617c638a8b35905b6691d8149481c6 to your computer and use it in GitHub Desktop.
FB_Hash
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
<POU Name="FB_Hash" Id="{19e70039-197b-0b95-2330-4461be85b2a8}" SpecialFunc="None">
<Declaration><![CDATA[{attribute 'reflection'}
FUNCTION_BLOCK FB_Hash
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
_enDB : BOOL;
msg : FB_TcMessage;
dataSorted : ARRAY [1..HASHSIZE] OF POINTER TO ST_HashItem;
data : ARRAY [1..HASHSIZE] OF ST_HashItem;
size : UINT;
k : UINT;
// Keeps track of all the collisions of this FB
collisions : UDINT;
END_VAR
VAR CONSTANT
// To reduce collisions use a prime number as hash size
// 101, 251, 503, 1009, 2503, 5003, 10007
HASHSIZE : UINT := 251;
EMPTYITEM : ST_HashItem := ();
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[///////////////////////////////////////////////////////////////////////////////
// FB_Hash
//
// v1.0.0
//
// Can be used for counter variables
// Also made persistent by making the FB definition inside global persistent
///////////////////////////////////////////////////////////////////////////////
]]></ST>
</Implementation>
<Method Name="_get" Id="{fd7b5c8d-a8e6-0f1d-2715-ee5061e3b677}">
<Declaration><![CDATA[METHOD PROTECTED _get : UDINT
VAR_INPUT
key : STRING(255);
ignore : BOOL;
END_VAR
VAR
ii : UINT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
IF key = '' THEN
msg.CreateEx(TC_Events.HashEventClass.EmptyKey, 0);
msg.Send(0);
ELSE
key := F_ToLCase(key);
FOR ii := 1 TO HASHSIZE DO
k := hash(key, ii-1);
IF data[k].key = key THEN
_get := data[k].value;
EXIT;
ELSIF data[k].key = '' THEN
IF NOT ignore THEN
msg.CreateEx(TC_Events.HashEventClass.KeyError, 0);
msg.ipArguments.Clear().AddString(key);
msg.Send(0);
END_IF
EXIT;
// ELSE
// Collision, hash again
END_IF
END_FOR
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="_sort" Id="{d29a3cdc-6b0c-0a80-2b67-e10b3788124b}">
<Declaration><![CDATA[METHOD PROTECTED _sort : BOOL
VAR_INPUT
first : DINT;
last : DINT;
END_VAR
VAR
ii : DINT;
jj : DINT;
pivot : DINT;
temp : POINTER TO ST_HashItem;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
IF first < last THEN
pivot := first;
ii := first;
jj := last;
WHILE ii < jj DO
WHILE dataSorted[ii]^.key <= dataSorted[pivot]^.key AND_THEN ii < last DO
ii := ii + 1;
END_WHILE
WHILE dataSorted[jj]^.key > dataSorted[pivot]^.key DO
jj := jj - 1;
END_WHILE
IF ii < jj THEN
temp := dataSorted[ii];
dataSorted[ii] := dataSorted[jj];
dataSorted[jj] := temp;
END_IF
END_WHILE
temp := dataSorted[pivot];
dataSorted[pivot] := dataSorted[jj];
dataSorted[jj] := temp;
THIS^._sort(first, jj - 1);
THIS^._sort(jj + 1, last);
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="clear" Id="{00478c4a-2509-0945-02b3-b32e47985d41}">
<Declaration><![CDATA[METHOD clear : BOOL
VAR_INPUT
END_VAR
VAR
ii : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[
size := 0;
FOR ii := 1 TO HASHSIZE DO
dataSorted[ii] := ADR(EMPTYITEM);
data[ii].key := '';
data[ii].hash := 0;
data[ii].value := 0;
END_FOR
clear := TRUE;
]]></ST>
</Implementation>
</Method>
<Method Name="FB_init" Id="{2ad491ce-33ec-0a93-2215-14771b60ef7b}">
<Declaration><![CDATA[METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
enDB : BOOL;
END_VAR
VAR
ii : UINT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
_enDB := enDB;
FOR ii := 1 TO HASHSIZE DO
dataSorted[ii] := ADR(EMPTYITEM);
END_FOR
// Call release to help if the FB is made persistent
msg.Release();
]]></ST>
</Implementation>
</Method>
<Method Name="get" Id="{c93e1584-0ffb-04a6-301c-678e5bbc582f}">
<Declaration><![CDATA[METHOD get : UDINT
VAR_INPUT
key : STRING(255);
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
get := _get(key, FALSE);
]]></ST>
</Implementation>
</Method>
<Method Name="hash" Id="{cebc5374-33d9-0076-2122-7e9e0a18de97}">
<Declaration><![CDATA[METHOD PROTECTED hash : UINT
VAR_INPUT
key : STRING(255);
off : UINT;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
hash := F_DATA_TO_CRC16_CCITT(ADR(key), TO_UDINT(len(key)), off);
hash := hash MOD HASHSIZE;
]]></ST>
</Implementation>
</Method>
<Method Name="inc" Id="{eec7c809-359a-0238-0721-10093d099764}">
<Declaration><![CDATA[METHOD inc : UDINT
VAR_INPUT
key : STRING(255);
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
inc := _get(key, TRUE);
IF inc = 0 THEN
set(key, 1);
inc := 1;
ELSE
inc := data[k].value := data[k].value + 1;
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="remove" Id="{4d62016b-dd7e-05c7-0d06-99c4e533c052}">
<Declaration><![CDATA[METHOD remove : UDINT
VAR_INPUT
key : STRING(255);
END_VAR
VAR
ii : UINT;
END_VAR]]></Declaration>
<Implementation>
<ST><![CDATA[
remove := get(key);
IF remove <> 0 THEN
size := size - 1;
// Find where this key was stored in the sorted keys
FOR ii := 1 TO HASHSIZE DO
IF dataSorted[ii] <> 0 AND_THEN dataSorted[ii]^.key = key THEN
EXIT;
END_IF
END_FOR
// Fill the empty spot if needed
IF ii < HASHSIZE THEN
MEMCPY(ADR(dataSorted[ii]), ADR(dataSorted[ii+1]), SIZEOF(dataSorted[1]) * size);
END_IF
dataSorted[HASHSIZE] := ADR(EMPTYITEM);
// Reset the entry
data[k].key := '';
data[k].hash := 0;
data[k].value := 0;
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="reset" Id="{313d3096-4522-061a-1754-150b54087ec4}">
<Declaration><![CDATA[METHOD reset : UDINT
VAR_INPUT
key : STRING(255);
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
reset := get(key);
IF reset <> 0 THEN
{IF defined (variable: DB.Pool)}
IF _enDB THEN
DB.Pool.exec2(ADR(DB.qryCounter), TRUE, F_STRING(data[k].key), F_UDINT(data[k].value));
END_IF
{END_IF}
data[k].value := 0;
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="set" Id="{33b96d78-7423-0772-0df9-4a2fc258aaaa}">
<Declaration><![CDATA[METHOD set : BOOL
VAR_INPUT
key : STRING(255);
val : UDINT;
END_VAR
VAR
//msg : FB_TcMessage;
ii : UINT;
END_VAR
VAR_INST
//msg : FB_TcMessage;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
IF key = '' THEN
msg.CreateEx(TC_Events.HashEventClass.EmptyKey, 0);
msg.Send(0);
ELSE
key := F_ToLCase(key);
FOR ii := 1 TO HASHSIZE DO
k := hash(key, ii-1);
IF data[k].key = key THEN
data[k].value := val;
set := TRUE;
EXIT;
ELSIF data[k].key = '' THEN
data[k].key := key;
data[k].hash := k;
data[k].value := val;
size := size + 1;
IF size > HASHSIZE * 0.7 THEN
IF size = HASHSIZE THEN
msg.CreateEx(TC_EVENTS.HashEventClass.FillLimitError, 0);
ELSE
msg.CreateEx(TC_EVENTS.HashEventClass.FillLimitWarn, 0);
END_IF
msg.send(0);
END_IF
IF ii > 1 THEN
collisions := collisions + ii - 1;
msg.CreateEx(TC_EVENTS.HashEventClass.InsertCollision, 0);
msg.ipArguments.Clear().AddUInt(ii-1);
msg.send(0);
END_IF
set := TRUE;
EXIT;
// ELSE
// Collision
END_IF
END_FOR
END_IF
]]></ST>
</Implementation>
</Method>
<Method Name="sort" Id="{cdc9b76a-998a-0b4e-0cea-70aa0422368c}">
<Declaration><![CDATA[METHOD PROTECTED sort : BOOL
VAR_INPUT
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[
IF size > 1 THEN
sort := _sort(1, size - 1);
END_IF
]]></ST>
</Implementation>
</Method>
</POU>
</TcPlcObject>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment