• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 707
  • Last Modified:

Delphi2010: how to split string in an array and then save them to a new array?

hi,

say there is array A contains following value:

A[0] = "[ 0 ] yy(z)"
A[1 ] = "[ 1 ] yyy(zz)"
A[ 2 ]="[ 12 ] yy(zz)"
A[ 2 ]="[ 123 ] yy(zzz)"

the format of the string are:  [ number ]String(String)
the length is flexible.

how could i get all number of this array and save these numbers into a new array?

thanks,

wantime

0
wantime
Asked:
wantime
  • 8
  • 7
  • 4
  • +2
4 Solutions
 
rfwoolfCommented:
Answer below:
0
 
rfwoolfCommented:
Auurgh stupid code attachment facility!!!!!!!!!!!!!!!!!!
Well you'll  need a function that will take the number out of your '[0] yy(z)' string:
//Note: This code is UNTESTED 
function CleanNumber(aString) : integer;
var
  PosOpenBracket : integer;
  PosCloseBracket : integer;
begin
  result := -1;
  PosOpenBracket := AnsiPos('[', aString); //produces 0 if not found
  PosCloseBracket := AnsiPos(']', aString); //produces 0 if not found
  result :=  MidStr(aString, PosOpenBracket, PosCloseBracket - PosOpenBracket);
  //Note: if the above line isn't working, try this: 
  //result :=  MidStr(aString, PosOpenBracket - 1, PosCloseBracket - PosOpenBracket - 1);  
end;

Then you could iterate through your array, and insert into a new array after cleaning
var
  i  : integer;
  newarray : array of integer;
begin
  setlength(newarray, 0);
  for i := 0 to length(A) do 
  begin
    setlength(newarray, length(newarray + 1));
    newarray[i] := CleanNumber(A[i]);
  end;
end;    

Note: it may be more effecient to not set the length of the array every time you add a new element. IF you know the length of the array must be the same as the length of A, then just set it once:
var
  i  : integer;
  newarray : array of integer;
begin
  setlength(newarray, length(A));
  for i := 0 to length(A) do 
    newarray[i] := CleanNumber(A[i]);
end;

Open in new window

0
 
rfwoolfCommented:
Oh shucks, you may get an access violation because it should be:
  for i := 0 to length(A) -1 do
(note the -1)
0
Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
wantimeAuthor Commented:
thanks.

but one more problem is that in each [ 0 ] there are two spaces before and after the nummber.

0
 
rfwoolfCommented:
ahh okay then we can use trim function to remove any spaces / line breaks / carriage returns between the square brackets []'s
We also put a try..except statement to try catch in problems if it tries to convert a bad string into an integer.

Also I see a mistake with my code in that the result must be INTEGER, but midstr returns a string.
Thus it should be something like this:

//Still untested
function CleanNumber(aString) : integer;
var
  PosOpenBracket : integer;
  PosCloseBracket : integer;
begin
  result := -1;
  PosOpenBracket := AnsiPos('[', aString); //produces 0 if not found
  PosCloseBracket := AnsiPos(']', aString); //produces 0 if not found
  try
    result :=  StrToInt(Trim(MidStr(aString, PosOpenBracket, PosCloseBracket - PosOpenBracket)));
  except
    //Catch the exception. And do nothing unless you disagree. The result will be -1
  end;
  //Note: if the above line isn't working, try this:
  //result :=  MidStr(aString, PosOpenBracket - 1, PosCloseBracket - PosOpenBracket - 1);  
end;
0
 
wantimeAuthor Commented:
okay, just use '[ ' and '] '
i will have a try...
0
 
rfwoolfCommented:
You must also watch out for AnsiPos. It returns the position of '[' and ']' but it might be 1-based, in other words if the first character is '[' it will return 1,
but,
MidStr might be 0-based, in other words '1' means 2nd position because '0' means first position.

So you may have to change the line with "MidStr" on it to fix this problem.
That's why you must test it.
0
 
Ephraim WangoyaCommented:
Just change CleanNumber to

function CleanNumber(const aString: string) : integer;
var
  loop: Integer;
  temp: string;
begin
  result := -1;
  temp := '';
  for loop := 1 to Length(aString) do
    if IsNumeric(aString[loop]) then
      temp := temp + aString[loop];
  Result := StrToIntDef(temp, 0);
end;
0
 
rfwoolfCommented:
That's a very nice optimization.
StrToIntDef also catches any problems converting StrToInt, and you can put 0 or -1 depending on your preference.
0
 
wantimeAuthor Commented:
it works with the solution by using Pos: i have changed AnsiPos to Pos.

btw, is method isNumeric a method from delphi class or should one write it himself like this:

function isNumeric(const S: string): Boolean;
var
  P: PChar;
begin
  P      := PChar(S);
  Result := False;
  while P^ <> #0 do
  begin
    if not (P^ in ['0'..'9']) then Exit;
    Inc(P);
  end;
  Result := True;
end;
0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
> That's a very nice optimization.
using IsNumeric to test each character ?
and to build the string char after char ?
That is no optimization at all. Your code was the right way to do it, rfwoolf. Use of StrToIntDef is certainly a convenience, it takes less code to do the same (but with some additional function calls). Just take your code, add this StrToIntDef and fix the eventual +/-1 there might be in your position using MidStr.

You can also use PosEx to look for the closing bracket only AFTER the opening bracket

Using Copy function, the correct code (having the 2 brackets positions) is this :

function CleanNumber(aString) : integer;
var
 PosOpenBracket : integer;
 PosCloseBracket : integer;
begin
 Result:=-2; // default value for brackets errors
 PosOpenBracket := Pos('[', aString); 
 if PosOpenBracket<1 Then Exit; 
 PosCloseBracket := PosEx(']', aString, PosOpenBracket);
 if PosCloseBracket <1 Then Exit; 
 Result:=
  StrToIntDef(Trim(
   Copy(aString,PosOpenBracket+1,PosCloseBracket-PosOpenBracket-1)
   ),-1); // -1 is the value for invalid int value inside the brackets, spaces non-withstanding
end;

Open in new window

0
 
rfwoolfCommented:
>>using IsNumeric to test each character ?
>>and to build the string char after char ?
Yes, in my mind this parses the string once only, instead of... 3 times

But if someone says my solution is correct, who am I to argue :D
0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
well if you want to be certain , execute 1000.000 times all algo , mesure time and see which one is best
0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
Pos only do one atomic comparison for each char, that is much quicker than doing IsNumeric for each char which is a set test operation.
PosEx ensures that the next search is done only from where the last stop. To gain one operation, it should even be
PosCloseBracket := PosEx(']', aString, PosOpenBracket [b]+1[/b] );

Open in new window

Then Copy just do one Move operation with the correct number of chars.
Only Trim is probably a bit expensive, but it is so handy...

With ewangoya algo, all characters are scanned, always. on a string like
[ 10 ] alongstring(anevenlongerstring)
the difference in performance would start to be very noticeable
A string like
[ 10 ] 2be3(whatever7)
would return 10237 as value, which is not correct. only by checking bracket positions such inconvenience can be avoided.
0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
oups , bold does not work in code section ( and ) ...
0
 
Ephraim WangoyaCommented:

@epasquier
    [ 10 ] 2be3(whatever7)
   I know the limitation, but look at the authors specification
   The same argument you would use with [ 10, ] 2be3(whatever7)

   Using Pos is obviously faster (I've not claimed otherwise) but we are not talking of a million items
   1000 lines is nothing, 100,000 lines is nothing, both algorithms should take less that 1/4 of a second for 100,000 items


Here's how you put it all together


function IsNumeric(const AValue: string): Boolean;
var
  I: Integer;
begin
  for I := 1 to Length(AValue) do
    case AValue[I] of
      '0'..'9':
        ;
      else
      begin
        Result := False;
        Exit;
      end;
    end;
  Result := Length(AValue) > 0;
end;

function CleanNumber(const AValue: string): Integer; //using IsNumeric
var
  I: Integer;
  temp: string;
begin
  temp := '';
  for I := 0 to Length(AValue) -1 do
    if IsNumeric(AValue[I]) then
      temp := temp + AValue[I];
  Result := StrToIntDef(Temp, -1);
end;

function CleanNumber(const AValue: string) : integer;  //using Pos
var
 PosOpenBracket: Integer;
 PosCloseBracket: Integer;
begin
  Result := -1;
  PosOpenBracket := Pos('[', AValue);
  if PosOpenBracket < 1 then
    Exit;
  PosCloseBracket := Pos(']', AValue);
  if PosCloseBracket < 1 then
    Exit;
  Result:= StrToIntDef(Trim(Copy(AValue, PosOpenBracket +1 ,PosCloseBracket-PosOpenBracket -1)), -1);
end;

procedure CopyArrayElements;
var
  I: Integer;
  Destination: array of integer;
begin
  SetLength(Destination, Length(SourceArray));

  for I := 0 to Length(SourceArray) -1 do
    Destination[I] := CleanNumber(SourceArray[I]);
end;

Open in new window

0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
FYI : compact IsNumeric function
function IsNumeric(const AValue: string): Boolean;
var i: Integer;
begin
 i:=Length(AValue);
 Result:=i>0;
 While (i>0) And Result do 
  begin
   Result:=(AValue[i]>='0') And (AValue[i]<='9');
   Dec(i);
  end;
end;

Open in new window

0
 
jimyXCommented:
Just to show a different way of doing that by using ExtractStrings and Tstringlist which will extract all the numbers in any string you pass and add them to the Tstringlist items by defining the unwanted characters.

The code below will fill a Tstringlist items with the numbers among the string you pass as follows:

'[ 123 ] yy(zzz)'
will result in:
123            // in the Stringlist[0]


'[ 123 ] yy 3 (zzz)'
will result in:
123           // in the Stringlist[0]
3               // in the Stringlist[1]


'4  [ 123 ] yy 3 (zzz)'
will result in:
4               // in the Stringlist[0]
123           // in the Stringlist[1]
3               // in the Stringlist[2]

and so on,
var
  Str:TStringlist;
begin
  Str := TStringlist.Create;
  // any chars other than 0..9 will be considered as separators 
  ExtractStrings(['[',']','(',')','a'..'z','A'..'Z'],[' '],'[ 123 ] yy(zzz)',Str);
  // for showing the first item which will be 123
  showmessage(IntToStr(StrToInt(Trim(Str[0]))));

  Str.Free;

Open in new window

0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
That's a costly but compact solution. Thanks for sharing it.
But you should declare the set this way, which is more correct and easier to understand at first look :
 
ExtractStrings( [ 0..#255] - ['0'..'9'],[],'[ 123 ] yy(zzz)',Str);

Open in new window

0
 
Emmanuel PASQUIERFreelance Project ManagerCommented:
where is gone my # before 0 ? am I sleeping already or that new editor just swallowed it ?
0
 
wantimeAuthor Commented:
thanks for the different solution and the analyse.
0
 
rfwoolfCommented:
I knew somebody would show a TStringlist solution - I just had no idea what it would be :D
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Introducing Cloud Class® training courses

Tech changes fast. You can learn faster. That’s why we’re bringing professional training courses to Experts Exchange. With a subscription, you can access all the Cloud Class® courses to expand your education, prep for certifications, and get top-notch instructions.

  • 8
  • 7
  • 4
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now