大字符串计算字数的效率问题。(200分)

  • 主题发起人 主题发起人 lps
  • 开始时间 开始时间
L

lps

Unregistered / Unconfirmed
GUEST, unregistred user!
说明:用RxRichEdit做一个简单的文本处理,要统计字数,发现和WORD 2000中的速度差得太远,如果有1M以上的字数CPU 100%,时间至少要1分钟以上。
procedure Tmain.SpeedButton17Click(Sender: TObject);
begin
if RxRichEdit1.SelLength >0
then CauText(RxRichEdit1.SelText) //只算选中部分的字数
else CauText(RxRichEdit1.Text);//计算RxRichEdit中全部的字数
end;


procedure Tmain.CauText( CauStr: string);
var
i, n: integer;
Instr, s, t: string;
TP: WideString;
begin
i := Length(CauStr);
S := inttostr(i);
Tp := StringReplace(CauStr,' ','',[rfReplaceAll]);//去掉空格
Tp := StringReplace(Tp,#13#10,'',[rfReplaceAll]);//去掉回车
n := length(tp);
T := inttostr(n);
Instr := '共有:' + #13 + s + '字节' + #13 + T + '个字';
MessageBox(Handle, Pchar(instr), '字数计算', MB_ICONASTERISK);
end;
 
经试验主要是StringReplace函数影响速度,但如果去掉则结果不太对。
 
看 StringReplace 源码前面部份就能得到字符个数。
 
StringReplace 做大量剪裁,能不用尽量不用。

c:=0;
i:=1;
while i<= length(CauStr) do
begin
if CauStr>#32 then inc(c);
if CauStr>#127 then inc(i);
inc(i);
end;
 
kinneng正解。单字节字符和双字节字符混合就是难处理。
楼主可能自己还没搞清楚到底要获取“字数”还是“字符数”,抛开效率问题,楼主的代码获取的是“字符数”,即一个汉字当成了2个字符。
kingneng不但没计算空格和回车,而且没计算所有ascii值小于32的控制字符。

另外请教kingneng,string类型在内存中一定是一个线性的数组吗?borland有规定吗?str取值是怎样实现的?是按线性数组的方法把i作为一个偏移量下标来取值呢,还是作为一个函数的参数来取值?
 
StringReplace 源码看过了,的确是效率很低。我的目的是很清楚的,字节数是要的(因为是要压缩后保存在数据库中的,太大花的时间很多),字数也是要统计,但是希望去掉空格和回车,标点和数字、英文等也可算一个字,事实上我原来的代码基本上是对的(最新发现一个小问题,下面再谈),只是想要解决一个效率问题。kinneng的代码结果是不对的,虽然思路可能是对(好象是和汉字的内码取值范围有关吧)。
 
现在新的问题出现了,例如从WORD中选中'章 战'并复制到剪贴板,再粘贴到RxRichEdit中,结果代码:
Tp := StringReplace(CauStr,' ','',[rfReplaceAll]); //去掉空格
并不能去掉空格。
并且下面去掉回车的代码也不能正常工作,好象RxRichEdit中回车是#13而不是#13$10,但是好象也有一部分是#13$10,因为我上面的结果和word中的字数统计是一样的。
到底RxRichEdit中的空格和换行有哪几种情况啊?
 
明白楼主的意思了,就是要统计原始的字节数和去掉空格回车后的字节数。这样把kinneng的 if CauStr>#127 then inc(i);去掉就行了。哈哈
 
老大那个是全角的空格吧,那么再加上一行判断:
if CauStr=Chr(Ord(' ')) and CauStr=Chr(Ord(' ')div 256) then inc(i);
 
cLen:= CharToByteLen(Str,MAX_SIZE_LEN); 字符数(计算汉字和英文混合的)
bLen:=ByteToCharLen(); 字节数
MAX_SIZE_LEN须为一足够大值

中文个数=bLen-cLen;
英文个数=bLen-2*(bLen-cLen)


上面写的有楼主需要的吗?
 
完整代码(改自kinneng):
c:=0;
i:=1;
while i<= length(CauStr) do
begin
if CauStr>#32 then inc(c);
if (PWORD(@CauStr))^=$A1A1 then inc(i);
inc(i);
end;
 
zmlmdzg:结果不对啊
 
试试这个:

procedure CauText(const AText: string);
var
P: PChar;
S: string;
TextLen, SpaceLen, CTRLLen: Integer;
begin
SpaceLen := 0;
CTRLLen := 0;
TextLen := Length(AText);

P := PChar(AText);
while True do
begin
case P^ of
#0:
break;
#13, #10:
Inc(CTRLLen);
#32:
Inc(SpaceLen);
end;
Inc(P);
end;
S := Format('共有:%d个字节'#13#10'%d个字', [TextLen, TextLen - CTRLLen - SpaceLen]);
MessageBox(0, PChar(S), '字数计算', MB_ICONASTERISK);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
CauText('ab cd '#13#10'ef')
end;
 
这个没有计算汉字
 
弄不懂楼主的意思了,给几个字符串例子,说明要求结果是多少,好给你调试。
 
字节数可以先不去管他,关键是字数,除了空格、全角空格、制表符、回车不算外,其他都算(就是标点也算),当然如果能把全角和半角的标点也算清楚就更好了。如果还不清楚可以试一下word中的字数统计,要的就是那样的效果(至少是接近的效果)
 
我来试试, 速度没有测试过,但应该比replace快

interface
........
type
TCharCounter = class
private
Len : integer;
Idx : integer;
CurrentChar : string[2];
function IsCHSPunctuation : boolean;
function IsANSIPunctuation : boolean;
function IsChsSkipChar : boolean;
public
CHS_char, ANSI_char : integer;
CHS_Punctuation, ANSI_Punctuation : integer;
procedure Calculate(const str : string);
end;

.......
implematation
......
{ CharCounter }

procedure TCharCounter.Calculate(const str: string);
begin
self.Len := length(str);
SetLength(self.CurrentChar,2);
self.CHS_char := 0;
self.ANSI_char := 0;
self.CHS_Punctuation := 0;
self.ANSI_Punctuation := 0;
self.Idx := 1;
while self.Idx <= self.Len do
begin
self.CurrentChar[1] := str[Idx];
if byte(str[self.Idx]) > 160 then //如果是全角字符的前半段
begin
if (self.Idx < self.Len) and (byte(str[self.Idx + 1]) > 160) then //如果是完整的全角字符
begin
self.CurrentChar[2] := str[self.Idx + 1];
if not self.IsCHSSkipChar then
begin
if self.IsCHSPunctuation then inc(self.CHS_Punctuation)
else inc(self.CHS_char);
end;
inc(self.Idx);
end;
end
else //否则是半角字符
begin
if self.IsANSIPunctuation then inc(self.ANSI_Punctuation)
else if str[self.Idx] > #32 then inc(self.ANSI_char)
end;
inc(self.Idx);
end;
end;

function TCharCounter.IsANSIPunctuation: boolean; //判断是否英文标点
begin
Result := self.CurrentChar[1] in [#33..#47,#58..#64,#91..#96,#123..#126];
end;

function TCharCounter.IsCHSPunctuation: boolean; //判断是否中文标点,为简单起见,这里的标准是只要不是空格和汉字都算标点。(请根据实际需要修改)
begin
Result := (self.CurrentChar >= '、' {区位码0102}) and (self.CurrentChar < '啊' {第一个汉字});
end;

function TCharCounter.IsCHSSkipChar: boolean; //判断是否中文的可忽略字符,为简单起见,现在的标准是只忽略空格。(请根据实际需要修改)
begin
Result := self.CurrentChar = ' '; {全角空格}
end;

使用的时候:
c := TCharCounter.Create;
try
c.Calculate(self.RichEdit1.Text);
//英文字符数在 c.ANSI_Char中
//英文标点数在 c.ANSI_Punctuation中
//中文字符数在 c.CHS_Char中
//中文标点数在 c.ANSI_Punctuation中
finally
c.Free;
end;
 
kidneyball强,类用得很熟练嘛,啊--,OO思想比较先进嘛,啊~~

完整代码(改自kinneng):
c:=0;
i:=1;
w:=0;
while i<= length(CauStr) do
begin
if CauStr>#32 then inc(c);
if (PWORD(@CauStr))^=$A1A1 then
inc(i)
else
if CauStr>#127 then inc(w);
inc(i);
end;
w:=w div 2;

i,w里面肯定有你需要的。
 
对汉字编码很熟悉嘛~~
其实我也有一个~呵呵~不过需要从别的东西里面'挖'出来~~就算了
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
I
回复
0
查看
819
import
I
I
回复
0
查看
466
import
I
后退
顶部