开发者

Why this search can not generate correct result?

Below is to find same customer and if he is in list, the number add one. If he is not in the list, just add him in the list.

I use Search function to do this, but failed and generated incorrect records. It can not find the customer or the right number of customers. But if I use For..loop to iterate the list, it does well and can find the customer and add new customer in that for..loop search procedure. (I did not paste for ..loop search procedrue here).

Another problem is that there is no difference between setting list.sorted true and false. It seems Search function is not correct. This search function is from an example of delphi textbook.

The below is with Delphi 7.

Thank you.

Procedure Form1.create;
begin
list:=Tstringlist.create;
list.sorted:=true;  // Search function will generate exactly Same and Incorrect 
                    //records no matter list.sorted is set true or false. 
//list.duplicates:=dupignore;

end;

Procedure addcustomer;
var
customer: string;
    begin
    //... Here is the code part that P1 is created as regular expression (omitted)
    while p1.MatchAgain do begin //p1 is regular expression
    customer:=p1.MatchedExpression; // try to match customer name.
    if (search(customer)=false) then begin //this line output wrong number
   // if (forloopsearch(customer)=false) then begin // this forloopsearch is ok
    list.Add(customer+'=1');
    end;
    allcustomer:=allcustomer+1; // global allcustomer is integer to count all customers

    end;


Function Tform1.search(customer: string): boolean;
var
 fre:string;
 num:integer;
 L:integer;
 R:integer;
 M: Integer;
 CompareResult: Integer;
 found: boolean;
begin
result:=false;
found:=false;
L := 0;
R := List.Count - 1;
while (L <= R) and ( not found ) do
begin
    M := (L + R) div 2;
    CompareResult := Comparetext(list.Names[m]), customer);
    if (compareresult=0) then
    begin
      fre:=list.ValueFromIndex [m];
      num:=strtoint(fre);
      num:=num+1;
      list.ValueFromIndex[m]:=inttostr(num);
      Found  := True;
      Result := true;
      exit;
      end
    else if compareresult > 0 then
      r := m - 1
    else
      l := m + 1;
  end;

end;

Edit:

Thank you all.

In order to clarify the problem, I copy the test code from my computer here and I am sorry for my redundancy.

Below is the name file that contains 3 persons' names (totally, 45 johns, 45 maries, and 45 erics).

mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,
mary, mary, mary, john, john, john, eric, eric, eric,

The forloopsearch will generate the following. Below is a copy result that reads key and value and add into a listview (using add method of listview. items). This result is correct and is what I want.

  mary  45
  john  45
  eric  45

Below is forloopsearch.

Function Tform1.forloopsearch(customer: string): boolean;
var
 fre:string;
 i: integer;
aname:string;
 num:integer;
begin
result:=false;
for i:=0 to list.count-1 do begin
  aname:=list.names[i];
  if aname=customer then
      begin
      fre:=list.ValueFromIndex [i];
      num:=strtoint(fre);
      num:=num+1;
      list.ValueFromIndex[i]:=inttostr(num);
      Result := true;
      End;
 end;
end;

The forloopsearch function output above is correct and it modifies values of keys in iteration.

The Search function will output the following. It is not correct and is not what I want. It also modifies values of keys in loop, but output is obviously wrong.

In both cases, list.sorted is set true when form is created as shown above. They both use same procedure to iterate the list and copy key and value in order into a listview (by using add method). Below is Search function result.

mary    3   
john    2   
john    1   
eric    1   
eric    4   
eric    40
mary    3   
john    1   
john    40
john    1   
mary    39开发者_StackOverflow中文版

How to solve it. Thank you again.


(The question initially was a failing binary search on a StringList, and the reason turned out to be that the list was not sorted while the search was executed. See comments on the question.)


What I would do would be to keep the stringlist sorted at all times, and instead of a 'Name=Value' pair to keep the frequency of occurrence, use the Objects property for that. This would help avoid the Integer<->String conversion the code originally had and can be achieved with sth like: list.Objects[i] := Pointer(f + 1);

Use the stringlist's Find method to find out if an entry already existed or not. Find executes a binary search itself, you don't need 3rd party code to do that. If the item exists already, just increment the frequency. If the item does not exist, the Index parameter that is set by the function is the place where the item should be inserted (see the documentation link above). Call the protected method InsertItem to insert the new item. The reason for the protected call is to avoid the find method run again needlessly (see TStringList.AddObject function in 'classes.pas'). This would be achieved with sth. like:

type
  TSL = class(TStringList);

var
  i: Integer;
  s: string;
begin
  s := 'Joe';
  if list.Find(s, i) then
    list.Objects[i] := Pointer(Integer(list.Objects[i]) + 1)
  else
    TSL(list).InsertItem(i, s, Pointer(1));
end;


But you really need to profile and test alternatives, otherwise it is all based on guesswork.

edit: I would like to note that if there's no adding/deleting items outside of this procedure, the stringlist's sorted property could be kept un-set, and then the protected hack would be unnecessary as one could use the InsertObject method then.


Perhaps this does what you need

var
    sl1, sl2: TStringList;
    sPrev: string;
    I: Integer;
begin
    sl1 := TStringList.Create;
    sl2 := TStringList.Create;
    try
        sl1.CommaText := 'mary, mary, eric, eric, john, mary, john, john,  eric, eric';
        sl1.Sorted := True;

        sPrev := '';
        for I := 0 to sl1.Count - 1 do
        begin
            if sl1[I] <> sPrev then
                sl2.Add(sl1[I]+'=1')
            else
                sl2.ValueFromIndex[sl2.Count-1] := IntToStr(StrToInt(sl2.ValueFromIndex[sl2.Count-1])+1);
            sPrev := sl1[I];
        end;

        for I := 0 to sl2.Count - 1 do
            ListView1.AddItem(sl2[I], nil);

    finally
        sl1.Free;
        sl2.Free;
    end;
end;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜