i have this problem: starting from an empty list (0 elements) i want check if an element is present or not present in this list. In case this record not is present in list then i add this record to list, otherwise update element in list.
I have tried writing this code:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Generics.Collections, System.Generics.Defaults;
type
TDBStats = record
Comb: Integer;
Freq: Integer;
end;
TDBStatsList = TList<TDBStats>;
procedure Add(ODBStats: TDBStatsList; const Item: TDBStats);
var
rItem: TDBStats;
begin
rItem := Item;
rItem.Freq := 1;
oDBStats.Add(rItem);
end;
procedure Update(ODBStats: TDBStatsList; const Item: TDBStats; const Index: Integer);
var
rItem: TDBStats;
begin
rItem := Item;
Inc(rItem.Freq);
oDBStats[Index] := rItem;
end;
var
oDBStats: TDBStatsList;
rDBStats: TDBStats;
myArr: array [0..4] of integer;
iIndex1: Integer;
begin
try
myArr[0] := 10;
myArr[1] := 20;
myArr[2] := 30;
myArr[3] := 40;
myArr[4] := 10;
oDBStats := TList<TDBStats>.Create;
try
for iIndex1 := 0 to 4 do
begin
rDBStats.Comb := myArr[iIndex1];
if oDBStats.Contains(rDBStats) then
Update(oDBStats, rDBStats, oDBStats.IndexOf(rDBStats))
else
Add(oDBStats, rDBStats);
end;
// Check List
for iIndex1 := 0 to Pred(oDBStats.Count) do
Writeln(oDBStats[iIndex1].Comb:3, oDBStats[iIndex1].Freq:10);
finally
oDBStats.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
and should return this result:
10 2
20 1
30 1
40 1
50 1
but return this result:
10 1
20 1
30 1
40 1
50 1
10 1
I have understood about problem: when i use oDBStats.Contains(rDBStats) it control if rDBStats element is contained in list; the first time not found it and add in list; but when it is added in list i update freq field to 1; so second time when i check again being rdbstats with freq = 0 not found it.
As i can solve this problem? I need to have a counter, where i get from input a "comb" and i want check if this "comb" is present in list, indipendetely from value of the other field of the record. In case i find "comb" in list, then i update, increasing freq field.
Thanks for help.
When you call Contains on a generic list, it looks if the given value is already inside the list. The value in your case is a record which consists of two fields. As you didn't specify a custom comparer, Delphi will use a default comparer which in case of a record does a binary compare. So only when two records are binary equal they will be treated as equal.
To make your example work you have to specify a custom comparer that compares only the comb field of the records. This is an example:
oDBStats := TList<TDBStats>.Create(TDelegatedComparer<TDBStats>.Create(
function(const Left, Right: TDBStats): Integer
begin
result := CompareValue(Left.comb, Right.comb);
end));
In addition you have an error in your update routine. Instead of incrementing the existing value, you are incrementing the undefined value of the item parameter. The change in the first line should make it work:
rItem := oDBStats[Index];
Inc(rItem.Freq);
oDBStats[Index] := rItem;
You have the wrong data structure since what you really need is a dictionary.
The fundamental problem with using a list is that you want to search on a subset of the stored record. But lists are not set up for that. Solve the problem by re-writing using TDictionary<Integer, Integer>.
I can recommend that you have a thorough read of the dictionary code example at the Embarcadero docwiki.
The key to the dictionary is what you call comb and the value is freq. To add an item you do this:
if Dict.TryGetValue(Comb, Freq) then
Dict[Comb] := Freq+1
else
Dict.Add(Comb, 1);
I'm assuming your dictionary is declared like this:
var
Dict: TDictionary<Integer, Integer>;
and created like this:
Dict := TDictionary<Integer, Integer>;
You can enumerate the dictionary with a simple for in loop.
var
Item: TPair<Integer, Integer>;
...
for Item in Dict do
Writeln(Item.Key:3, Item.Value:10);
Although be warned that the dictionary will enumerate in an odd order. You may wish to sort before printing.
If you wish to store more information associated with each entry in the dictionary then put the additional fields in a record.
type
TDictValue = record
Freq: Integer;
Field1: string;
Field2: TDateTime;
//etc.
end;
Then your dictionary becomes TDictionary<Integer, TDictValue>.
Related
Does anyone know how to make a distinct count in fastreport?
Example
I have the report:
Name sex
João m
João m
Maria f
In the normal count, the result would be 3, but I want one that takes only the number of rows that do not repeat the field name .
In this case, the result would be 2.
Can anyone help me? That's just an example.
I can not do a group by in SQL because I have several fields.
I'm not skilled in using FastReport but I've found this page on the FastReport's official forum.
I think you can change the example by adapting it to your scenario (Note that the syntax could require some adjustments).
Bands:
GroupHeader1 <Sex>
MasterData1 [Name, Sex, ...]
GroupFooter1 [GetDistinctCount]
Script (Only working with dataset sorted by the field to count):
var
LastValue : string;
DistinctCount : integer;
//create this event by double-clicking the event from the Object Inspector
procedure OnGroupHeader1.OnBeforePrint;
begin
if LastValue <> (<Datasetname."Sex">) then
Inc(DinstinctCount);
LastValue := <Datasetname."Sex">
end;
function GetDistinctCount: string;
begin
Result := IntToStr(DistinctCount);
end;
The base idea is that the DistinctCount variable is incremented each time the field value changes.
Script (Should works also with unsorted dataset):
var
FoundValues : array of string;
(* !!IMPORTANT!!
You need to initialize FoundValues array before to start counting: *)
SetLength(FoundValues, 0);
function IndexOf(AArray : array of string; const AValue : string) : integer;
begin
Result := 0;
while(Result < Length(AArray)) do
begin
if(AArray[Result] = AValue) then
Exit;
Inc(Result);
end;
Result := -1;
end;
//create this event by double-clicking the event from the Object Inspector
procedure OnGroupHeader1.OnBeforePrint;
begin
if(IndexOf(FoundValues, <Datasetname."Sex">) = -1) then
begin
SetLength(FoundValues, Length(FoundValues) + 1);
FoundValues[Length(FoundValues) - 1] := <Datasetname."Sex">;
end;
end;
function GetDistinctCount: string;
begin
Result := IntToStr(Length(FoundValues));
end;
The base idea is that each different value found is added to the FoundValues array.
You can do it in Firebird without GROUP BY as:
DECLARE #T TABLE (ID INT IDENTITY (1,1), Name NVARCHAR(25) , Sex CHAR(1));
INSERT INTO #T VALUES
('Sami','M'),
('Sami','M'),
('Maria','F');
SELECT DISTINCT Name , Sex FROM #T
You can also create a View , and then use it in your report.
If you really need to do that in FastReport, you have to use a GroupHeader and a GroupFooter to that.
How ?
You have to write your script in OnBeforePrint event.
procedure OnGroupHeader1.OnBeforePrint;
Create this one by double-clicking in the event in the object inspector.
I am creating an application which uses the AMB MyLaps decoder P3 Protocols.
I can't get my head around a way to sort the racers out based on laps and lap times. For example, the person in 1st has done 3 laps, the person in 2nd has done 2 laps. But then how do I order a situation where 2 people are on the same lap?
This is the record I'm using to hold the information:
type
TTimingRecord = record
position: integer;
transId: integer;
racerName: string;
kartNumber: integer;
lastPassingN: integer;
laps: integer;
lastRTCTime: TDateTime;
bestTimeMs: Extended;
lastTimeMs: Extended;
gapTimeMs: Extended;
splitTimeMs: Extended;
timestamp: TDateTime;
end;
A new record is created for each racer.
The code I'm currently using is:
procedure sortRacers();
var
Pos, Pos2: Integer;
Temp: TTimingRecord;
GapTime: Extended;
begin
for Pos := 0 to length(DriversRecord)-1 do
begin
for Pos2 := 0 to Length(DriversRecord)-2 do
begin
if(DriversRecord[Pos2].laps < DriversRecord[Pos2+1].laps)then
begin
Temp := DriversRecord[Pos2];
DriversRecord[Pos2] := DriversRecord[Pos2+1];
DriversRecord[Pos2+1] := Temp;
end
else if DriversRecord[Pos2].laps = DriversRecord[Pos2+1].laps then
begin
if DriversRecord[Pos2].lastRTCTime > DriversRecord[Pos2+1].lastRTCTime then
begin
Temp := DriversRecord[Pos2];
DriversRecord[Pos2] := DriversRecord[Pos2+1];
DriversRecord[Pos2+1] := Temp;
end;
end;
end;
end;
for pos := 1 to length(DriversRecord) -1 do //Gap Time
begin
if DriversRecord[Pos].laps = DriversRecord[0].laps then
begin
DriversRecord[Pos].gapTimeMs := DriversRecord[Pos].lastRTCTime - DriversRecord[0].lastRTCTime;
DriversRecord[Pos].splitTimeMs := DriversRecord[Pos].lastRTCTime - DriversRecord[Pos-1].lastRTCTime;
end;
end;
end;
But doesn't work too well :)
I'm assuming from your comment to the question, that you have decomposed the problem into sorting and comparing, and that you have got the sorting part covered. Which leaves order comparison.
You need a function that will perform a lexicographic order comparison based first on the number of laps completed, and secondly on the time since the start of this lap. Basically it will look like this:
function CompareRacers(const Left, Right: TTimingRecord): Integer;
begin
Result := CompareValue(Left.laps, Right.laps);
if Result=0 then
Result := CompareDateTime(Left.lastRTCTime, Right.lastRTCTime);
end;
You'll find CompareValue in Math and CompareDateTime in DateUtils.
What I'm not sure about is what the sense of the lastRTCTime values is. You may need to negate the result of the call to CompareDateTime to get the result you desire.
Result := -CompareDateTime(Left.lastRTCTime, Right.lastRTCTime);
Also, what happens if there is overtaking during the lap? Presumably you won't be able to detect that until the racers complete the current lap.
Instead of doing the sort algorithm yourself, try this technique (if you have a Delphi version compatible) : Best way to sort an array
And your function could look like this :
uses Types;
function CustomSort(const Left, Right: TTimingRecord): Integer
begin
If (left.laps > right.laps) then
result := GreaterThanValue
else
if (left.laps < right.laps) then
result := LessThanValue
else
begin
// Same laps count... Test on LastRTCTime
if (left.lastRTCTime < right.lastRTCTime) then
result := GreaterThanValue1
else
if (left.lastRTCTime > right.lastRTCTime) then
result := LessThanValue
else
result := EqualsValue;
end;
end));
It might be easier to look at this as 2 separate sorts.
Obviously you know the bubble-sort method, so I will not go into that.
Make 2 passes on your sorting.
1st, you sort the laps.
2nd, you run through the entire list of sorted laps. find begin point and end point in array for identical lap-values. Do the sorting again from begin and end points, but this time compare only the secondary value. iterate through all identical secondary values if the count of identical values are larger than 1.
This code is about sorting data using an Index. Way faster than bubble-sort.
It is dynamic and provides for ability to sort from a start-point to an end-point in an array.
The code itself is bigger than Bubble-Sort, but not many algorithms can compare on speed.
The code (when understanding how it works) can easily be modified to suit most kinds of sorting. On an array of 65536 strings, it only need to do 17 compares (or there about)
Some more CPU Cycles per compare cycle compared with Bubble Sort, but still among the fastest methods.
To search is equally as fast as BTREE. The actual sorting is perhaps slower, but the data is easier manageable afterwards with no real need for balancing the tree.
Enjoy.
Note: The routine is not the full solution to the actual problem, but it provides the beginning of an extreemely fast approach.
TYPE
DynamicIntegerArray = ARRAY OF INTEGER;
DynamicStringArray = ARRAY OF STRING;
VAR
BinSortLo, BinSortMid, BinSortHi : INTEGER;
FUNCTION FindMid:INTEGER;
BEGIN
FindMid:=BinSortLo+((BinSortHi-BinSortLo) DIV 2);
END;
PROCEDURE ShiftIndexUpAndStorePointer(VAR ArrParamIndex: DynamicIntegerArray; HighBound:INTEGER);
VAR
x : INTEGER;
BEGIN
FOR x:=HighBound-1 DOWNTO BinSortMid DO ArrParamIndex[x+1] := ArrParamIndex[x];// Shift the index.
ArrParamIndex[BinSortMid]:=HighBound;// Store the pointer to index at its sorted place
END;
PROCEDURE BinarySortUp(CONST ArrParam:DynamicStringArray; VAR ArrParamIndex: DynamicIntegerArray; CONST LoBound,HighBound:INTEGER); OVERLOAD;
VAR
TempVar : STRING;
BEGIN
BinSortLo:=LoBound; BinSortHi:=HighBound; BinSortMid:=FindMid;
TempVar := ArrParam[HighBound];
REPEAT
IF TempVar>ArrParam[ArrParamIndex[BinSortMid]] THEN BinSortLo:=BinSortMid ELSE BinSortHi:=BinSortMid;
BinSortMid:=FindMid;
UNTIL (BinSortMid=BinSortLo); {OR (BinSortMid=BinSortHi);}
IF TempVar>ArrParam[ArrParamIndex[BinSortMid]] THEN INC(BinSortMid);// We always need a last check just in case.
ShiftIndexUpAndStorePointer(ArrParamIndex,HighBound);
END;
PROCEDURE DynamicCreateIndex(CONST ArrParam:DynamicStringArray; VAR ArrParamIndex: DynamicIntegerArray; CONST LoBound,HighBound:INTEGER);
VAR
x : INTEGER;
BEGIN
FOR x:=LoBound TO HighBound DO
BinarySortUp(ArrParam,ArrParamIndex,LoBound,x);
END;
BEGIN
{
1. Create your STRING ARRAY as a DynamicStringArray.
2. Create your INDEX ARRAY as a DynamicIntegerArray.
3. Set the size of these arrays to any INTEGER size and fill the strings with data.
4. Run a call to DynamicCreateIndex(YourStringArray,YourIndexArray,0,SizeOfArray
Now you have a sorted Index of all the strings.
}
END.
i have this problem: as i can add list to one list? i have tried so, but main list return always one list, and not understand where i mistake.
The structure is this:
PCombArray = array of Integer;
PStatsRecord = record
Comb: PCombArray;
Freq: Integer;
end;
PStatsList = TList<PStatsRecord>;
TStatsList = TList<PStatsList>;
Where Comb is a field that work as primary key. But here all ok. i define main list as:
var
MainList: TStatsList;
MySubList: PStatsList;
and create it, without problem. A procedure work for populate a subList; for esample i call this procedure as MakeSubList and assign a MySubList the list maked, then i add it to main list:
MainList := TList<PStatsList>.Create;
try
MainList.Clear;
for index := 1 to N do // generate N subList
begin
...
MySubList := MakeSubList(index); // contain correct sub list, no problem here
...
MainList.Add(MySubList); // add mysublist to mainlist
end;
writeln (mainList.count); // return 1, and not N-1 sublist
finally
MainList.Free;
end;
Thank who help me to understand so i can solve it. Thanks again.
Your make sub list function might be wrong, or you might have confused yourself with your naming conventions which did not follow in any way the usual Delphi/Pascal naming conventions for types.
The following code works, though.
Note that a TList is an Object so if you want to make a List of TList be sure to use TObjectList instead of plain TList unless you like memory leaks. Your inner list is around a record type and does not need to be TObjectList, but if you changed StatsRecord to TStatsData as a TObject (class) type, you should also change to TObjectList.
unit aUnit5;
interface
uses Generics.Collections;
procedure Test;
implementation
type
CombArray = array of Integer;
StatsRecord = record
Comb: CombArray;
Freq: Integer;
end;
TStatsList2 = TList<StatsRecord>;
TStatsList = TObjectList<TStatsList2>;
var
MainList: TStatsList;
MySubList: TStatsList2;
index:Integer;
procedure Test;
begin
MainList := TStatsList.Create;
try
for index := 1 to 10 do // generate N subList
begin
MySubList := TStatsList2.Create;
MainList.Add(MySubList); // add mysublist to mainlist
end;
finally
MainList.Free;
end;
end;
end.
MainList is a list of PStatsList, so you're certainly allowed to add instances of PStatsList to it. If you weren't allowed, your code would not have compiled or run at all.
If the loop runs N times, then MainList.Add will be called N times, and MainList.Count will be N. So, if WriteLn(MainList.Count) prints 1, then we can only conclude that N = 1.
TList allows duplicates, so even if MakeSubList is returning the same object each time, it can still get added to the main list multiple times.
..
implementation
type
S64 = string[64];
art_ptr = ^art_rec;
art_rec = record
sDes: S64;
rCost: real;
iQty: integer;
end;
art_file = file of art_rec;
var
lCatalog: TList;
procedure disp_all;
var
lArt: TList;
pA: art_ptr;
c,
a: integer;
begin
for c:= 0 to lCatalog.Count -1 do begin
lArt:= lCatalog[c];
for a:= 0 to lArt.Count -1 do begin
pA:= lArt[a];
Dispose(pA);
end;
lArt.Clear;
lArt.Free;
end;
lCatalog.Clear;
end;// disp_all
procedure TfrmMain.FormCreate(Sender: TObject);
begin
lCatalog:= TList.Create;
end;// FormCreate
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
disp_all;
lCatalog.Free;
end;// FormDestroy
// a normal BitButton click that adds 3 records to each art list (2)
procedure TfrmMain.bbMCreate_Click(Sender: TObject);
const
nArt = 2;
nRec = 3;
var
pA: art_ptr;
lArt: TList;
c: integer;
begin
// clears any previous added pointer record to art list that was added to the catalog
disp_all;
// creates art lists and add 'em to the catalog list
for c:= 0 to nArt -1 do begin
lArt:= TList.Create;
lCatalog.Add(lArt);
// creates records and add 'em to the art list
for a:= 0 to nArt -1 do begin
pA:= New(art_ptr);
with pA^ do begin
sDes:= Format('cat%d-art%d', [c, a]);
rCost:= (c+1) * (a +1) *1.0;
iQty:= (c+1) *10 + (a +1);
end;
lArt.Add(pA);
end;
end;
end;// bbMCreate_Click
// a normal BitButton click that reads 1 record from 1 art list
procedure TfrmMain.bbMRead_Click(Sender: TObject);
const
c = 1;
a = 2;
var
pA: art_ptr;
lArt: TList;
begin
// gets art list #2 from catalog (index is 0 based. c=1)
lArt:= lCatalog[c];
// gets record #3 from art list (index is 0 based. a=2)
pA:= lArt[a];
// displays the record in a string grid called sgArt... at row a+1
with sgArt, pA^ do begin
// col 0 contains cat index and art index, can be useful...
cells[0,a+1]:= Format('\%d\%d', [c,a]);
cells[1,a+1]:= sDes;
cells[2,a+1]:= FormatFloat('0.00', rCost);
cells[3,a+1]:= IntToStr(iQty);
end;
end;// bbMRead_Click
i have java-code filling a hashmap from a textfile.
HashMap<String, String[]> data = new HashMap<String, String[]>();
i use this to make key-value-pairs. the values are an array of string. i have to iterate over every possible combo of the key-value-pairs (so also have to iterate over the String[]-array). This works with java but now i have to port this to delphi. is it possible to do so? and how?
thanks!
In Delphi 2009 and higher, you can use TDictionary<string, TStringlist> using Generics.Collections.
In older versions, you can use TStringlist where every item in the TStringlist has an associated object value of type TStrings.
The Docwiki has a page to get started with TDictionary
If you have an older version of Delphi (Delphi 6 and up), you could also use a dynamic array of record, then our TDynArray or TDynArrayHashed wrappers to create a dictionary with one field of the dynamic array records. See this unit.
The TDynArrayHashed wrapper was developed to be fast.
Here is some sample code (from supplied unitary tests):
var ACities: TDynArrayHashed;
Cities: TCityDynArray;
CitiesCount: integer;
City: TCity;
added: boolean;
N: string;
i,j: integer;
const CITIES_MAX=200000;
begin
// valide generic-like features
// see http://docwiki.embarcadero.com/CodeExamples/en/Generics_Collections_TDictionary_(Delphi)
ACities.Init(TypeInfo(TCityDynArray),Cities,nil,nil,nil,#CitiesCount);
(...)
Check(ACities.FindHashed(City)>=0);
for i := 1 to 2000 do begin
City.Name := IntToStr(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.FindHashedAndUpdate(City,true)=i+2,'multiple ReHash');
Check(ACities.FindHashed(City)=i+2);
end;
ACities.Capacity := CITIES_MAX+3; // make it as fast as possible
for i := 2001 to CITIES_MAX do begin
City.Name := IntToStr(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.FindHashedAndUpdate(City,true)=i+2,'use Capacity: no ReHash');
Check(ACities.FindHashed(City.Name)=i+2);
end;
for i := 1 to CITIES_MAX do begin
N := IntToStr(i);
j := ACities.FindHashed(N);
Check(j=i+2,'hashing with string not City.Name');
Check(Cities[j].Name=N);
CheckSame(Cities[j].Latitude,i*3.14);
CheckSame(Cities[j].Longitude,i*6.13);
end;
end;
So for your problem:
type
TMyMap = record
Key: string;
Value: array of string;
end;
TMyMapDynArray = array of TMyMap;
var
Map: TMyMap;
Maps: TMyMapDynArray;
MapW: TDynArrayHashed;
key: string;
i: integer;
begin
MapW.Init(TypeInfo(TMyMapDynArray),Maps);
Map.Key := 'Some key';
SetLength(Map.Value,2);
Map.Value[0] := 'One';
Map.Value[1] := 'Two';
MapW.FindHashedAndUpdate(Map,true); // ,true for adding the Map content
key := 'Some key';
i := MapW.FindHashed(key);
// now i=0 and Maps[i].Key=key
for i := 0 to MapW.Count-1 do // or for i := 0 to high(Maps) do
with Maps[i] do
// now you're enumerating all key/value pairs
end;
Since Delphi 6, the set of predefined container classes includes TBucketList and TObjectBucketList. These two lists are associative, which means they have a key and an actual entry. The key is used to identify the items and search for them. To add an item, you call the Add method with two parameters: the key and the data. When you use the Find method, you pass the key and retrieve the data. The same effect is achieved by using the Data array property, passing the key as parameter.
const
states : array [0..49,0..1] of string =
(
('Alabama','AL'),
('Montana','MT'),
('Alaska','AK'),
('Nebraska','NE'),
('Arizona','AZ'),
('Nevada','NV'),
('Arkansas','AR'),
('New Hampshire','NH'),
('California','CA'),
('New Jersey','NJ'),
('Colorado','CO'),
('New Mexico','NM'),
('Connecticut','CT'),
('New York','NY'),
('Delaware','DE'),
('North Carolina','NC'),
('Florida','FL'),
('North Dakota','ND'),
('Georgia','GA'),
('Ohio','OH'),
('Hawaii','HI'),
('Oklahoma','OK'),
('Idaho','ID'),
('Oregon','OR'),
('Illinois','IL'),
('Pennsylvania','PA'),
('Indiana','IN'),
('Rhode Island','RI'),
('Iowa','IA'),
('South Carolin','SC'),
('Kansas','KS'),
('South Dakota','SD'),
('Kentucky','KY'),
('Tennessee','TN'),
('Louisiana','LA'),
('Texas','TX'),
('Maine','ME'),
('Utah','UT'),
('Maryland','MD'),
('Vermont','VT'),
('Massachusetts','MA'),
('Virginia','VA'),
('Michigan','MI'),
('Washington','WA'),
('Minnesota','MN'),
('West Virginia','WV'),
('Mississippi','MS'),
('Wisconsin','WI'),
('Missouri','MO'),
('Wyoming','WY')
);
function getabb(state:string):string;
var
I:integer;
begin
for I := 0 to length(states) -1 do
if lowercase(state) = lowercase(states[I,0]) then
begin
result:= states[I,1];
end;
end;
function getstate(state:string):string;
var
I:integer;
begin
for I := 0 to length(states) -1 do
if lowercase(state) = lowercase(states[I,1]) then
begin
result:= states[I,0];
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
edit1.Text:=getabb(edit1.Text);
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
edit1.Text:=getstate(edit1.Text);
end;
end.
Is there a bette way to do this?
Should this kind of data be hard coded?
Wouldn't it be better to use something like a XML file or even just a CSV.
Or Name Value Pairs, i.e. IA=Iowa
then loaded into a TStringList to get
States.Values['IA'] = 'Iowa';
Then you just need to write something to search the Values to work backwards like
//***Untested***
//Use: NameOfValue(States, 'Iowa') = 'IA'
function NameOfValue(const strings: TStrings; const Value: string): string;
var
i : integer;
P: Integer;
S: string;
begin
for i := 0 to strings.count - 1 do
begin
S := strings.ValueFromIndex[i];
P := AnsiPos(strings.NameValueSeparator, S);
if (P <> 0) and (AnsiCompareText(Copy(S, 1, P - 1), Value) = 0) then
begin
Result := strings.Names[i];
Exit;
end;
end;
Result := '';
end;
I'm fairly sure its case insensitive too
If you're on D2009 or D2010, use a TDictionary<string, string> from Generics.Collections. Declare the array of constants like you have it, then set up your dictionary by putting each pair in to the dictionary. Then just use the dictionary's default property to do your lookups.
Notice that lowercase(a) = lowercase(b) is slower than sameText(a, b).
In addition, you can speed up the procedure further by storing the strings in the array as lower-case only, and then in the look-up routine start with converting the input to lower-case as well. Then you can use the even faster function sameStr(a, b). But of course, when a match is found, you then need to format it by capitalizing the initial letters. This speed-up approach is probably not very important for such a small list of strings. After all, there are not too many states in the US.
Also, you should declare the functions using const arguments, i.e. write
function getabb(const state:string):string;
instead of
function getabb(state:string):string;
(unless you want to change state in the routine).
Finally, you could make the code more compact and readable by omitting the begin and end of the for loops.
I would have your lists sorted. That way you can use a binary search to cut the lookup times down. It all depends on the number of iterations you will be exercising. Around 50 items doesn't seem like much, until your iterating over the list a few thousand times looking for the last item in the list.
Also you should ALWAYS bail from your loops as soon as you get get a match if you know the rest of the list will not match.
Arrays are fine, and depending on how your using the data, you might need to add some of the "territories" that also have abbreviations (PR = PUERTO RICO, GU = GUAM, etc.).