【问题标题】:Improving speed with randomizing pairs in Delphi在 Delphi 中使用随机对提高速度
【发布时间】:2015-02-23 22:34:24
【问题描述】:

我正在努力提高例行程序的速度,并希望得到一些意见。 情况如下:

在锦标赛环境中使用拼字游戏,以下条件适用:

  1. 锦标赛由 20 名或更多玩家组成。
  2. 玩的游戏数量在 2 到 14 之间,具体取决于锦标赛。
  3. 玩家可能会根据锦标赛规模按年龄划分。

我们的目标是为锦标赛进行两个特殊的边注:

  1. 每场比赛的玩家将随机配对
  2. 玩家将被随机配对,但每场比赛都有相同的伙伴。
  3. 适用的条件是边注必须有偶数玩家。 有一个玩家桌:

ID:整数,名称:字符串,除法:整数;

有一个边注表:

PlayerID:整数,RandomeEachGame:布尔值,RandomAllSame:布尔值;

有一个分数表:

PlayerID: Integer, GameNumber: Integer, Score: Integer;

有一个 RandomSidebets 表:

Player1ID:整数;

Player2ID:整数

游戏编号:整数; // 哪个游戏得分

SameAllGames:布尔值; // 如果为真,则配对为同一个伙伴边注,否则为不同伙伴边注。

我正在尝试创建随机配对,但处理似乎过多。我将特定分区中的玩家加载到动态数组中,然后我从动态数组中随机拉出,从数组中删除该项目,因此我不会在每场比赛中多次使用同一个玩家,并对合作伙伴,然后将合作伙伴写到桌子上以了解谁是合作伙伴。

    Var
        Division : integer;
    I, J     : integer;
    DSourceUsed : TIntArray;
    DSource     : TIntArray;
    rndnum     : integer;
    Fld1       : TField;


    for Division := 1 to NumberofDivisions do begin
      SQLTEXT := 'SELECT sb.PlayerID FROM SIDEBET sb ';
  SQLTEXT := SQLTEXT + ' INNER JOIN PLAYERS p ON Sb.PlayerID=p.BPlayerID ';
  SQLTEXT := SQLTEXT + Format( ' WHERE (sb.RandomEachGame=) AND (p.Division=%d)', [ Division ] );
  Qry.SQL.Text := SQLTEXT;
  Qry.Open;
  if ( Qry.RecordCount > 0 ) then begin
        if RandomMethod = mrmAllSame then begin
          SetLength( DSource, Qry.RecordCount );
          SetLength( DSourceUsed, Qry.RecordCount );
      Qry.First;
      Fld1 := Qry.FieldByName( 'PlayerID' );
          I := 0;
      while not Qry.eof do begin
        DSourceUsed[ I ] := Fld1.AsInteger;
            DSource[ I ] := Fld1.AsInteger;
            inc( I );
    Qry.Next;
      end;
          for I := 0 to Qry.RecordCount - 1 do begin
        rndnum := RandomRange( 0, Length( DSourceUsed ) - 1 );
    DSource[ I ] := DSourceUsed[ rndnum ];
    DeleteX( DSourceUsed, rndnum ); // Routine that removes index from array so we don't repeat partners
      end;
      for I := 0 to Qry.RecordCount - 1 do begin
        if not Odd( I ) then begin
              for J := 1 to NumberofGames do begin
                QryGame.SQL.Text := Format( 'INSERT INTO RandomSideBets (Player11ID, Player2ID, Game) Values (%d, %d, %d, %d)', [ DSource[ I ], DSource[ I + 1 ], J, True ] );
        QryGame.Execute;
      end;
    end;
      end;
    end
    else begin
    for J := 1 to NumberOfGames do begin
      SetLength( DSource, Qry.RecordCount );
      SetLength( DSourceUsed, Qry.RecordCount );
      Qry.First;
      Fld1 := Qry.FieldByName( 'PlayerID' );
          I := 0;
      while not Qry.eof do begin
        DSourceUsed[ I ] := Fld1.AsInteger;
    DSource[ I ] := Fld1.AsInteger;
        inc( I );
    Qry.Next;
      end;
          for I := 0 to Qry.RecordCount - 1 do begin
        rndnum := RandomRange( 0, Length( DSourceUsed ) - 1 );
    DSource[ I ] := DSourceUsed[ rndnum ];
    DeleteX( DSourceUsed, rndnum ); // Delete index from array
      end;
          for I := 0 to Qry.RecordCount - 1 do begin
        if not Odd( I ) then begin
      QryGame.SQL.Text := Format( 'INSERT INTO RANDOMSIDEBET (Player1ID, Player2ID, Game, SameAllGames) Values (%d, %d, %d, %b)', [ DSource[ I ], DSource[ I + 1 ], J, FALSE ] );
      QryGame.Execute;
    end;
      end;
        end;
      end;
end;
Qry.Close;
    end;

【问题讨论】:

  • 您的数据库引擎可能支持以随机顺序返回查询结果的方法。这将消除代码中的几个步骤,因为您只需将读取的每两个结果配对即可。您也许甚至可以将整个事情表达为单个 INSERT INTO SELECT 语句。
  • 我不明白你为什么在这里涉及数据库。这不只是一个概率问题吗?

标签: delphi


【解决方案1】:

您正在进行大量的同步查询,每个查询都需要数据到达服务器并返回(2*延迟,加上处理时间)。

要么在本地缓存你的插入,然后在你完成算法后一次性发送它们(你没有费心提及你的 SQL 风格,所以你自己),或者删除整个事情并编写一个存储过程来在服务器端执行此操作(与以前相同的附录)。存储过程将避免服务器之间的所有大型数据传输,对服务器本身来说是本地的。

另外,也许是时候利用异步处理了。现在是 2015 年。您的大部分 CPU 时间都浪费在等待 I/O(您甚至使用的一个内核)或什么都不做(可能是您花钱购买的其他 7 个内核)。

【讨论】:

  • 缓存插入引导我查看事务支持。你是对的,我忘了提到数据库。我正在使用 Devart 的 LiteDac 对 SQLite 进行尝试。我发现他们支持交易。因此,我使用 .StartTransaction 和 .Commit 将代码包装在读取和插入语句的循环中。是一个重大改进。我从大约 7485 毫秒的时间降到了…… 164 毫秒。我不确定你建议的后半部分。围绕函数执行异步以存储数据。这不会对数据造成“危险”吗? (在 FTP 下载时,我得到了异步)。
  • 为什么会有危险?你在整个函数中拥有的那个大的for,你可以简单地并行化它,直接获得NumberofDivisions 倍的改进。我不做 Delphi,但我确信它有相当于线程池或任务或类似的东西。使用它!
【解决方案2】:

您只需将数据库记录加载到单个数组中,然后无需对物理记录进行排序或从列表中删除即可获得随机化。而只是随机化一个整数数组作为玩家列表的索引。

例如你加载了一个包含 50 个玩家的列表(我已经硬编码了 50 个作为示例,你可以为实际计数使用适当的变量)。

var playerindex: array of integer;
SetLength(playerindex, 50);
for i := 0 to 50 - 1 do
    playerindex[i] := Random(50);

现在使用 playerindex 值索引到您的实际玩家列表以获取随机对:

for pair := 0 to 50 div 2 - 1 do // 25 pairs
begin
  p1 := players[playerindex[pair * 2]];
  p2 := players[playerindex[pair * 2 + 1]];
  // record p1 and p2 as each pair of players
end;

【讨论】:

  • 这部分很有道理,我喜欢你设置索引的方式。问题是(我可能不清楚)虽然合作伙伴是随机的,但他们必须是独一无二的。因此,如果 #1 与 #50 合作,则 1 或 50 不能与其他任何人合作。所以下面的截图是行不通的。 for i := 0 to 50 - 1 do playerindex[i] := Random(50);
  • playerindex[i] := i 初始化数组。然后洗牌(就像一副纸牌一样)。取每一对连续的。
  • en.wikipedia.org/wiki/Fisher–Yates_shuffle 是一种简单有效的随机数组方法
  • 有趣的是,我知道索引需要是唯一的——代表所有 50 名玩家,并打算这样写,但由于某种原因最终没有这样做。以 Rob 的修正为例。对于洗牌,您只需要遍历所有条目一次,每次将该条目与随机的第二个条目交换。这意味着给定条目可以简单地与自身交换,但这没关系,这是一个有效的随机化。
  • 你描述的算法有缺陷,用户:blog.codinghorror.com/the-danger-of-naivete坚持Fisher-Yates。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-28
  • 1970-01-01
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
  • 1970-01-01
  • 2013-01-07
相关资源
最近更新 更多