Link to home
Start Free TrialLog in
Avatar of wildzero
wildzero

asked on

Remove all space, line breaks and paragrahs from text

I have a bunch of text in a Tmemo component, could look like this (could be any text)

Hello my name is bob and
I have two cats, each can catch
four mice a day

Or whatever
What I need is to make it

Hello my name is bob and I have two cats, each can catch four mice a day

Even
HellomynameisbobandIhavetwocats,eachcancatchfourmiceaday

I have tired replacing all the ' ' with '' but no go, have tried using trim, again no go.
Basically, if I had a 20 line string in a Memo, I want it all on 1 line. Yes I have turned off word wrap.



Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

Memo.Lines.Text := StringReplace(Memo.Lines.Text, #13#10, '', [rfReplaceAll]);

Tried that already? :-)
procedure TForm1.Button1Click(Sender: TObject);
var
  I:      Integer;
  P:      Integer;
  S:      string;
  T:      string;
begin
  T := '';
  Memo2.Clear;
  for I := 0 to Memo1.Lines.Count-1 do
  begin
    S := Memo1.Lines[I];
//    StringReplace(S, ' ', '', [rfReplaceAll]);
    P := Pos(' ', S);
    while (P>0) do
    begin
      Delete(S, P, 1);
      P := Pos(' ', S);
    end;
    T := T + S;
  end;
  Memo2.Text := T;
end;
ASKER CERTIFIED SOLUTION
Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of wildzero
wildzero

ASKER

Yea I get that ;)
What am getting is with the memo1, if I have 5 pages of text it reduces it to 6-7 lines
WIth a richedit I get it down to 3 lines

So I guess it's a limit with the components, if I load it into a string list ,or maybe just assign it to a stringvar do you think it'd keep 1 line?
try to use stringvar as esoftbg wrote and also you  can try this :

procedure TForm1.Button1Click(Sender: TObject);
var delimitedtext:String;i,j:integer;
    delimitedtextremoveallspaces:string;
begin
memo1.Lines.Delimiter:=' ';
memo1.Lines.QuoteChar:='~';
setlength(delimitedtext,length(memo1.text));
setlength(delimitedtextremoveallspaces,length(memo1.text));


delimitedtext:=memo1.lines.DelimitedText;
  while Pos('~', delimitedtext) > 0 do
    delimitedtext[Pos('~', delimitedtext)] := ' ';
// delimitedtext is now delimited text without removing spaces


// delimitedtextremoveallspaces is now delimited text with removing spaces
j:=1;
for i:=1 to length(delimitedtext) do
 if delimitedtext[i]<>' ' then
  begin
   delimitedtextremoveallspaces[j]:=delimitedtext[i];
   inc(j);
 end;


end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Text := StringReplace(Memo1.Text, Chr(13) + Chr(10), ' ', [rfReplaceAll]);
  Memo1.Text := StringReplace(Memo1.Text, ' ', '', [rfReplaceAll]);
end;
Wow, I react really slow today :-)
Well, okay. Some pretty fast code that strips unwanted characters faster than StringReplace:

type
  TSkipChars = set of char;

function StripChars( const Value: string; SkipChars: TSkipChars ): string;
var
  I, J: Integer;
  Max: Integer;
begin
  Result := Value;
  Max := Length( Value ); // Avoids recalculating the length.
  I := 1;
  while ( I <= Max ) and not ( Result[ I ] in SkipChars ) do
    Inc( I );
  if ( I <= Max ) then begin
    J := I + 1;
    while ( J <= Max ) do begin
      if not ( Result[ J ] in SkipChars ) then begin
        Result[ I ] := Result[ J ];
        Inc( I );
      end;
      Inc( J );
    end;
  end;
  SetLength( Result, I - 1 );
end;

Yes, it looks complex. It took me over 10 minutes to write so it really must be complex... :-)
50 points for the person who finds a bug in that piece of code of mine... ;-)
Yes, Geo... You're slow today. Old age? :-)

About that function of mine, call it like:
  Memo1.Lines.Text := StripChars( Memo1.Lines.Text, [ #10, #13, #32, '.', ',' ] ) );

However, keep in mind that the line length of a memo isn't endless. Very long strings are either cut off at the end or split in multiple lines. I'm not sure exactly about the maximum line length of a TMemo and TRichBox but it's less than a TStringist can have, per line. A memo or richtext component in Delphi doesn't have a real stringlist connected to it. It just has some stringlist-compatible object linked to the Windows control. It's the Windows control that enforces this limit. The stringlist used by the memo is the TMemoStrings class. I'm not sure about the maximum line length of a Memo, but think it's around 1024, since that's the limit of the Delphi editor.
Alex, exactly, ooold age combined with slooow internet connection :-)
Howzit Alex,

I couldn't find a bug, but here's a more "english" (as opposed to greek... hehehe) version of your solution:

function PCStripChars( const Value: string; SkipChars: TSkipChars ): string;
var
  I, J: Integer;
  Max: Integer;
begin
  Result := Value;
  Max := Length( Value ); // Avoids recalculating the length.
  j:= 0;

  for i:= 1 to Max do
    if NOT (Value[i] in SkipChars)
    then begin
      Inc(j);
      result[j]:= Value[i];
    end;
  SetLength(result,j);
end;

Speedwise, it results in the same as your function.
Avg time "English" = 0.2083 milliseconds

avg time "Greek" = 0.2173 milliseconds
True, same result. You're using a for-next loop, which is where you gain more speed. :-) Still, we're talking about a 1% difference on a millisecond. :-P
I'll give you (or anyone else) 500 points (in a new topic) if you manage to create a function that's at least twice as fast as mine. :-)
https://www.experts-exchange.com/questions/21165246/Impossible-A-speed-challenge.html for the challenge...
Not exactly that fast... maybe 30% faster (based on a 2mb text file, averaged over 10,000 runs):

function MyStrip(const sValue: string): string;
var
  Max: Integer;
  i, j: Integer;
begin
  Max := Length(sValue);
  SetLength(Result, Max);
  j := 0;
  for i := 1 to Max do
    if sValue[i] in ['A' .. 'Z', 'a' .. 'z', '0' .. '9'] then
    begin
      Inc(j);
      Result[j] := sValue[i];
    end;
  SetLength(Result, j);
end;

Okay, so I cheated a bit in the code =p


DragonSlayer.
>> if sValue[i] in ['A' .. 'Z', 'a' .. 'z', '0' .. '9'] then

Even if the set is a parameter, it would still be faster because it has no Result := Value.  There is no need for such an assignment, SetLength(Result, Max) is perfect. Workshop_Alex's would suffer very much if most of the chars in Value are in SkipChars and Value is a very large string. :-)

Another improvement can be gained if we make Value a reference parameter. We know for sure that we're not changing it inside, right?
DragonSlayer,
your code does not work properly ....
Emil, what is wrong with it?
Your code is faster, but it eliminates many symbols as
','
'.'
':'
'!'
'@'
'#'
'$'
'%'
'&'
'*'
'('
')'
'-'
'+'
........
I think only improvement for your code makes it a perfect one:

function MyStrip(const sValue: string): string;
var
  Max: Integer;
  i, j: Integer;
begin
  Max := Length(sValue);
  SetLength(Result, Max);
  j := 0;
  for i := 1 to Max do
    if sValue[i] in [#0..#9, #11..#12, #14..#31, #33..#255] then
    begin
      Inc(j);
      Result[j] := sValue[i];
    end;
  SetLength(Result, j);
end;
@joncmora, if you look at https://www.experts-exchange.com/questions/21165246/Impossible-A-speed-challenge.html then you'll see that I've actually organized a benchmark for my own stripchar function and for other alternative solutions. While my suggested solution might be slow, no one really managed to create a similar function that would be twice as fast in all situations. However, even if the memo is about 2500 lines long, these functions are just too fast to notice any real speed improvement. My method requires 9.425.846 processor ticks to handle 2500 strings. It's about twice as fast when it has to strip nothing or everything. On a 1 GHz machine you would have 1.000.000.000 processor ticks per second. Thus the speed is 1 millisecond for my method and about a half for the fastest one.

And since we would use:
  Memo1.Lines.Text := StripChars(Memo1.Lines.Text, [#10, #13]);
to remove all linebreaks, we just have only one single string manipulation. (We're not stripping each memo line since that way we would never see the linebreaks!)

The problem with Madshi's KillChar solution which uses this function header is simple:
  function KillChars( var str: string; killChrs: TSkipChars ): boolean;

The TMemo.Lines.Text property is just that! A property! Properties cannot be passed as var parameters to a function or procedure. If you would try to use:
  KillChars(Memo1.Lines.Text, [#10, #13]);

then the compiler will not compile this line of code, since you cannot pass a property as var parameter. Thus you would have to assign the property to a variable, pass the variable to the function and then assign the value back to the property. This additional overhead will just slow things down again.

One other solution would be this:

var
  I:Integer;
  Value: string;
begin
  Value := '';
  for I := 0 to Pred(Memo1.Lines.Count) do Value := Value + Memo1.Lines[I];
  Memo1.Lines.Text := Value;
end;

But this code would also be slower than just using one of the SkipChars versions in the other question.
I thought we're talking about a "fast" char stripper. So the source string will not be considered. If you're saying that the source will always be from <TString>.Text, then why not pass the <TString> instead. :-)