<commit_before>class PokerHand
  include Comparable
  include Enumerable
  attr_reader :hand

  @@allow_duplicates = true    # true by default
  def self.allow_duplicates; @@allow_duplicates; end
  def self.allow_duplicates=(v); @@allow_duplicates = v; end

  # Returns a new PokerHand object. Accepts the cards represented
  # in a string or an array
  #
  #     PokerHand.new("3d 5c 8h Ks")   # => #<PokerHand:0x5c673c ...
  #     PokerHand.new(["3d", "5c", "8h", "Ks"])  # => #<PokerHand:0x5c2d6c ...
  def initialize(cards = [])
    @hand = case cards
    when Array
      cards.map do |card|
        if card.is_a? Card
          card
        else
          Card.new(card.to_s)
        end
      end
    when String
      cards.scan(/\S{2}/).map { |str| Card.new(str) }
    else
      cards
    end

    check_for_duplicates unless allow_duplicates
  end

  # Returns a new PokerHand object with the cards sorted by suit
  # The suit order is spades, hearts, diamonds, clubs
  #
  #     PokerHand.new("3d 5c 8h Ks").by_suit.just_cards   # => "Ks 8h 3d 5c"
  def by_suit
    PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
  end

  # Returns a new PokerHand object with the cards sorted by face value
  # with the highest value first.
  #
  #     PokerHand.new("3d 5c 8h Ks").by_face.just_cards   # => "Ks 8h 5c 3d"
  def by_face
    PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
  end

  # Returns string representation of the hand without the rank
  #
  #     PokerHand.new(["3c", "Kh"]).just_cards     # => "3c Kh"
  def just_cards
    @hand.join(" ")
  end
  alias :cards :just_cards

  # Returns an array of the card values in the hand.
  # The values returned are 1 less than the value on the card.
  # For example: 2's will be shown as 1.
  #
  #     PokerHand.new(["3c", "Kh"]).face_values     # => [2, 12]
  def face_values
    @hand.map { |c| c.face }
  end

  # The =~ method does a regular expression match on the cards in this hand.
  # This can be useful for many purposes. A common use is the check if a card
  # exists in a hand.
  #
  #     PokerHand.new("3d 4d 5d") =~ /8h/           # => nil
  #     PokerHand.new("3d 4d 5d") =~ /4d/           # => #<MatchData:0x615e18>
  def =~ (re)
    re.match(just_cards)
  end

  def royal_flush?
    if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
      [[10], arrange_hand(md)]
    else
      false
    end
  end

  def straight_flush?
    if (md = (/.(.)(.)(?: 1.\2){4}/.match(delta_transform(true))))
      high_card = Card::face_value(md[1])
      arranged_hand = fix_low_ace_display(md[0] + ' ' +
          md.pre_match + ' ' + md.post_match)
      [[9, high_card], arranged_hand]
    else
      false
    end
  end

  def four_of_a_kind?
    if (md = (by_face =~ /(.). \1. \1. \1./))
      # get kicker
      result = [8, Card::face_value(md[1])]
      result << Card::face_value($1) if (md.pre_match + md.post_match).match(/(\S)/)
      return [result, arrange_hand(md)]
    end
    false
  end

  def full_house?
    if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./))
      arranged_hand = arrange_hand(md[0] + ' ' +
          md.pre_match + ' ' + md[2] + ' ' + md.post_match)
      [
        [7, Card::face_value(md[1]), Card::face_value(md[3])],
        arranged_hand
      ]
    elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/))
      arranged_hand = arrange_hand(md[4] + ' '  + md[1] + ' ' +
          md.pre_match + ' ' + md[3] + ' ' + md.post_match)
      [
        [7, Card::face_value(md[5]), Card::face_value(md[2])],
        arranged_hand
      ]
    else
      false
    end
  end

  def flush?
    if (md = (by_suit =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/))
      [
        [
          6,
          Card::face_value(md[1]),
          *(md[3..6].map { |f| Card::face_value(f) })
        ],
        arrange_hand(md)
      ]
    else
      false
    end
  end

  def straight?
    result = false
    if hand.size >= 5
      transform = delta_transform
      # note we can have more than one delta 0 that we
      # need to shuffle to the back of the hand
      i = 0
      until transform.match(/^\S{3}( [1-9x]\S\S)+( 0\S\S)*$/) or i >= hand.size  do
        # only do this once per card in the hand to avoid entering an
        # infinite loop if all of the cards in the hand are the same
        transform.gsub!(/(\s0\S\S)(.*)/, "\\2\\1")    # moves the front card to the back of the string
        i += 1
      end
      if (md = (/.(.). 1.. 1.. 1.. 1../.match(transform)))
        high_card = Card::face_value(md[1])
        arranged_hand = fix_low_ace_display(md[0] + ' ' + md.pre_match + ' ' + md.post_match)
        result = [[5, high_card], arranged_hand]
      end
    end
  end

  def three_of_a_kind?
    if (md = (by_face =~ /(.). \1. \1./))
      # get kicker
      arranged_hand = arrange_hand(md)
      matches = arranged_hand.match(/(?:\S\S ){2}(\S\S)/)
      if matches
        result = [4, Card::face_value(md[1])]
        matches = arranged_hand.match(/(?:\S\S ){3}(\S)/)
        result << Card::face_value($1) if matches
        matches = arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
        result << Card::face_value($2) if matches
        return [result, arranged_hand]
      end
    end
    false
  end

  def two_pair?
    # \1 is the face value of the first pair
    # \2 is the card in between the first pair and the second pair
    # \3 is the face value of the second pair
    if (md = (by_face =~ /(.). \1.(.*?) (.). \3./))
      # to get the kicker this does the following
      # md[0] is the regex matched above which includes the first pair and
      # the second pair but also some cards in the middle so we sub them out
      # then we add on the cards that came before the first pair, the cards
      # that were in-between, and the cards that came after.
      arranged_hand = arrange_hand(md[0].sub(md[2], '') + ' ' +
          md.pre_match + ' ' + md[2] + ' ' + md.post_match)<commit_msg>PokerHand#straight? should return false instead of nil for no match<commit_after>class PokerHand
  include Comparable
  include Enumerable
  attr_reader :hand

  @@allow_duplicates = true    # true by default
  def self.allow_duplicates; @@allow_duplicates; end
  def self.allow_duplicates=(v); @@allow_duplicates = v; end

  # Returns a new PokerHand object. Accepts the cards represented
  # in a string or an array
  #
  #     PokerHand.new("3d 5c 8h Ks")   # => #<PokerHand:0x5c673c ...
  #     PokerHand.new(["3d", "5c", "8h", "Ks"])  # => #<PokerHand:0x5c2d6c ...
  def initialize(cards = [])
    @hand = case cards
    when Array
      cards.map do |card|
        if card.is_a? Card
          card
        else
          Card.new(card.to_s)
        end
      end
    when String
      cards.scan(/\S{2}/).map { |str| Card.new(str) }
    else
      cards
    end

    check_for_duplicates unless allow_duplicates
  end

  # Returns a new PokerHand object with the cards sorted by suit
  # The suit order is spades, hearts, diamonds, clubs
  #
  #     PokerHand.new("3d 5c 8h Ks").by_suit.just_cards   # => "Ks 8h 3d 5c"
  def by_suit
    PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
  end

  # Returns a new PokerHand object with the cards sorted by face value
  # with the highest value first.
  #
  #     PokerHand.new("3d 5c 8h Ks").by_face.just_cards   # => "Ks 8h 5c 3d"
  def by_face
    PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
  end

  # Returns string representation of the hand without the rank
  #
  #     PokerHand.new(["3c", "Kh"]).just_cards     # => "3c Kh"
  def just_cards
    @hand.join(" ")
  end
  alias :cards :just_cards

  # Returns an array of the card values in the hand.
  # The values returned are 1 less than the value on the card.
  # For example: 2's will be shown as 1.
  #
  #     PokerHand.new(["3c", "Kh"]).face_values     # => [2, 12]
  def face_values
    @hand.map { |c| c.face }
  end

  # The =~ method does a regular expression match on the cards in this hand.
  # This can be useful for many purposes. A common use is the check if a card
  # exists in a hand.
  #
  #     PokerHand.new("3d 4d 5d") =~ /8h/           # => nil
  #     PokerHand.new("3d 4d 5d") =~ /4d/           # => #<MatchData:0x615e18>
  def =~ (re)
    re.match(just_cards)
  end

  def royal_flush?
    if (md = (by_suit =~ /A(.) K\1 Q\1 J\1 T\1/))
      [[10], arrange_hand(md)]
    else
      false
    end
  end

  def straight_flush?
    if (md = (/.(.)(.)(?: 1.\2){4}/.match(delta_transform(true))))
      high_card = Card::face_value(md[1])
      arranged_hand = fix_low_ace_display(md[0] + ' ' +
          md.pre_match + ' ' + md.post_match)
      [[9, high_card], arranged_hand]
    else
      false
    end
  end

  def four_of_a_kind?
    if (md = (by_face =~ /(.). \1. \1. \1./))
      # get kicker
      result = [8, Card::face_value(md[1])]
      result << Card::face_value($1) if (md.pre_match + md.post_match).match(/(\S)/)
      return [result, arrange_hand(md)]
    end
    false
  end

  def full_house?
    if (md = (by_face =~ /(.). \1. \1. (.*)(.). \3./))
      arranged_hand = arrange_hand(md[0] + ' ' +
          md.pre_match + ' ' + md[2] + ' ' + md.post_match)
      [
        [7, Card::face_value(md[1]), Card::face_value(md[3])],
        arranged_hand
      ]
    elsif (md = (by_face =~ /((.). \2.) (.*)((.). \5. \5.)/))
      arranged_hand = arrange_hand(md[4] + ' '  + md[1] + ' ' +
          md.pre_match + ' ' + md[3] + ' ' + md.post_match)
      [
        [7, Card::face_value(md[5]), Card::face_value(md[2])],
        arranged_hand
      ]
    else
      false
    end
  end

  def flush?
    if (md = (by_suit =~ /(.)(.) (.)\2 (.)\2 (.)\2 (.)\2/))
      [
        [
          6,
          Card::face_value(md[1]),
          *(md[3..6].map { |f| Card::face_value(f) })
        ],
        arrange_hand(md)
      ]
    else
      false
    end
  end

  def straight?
    if hand.size >= 5
      transform = delta_transform
      # note we can have more than one delta 0 that we
      # need to shuffle to the back of the hand
      i = 0
      until transform.match(/^\S{3}( [1-9x]\S\S)+( 0\S\S)*$/) or i >= hand.size  do
        # only do this once per card in the hand to avoid entering an
        # infinite loop if all of the cards in the hand are the same
        transform.gsub!(/(\s0\S\S)(.*)/, "\\2\\1")    # moves the front card to the back of the string
        i += 1
      end
      if (md = (/.(.). 1.. 1.. 1.. 1../.match(transform)))
        high_card = Card::face_value(md[1])
        arranged_hand = fix_low_ace_display(md[0] + ' ' + md.pre_match + ' ' + md.post_match)
        return [[5, high_card], arranged_hand]
      end
    end
    false
  end

  def three_of_a_kind?
    if (md = (by_face =~ /(.). \1. \1./))
      # get kicker
      arranged_hand = arrange_hand(md)
      matches = arranged_hand.match(/(?:\S\S ){2}(\S\S)/)
      if matches
        result = [4, Card::face_value(md[1])]
        matches = arranged_hand.match(/(?:\S\S ){3}(\S)/)
        result << Card::face_value($1) if matches
        matches = arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
        result << Card::face_value($2) if matches
        return [result, arranged_hand]
      end
    end
    false
  end

  def two_pair?
    # \1 is the face value of the first pair
    # \2 is the card in between the first pair and the second pair
    # \3 is the face value of the second pair
    if (md = (by_face =~ /(.). \1.(.*?) (.). \3./))
      # to get the kicker this does the following
      # md[0] is the regex matched above which includes the first pair and
      # the second pair but also some cards in the middle so we sub them out
      # then we add on the cards that came before the first pair, the cards
      # that were in-between, and the cards that came after.
      arranged_hand = arrange_hand(md[0].sub(md[2], '') + ' ' +
          md.pre_match + ' ' + md[2] + ' ' + md.post_match)
{'fraction_non_alphanumeric': 0.15228511530398323, 'fraction_numerical': 0.02180293501048218, 'mean_word_length': 2.106538160979422, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2201587', 'n_tokens_mistral': 4447, 'n_tokens_neox': 4325, 'n_words': 1221}