How to get last string in TStringList - string

I've been searching for days on how to do this, and nothing is exactly what I need to do (or I just don't understand how to implement the solution).
What I need to do is parse a string, which is a street address, into a TStringList, and then set those strings from the list to variables I can then pass to the rest of the program. I already have the list working ok:
var
AddressList : TStringList;
i : integer;
begin
AddressList := TStringList.Create;
AddressList.Delimiter := ' ';
AddressList.DelimitedText := RawAddressStr; //RawAddressStr is parsed from another file and the string result is something like '1234 Dark Souls Lane Wyoming, MI 48419'
for i := 0 to AddressList.Count-1 do
AddressList.Strings[i]; //Not sure what to do here
The issue is that the address isn't always the same length. The address will sometimes be like '1234 Dark Souls Two Lane Wyoming...' or '1234 Dark Lane Wyoming...'
So I need to be able to use this TStringList to set the Zip, State and City into variables for later use. I would use TStringList.Find, but the ZIP code isn't always the same. Is there a way to get the last string and then go backwards from there? (Going backwards because once I get the City, State ZIP I can remove that from the RawAddressStr and then set the rest to the address string.)
Thanks.
Edit, here's the code I needed (thanks to below comments):
AddressList := TStringList.Create;
AddressList.Delimiter := ' ';
AddressList.DelimitedText := RawAddressStr;
for i := 0 to AddressList.Count-1 do
LastIndex := AddressList.Count - 1;
ZipStr := AddressList[LastIndex];
StateStr := AddressList[LastIndex - 1];
CityStr := AddressList[LastIndex - 2];
Now I can use these with StringReplace to take out the City, State Zip from the full address string, and set that as the Address string to use.

I am a little bit unsure of exactly what you're looking for since you ask
of how to get the last string of your StringList,
then at the end of the question you ask
Is there a way to get the last string and then go backwards from
there?
If you want to get the last string of a StringList you can use
var AddressList : TStringList;
MyString: string;
begin
MyString := AddressList.Last; //this...
MyString := AddressList.Strings[AddressList.Count-1]; //...and this is essentially the same thing
end;
if you would like to for-loop in reverse or backwards you should write:
for i := AddressList.Count-1 downto 0 do
AddressList.Strings[i]; //Not sure what to do here
Notice that it says "DOWNTO" and not "TO".
Now, if you would like to stop at a specific string, lets say the ZIP code,
you need to make your software understand what it is reading.
Which one of the delimited strings is the City?
Which one is the address?
To the software, a string is a string, it doesn't know, or even care what it is reading.
So I would like to suggest that you have a database of citys which it can compare the strings
of AddressList t,o and see if there is a match.
You could also implement some logic in to your algorithm.
If you know that the last string of your delimited AdressList string always is the City-name,
then you know you have the city name right there which you can use.
If you know that everything between the ZIP code and the City Name is the Street Address,
then just copy everything between the ZIP and the City-name and use that as a Street-name information.

Related

Find a variable string in another string using Delphi

Given a field/string like 'G,H,1,AA,T,AAA,1,E,A,H,....'. The characters can be in any combination/order.
How do I search that string and return True when searching for just 'A' or 'AA'?
i.e. If doing a search for say 'A', it should only find the 'A' between the E & H.
Regards & TIA,
Ian
You can simply split your string into an array by your delimiter and search in that array, e.g.
function FindItem(const List, Item: string): Boolean;
var
SArr: TArray<string>;
S: string;
begin
Result := False;
//Separators could also be a parameter
SArr := List.Split([',']);
for S in SArr do
begin
//use S.Trim if needed
//use AnsiSameText(S, Item) for case insensitive check
if Item = S then
Exit(True);
end;
end;
If you need to search for multiple items in your data, you might want to sort the array and use a binary search.
TArray.Sort<string>(SArr);
Result := TArray.BinarySearch(SArr, Item, Tmp);
Another approach would be using regular expressions with word boundaries to search for whole words only
Result := TRegex.IsMatch(List, '\bA\b');
Split this string into a list, for example with TStringList.CommaText (alternatively, into an array with StrUtils.SplitString()).
Then, just walk through the list and check every string (or use TStrings.IndexOf() - note: it uses CaseSensitive property, as Remy mentioned in comments).
If you are going to make many queries for the same list - sort it and use an effective binary search (TStringList.Find()).

Exercice with type String in Pascal

(Translated from German to Englisch)
I need help in this exercise :
Thread: String processing The user can make simple changes to an input sentence.
conditions
The program displays a menu for the user to select the following action. This is also displayed again after the action has been completed until the user terminates the program (a loop is therefore required).
The menu contains the following items, which should be executed when the specified letter is entered:
A. Enter the sentence
B. Determine the number of words
C. Determine the number of characters that are less than their sequence character
D. Replace all the words in the sentence with their uppercase initials
X. end
If the user enters a different letter, nothing happens or the menu is output again.
If the menu item A is selected, a prompt is issued to enter a set which is read into a string variable. This variable can not be changed by the actions of menu items B, C and D! Possibly. A copy of the set has to be prepared beforehand in another string variable.
In menu point B the number of all words in the block is to be counted. For simplicity, you can assume that there is always one space between two words. At the beginning and end of the sentence there are no spaces. The number of words is output after the calculation (e.g., "The set is 4 words").
If the user executes menu item C, the set is traversed character-by-character, and for each character it is checked whether it is smaller than its trailing character. Here is a simple character comparison (you can also write directly something like '1' <'d'). The number of characters so found is then output (e.g., "13 characters found in the sentence less than the trailing character").
In menu item D, the sentence is traversed and every word contained in it is replaced by its upper-case initial character. The capitalization is of course only made if the first character is a letter, otherwise the character remains unchanged. You can assume that the sentence never starts or ends with a space. Between two words there is always exactly one space and so it should be between the initial letters. For example, from "123 good mood" becomes "1 G L".
It is not permissible here to build up a completely new string piece by piece! Instead, you should work in a loop on a copy of the original sentence with pos, copy, length, delete and insert! It is also forbidden to "gather" the initial characters all at the beginning or end of the string; These should be inserted directly into the string at the position of the corresponding word!
Furthermore, a string can not be accessed at menu point D, because the work with string routines is to be practised explicitly here. Menu items B, C and D may only be selectable if a record has already been entered. Otherwise nothing happens or a fault message is entered when entering B, C or D in the menu and the menu is output again.
Each call to the menu items B, C or D will always work on the original set entered by the user and not on a set that has already been altered by previously executed menu items!
By entering the menu item A again, the entered block can be overwritten by a new one.
With an 'X' the user can terminate the program.
Use wherever it is the predefined string functions and do not write it yourself with difficulty loops, etc.! However, the use of the strreplace or reverseString functions is forbidden!
Here's my work till now, I only have problems with part D:
program Project2;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
const
lz = ' ';
var
Satz: string;
Buchstabe: char;
i, p, j, zaehler2, index, count: integer;
writeln('A) Write Sentence');
readln(Satz);
'D':
begin
index := 2;
insert(lz, Satz, length(Satz)+1);
count := (pos(lz,Satz));
repeat
delete(Satz, index,(count - index));
index := index + 2;
count := pos(lz,copy(Satz,index,(length(Satz)-index)))+index-1;
until ;
writeln(uppercase(Satz));
end
I'm glad you've found your own solution, well done!
While you've been doing that, I've been writing the answer below and, as I
have finished it, I thought I'd post it here to show you another way
to go about the problem of extracting words from a string. There are
dozens of ways of doing that task, but I hope the one I've used is fairly
easy to follow.
Maybe the reason you were having a problem with this is that your string-indexing
expressions are a bit too complicated. I bet if you come back to your code in 6 months
it will take you a while to figure out what it is supposed to be doing and longer
to tell whether it is actually doing it. The key to avoiding problems like that
is to break your code up into chucks which are easier to follow and easier
to test. So instead of just telling you what your repeat condition should be,
I'll show you a way which is easier to follow.
The first thing to do is to extract a singe word from the input. So, first thing
I've written is a function, ExtractFirstWord which returns the first word in
the input string, whether or not the input includes spaces, and also returns
a Remainder string which is what is left or the input string after the first
word (and any spaces immediately following it have been removed). This is done
using some simple while loops which are coded to skip over the leading spaces
and then build a string from the non-space characters which follow.
Code
const
lz = ' ';
var
Satz: string;
FirstWord : String;
Remainder : String;
function ExtractFirstWord(const InputStr : String; var Remainder : String) : String;
var
P : Integer;
WordStart : Integer;
begin
Result := '';
P := 1;
// The following skips over any spaces at the start of InputStr
while (P <= Length(InputStr)) and (InputStr[P] = lz) do
Inc(P);
// Now we know where the first word starts
WordStart := P;
// Now we can get the first word, if there is one
while (P <= Length(InputStr)) and (InputStr[P] <> lz) do
Inc(P);
Result := Copy(InputStr, WordStart, P - WordStart);
Remainder := Copy(InputStr, P, Length(InputStr));
// the following is one way to remove spaces at the start of Remainder
while (Length(Remainder) > 0) and (Remainder[1] = lz) do
Delete(Remainder, 1, Length(lz));
// instead you could do something simlar to the first `while` loop above
end;
begin
Satz := ' cat dog ';
repeat
FirstWord := ExtractFirstWord(Satz, Remainder);
FirstWord := UpperCase(FirstWord);
Satz := Remainder;
writeln('First word: ', FirstWord, ' remainder: ', Remainder);
until Remainder = '';
readln;
end.
This particular way of doing it is not an ideal fit with the other requirements
specified in your task but should be easily adaptable to them. E.g, the upper-casing of words could be done "in place" on the input string by upper-casing the current character of it in the second While loop.
Btw, if you are using Delphi or Free Pascal/Lazarus, there is a much simpler
way of extracting the words from a string. It uses a TStringList. Try
looking it up in the online help and have a thing about how you might use it
to do the task.

Why does ShowMessage give unexpected output when my string contains #0 characters?

Today I stumbled upon something mysterious. This line of code:
showmessage(menuMain.player[2] + ' ready!');
Generates this message (For example menuMain.player[2] = Player):
Player
But if I put code this way (For example menuMain.player[2] = Player):
showmessage('Test: ' + menuMain.player[2]);
It will generate this message:
Test: Player
According to the debugger, the exact value of the string (buffer := menuMain.player[2] + ' ready!') is this:
'Player'#0#0#0#0#0#0#0#0' ready!'
I do honestly believe this is a compiler glitch, because I have the exact same line in another block of code, and it works flawlessly.
Now the tough part for me, is that me being dumb, or this is indeed a glitch?
the exact value of buffer string (buffer = menuMain.player[2] + ' ready!'): 'Player'#0#0#0#0#0#0#0#0' ready!'
The problem is the embedded null characters (the #0). Those characters act as string terminators, which means the string stops being processed at that point for most WinAPI functions, including those that output or paint text. When the first #0 is found, the string is considered to have ended.
You can test this easily enough yourself, with code something like this:
var
TestStr: string;
begin
TestStr := 'This is a test';
ShowMessage(TestStr); // Outputs This is a test
TestStr[5] := #0;
ShowMessage(TestStr); // Outputs This
end;
The remaining question, of course, is how did you end up with those embedded nulls in the first place? Since you've not posted the code that populates menuMain.player, it's impossible to say, but that's that's the area you need to inspect, because string array elements in Delphi do not contain nulls on their own. You can also check that yourself:
var
TestArr: array[1..2] of string;
begin
TestArr[1] := 'Player one';
TestArr[2] := 'Player two';
ShowMessage(TestArr[1] + ' defeated ' + TestArr[2]);
end;
So the answer to your question
Now the tough part for me, is that me being dumb, or this is indeed a glitch?
It's a glitch, but the glitch is somewhere in your code. It's not a glitch in Delphi or its strings.

inno setup - Delete arbitrary substring from a string

I need to delete a substring which is arbitrary each time. For example:
..\HDTP\System\*.u should become ..\System\*.u and/or ..\New Vision\Textures\*.utx should become ..\Textures\*.utx. More specifically: Ignore the first three characters, delete whatever comes after, until the next \ character (including that character), leave the rest of the string intact. Could you, please, help me with this? I know, I have the worst explaining skills in the whole world, if something isn't clear, I'll try to explain again.
This is a little bit of copy and split work for Inno Setup but I have here a function for you with some extra comments.
Read it carefully as it isn't tested properly and if you have to edit it you will
have to know what it is doing ;)
function FormatPathString(str : String) : String;
var
firstThreeChars : String;
charsAfterFirstThree : String;
tempString : String;
finalString : String;
dividerPosition : Integer;
begin
firstThreeChars := Copy(str, 0, 3); //First copy the first thee character which we want to keep
charsAfterFirstThree := Copy(str,4,Length(str)); //copy the rest of the string into a new variable
dividerPosition := Pos('\', charsAfterFirstThree); //find the position of the following '\'
tempString := Copy(charsAfterFirstThree,dividerPosition+1,Length(charsAfterFirstThree)-dividerPosition); //Take everything after the position of '\' (dividerPosition+1) and copy it into a temporary string
finalString := firstThreeChars+tempString; //put your first three characters and your temporary string together
Result := finalString; //return your final string
end;
And this is how you would call it
FormatPathString('..\New Vision\Textures\*.utx');
You will have to rename the function and the var's so that it will match your program but I think this will help you.

Printing a string as a variable in Pascal

This is the string variable that I have:
question1 := 'Please enter 1, 2 or 3.';
I also have a function which is supposed to print out the question1 variable, but it generates the question number before printing. Here's a fragment of the function, which turns the question number (question : integer) into a string variable (test : string) and then concatenates the string 'question' with this string variable (test : string).
str(question,test);
test := concat('question',test);
writeln(test);
The result of this writeln is 'question1' (without the quotes). I want it to output the question1 variable as a text string, not just this variable's name, so that the writeln prints Please enter 1, 2 or 3. I've tried writeln(question1) and it works, however, it appears that my function above (or the fragment of it) does this: writeln('question1'). How do I solve this?
Pascal doesn't support dynamic name resolution, like you want. You might consider using arrays instead:
…
var
Questions: array[1..3] of string;
…
procedure InitQuestions;
begin
Questions[1] := 'Please enter 1, 2 or 3.';
Questions[2] := '…';
Questions[3] := '…';
end;
procedure YourFunction(question: Integer);
begin
…
WriteLn(Questions[question]);
…
end;
…
begin
…
InitQuestions;
…
YourFunction(1);
…
end.
As far as I can figure out from your question, you need the following
writeln (question1, 'question ', question);
You don't need to concatenate string values, nor do you need to convert the question number to a string - writeln has the magical ability to accept any number of variables and print them according to their default format (strings, integers, reals and booleans).
In fact, you don't really need to create the 'question1' variable - you could simply write
writeln ('Please enter 1, 2 or 3. Question', question);

Resources