将多个数组排在一起并返回全数组合分数中的排名数

时间:2014-01-15 23:06:05

标签: delphi sorting external-sorting

我有2张这样的表

enter image description here

如您所见,如果您查看总计,您可以在3轮中看到每位玩家的得分。我必须做一个清单(从1日到12日),表示得分最高。

这里28分的玩家必须拥有数字1(而不是默认生成的8),22的玩家必须拥有数字2而不是11 ...所以我必须排序 TOTAL 列并返回正确标签中的位置。

当我点击带有下划线的按​​钮时,会调用该过程:

var vettore:array[1..12] of integer;
    indici:array[1..12] of integer;
    i:smallint;
begin
 for i := 1 to 6 do
  begin
   vettore[i]:= StrToInt(StringGrid1.Cells[5,i]); //col,row
   indici[i] := i;
  end;
 for i := 6 to 12 do
  begin
   vettore[i]:= StrToInt(StringGrid2.Cells[5,i]); //col,row
   indici[i] := i;
  end;

通过这种方式,我在vettore内加载了两个表的行中的所有TOTAL数字,在indici中,您可以找到表格右侧的标签编号(它们表示位置)。现在我想我可以使用任何排序方法,因为我只有12个元素(比如快速排序)。

我的问题是:如何根据排序的数组更改标签文本(表格右侧的标签)?它就像上面的图片所示。

每个标签都会被调用(从1开始)mvp1mvp2mvp3mvp4 ...我认为这可能会有所帮助,因为如果(也许)我我必须使用for循环来更改每个标签的文本,我可以使用TFindComponent


如果它可能有用,这里有我在我的网站上用javascript编写的功能(它有效):

var totals = [],   //array with the scores
    indices = [];  //array with the indices

for (var i=0; i<6; i++) {
    totals[i] = parseInt(document.getElementById('p'+i).value, 10);
    indices[i] = i;
}

for (var i=6; i<12; i++) {
    totals[i] = parseInt(document.getElementById('p'+i).value, 10);
    indices[i] = i;
}

indices.sort(function(a, b) {
    return totals[b]- totals[a];
});

for (var i=0; i<indices.length; i++) {
    document.getElementById('mvp'+(indices[i]+1)).value = (i+1);    
}

1 个答案:

答案 0 :(得分:2)

AS。由于标签中仅列出了,这意味着任何Delphi版本都可以。我引用

1,我们将使用高级记录来保存单个参与者的数据。下面是一些链接,谷歌更多。

type
  TClanResults = record
  public
     type All_GPs = 1..3;
     var GP: array [All_GPs] of Cardinal;
     var Players: string;
     var Clan_ID: integer;
  private
     function CalcTotal: Cardinal;
     function CalcAverage: single; inline;
  public
     property Total: Cardinal read CalcTotal;
     property AVG: single read CalcAverage;
  end;

{ TClanResults }

function TClanResults.CalcAverage: single;
begin
  Result := Self.Total * ( 1.0 / Length(GP) );
end;

function TClanResults.CalcTotal: Cardinal;
var score: cardinal;
begin
  Result := 0;
  for score in GP do
    Inc(Result, score);
end;

表达式Self.Total * ( 1.0 / Length(GP) );也可以写成Self.Total / Length(GP)。不过,我想在这里强调一些德尔福的怪癖。

  • 在Pascal中有两个除法运算符:float和integer; 3 div 2 = 13 / 2 = 1.5。选错了会导致编译错误,最坏的情况是数据精度损失。
  • 我更喜欢从整数Length的显式类型转换来浮动,但是Delphi不支持它。所以我乘以1.0来演员。或者我可以加0.0。
  • 分区比乘法需要更长的时间 - 只需用笔和纸来看就可以了。当你有一个数据运算循环,其中所有元素被相同的数字分割时,最好将1 / value缓存到临时变量中,然后用它来替换每个元素。由于GP具有固定大小,因此编译器会计算(1.0 / Length(GP))并替换此常量。如果你允许不同的部族拥有不同数量的游戏 - 并将GP变成不同大小的动态数组 - 你可以在函数内部显式添加一个变量,并在循环开始之前计算coeff := 1.0 / Length(GP);

现在我们应该创建一个容器来保存结果并对它们进行排序。可以有多种方法,但我们使用基于泛型的TList<T>

TList 是一个对象,所以你必须创建它并释放它。我认为您可以将其设为 MainForm 的PUBLIC属性,然后在TMainForm.OnCreate事件中创建列表并在TMainForm.OnDestroy事件中释放它。

另一种更懒惰的方法是使用常规dynamic array及其扩展名。

但是,我将在下面使用TList。同样,我假设你编程中的其他例程已经正确地创建并销毁给定的var ClanData: TList<TClanResults>;对象实例。

type
  TClansTable = TList<TClanResults>;

procedure TMainForm.Input;
var row: TClanResults
begin
  Self.ClanData.Clear;

  row.Clan_ID := 1;
  row.Players := JclStringList.Add(['John', 'James', 'Jenny']).Join(' and ');
  row.GP[1]   := 2;
  row.GP[1]   := 5;
  row.GP[1]   := 7;
  Self.ClanData.Add(row);

  row.Clan_ID := 2;
  row.Players := JclStringList.Add(['Mary', 'Mark', 'Marge']).Join(' and ');
  row.GP[1]   := 3;
  row.GP[1]   := 6;
  row.GP[1]   := 2;
  Self.ClanData.Add(row);

  ...
end;

procedure SortOnTotal(const Table: TClansTable);
begin
   Table.Sort(
      TComparer<TClanResults>.Construct(
         function(const Left, Right: TClanResults): Integer
         begin Result := - (Left.Total - Right.Total) end
         // negating since we need reversed order: large to little
      )
   );
end;

现在终于我们需要知道如何在屏幕上显示该表。我会使用典型的TStringGrid作为最简单的小部件。我建议你从 JediVCL 或者来自 Torry.net 的东西看一些高级字符串网格,这样你就可以指定列样式了。很明显,整数应该在屏幕上右对齐,平均值应该以逗号对齐。但是,库存TStringGrid没有 GetCellStyle 事件,因此您需要一些高级网格衍生物来添加它。它留作你的家庭任务。

procedure TMainForm.DumpTableToGrid(const Data: TClansTable; const grid: TStringGrid);
const TableFields = 8;
var row: integer;
    ss: array of string;
    res: TClanResults;
  procedure DumpTheRow; var col: integer;
  begin
    for col := 0 to TableFields - 1 do begin
        grid.Cells[ col, row ] := ss[ col ]; 
  end;
begin
  grid.Options := [ goFixedVertLine, goVertLine, goHorzLine, goColSizing, goColMoving, goThumbTracking ];
  grid.ColCount := TableFields;
  SetLength( ss, TableFields );
  grid.RowCount := 1 + Data.Count;
  grid.FixedRows := 1;
  grid.FixedColumns := 1;    
  row := 0; // headers
    ss[0] := ''; // number in the row, self-evident
    ss[1] := 'Players';
    ss[2] := 'GP 1';
  ....
    ss[7] := 'Clan ID';
  DumpTheRow;

  for res in Data do begin // we assume Data already sorted before calling this
    Inc(row);
      ss[0] := IntToStr( row );
      ss[1] := res.Players;
      ss[2] := IntToStr( res.GP[1] );
    ...
      ss[6] := FloatToStrF( res.AVG, ffFixed, 4, 2);
      ss[7] := IntToStr( res.Clan_ID );
    DumpTheRow;
  end;
end;

现在,尚不清楚这些标签的含义。我可以猜测,你想根据你的两个部族组合位置显示排名。外部标签是一个坏主意,原因不多。

  • FindComponent并不太快。好的,您可以找到它们一次,在array of TLabel中缓存并完成。但为什么还需要额外的解决方法呢?
  • 用户可以调整窗口大小,使其更高或更短。现在有3个标签可见,一分钟就会看到30个标签,一分钟就会有10个标签......你会如何在运行时重新生成它们?那么总会有足够的人处于适当的位置?实际上只是将它们放入网格本身。
  • VCL糟透了表格缩放。现在Winodws 8.1已经出来了,不同显示器上的字体分辨率可能会有所不同。主显示器上通常会有96DPI,但是当您将窗口拖到辅助显示器上时,会有120DPI,并且在您的配对笔记本电脑上(例如:联想ThinkPad Yoga Pro和联想IdeaPad Yoga 2)如200DPI或Retina级300DPI。仍然你必须控制你的标签,以便它们的文本将精确地显示在网格行文本的右侧,无论每个高度和每种字体的行是什么值。

所以,我认为他们应该在这一排。如果要突出显示它们 - 使用粗体字体,或彩色,或大,或网格内的任何内容。

TRanks = record min, max: word; end;
TClanResults = record
...
  RanksCombined: TRanks;
...
end;

您正确地显示某些部落可能会有相同的结果并分享排名。


在继续之前,作为JS用户,必须注意recordclass数据类型之间的基础差异。 record按值进行操作,class按参考操作。这意味着对于类实例和变量,您必须为新元素手动分配内存并将其置于不再使用的元素中。由于类变量是对某些匿名类实例(数据)的引用。因此,类型元素的不同容器可以指向单个真实元素(数据,实例),从而提供简单的数据更改和更便宜的排序。然后,对于记录实例(和记录变量IS记录数据),您不必关心内存分配和生命周期,但是会在不同的记录实例之间复制数据,如果您更改了一个实例,则将其应用于其他容器你必须把它复制回来。这种差异在for element in container循环中非常明显,我们是否可以更改element.field


因此,让我们有更多的数据结构进行排序和计算。例如

TAvgAndRanks = class 
   avg: single; rank: TRanks; 
   table: TClansTable; idx: integer; 
end;

我们将对数据转储程序进行修改:

procedure TMainForm.DumpTableToGrid(const Data: TClansTable; const grid: TStringGrid);
const TableFields = 9;
...
  row := 0; // headers
  ....
    ss[7] := 'Clan ID'; 
    ss[8] := 'Rank';
  DumpTheRow;
...
      ss[7] := IntToStr( res.Clan_ID );
      with res.RanksCombined do
        if min = max 
           then ss[9] := IntToStr(min)
           else ss[9] := IntToStr(min) + ' - ' + IntToStr(max);
    DumpTheRow;

另一种方法是使用类似

的方式在外部保持排名
  TClanPtr = record table: TClansTable; idx: integer;  end;
  TClanSortData = record avg: single; rank: TRanks;  end;
  TClanRanksCombined = TDictionary<TClanPtr, TClanSortData>;

这种方法更具扩展性(允许在不同的窗口&#34;附加&#34;不同的扩展数据到部落),但需要更多的样板。如果你更多地说谎,你的功课就是实现它。

procedure MakeRanks(const clans: array of TClansTable);
var tab: TClansTable; idx: integer;
    total: TObjectList<TAvgAndRanks>;
    ar : TAvgAndRanks;
    res: TClanResults;

    // for spanning ranks with same avg
    r_curr, r_min: word;
    r_span, r_idx: integer; 
    r_avg: single; 
    r_chg: boolean;
begin
  total := TObjectList<TAvgAndRanks>.Create( True ); // auto-free by container
  try
    for tab in clans do
      for idx := 0 to tab.Count - 1 do begin
        res := tab[ idx ];

        ar := TAvgAndRanks.Create; // but creation is still manual
        ar.table := tab;
        ar.idx := idx;
        ar.avg := res.AVG;

        total.Add(ar);
      end;

    if total.Count <= 0 then Abort;

    if total.Count = 1 then begin
       ar := total[0]; 

       res := ar.table[ ar.idx ];
       res.RanksCombined.min := 1;
       res.RanksCombined.max := 1;
       ar.table[ ar.idx ] := res; // copying back updated data

       Exit; // from procedure - nothing to do
    end;

    total.Sort(  
      TComparer<TAvgAndRanks>.Construct(
        function(const Left, Right: TAvgAndRanks): Integer
        begin Result := - (Left.avg - Right.avg) end
        // negating since we need reversed order: large to little
      )
    );

    (***** calculating ranks with spans ****)

    r_curr := 1;
    r_min := 1;
    r_span := 0;
    r_idx := 0;
    r_avg := total[0].avg;

    for idx := 1 to total.Count - 1 do begin
      ar := total[ idx ];

      inc(r_curr);

      if r_avg = ar.avg then inc(r_span);

      if (r_avg <> ar.avg) or (idx = total.Count - 1) then begin

         for r_idx := r_idx to r_idx + r_span do begin
            with total[ r_idx ] do begin // class == reference, can update directly
              rank.min := r_min;
              rank.max := r_min + r_span;
            end;
         end;        

         Assert( (r_curr = r_min + r_span + 1) or ( r_avg = ar.avg ) );

         r_min := r_curr;
         r_span := 0;
         r_idx := idx;
         r_avg := ar.avg;
      end;
    end;

    (*** saving calculated ranks  ***)

    for ar in total do begin
        res := ar.table[ ar.idx ];
        res.RanksCombined := ar.ranks;
        ar.table[ ar.idx ] := res; // copying back updated data
    end;

  finally
    Total.Destroy;
  end;
end;