How many Blackjack sequences in that list?

21

Your task is to find how many distinct Blackjack sequences can be found in an ordered list of 12 cards.

A Blackjack sequence is defined as a sequence of consecutive cards whose sum of points is exactly 21. Points are counted according to the following table:

Symbol | Name  | Points     Symbol | Name  | Points
-------+-------+--------    -------+-------+--------
   2   | Two   | 2             9   | Nine  | 9
   3   | Three | 3             T   | Ten   | 10
   4   | Four  | 4             J   | Jack  | 10
   5   | Five  | 5             Q   | Queen | 10
   6   | Six   | 6             K   | King  | 10
   7   | Seven | 7             A   | Ace   | 1 or 11
   8   | Eight | 8

Input

A 12-character string, using the symbols described above. We do not care about the colors of the cards, so they are not provided.

Example:

K6K6JA3Q4389

Output

The number of distinct Blackjack sequences that can be found in the input string.

Example:

K6K6JA3Q4389 includes two distinct Blackjack sequences:

example

  • JA, with the Ace being counted as 11 points (10 + 11 = 21)
  • A3Q43, with the Ace being counted as 1 point (1 + 3 + 10 + 4 + 3 = 21)

So the answer would be 2.

Rules

  • Two Blackjack sequences are considered distinct if they contain different cards or the same cards in different orders. If the exact same sequence appears at different positions in the input list, it must be counted only once.
  • The Blackjack sequences may overlap each other.
  • Each kind of card may appear up to 12 times in the sequence. (We assume that the cards are picked from at least 3 different decks.)
  • If no Blackjack sequence can be found in the input string, you must return 0 or any other falsy value.
  • This is code-golf, so the shortest answer in bytes wins. Standard loopholes are forbidden.

Test cases

The sequences are provided for information purposes, but you're only required to output the number of them.

Input        | Output | Distinct sequences
-------------+--------+--------------------------------------------------------
3282486Q3362 | 0      | (none)
58A24JJ6TK67 | 1      | 8A2
Q745Q745Q745 | 1      | Q74
AAAAAAAAAAAA | 1      | AAAAAAAAAAA
T5AQ26T39QK6 | 2      | AQ, 26T3
JQ4A4427464K | 3      | A442, 44274, 7464
Q74Q74Q74Q74 | 3      | Q74, 74Q, 4Q7
37AQKA3A4758 | 7      | 37A, 37AQ, AQ, AQK, QKA, KA, A3A475
TAQA2JA7AJQA | 10     | TA, TAQ, AQ, QA, A2JA7, 2JA7A, JA, AJ, AJQ, JQA
TAJAQAKAT777 | 13     | TA, TAJ, AJ, JA, JAQ, AQ, QA, QAK, AK, KA, KAT, AT, 777

Arnauld

Posted 2017-02-15T17:49:54.567

Reputation: 111 334

1Hmm, shouldn't the sequences be limited to those of length 5 or less? – Jonathan Allan – 2017-02-15T18:29:41.317

@JonathanAllan That's a good point. I think that would be indeed the limit in a casino. But this is no real Blackjack game. Instead, I've chosen to limit the input to 12 cards so that many Aces do not require too much computation time. Does that sound OK? – Arnauld – 2017-02-15T18:37:32.603

Next challenge: Find the 12-char string with the most unique Blackjack sequences :D – ETHproductions – 2017-02-15T23:15:40.373

Limiting the input to 10 cards would have been so much easier... – Neil – 2017-02-16T09:00:16.217

@Neil Well, that would have made the 'eleven Aces' case impossible, but is there really a significant optimization behind that? I suppose you may have something else in mind. – Arnauld – 2017-02-16T09:16:16.950

I had to use a different approach, although @ETHproductions helped me cut my losses to a single byte. – Neil – 2017-02-16T11:05:51.747

Answers

6

Jelly, 30 29 bytes

1e×5,⁵Ḥ‘
O_48«26%⁴µSeÇ
ẆÇÐfQL

Try It Online! or check out the test suite

How?

Note that, if we always value an ace as 1 then the only valid sums are 21 and 11, the latter being acceptable iff an ace appears in the sequence.

ẆÇÐfQL - Main link: string
Ẇ      - all non-empty contiguous sublists
  Ðf   - filter keep if...
 Ç     -     last link (2) as a monad ...is truthy
    Q  - unique results
     L - length

O_48«26%⁴µSeÇ - Link 2, isBlackjackSubtring: char array  e.g. ['A','2','8','Q']
O             - cast to ordinal values                        [ 65, 50, 56, 81]
 _48          - subtract 48                                   [ 17,  2,  8, 33]
     26       - 26
    «         - minimum (vectorises)                          [ 17,  2,  8, 26]
        ⁴     - 16
       %      - modulo                                        [  1,  2,  8, 10]
         µ    - monadic chain separation (call the result v)
          S   - sum(v)                                        21
            Ç - last link (1) as a monad link_1(v)            [11,21]
           e  - exists in?                                    1

1e×5,⁵Ḥ‘ - Link 1 validSums: value list (where A is 1, and {T,J,Q,K} are 10)
1e       - 1 exists in? (are there any aces? Yields 1 or 0)
  ×5     - multiply by 5 (5 or 0)
     ⁵   - 10
    ,    - pair ([5,10] or [0,10])
      Ḥ  - double ([10,20] or [0,20])
       ‘ - increment ([11,21] or [1,21])
         -                        ^
         -     note: if no ace is in the sequence it's sum can't be 1 anyway

Jonathan Allan

Posted 2017-02-15T17:49:54.567

Reputation: 67 804

7

Python 2, 215 bytes

def b(s,a=[],r=range):
 S=map(lambda x:":">x>"1"and int(x)or 10-(x=="A")*9,s)
 for i in r(12):
  for j in r(13):
   if 21in[x*10+sum(S[i:j])for x in r(S[i:j].count(1)+1)]and s[i:j]not in a:a+=s[i:j],
 return len(a)

Comments added:

def b(s,a=[],r=range):                                      # Define the function b and a list, a, which holds all the blackjack sequences
 S=map(lambda x:":">x>"1"and int(x)or 10-(x=="A")*9,s)      # Set S to the score of each card in b
 for i in r(12):                                            # Loop with i from 0 to 11
  for j in r(13):                                           # Loop with j from 0 to 12
   if 21in[x*10+sum(S[i:j])for x in r(S[i:j].count(1)+1)]\  # If 21 is included in all the possible sums that the scores between i and j in S can be
           and s[i:j]not in a:                              # And that sequence is not already included,
               a+=s[i:j],                                   # Append that sequence to a
 return len(a)                                              # Return the amount of elements in a

Loovjo

Posted 2017-02-15T17:49:54.567

Reputation: 7 357

3

JavaScript (ES6), 144 138 129 128 126 124 bytes

g=([c,...s],a=[],q=new Set)=>c?g(s,[...a,[,21]].map(([x,y,A])=>[x+=c,y-=+c||(c<'B'?A=1:10),A,y&&y^10*A||q.add(x)]),q):q.size

Old attempt at 128:

s=>(q=new Set,f=s=>s?f(s.slice(1))&f(s.slice(0,-1))&[...s].map(c=>t+=-c||~(c<'B'?A=0:9),t=A=21)|t&&t-10*!A?q:q.add(s):q)(s).size

ETHproductions

Posted 2017-02-15T17:49:54.567

Reputation: 47 880

s.search\A`>-1could be~s.search`A`` – Luke – 2017-02-15T20:57:35.997

@Luke No, actually, because that returns values such as -2, and 1&-2 == 0 – ETHproductions – 2017-02-15T20:58:29.810

True. Maybe set t to 0 in the .slice(0,-1) call (saves 2B)? – Luke – 2017-02-15T21:01:09.877

@Luke I don't think that would work, as t is a global variable and it would be reset because of the call to f(s.slice(0,-1)). But I found a way around s.search`A`>-1 :-) – ETHproductions – 2017-02-15T23:14:09.433

I'm curious to see what you have when you're done golfing this. I seem to be stuck at 113 for now. – Arnauld – 2017-02-16T01:40:19.303

+c||(c<'B'?A=1:10) could be +c||10-9*(c=="A") for 1B :-) – traktor53 – 2017-02-16T10:39:39.560

@Traktor53 That wouldn't work because we need to set the A variable to 1 at the same time to remember that there's at least one Ace in the sequence. – Arnauld – 2017-02-16T11:03:45.760

@Arnauld 113?! I've got quite aways to go... – ETHproductions – 2017-02-16T17:07:13.353

I've posted my solution. It has a lot in common with your answer, but I tried to avoid using a Set. – Arnauld – 2017-02-17T11:50:58.837

3

JavaScript (ES6), 123 bytes

f=
t=>eval("for(s=new Set,i=0;t[i];i++)for(a=0,b=21,j=i;c=t[j++];b&&b-a*10||s.add(t.slice(i,j)))b-=+c||(c>'A'?10:a=1);s.size")
<input oninput=o.textContent=/[^2-9TJQKA]/.test(this.value)?'':f(this.value)><pre id=o>

Neil

Posted 2017-02-15T17:49:54.567

Reputation: 95 035

Great idea, but this returns 0 for AAAAAAAAAAAA rather than 1. (A can simultaneously be 1 and 11) – ETHproductions – 2017-02-15T21:09:46.697

By combining our two entries you can get s=>eval("q=new Set;for(i=0;s[i];i++)for(t=A=0,j=i;c=s[j++];t==21|t==11&A&&q.add(s.slice(i,j)))t+=+c||(c<'B'?A=1:10);q.size") for 124 bytes – ETHproductions – 2017-02-15T23:09:35.450

@ETHproductions Starting from 21 still seems to save me a byte. – Neil – 2017-02-16T01:21:18.023

@ETHproductions ... it does help if I post the correct byte count ... – Neil – 2017-02-16T08:59:07.760

3

Python, 134 130 bytes

lambda x:len({x[i:j]for i in range(12)for j in range(13)if sum(min(26,ord(c)-48)%16for c in x[i:j])in([11,21][~('A'in x[i:j]):])})

Try it online!

How?

An unnamed function, taking the string of length 12 as x.

x[i:j] is a slice of the string from the i+1th to the jth character.

Slices are taken such that we have all sublists by traversing from i=0 to i=11 with for i in range(12), for each of which we traverse from j=0 to j=12 with for j in range(13).

(We only need j=i+1 and up, but slices with j<=i are just empty strings, so we can golf off 4 bytes from for j in range(i+1,13))

These are filtered for those with the correct sum...

Valid sums are 11 and 21 if there is an ace in a slice, or just 21 if not. 'A'in x[i:j] gives us this information and ~(v) performs -1-v, which we use to slice [11,21] - thus if an ace is in the sequence we get [11,21][-2:] and if not we get [11,21][-1:], resulting in [11,21] and [21] respectively.

The sum itself needs to treat A as 1, digits as their values, and T, J, Q, and K as 10. This mapping is achieved by first casting to ordinals:
" 2 3 4 5 6 7 8 9 T J Q K A" (without the spaces) becomes
[50, 51, 52, 53, 54, 55, 56, 57, 84, 74, 81, 75, 65], subtracting 48 to get
[ 2, 3, 4, 5, 6, 7, 8, 9, 36, 26, 33, 27, 17], taking the min with 26 yields
[ 2, 3, 4, 5, 6, 7, 8, 9, 26, 26, 26, 26, 17], and mod (%) sixteen those are
[ 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 1], as required for the sum, sum(...).

The filtered results are place into a set with {...}, so only the unique results remain and the length, len(...) is the count

Jonathan Allan

Posted 2017-02-15T17:49:54.567

Reputation: 67 804

3

05AB1E, 39 38 37 bytes

'A1:vTy‚ydè})ŒvyO¸y1åiDT+ì}21å})¹ŒÏÙg

Try it online!

Explanation

'A1:                  # replace A with 1 in input

v      }              # for each card
 Ty‚                  # pair 10 and the card
    yd                # check if the card is a digit
      è               # use this to index into the pair, giving 10 for JQK
        )             # wrap in list
                      # we now have a list of cards as numbers in range [1 ... 10]

Œv               }    # for each sublist
  yO¸                 # create a list with the sum of the sublist
     y1åi    }        # if the sublist contain 1
         DT+ì         # add sum+10 to the list
              21å     # check if 21 is in that list
                  )   # wrap in list
                      # we now have a list with 1 where the sublist sums to 21 and
                      # 0 otherwise

¹Œ                    # get sublists of the input
  Ï                   # keep only those which sum to 21
   Ù                  # remove duplicates
    g                 # count the number of lists

Emigna

Posted 2017-02-15T17:49:54.567

Reputation: 50 798

3

JavaScript (ES6), 112 bytes

f=(s,x=[k=0])=>s?f(s.slice(1),x,[...s].map(c=>x[t+=+c||10^(c<'B'?a=11:0),b+=c]||t-21&&t-a?0:x[b]=++k,a=b=t=0)):k

This code logic is quite similar to the one used in existing JS answers from ETHproductions and Neil. But it's using a basic array to keep track of the encountered Blackjack sequences rather than a Set.

Formatted and commented

f = (                     // given:
  s,                      //  - s = list of cards
  x = [k = 0]             //  - x = array of Blackjack sequences
) =>                      //  - k = number of distinct Blackjack sequences 
  s ?                     // if s is not empty:
    f(                    //   do a recursive call:
      s.slice(1),         //     starting at the next card in the list
      x,                  //     without re-initializing x[]
      [...s].map(         //   for each card 'c' in the list:
        c => x[           //
          t+ =            //   update the total number of points:
            +c ||         //     using the number of the card (for 2 ... 9)
            10 ^ (        //     or using 10 for all other cards
              c < 'B' ?   //     except the Ace which is
                a = 11    //     counted as 1 point and sets 'a' to 11
              :           //     (which means that a total number of points
                0         //     of 11 will be considered valid from now on)
            ),            //
          b += c          //   update the current sequence 'b'
        ] ||              //   if x[b] was previously stored as a Blackjack sequence
        t - 21 &&         //   or the total number of points is not equal to 21
        t - a ?           //   and not equal to 'a':
          0               //     do nothing
        :                 //   else:
          x[b] = ++k,     //     store the current sequence in x[] and increment k
        a = b = t = 0     //   initialization of all variables used in map()
      )                   //
    )                     //
  :                       // else:
    k                     //   return k

Test cases

f=(s,x=[k=0])=>s?f(s.slice(1),x,[...s].map(c=>x[t+=+c||10^(c<'B'?a=11:0),b+=c]||t-21&&t-a?0:x[b]=++k,a=b=t=0)):k

console.log(f('3282486Q3362')) // 0 
console.log(f('58A24JJ6TK67')) // 1 
console.log(f('Q745Q745Q745')) // 1 
console.log(f('AAAAAAAAAAAA')) // 1 
console.log(f('T5AQ26T39QK6')) // 2 
console.log(f('JQ4A4427464K')) // 3 
console.log(f('Q74Q74Q74Q74')) // 3 
console.log(f('37AQKA3A4758')) // 7 
console.log(f('TAQA2JA7AJQA')) // 10
console.log(f('TAJAQAKAT777')) // 13

Arnauld

Posted 2017-02-15T17:49:54.567

Reputation: 111 334

I tried double recursion, moving backward through the string, cumulatively calculating each possible string as each character is consumed... and yet the shortest approach is simply to run through each slice. Nice one! (Using a Set appears to be three bytes longer, if I calculated correctly) – ETHproductions – 2017-02-17T15:03:07.807

2

05AB1E, 40 39 38 37 36 bytes

-4 Thanks to Emigna

Ç<çJŒÙ'@0:[Ž„èµJuS9:S>D1å2‚T*>sOå½]¾

Try it online!

Ç<ç                                  # decrement characters by 1
   JŒÙ                               # get all unique substrings
      '@0:                           # replace @ (was A) with 0
          [Ž                      ]  # for everything on the stack
            „èµJuS9:                 # replace what was T,J,Q,K with 9
                    S>D              # increment all values
                       1å2‚T*>       # push [11,21] if there was an A, [1,21] otherwise
                              sO     # sum the values of the cards
                                å½   # increment the counter_variable if the sum 
                                     # is in the array
                                   ¾ # end loop and push (print) the counter_variable

We need to do the decrement -> substring -> increment thing so that face cards are represented by a single digit number.

Riley

Posted 2017-02-15T17:49:54.567

Reputation: 11 345

Nice way of getting around double digits! You can remove the first S as Ç turns the string into a list of character codes. – Emigna – 2017-02-16T16:55:57.443

Also, "SIPJ" could be „èµJu – Emigna – 2017-02-16T17:05:31.383

@Emigna Thanks. I thought there was a way to do that, but I couldn't find how to use in the documentation. – Riley – 2017-02-16T17:10:28.343

You can save 2 more bytes rewriting it as Ç<çJŒÙ'@0:)vy„èµJuS9:S>D1å2‚T*>sOå}O Then you're 1 byte shorter than my answer :) – Emigna – 2017-02-16T17:12:53.750

@Emigna This is the same byte count and is more like my original. – Riley – 2017-02-16T17:21:29.253

You can still replace 5*T‚· with 2‚T* to save another byte (while keeping your looping). – Emigna – 2017-02-16T17:26:38.470

1

Bash + Unix utilities, 145 142 141 bytes

for n in {9..155}
{ echo ${1:n%12:n/12};}|sort -u|sed 's/\(.\)/+\1/g;s/A/{1,11}/g;s/[J-T]/10/g;s/^/eval echo $[0/;s/$/]/'|sh|grep -c '\<21\>'

Try it online!

Test runs:

for x in 3282486Q3362 58A24JJ6TK67 Q745Q745Q745 AAAAAAAAAAAA T5AQ26T39QK6 JQ4A4427464K Q74Q74Q74Q74 37AQKA3A4758 TAQA2JA7AJQA TAJAQAKAT777
  do
    echo -n "$x "
    ./21 "$x"
  done

3282486Q3362 0
58A24JJ6TK67 1
Q745Q745Q745 1
AAAAAAAAAAAA 1
T5AQ26T39QK6 2
JQ4A4427464K 3
Q74Q74Q74Q74 3
37AQKA3A4758 7
TAQA2JA7AJQA 10
TAJAQAKAT777 13

Mitchell Spector

Posted 2017-02-15T17:49:54.567

Reputation: 3 392

1

PHP, 240 bytes

$a=str_split($argv[1]);foreach($a as$k=>$v)$n[$k]=$v=='A'?1:($v==0?10:$v);for($i=0;$i<=$k;$i++){$s=$a[$i];$r=$n[$i];for($j=$i+1;$j<=$k;$j++){$s.=$a[$j];$r+=$n[$j];if ($r==21||($r==11&&stristr($s,'A')))$f[]=$s;}}echo count(array_unique($f));

Ungolfed :

$a = str_split($argv[1]);
foreach ($a as $k=>$v)
    $n[$k] = $v == 'A' ? 1 : ($v == 0 ? 10 : $v);
for ($i=0; $i<=$k; $i++) {
    $s = $a[$i];
    $r = $n[$i];
    for ($j=$i+1; $j<=$k; $j++) {
        $s .= $a[$j];
        $r += $n[$j];
        if ($r == 21 || ($r == 11 && stristr($s,'A')) )
            $f[] = $s;
    }
}
echo count(array_unique($f));

Try it here!

roberto06

Posted 2017-02-15T17:49:54.567

Reputation: 351

1Weird, I could have sworn it worked on my local tests, but it seems you're right. Anyway; the problem came from the fact that $i wasn't declared. Added 4 bytes and it works perfectly. – roberto06 – 2017-02-21T13:02:02.533