如何在不存储卡片的情况下实施经销商类别?

  • 即使只有52张牌,我在“ 解释”部分描述的permutationIndex也是一个巨大的数字; 它是52!一个数字52! ,需要29个字节来存储。

    因此, 我不知道计算大范围的permutationIndex简单方法 ,并以最小成本存储索引,或者也可以计算它。 我在想这个问题的解决方案是三种算法:

    1. 一种算法,它计算正确的permutationIndex实现Dealing方法

    2. 一种计算正确permutationIndex实现Collect方法的算法

    3. 一种以最小成本存储(或计算) permutationIndex的算法

  • 说明

    我最初尝试使用置换实现一个范围从int.MinValeint.MaxValue整数句柄生成器

    因为范围非常大,所以我从实现一个Dealer开始, 有52张卡,它们并不真正存储像hashset或array这样的卡片组,甚至不需要随机 (初始除外)。

    对于给定范围的序数,我认为其中一个完整排列的每个序列都有一个索引,并将其命名为permutationIndex 。 我使用索引来记住它是哪个排列而不是真正存储序列。 序列是卡片组的可能顺序之一。

    这里有一个动画图形模拟示例,以显示我的想法。 在此输入图像描述

    每次我发卡时,我都会更改permutationIndexdealt (已发卡的数量),我知道哪些卡是那些卡,哪些卡还在手中。 当我收回已发卡时,我会知道卡号,并将其放在顶部,它也会成为下次交易的卡。 在动画中, colleted卡号


有关更多信息,请按以下方式

  • 代码说明

    仅有三个3的概念样本Dealer类如下。 代码是用c#编写的,我也在考虑任何与语言无关的解决方案。

    以下是示例代码的一些描述

    • 使用Dealing()方法,我们得到一些处理的卡片。 它总是返回最右边的数字(与数组相关),然后通过更改permutationIndex将其左边的数字(比如下一个可用的数字)滚动到最右边的位置。

    • 方法Collect(int)用于收集并将处理后的卡片放回到牌组中。 它会改变permutationIndex ,根据卡的数量返回给经销商。

    • dealt的整数表示我们dealt了多少张牌; 从最左边到存储在dealt中的计数都是发牌。 使用permutationIndex ,我们知道卡的顺序。

    • 不使用示例代码中的int[,]数组,只是为了帮助设想排列。 switch语句被认为是用计算permutationIndex算法实现的。

    • permutationIndex与此答案中描述的内容相同
      快速置换 – >数字 – >置换映射算法

  • 示例代码

     public static class Dealer { public static void Collect(int number) { if(1>dealt) throw new IndexOutOfRangeException(); switch(permutationIndex) { case 5: case 0: switch(number) { case 3: break; case 2: permutationIndex=1; break; case 1: permutationIndex=4; break; } break; case 4: case 3: switch(number) { case 3: permutationIndex=5; break; case 2: permutationIndex=2; break; case 1: break; } break; case 2: case 1: switch(number) { case 3: permutationIndex=0; break; case 2: break; case 1: permutationIndex=3; break; } break; } --dealt; } public static int Dealing() { if(dealt>2) throw new IndexOutOfRangeException(); var number=0; switch(permutationIndex) { case 5: permutationIndex=3; number=3; break; case 4: permutationIndex=0; number=1; break; case 3: permutationIndex=1; number=1; break; case 2: permutationIndex=4; number=2; break; case 1: permutationIndex=5; number=2; break; case 0: permutationIndex=2; number=3; break; } ++dealt; return number; } static int[,] sample= new[,] { { 1, 2, 3 }, // 0 { 1, 3, 2 }, // 1 { 3, 1, 2 }, // 2 { 3, 2, 1 }, // 3 { 2, 3, 1 }, // 4 { 2, 1, 3 }, // 5 }; static int permutationIndex; static int dealt; } 

这不完全是你想要在这里完成的,但如果你想从一副牌的随机排序处理,你使用一个shuffle算法。 典型的混洗算法是Fisher-Yates 。 shuffle算法将创建一个以随机顺序列出卡号的数组(13,5,7,18,22,…等)。 处理你从数组中的第一个元素开始并继续前进。

我也在努力查看整个图片,但你可以将每个排列转换为base(52),其中一个字符代表每张卡片,并且有一个代表每个排列的字符串。

所以黑桃可能是1-9 (ace - 9)0ABC (10, JQK) ,然后是DEFG ……开始心灵等等。

所以一张3张牌,2张Spade(2),3张Heart(F)和2张Diamond(比如说e),会有这些排列数字:

 2Fe 2eF F2e Fe2 eF2 e2F 

您可以通过将基数52转换为基数为10的转换来来回转换为int / long / bigint。

这是对基数之间转换的介绍 。

所以e2F将是F + 2*52 + e * 52^2 ,这将是16 + 2*52 + 43*52*52 = 116392

所以116392将是你的排列数。

(顺便说一下。我猜它是2颗钻石是’e’和43,你可以数一数,看看它究竟是什么样的)

解决此问题的一种方法是使用(伪)随机数生成器(如Mersenne Twister ),然后仅存储每笔交易的种子编号。 由于每次从同一种子获得相同的随机数序列,它用于表示整个交易(使用从该种子生成的随机数来驱动所发出的卡)。

[编辑…]

该交易的一些伪代码:

 while (#cards < cardsNeed) card = getCard(random()) if (alreadyHaveThisCard(card)) continue [do something with the card...] 

如果我理解正确,以下代码实现了这一点:

 public class Dealer { public int Dealing() { var number= _freeCards.Count>0 ?_freeCards.Dequeue() :_lastNumber++; _dealtCards.Add(number); return number; } public void Collect(int number) { if(!_dealtCards.Remove(number)) throw new ArgumentException("Card is not in use", "number"); _freeCards.Enqueue(number); } readonly HashSet _dealtCards=new HashSet(); readonly Queue _freeCards=new Queue(); // "Pool" of free cards. int _lastNumber; } 

虽然我在理解你在这里想要实现的内容时遇到了一些问题,但我认为互质会产生一堆排列数; 那就是:如果你不太关心分配。 你可以使用Euclidian算法 。

代数(集合论)表明你可以简单地使用x =(x + coprime)%set.Length来获取集合中的所有元素。 我想每个互质都是你描述的排列数。

那就是说我不确定使用生成的互质作为“随机数发生器”时你得到的分布是什么; 我想某些分布将比其他分布更频繁地发生,并且许多分布将从生成的数字中排除,原因很简单,因为生成器将在环中选择数字。 我在这里有点创意,所以也许它符合你的需求,虽然它可能不是你想要的答案。

我真的不明白你的问题,但我这样解释:你想要计算一系列52张牌的permutationIndex 。 给定的排列索引一对一地映射到一系列卡片。 既然有52个! 52张卡的可能安排,你需要至少226位,或29字节。 那么,你的permutationIndex已经非常大了!

由于您的排列索引已经是29个字节长,因此一些额外的字节不会产生很大的差异并使解决方案变得更容易。


例如,您可以将拉丁字母的每个字母映射到卡片。 鉴于我们有26个小写字母,26个大写字母,我们有52个字母可以代表52张卡片。

   abcdefghijklm nopqrstuvwxyz
 ♥A234567890JQK♦A234567890JQK

   ABCDEFGHIJKLM NOPQRSTUVWXYZ
 ♣A234567890JQK♠A234567890JQK

现在你可以创建一个包含52个字母的字符串。 每个唯一的字母串代表52张卡的独特排列。 有了这个你可以:

  • 生成随机字符串以获得随机排列。
  • 通过查看给定位置的字母,立即找出卡片在哪里。
  • 轻松随机播放,重新排序,插入和移除卡片。

字符串中的每个字符都表示为(在C#中)为16位Unicode值,但对于52个卡,您只需要6位。 所以你有更多的选择来选择一个表示:

  1. 832位,或10​​4字节:52个Unicode字符的字符串
  2. 416位,或52字节:52字节的数组
  3. 320位或40字节:10个32位整数的数组,用于保存52 * 6位
  4. 312位或39字节:39字节的数组,用于保存52 * 6位
  5. 226位,或29个字节:绝对下限

表示3和4需要相当一些聪明的比特摆弄以获得特定卡的6位不在序列中。 我会推荐代表2,它保留了上面提到的大部分优点。


当您使用二进制表示而不是字符串表示时,您可以为每张卡创建一个具有唯一值的枚举,并使用:

 public enum Cards : byte { HeartsAce HeartsTwo // ... HeartsTen HeartsJack HeartsQueen HeartsKing DiamondsAce DiamondsTwo // ... SpadesTen SpadesJack SpadesQueen SpadesKing } 

你有一个非常有效的c#例子,在这个非常古老的post中, 对于n阶的第k个排列 (又名PermutationIndex):

对于那些对组合主题感兴趣的人:


在进入具体实施之前,我建议你仔细阅读。

和其他人一样,我不确定你想要做什么,但是如果你想在通信卡的通信/存储上节省尽可能多的空间,我会做以下事情:

我会使用带有flag属性的枚举来存储在单个Long上处理的卡片,因此我可以使用按位比较来查看已经处理了哪个卡片。

因为每张卡片都是一个单独的“旗帜”,其唯一编号设置为2的指数,所以它们永远不会发生冲突。

总的来说,即使您处理所有卡,存储仍将是8个字节。 你需要的任何额外数据,你可以在最后。

请参阅下面的工作示例。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication12 { class Program { static void Main(string[] args) { // Because each card is unique you could use Flag attributed Enum see the enum below and set each item a unique value I used 2 to the power of 52 Cards cardsdealt = Cards.Clubs_10 | Cards.Clubs_2 | Cards.Diamonds_3; if ((cardsdealt & Cards.Clubs_10) == Cards.Clubs_10) { Console.WriteLine("Card.Clubs_10 was dealt"); } // Storage would always be 8 bytes for the long data type } [Flags] public enum Cards : long { Spades_Ace = 1, Spades_2 = 2, Spades_3 = 4, Spades_4 = 8, Spades_5 = 16, Spades_6 = 32, Spades_7 = 64, Spades_8 = 128, Spades_9 = 256, Spades_10 = 512, Spades_Jack = 1024, Spades_Queen = 2048, Spades_King = 4096, Hearts_Ace = 8192, Hearts_2 = 16384, Hearts_3 = 32768, Hearts_4 = 65536, Hearts_5 = 131072, Hearts_6 = 262144, Hearts_7 = 524288, Hearts_8 = 1048576, Hearts_9 = 2097152, Hearts_10 = 4194304, Hearts_Jack = 8388608, Hearts_Queen = 16777216, Hearts_King = 33554432, Diamonds_Ace = 67108864, Diamonds_2 = 134217728, Diamonds_3 = 268435456, Diamonds_4 = 536870912, Diamonds_5 = 1073741824, Diamonds_6 = 2147483648, Diamonds_7 = 4294967296, Diamonds_8 = 8589934592, Diamonds_9 = 17179869184, Diamonds_10 = 34359738368, Diamonds_Jack = 68719476736, Diamonds_Queen = 137438953472, Diamonds_King = 274877906944, Clubs_Ace = 549755813888, Clubs_2 = 1099511627776, Clubs_3 = 2199023255552, Clubs_4 = 4398046511104, Clubs_5 = 8796093022208, Clubs_6 = 17592186044416, Clubs_7 = 35184372088832, Clubs_8 = 70368744177664, Clubs_9 = 140737488355328, Clubs_10 = 281474976710656, Clubs_Jack = 562949953421312, Clubs_Queen = 1125899906842620, Clubs_King = 2251799813685250, } } }