Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you check an array for a range in Ruby?

Tags:

arrays

ruby

I'm writing a poker program, and I can't figure out how to handle straights.

Straight: All cards in a hand of 5 cards are consecutive values. ex. 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"]

How can I check a hand, which is an array, for these combinations? Preferably I can check it to see if it's 5 in a row in the cards array.

like image 307
Rachel Avatar asked Apr 13 '15 05:04

Rachel


People also ask

How do you find the range of an array in Ruby?

Getting Started with Ranges We use the double dot (..) and triple dot (…) to create a range in Ruby. The double dot notation produces a range of values, including the start and end values of the range. On the other hand, the three-dot notation will exclude the end (high) value from the list of values.

How do I check if an array contains something in Ruby?

The include?() method checks whether or not the given object is present in the array.

Can I use range in an array?

For example, you can reduce 12 to 2, 3 or 6. The range of an array is the difference between the maximum element of the array and the minimum element. You want to find the minimum range of the array by doing any number of operations on the array. You can do multiple operations on a single element as well.


2 Answers

Edit 2: This is my absolutely final solution:

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 

Edit 1: @mudasobwa upset the apple cart by pointing out that 'A',2,3,4,5 is a valid straight. I believe I've fixed my answer. (I trust he's not going to tell me that 'K','A',2,3,4 is also valid.)

I would suggest the following:

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
like image 122
Cary Swoveland Avatar answered Sep 30 '22 20:09

Cary Swoveland


If we map each card to a value (9 is 9, "T" is 10, "J" is 11, etc.), then there are two facts that are true of all straights that we can use to solve our problem:

  1. All straights have exactly five unique card values
  2. The difference between the last and first cards' values is always 4

And so:

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

This method (1) converts each card to its numeric value with map, then (2) sorts them, and then (3) throws out duplicates with uniq. To illustrate with various hands:

    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

Alternatively...

I originally posted the following solution, which isn't bad, but is definitely more convoluted:

If the hand is sorted, this is easy. You can use Enumerable#each_cons to check each possible straight.

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) returns each consecutive set of 5 items, so in the above example hand is first compared to [ 2, 3, 4, 5, 6 ], then [ 3, 4, 5, 6, 7 ], and then [ 4, 5, 6, 7, 8 ], which is a match, so any? returns true.

Note that this is not the most efficient solution, but unless you need to check many thousands of hands per second, this is more than adequately performant.

If your hands aren't sorted yet, you'll need to do that first. The simplest way to do that is create a Hash that maps cards to a numeric value (as above) and then use 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" ]
like image 29
Jordan Running Avatar answered Sep 30 '22 19:09

Jordan Running