请帮我加快这个麻将算法的速度

发布于 2024-08-14 19:15:44 字数 4621 浏览 4 评论 0原文

我正在用 JavaScript 编写一些与麻将相关的函数。

这是我下面的内容,其中包含测试用例的代码。

请注意,麻将手牌是用数组表示的,其中:

  • 元素 0 是这手牌中的牌总数
  • 元素 1 到 34 是这手牌中每种类型的牌数
    • 首先是嘎嘎声,然后是点,然后是砰砰声,然后是风,最后是龙

查找等待的函数运行得非常慢。我怎样才能加快速度?

// tiles are indexed as follows:
// 1..9 = 1 crak .. 9 crak
// 10..18 = 1 dot .. 9 dot
// 19..27 = 1 bam .. 9 bam
// 28..34 = east, south, west, north, white, green, red

var wall = new Array();
set_up_wall();

function set_up_wall() {
  for (var i=1; i<=34; i++) wall[i] = 4;
  wall[0]=136;
}

// draw tile from wall
function draw() {
  var fudge = 1-(1e-14);
  var n = Math.floor(Math.random()*wall[0]*fudge);
  var i = 1;
  while (n>=wall[i]) n-=wall[i++];
  wall[i]--;
  wall[0]--;
  return i;
}

// get number of a tile (or 0 if honor)
// e.g. 8 bams = 8
function tilenum(i) {
  if (i>27) return 0;
  if (i%9==0) return 9;
  return i%9;
}

// get suit of a tile (or 0 if honor)
function tilesuit(i) {
  var eps = 1e-10;
  return Math.ceil(i/9-eps)%4;
}

// is this a well-formed hand?
function well_formed(h) {
  // this function is recursive
  if (h[0]==2) return only_pairs(h);
  if (h[0]==14) {
    if (only_pairs(h)) return true;
    if (thirteen_orphans(h)) return true;
  }
  if (h[0]%3 != 2) return false; // wrong no. of tiles in hand
  // now we start splitting up the hand
  // look for three of a kind
  for (var i=1; i<=34; i++) {
    if (h[i]>=3) {
      // create new hand minus the three of a kind
      hh = new Array();
      for (var j=0; j<=34; j++) hh[j]=h[j];
      hh[0]-=3;
      hh[i]-=3;
      if (well_formed(hh)) return true;
    }
  }
  // look for a run of three
  for (var i=1; i<=25; i++) {
    if (tilenum(i)<=7) {
      if (h[i]>=1 && h[i+1]>=1 && h[i+2]>=1) {
      // create new hand minus the run
      hh = new Array();
      for (var j=0; j<=34; j++) hh[j]=h[j];
      hh[0]-=3;
      hh[i]--; hh[i+1]--; hh[i+2]--;
      if (well_formed(hh)) return true;
      }
    }
  }
  // if we reach here, we have exhausted all possibilities
  return false;
}

// is this hand all pairs?
function only_pairs(h) {
  for (var i=1; i<=34; i++) if (h[i]==1 || h[i]>=3) return false;
  return true;
}

// thirteen orphans?
function thirteen_orphans(h) {
  var d=0;
  var c=new Array(14, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,1,1,1,1,1,1);
  for (var i=0; i<=34; i++) {
    if (c[i]==0 && h[i]>0) return false;
    if (h[i]!=c[i]) d++;
  }
  return d==1;
}

// this is inefficient
function waits(h) {
  var w=new Array();
  for (var j=0; j<=34; j++) w[j]=false;  
  if (h[0]%3!=1) return w; // wrong no. of tiles in hand
  // so we don't destroy h
  var hh = new Array();
  for (var j=0; j<=34; j++) hh[j]=h[j];
  for (var i=1; i<=34; i++) {
    // add the tile we are trying to test
    hh[0]++; hh[i]++;
    if (hh[i]<5) { // exclude hands waiting for a nonexistent fifth tile
      if (well_formed(hh)) {
        w[0] = true;
        w[i] = true;
      }
    }
    hh[0]--; hh[i]--;
  }
  return w;
}

function tiles_to_string(t) { // strictly for testing purposes
  var n;
  var ss="";
  var s = "x 1c 2c 3c 4c 5c 6c 7c 8c 9c 1d 2d 3d 4d 5d 6d 7d 8d 9d ";
  s += "1b 2b 3b 4b 5b 6b 7b 8b 9b Ew Sw Ww Nw Wd Gd Rd";
  s=s.split(" ");
  for (var i=1; i<=34; i++) {
    n=t[i]*1; // kludge
    while (n--) ss+=(" "+s[i]);
  }
  return ss;
}

// tests

var x;
x = new Array(13, 0,0,0,0,0,1,2,2,2, 2,2,2,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,1,1,1,1,1,1);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 0,0,0,0,0,0,0,0,0, 3,1,1,1,1,1,1,1,3, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 4,0,0,3,3,3,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 0,0,4,3,3,3,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");

I am writing some mahjong-related functions in JavaScript.

Here is what I have below, with code for test cases.

Note that mahjong hands are represented by arrays, with:

  • element 0 being the total number of tiles in the hand
  • elements 1 through 34 being the number of tiles of each type in the hand
    • first craks, then dots, then bams, then winds, and finally dragons

The function to find waits runs really slow. How can I speed it up?

// tiles are indexed as follows:
// 1..9 = 1 crak .. 9 crak
// 10..18 = 1 dot .. 9 dot
// 19..27 = 1 bam .. 9 bam
// 28..34 = east, south, west, north, white, green, red

var wall = new Array();
set_up_wall();

function set_up_wall() {
  for (var i=1; i<=34; i++) wall[i] = 4;
  wall[0]=136;
}

// draw tile from wall
function draw() {
  var fudge = 1-(1e-14);
  var n = Math.floor(Math.random()*wall[0]*fudge);
  var i = 1;
  while (n>=wall[i]) n-=wall[i++];
  wall[i]--;
  wall[0]--;
  return i;
}

// get number of a tile (or 0 if honor)
// e.g. 8 bams = 8
function tilenum(i) {
  if (i>27) return 0;
  if (i%9==0) return 9;
  return i%9;
}

// get suit of a tile (or 0 if honor)
function tilesuit(i) {
  var eps = 1e-10;
  return Math.ceil(i/9-eps)%4;
}

// is this a well-formed hand?
function well_formed(h) {
  // this function is recursive
  if (h[0]==2) return only_pairs(h);
  if (h[0]==14) {
    if (only_pairs(h)) return true;
    if (thirteen_orphans(h)) return true;
  }
  if (h[0]%3 != 2) return false; // wrong no. of tiles in hand
  // now we start splitting up the hand
  // look for three of a kind
  for (var i=1; i<=34; i++) {
    if (h[i]>=3) {
      // create new hand minus the three of a kind
      hh = new Array();
      for (var j=0; j<=34; j++) hh[j]=h[j];
      hh[0]-=3;
      hh[i]-=3;
      if (well_formed(hh)) return true;
    }
  }
  // look for a run of three
  for (var i=1; i<=25; i++) {
    if (tilenum(i)<=7) {
      if (h[i]>=1 && h[i+1]>=1 && h[i+2]>=1) {
      // create new hand minus the run
      hh = new Array();
      for (var j=0; j<=34; j++) hh[j]=h[j];
      hh[0]-=3;
      hh[i]--; hh[i+1]--; hh[i+2]--;
      if (well_formed(hh)) return true;
      }
    }
  }
  // if we reach here, we have exhausted all possibilities
  return false;
}

// is this hand all pairs?
function only_pairs(h) {
  for (var i=1; i<=34; i++) if (h[i]==1 || h[i]>=3) return false;
  return true;
}

// thirteen orphans?
function thirteen_orphans(h) {
  var d=0;
  var c=new Array(14, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,1,1,1,1,1,1);
  for (var i=0; i<=34; i++) {
    if (c[i]==0 && h[i]>0) return false;
    if (h[i]!=c[i]) d++;
  }
  return d==1;
}

// this is inefficient
function waits(h) {
  var w=new Array();
  for (var j=0; j<=34; j++) w[j]=false;  
  if (h[0]%3!=1) return w; // wrong no. of tiles in hand
  // so we don't destroy h
  var hh = new Array();
  for (var j=0; j<=34; j++) hh[j]=h[j];
  for (var i=1; i<=34; i++) {
    // add the tile we are trying to test
    hh[0]++; hh[i]++;
    if (hh[i]<5) { // exclude hands waiting for a nonexistent fifth tile
      if (well_formed(hh)) {
        w[0] = true;
        w[i] = true;
      }
    }
    hh[0]--; hh[i]--;
  }
  return w;
}

function tiles_to_string(t) { // strictly for testing purposes
  var n;
  var ss="";
  var s = "x 1c 2c 3c 4c 5c 6c 7c 8c 9c 1d 2d 3d 4d 5d 6d 7d 8d 9d ";
  s += "1b 2b 3b 4b 5b 6b 7b 8b 9b Ew Sw Ww Nw Wd Gd Rd";
  s=s.split(" ");
  for (var i=1; i<=34; i++) {
    n=t[i]*1; // kludge
    while (n--) ss+=(" "+s[i]);
  }
  return ss;
}

// tests

var x;
x = new Array(13, 0,0,0,0,0,1,2,2,2, 2,2,2,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1, 1,1,1,1,1,1,1);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 0,0,0,0,0,0,0,0,0, 3,1,1,1,1,1,1,1,3, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 4,0,0,3,3,3,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");
x = new Array(13, 0,0,4,3,3,3,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0);
document.write("Hand: "+tiles_to_string(x)+"<br />");
document.write("Waits: "+tiles_to_string(waits(x))+"<br /><br />");

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

与风相奔跑 2024-08-21 19:15:44

您有一个数组来保存手牌的内容,然后您在递归函数中创建一个新数组来保存每次减去一组特定图块的内容。与其创建所有这些数组,不如创建两个数组 - 一个用于容纳正在考虑的手,另一个用于容纳您已经考虑过的手上的牌 - 然后将它们都传递出去。所以这个:

hh = new Array();
for (var j=0; j<=34; j++) hh[j]=h[j];
hh[0]-=3;
hh[i]-=3;
if (well_formed(hh)) return true;

变成这样:

h[0]-=3;
h[i]-=3;
hc[0]+=3;
hc[i]+=3;
if (well_formed(h,hc)) return true;

你传递 h 和 hc ,并记住要重建整个手牌,你需要添加两个数组。但这可以在考虑 hnd 是否完整时进行。

编辑:这是我更详细的意思:
编辑:我希望现在可以工作......第一次尝试时出现拼写错误。

// is this a well-formed hand?
function well_formed(h) {
  hc = new Array();
  for (var i=0; i<=34; i++) hc[i]=0;
  result = well_formed_recursive(h, hc);
  for (var i=0; i<=34; i++) h[i]+=hc[i];
  return result;
}

function well_formed_recursive(h, hc) {
  // this function is recursive
  if (h[0]==2) return only_pairs(h);
  if (h[0]==14) {
    if (only_pairs(h)) return true;
    if (thirteen_orphans(h)) return true;
  }
  if (h[0]%3 != 2) return false; // wrong no. of tiles in hand
  // now we start splitting up the hand
  // look for three of a kind
  for (var i=1; i<=34; i++) {
    if (h[i]>=3) {
      h[0]-=3;
      h[i]-=3;
      hc[0]+=3;
      hc[i]+=3;
      if (well_formed_recursive(h,hc)) return true;
    }
  }
  // look for a run of three
  for (var i=1; i<=25; i++) {
    if (tilenum(i)<=7) {
      if (h[i]>=1 && h[i+1]>=1 && h[i+2]>=1) {
        h[0]-=3;
        h[i]--; h[i+1]--; h[i+2]--;
        hc[0]+=3;
        hc[i]++; hc[i+1]++; hc[i+2]++;
        if (well_formed_recursive(h,hc)) return true;
      }
    }
  }
  // if we reach here, we have exhausted all possibilities
  return false;
}

You have an array to hold the contents of the hand, and then you are creating a new array to hold the contents minus a particular set of tiles each time - in a recursive function. Instead of all this array creation, create two arrays - one to hold the hand under consideration, the other to hold the tiles from the hand that you have already considered - and just pass them both around. So this:

hh = new Array();
for (var j=0; j<=34; j++) hh[j]=h[j];
hh[0]-=3;
hh[i]-=3;
if (well_formed(hh)) return true;

becomes this:

h[0]-=3;
h[i]-=3;
hc[0]+=3;
hc[i]+=3;
if (well_formed(h,hc)) return true;

You pass both h and hc around, and bear in mind that to reconstruct the whole hand, you need to add the two arrays. But this can come right at the end of considering whether or not the hnd is complete.

EDIT: Here is what I mean in more detail:
EDIT: Now working I hope... typo in first attempt.

// is this a well-formed hand?
function well_formed(h) {
  hc = new Array();
  for (var i=0; i<=34; i++) hc[i]=0;
  result = well_formed_recursive(h, hc);
  for (var i=0; i<=34; i++) h[i]+=hc[i];
  return result;
}

function well_formed_recursive(h, hc) {
  // this function is recursive
  if (h[0]==2) return only_pairs(h);
  if (h[0]==14) {
    if (only_pairs(h)) return true;
    if (thirteen_orphans(h)) return true;
  }
  if (h[0]%3 != 2) return false; // wrong no. of tiles in hand
  // now we start splitting up the hand
  // look for three of a kind
  for (var i=1; i<=34; i++) {
    if (h[i]>=3) {
      h[0]-=3;
      h[i]-=3;
      hc[0]+=3;
      hc[i]+=3;
      if (well_formed_recursive(h,hc)) return true;
    }
  }
  // look for a run of three
  for (var i=1; i<=25; i++) {
    if (tilenum(i)<=7) {
      if (h[i]>=1 && h[i+1]>=1 && h[i+2]>=1) {
        h[0]-=3;
        h[i]--; h[i+1]--; h[i+2]--;
        hc[0]+=3;
        hc[i]++; hc[i+1]++; hc[i+2]++;
        if (well_formed_recursive(h,hc)) return true;
      }
    }
  }
  // if we reach here, we have exhausted all possibilities
  return false;
}
月光色 2024-08-21 19:15:44

要复制数组,请使用 concat 函数。

var a=[1,2,3,4];
var b=a.concat();

To duplicate an array use the concat function.

var a=[1,2,3,4];
var b=a.concat();
深海不蓝 2024-08-21 19:15:44

我可以看到,在性能方面存在两件事错误。

首先是 David M 已经指出的:每次在 well_formed() 中递归时停止复制整个数组,只需在递归之前添加更改并在返回时取消添加,就像在 waits() 函数中所做的那样。

其次,在 well_formed() 中,每次对手进行一次增量更改时,都会重新扫描整个数组。这本质上是低效的,相反,您应该寻找机会保留“状态计数器”。

例如,如果您始终知道自己有多少对,则可以轻松检查 only_pairs() 。您无需扫描 hand() 数组中的任何非对,而是保留pairs_counter作为数组(或其关联上下文)的一部分,每当您向 hand[i] 添加一张牌时,您都会检查 hand[i]=2如果对计数器为 3,则增加它,如果它为 3,则减少它。同样,当您移除一张牌时,如果 hand[j] = 2,您将增加配对计数器,但如果它等于 1,您将减少它。

您可以在很多地方采用此策略,它将对您的绩效产生重大影响。

Two things wrong performance-wise, that I can see.

First is what David M has already noted: stop copying the whole array every time you recurse in well_formed(), just add in the changes before you recurse and back out the additions when you return, just as you did in your waits() function.

Second is that in well_formed() you keep rescanning the whole array every time you make a single incremental change to the hand. That's inherently inefficient, instead you should look for opportunities to keep "state counters" instead.

For instance you could easily check for only_pairs() if you always know how many pairs you had. Instead of scanning the hand() array for any non-pairs, you keep a pairs_counter as part of the array (or its associated context), whenever you add a card to hand[i] then you check if hand[i]=2 you increment the pairs counter if it's 3, you decrement it. Likewise when you remove a card, if hand[j] = 2 you increment the pairs counter, but if it equals 1 your decrement it.

There are a number of places where you can employ this strategy and it will have a big impact on your performance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文