我有2张这样的表
如您所见,如果您查看总计,您可以在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开始)mvp1
,mvp2
,mvp3
,mvp4
...我认为这可能会有所帮助,因为如果(也许)我我必须使用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);
}
答案 0 :(得分:2)
AS。由于标签中仅列出了delphi,这意味着任何Delphi版本都可以。我引用delphi-xe2。
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)
。不过,我想在这里强调一些德尔福的怪癖。
3 div 2 = 1
和3 / 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
中缓存并完成。但为什么还需要额外的解决方法呢?所以,我认为他们应该在这一排。如果要突出显示它们 - 使用粗体字体,或彩色,或大,或网格内的任何内容。
TRanks = record min, max: word; end;
TClanResults = record
...
RanksCombined: TRanks;
...
end;
您正确地显示某些部落可能会有相同的结果并分享排名。
在继续之前,作为JS用户,必须注意record
和class
数据类型之间的基础差异。 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;