如何在Ruby中检查数组是否在范围内?

5

我正在编写一款扑克游戏程序,但是我无法解决如何处理顺子。

顺子:五张牌的手牌中所有牌面值都是连续的。 例如:2..6、3..7、4..8、5..9、6..T、7..J、8..Q、9..K、T..A。

cards = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]

我该如何检查一个手牌(即数组)中是否包含以下组合?最好的方法是检查它是否在卡牌数组中连续有5张。

请发布您目前所拥有的代码。 - Jordan Running
请注意,您的问题中缺少 1..5。在普通扑克牌中,Ace也被视为价值1。 - hirolau
7个回答

4
如果我们将每张牌映射到一个值(9是9,“T”是10,“J”是11等),那么所有顺子都有两个事实,可以用来解决我们的问题:
  1. 所有顺子都具有五个唯一的牌值
  2. 最后一张牌和第一张牌的值之间的差始终为4
因此:
CARD_VALUES = {
    2 =>  2,    3 =>  3,    4 =>  4,
    5 =>  5,    6 =>  6,    7 =>  7,
    8 =>  8,    9 =>  9,  "T" => 10,
  "J" => 11,  "Q" => 12,  "K" => 13,
  "A" => 14
}

def is_straight?(hand)
  hand_sorted = hand.map {|card| CARD_VALUES[card] }
    .sort.uniq

  hand_sorted.size == 5 &&
    (hand_sorted.last - hand_sorted.first) == 4
end

这种方法 (1) 使用 map 将每张牌转换为它的数字值,然后 (2) 进行排序,最后 (3) 使用 uniq 去除重复项。以下是各种手牌的示例:

    hand |  4   A   T   A   2 |  2   2   3   3   4 |  5   6   4   8   7 |  3  6  2  8  7
---------+--------------------+--------------------+--------------------+----------------
 1. map  |  4  14  10  14   2 |  2   2   3   3   4 |  5   6   4   8   7 |  3  6  2  8  7
 2. sort |  2   4  10  14  14 |  2   2   3   3   4 |  4   5   6   7   8 |  2  3  6  7  8
 3. uniq |  2   4  10  14     |  2   3   4         |  4   5   6   7   8 |  2  3  6  7  8

或者……

我最初发布了以下解决方案,虽然不错,但有点复杂:

如果手牌已排序,则很容易实现。您可以使用Enumerable#each_cons检查每个可能的顺子。

CARDS = [ 2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A" ]
hand = [ 4, 5, 6, 7, 8 ]

def is_straight?(hand)
  CARDS.each_cons(5).any? do |straight|
    hand == straight
  end
end

if is_straight?(hand)
  puts "Straight!"
else
  puts "Not straight!"
end
# => Straight!
each_cons(5)函数返回连续的5项,因此在上面的示例中,hand首先与[ 2, 3, 4, 5, 6 ]进行比较,然后是[ 3, 4, 5, 6, 7 ],再是[ 4, 5, 6, 7, 8 ],这是一个匹配,因此any?函数返回true
请注意,这不是最有效的解决方案,但除非您需要每秒检查成千上万个手牌,否则它的性能已经足够。
如果您的手牌尚未排序,则需要先进行排序。最简单的方法是创建一个将卡片映射到数字值的哈希表(如上所述),然后使用sort_by函数:
def sort_hand(hand)
  hand.sort_by {|card| CARD_VALUES[card] }
end

hand = [ 4, "A", 2, "A", "T" ]
sort_hand(hand)
# => [ 2, 4, "T", "A", "A" ]

1
好的回答。考虑使用hand.sort_by { |card| CARDS.index(card) }代替哈希表。 - Cary Swoveland
我更喜欢哈希表,因为它的时间复杂度是O(1),而使用index则是O(c)。在这种情况下,由于c=12(非常小!),所以我认为这取决于个人偏好。 - Jordan Running
我可以理解在玩七张牌扑克时担心效率问题,但对于各种五张牌游戏,我不会太过担心效率。 - Cary Swoveland

4

编辑2: 这是我的绝对最终解决方案:

require 'set'
STRAIGHTS = ['A',*2..9,'T','J','Q','K','A'].each_cons(5).map(&:to_set)
  #=> [#<Set: {"A", 2, 3, 4, 5}>, #<Set: {2, 3, 4, 5, 6}>,
  #   ...#<Set: {9, "T", "J", "Q", "K"}>, #<Set: {"T", "J", "Q", "K", "A"}>]

def straight?(hand)
  STRAIGHTS.include?(hand.to_set)
end

STRAIGHTS.include?([6,3,4,5,2].to_set)
  # STRAIGHTS.include?(#<Set: {6, 3, 4, 5, 2}>)
  #=> true 

straight?([6,5,4,3,2])            #=> true 
straight?(["T","J","Q","K","A"])  #=> true 
straight?(["A","K","Q","J","T"])  #=> true
straight?([2,3,4,5,"A"])          #=> true 

straight?([6,7,8,9,"J"])          #=> false 
straight?(["J",7,8,9,"T"])        #=> false 
编辑1: @mudasobwa指出'A',2,3,4,5是有效的顺子,打破了常规。我相信我已经修正了我的回答。(我相信他不会告诉我'K','A',2,3,4也是有效的。)
我建议采取以下措施:
CARDS     = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
STRAIGHTS = CARDS.each_cons(5).to_a
  #=>[[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8],
  #   [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"],
  #   [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"],
  #   ["T", "J", "Q", "K", "A"]] 

def straight?(hand)
  (hand.map {|c| CARDS.index(c)}.sort == [0,1,2,3,12]) ||
  STRAIGHTS.include?(hand.sort {|a,b| CARDS.index(a) <=> CARDS.index(b)})
end

我认为hand.sort_by {|card| CARDS.index(card) }更好一些。 - Jordan Running
@Jordan,也许吧。在看了你的解决方案之前,我没想到可以使用 sort_by - Cary Swoveland
我有同样的问题。'K','A',2,3,4是否有效?以及所有其他类似的,直到"J","Q","K","A",2 - shivam
1
我建议如果@udasobwa邀请你加入他的周四晚扑克小组,你应该尊重地拒绝。 - Cary Swoveland
1
@Gagan,这是hirolau建议的将"A",2,3,4,5包含在顺子手牌中的方法。 - Cary Swoveland
显示剩余4条评论

1
生成有效手牌列表:
valid_hands = cards[0..8].each_with_index.map{|b,i| cards[i..i+4]}
#=> [[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8], [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"], [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"], ["T", "J", "Q", "K", "A"]]

一旦你获取了所有有效手牌的列表,你现在可以检查提供的手牌是否在它们(有效手牌)中的any?之一:

if valid_hands.any? { |h| (h - hand).empty? } 
   puts "Valid hand"
else
   puts "Not Valid"
end

更新

在情况 2,3,4,5,“A”2,3,4,“K”,“A”2,3,“Q”,“K”,“A”2,“J”,“Q”,“K”,“A” 中,也将其视为有效的手牌,并根据以下方式进行计算:

valid_hands = cards.each_with_index.map { |b,i| i < 9 ? cards[i..i+4] : cards[0..i-9] + cards[i..-1] }
# => [[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8], [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"], [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"], ["T", "J", "Q", "K", "A"], [2, "J", "Q", "K", "A"], [2, 3, "Q", "K", "A"], [2, 3, 4, "K", "A"], [2, 3, 4, 5, "A"]]

1
cards.each_with_index.map - Aleksei Matiushkin
嗯。hand = [3,4,5,"A","Q"]; hand.sort #=> ArgumentError: comparison of Fixnum with String failed. 如果你将整数改为字符串,仍然存在问题:hand.map(&:to_s).sort #=> ["3", "4", "5", "A", "Q"] - Cary Swoveland
@CarySwoveland 已修复 :) - shivam
1
我喜欢你修改后的答案。在我看来,这是目前为止最好的答案。我也在考虑那个方向,但认为重复可能会成为一个问题,但直接牌没有重复。干得好。 - Cary Swoveland
顺便说一句,这个答案仍然是错误的。A2345是一个完全有效的街道牌组。 - Aleksei Matiushkin
1
@mudasobwa 是一个高手。如果他说是这样,那就是这样。我想我们都得重新开始... - Cary Swoveland

1

我本不想参与,但看到周围这些过于复杂的解决方案,我无法保持沉默。

hand = [2, 5, 7, 'A', 'J'].map(&:to_s)

'23456789TJQKA' =~ hand.sort_by{|hc| '23456789TJQKA'.index(hc)}.join ||
   'A23456789TJQK' =~ hand.sort_by{|hc| 'A23456789TJQK'.index(hc)}.join

以非愚蠢的硬编码方式:
suit = '23456789TJQKA'

suit =~ hand.sort_by{|hc| suit.index(hc)}.join ||
   suit.rotate(-1) =~ hand.sort_by{|hc| suit.rotate(-1).index(hc)}.join

1

我建议编写类来表示一张卡牌(也许是牌堆和手牌)。目标是实现以下接口:

deck = Deck.new.shuffle!
hand = Hand.new(deck.draw 5)
hand.straight?
#=>false
puts hand
88♦ T♠ 27

封装功能可提高代码可读性并便于扩展(例如添加花色)。
这里有一个更简单的版本,实现为单个Card类。不过我确实添加了花色。
class Card
  include Enumerable #enables sorting
  attr_accessor :value, :suit

  @values = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
  @suits  = ["♣","♦","♥","♠"]

  def self.all
    @values.product(@suits).map{|c| Card.new c}
  end

  def self.straight?(cards)
    ["A", *@values].each_cons(5).include?(cards.map(&:value))
  end

  def self.flush?(cards)
    cards.map(&:suit).uniq.size == 1
  end

  def initialize(v)
    @value, @suit = *v
  end

  def <=>(other) #for sorting
    @values.index(value) <=> @values.index(other.value)
  end

  def to_s
    "#{value}#{suit}"
  end
end

这个的意思是:"

这的工作原理如下

"。
deck = Card.all
puts deck
#=> 2♣ 2♦ 2♥ 2♠ 3♣ 3♦ 3♥ 3♠ 4♣ 4♦ 4♥ 4♠ 5♣ 5♦ 5♥ 5♠ 6♣ 6♦ 6♥ 6♠ 7♣ 7♦ 7♥ 7♠ 8♣ 8♦ 8♥ 8♠ 9♣ 9♦ 9♥ 9♠ T♣ T♦ T♥ T♠ J♣ J♦ J♥ J♠ Q♣ Q♦ Q♥ Q♠ K♣ K♦ K♥ K♠ A♣ A♦ A♥ A♠
hand = deck.sample 5
puts hand
#=> Q♥ 6♦ 2♣ T♠ Q♦
Card.straight?(hand)
#=>false

0

步骤0:从一个空类开始

class CardUtils
end

步骤 1:将卡的值存储在哈希中

哈希 可以快速引用卡的值。

@@card_values = {
    'A' => 1,   2  => 2,   3  => 3, 4 => 4,  5 => 5,
     6  => 6,   7  => 7,   8  => 8, 9 => 9, 'T' => 10,
    'J' => 11, 'Q' => 12, 'K' => 13
}

因此,您可以像以下这样简单地引用卡的值。
@@card_values['A']
# => 1

@@card_values[8]
# => 8

步骤2:整理手牌

参考卡牌点数,使用排序方法对手牌进行整理。

def self.sort(hand)
    hand.sort {|x,y| @@card_values[x] <=> @@card_values[y]}
end
#  => ["A", 2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K"] 

步骤三:判断两张牌是否连续的函数

def self.is_consecutive(x, y)
    val_x = @@card_values[x]
    val_y = @@card_values[y]

    val_x == val_y - 1 || val_x + 13 == val_y
end
# is_consecutive('A', 2)
#  => true
# is_consecutive('K', 'A')
#  => true
# is_consecutive('A', 3)
#  => false

步骤四:检查是否为“直”的

可以通过简单的迭代来完成。

def self.has_straight(hand)
    hand = sort(hand)

    max_consecutive_count = 0
    consecutive_count = 0

    hand.each_with_index do |curr, i|
        prev = hand[i - 1]

        if is_consecutive(prev, curr) then
            consecutive_count += 1
        else
            consecutive_count = 0
        end

        if consecutive_count > max_consecutive_count then
            max_consecutive_count = consecutive_count
        end
    end

    max_consecutive_count >= 5
end
# hand = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
# CardUtils.has_straight(hand)
#  => true

最终结果

class CardUtils
    @@card_values = {
        'A' => 1,   2  => 2,   3  => 3, 4 => 4,  5 => 5,
         6  => 6,   7  => 7,   8  => 8, 9 => 9, 'T' => 10,
        'J' => 11, 'Q' => 12, 'K' => 13
    }

    def self.is_consecutive(x, y)
        val_x = @@card_values[x]
        val_y = @@card_values[y]

        val_x == val_y - 1 || val_x + 13 == val_y
    end

    def self.sort(hand)
        hand.sort {|x,y| @@card_values[x] <=> @@card_values[y]}
    end

    def self.has_straight(hand)
        hand = sort(hand)

        max_consecutive_count = 0
        consecutive_count = 0

        hand.each_with_index do |curr, i|
            prev = hand[i - 1]

            if is_consecutive(prev, curr) then
                consecutive_count += 1
            else
                consecutive_count = 0
            end

            if consecutive_count > max_consecutive_count then
                max_consecutive_count = consecutive_count
            end
        end

        max_consecutive_count >= 5
    end
end

жҳҫ然пјҢжӮЁд»Һз»“жһңдёӯеҲ йҷӨдәҶTJQKAиЎ—йҒ“гҖӮ - Aleksei Matiushkin
@mudasobwa 谢谢,我已经添加了连续卡牌检查的确切实现。 - Gavin
我没有仔细检查,但现在你很可能将 QKA23 包含在结果中。 - Aleksei Matiushkin
@mudasobwa 第二个条件检查了环绕: val_x + 13 == val_y - Gavin

0

这是我会写的方式:

hand  = [3,4,5,2,'A']


def is_straight(hand)

  # No need to check further if we do not have 5 unique cards.
  return false unless hand.uniq.size == 5

  # Note the A at beginning AND end to count A as 1 or 14.
  list_of_straights = 'A23456789TJQKA'.chars.each_cons(5)

  sorted_hand = hand.map(&:to_s).sort

  list_of_straights.any? do |straight| 
    straight.sort==sorted_hand
  end

end

puts is_straight(hand) #=> true  

或者,如果您不喜欢所有的排序,您可以将最后一部分更改为:

  hand_as_stings = hand.map(&:to_s)

  list_of_straights.any? do |straight| 
    (straight-hand_as_stings).empty?
  end

@mudasobwa 你的意思是什么?在代码后面,我们会执行 straight.sort 并且它会以同样的方式进行排序。 - hirolau
糟糕,抱歉,是我的错误。 - Aleksei Matiushkin
你需要检查 hand.uiq.size==5 吗? - Cary Swoveland
@CarySwoveland 这不会改变算法的结果,但由于大多数手牌不是顺子,因此在开始时进行简单检查将节省大量计算。话虽如此,我可能在这里犯了过早优化的错误。 - hirolau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接