Generate a Baseball Pitch String

11

2

Goal

Write a program or function that takes a positive integer n and randomly generate a legal series of pitches (henceforth called a Pitch string) of length n.

Input

A non-zero, positive integer n <= 100

Output

Return a random string, or list of characters, that represent a possible, valid pitch string of length n. The characters used will be:

  • B - Ball. If you accumulate 4 of these, the batter is walked and finished batting.
  • S - Strike. If you accumulate 3 of these, the batter is out and finished batting.
  • F - Foul. Will also increase the Strike count but cannot get the batter out. I.e., you cannot have a Foul be the last pitch in a valid string. Any fouls past two strikes/fouls will not increase the Strike count (the batter already has 2 strikes at that point and a 3rd would get him out).
  • H - Hit. The batter has hit a ball into play and is finished batting.

(This is simplified slightly but don't you worry about that)

Valid pitch strings are those that end in a strike-out, a walk, or a hit.

I.e., an invalid pitch string has either

  • additional pitches after the 4th Ball, 3rd Strike, or Hit
  • terminated before generating a 4th ball, 3rd strike, or Hit.

Rules

  • Your program must be able to produce all possible results for a given input.
  • Your program does not have to be uniformly random but still must follow the previous rule.
  • This is .

Examples

Input => Possible Outputs
1 => [H] #Can only end with a hit
2 => [S,H], [B,H], [F,H] #Can only end with a hit
3 => [S,S,S], [F,F,S], [B,B,H], ... #Can now strike-out, otherwise must end with a hit
4 => [B,B,B,B], [S,B,S,S], [B,F,S,S], [B,B,B,H], ... #Can now be walked, struck-out, or get a hit
6 => [S,B,S,B,B,H], [F,F,F,F,F,S], ... #Can now have a full-count (3 balls, 2 strikes) before finishing 

Input => Invalid Outputs
1 => [S], [B]    #Not enough for a strike-out/walk
2 => [S,S]       #Not enough for a strike-out/walk
2 => [H,H]       #Batter has already scored a hit
3 => [S,S,F]     #Fouls will not cause a strike-out
4 => [S,S,S,H]   #Batter has already struck out
5 => [B,B,B,B,B] #Batter has already walked

Veskah

Posted 2019-02-27T02:14:09.727

Reputation: 3 580

1So we have to be able to produce anywhere from 1 to infinity F's? – Quintec – 2019-02-27T02:34:19.113

The string will be at most 100 characters long. Fouls are what allows such long pitching strings, e.g. 99 Fs and a S is a strike-out – Veskah – 2019-02-27T02:37:53.297

Oh, got it, missed that – Quintec – 2019-02-27T02:38:29.947

@Quintec Reworded it to be a bit more explicit just in case – Veskah – 2019-02-27T02:40:11.927

Answers

4

Python 2, 128 bytes

from random import*
def g(n):
 x=i=S=0;r=''
 while(S>2)+x<3>=i-S:x=randint(0,3);r+='BFSH'[x];S+=x>0;i+=1
 return(i==n)*r or g(n)

Try it online!

Randomly generate the pitch string until the batter is done, output it if it turns out the right length, and otherwise try again from scratch.


Python 2, 136 bytes

from random import*
def g(n):
 B=H=F=S=0;r=''
 while(F+S<3or'S'>x)>B/4+H:x=choice('BHFS');r+=x;exec x+"+=1"
 return(len(r)==n)*r or g(n)

Try it online!

xnor

Posted 2019-02-27T02:14:09.727

Reputation: 115 687

Kevin's port of this made me realize this breaks down for higher numbers. n=8 can generate a chain of Fs at the end – Veskah – 2019-02-27T21:45:27.630

2@Veskah Nice catch. I hadn't accounted for the strike count (counting fouls) possibly going up to 6, and changing S/3 to (S>2) fixes it. – xnor – 2019-02-28T01:47:23.800

4

05AB1E,  44  50 44 bytes

Crossed out &nbsp;44&nbsp; is no longer 44 :)

[õ0U.µ["BFSH"3ÝΩ©è«®ĀX+U¼X2›®+3@¾X-3›~#}I¾Q#

Port of @xnor's Python 2 answer, so make sure to upvote him as well if you like this answer!
+6 bytes due to a bug-fix, and after that -6 bytes again thanks to @xnor by porting his way more efficient fix in comparison to my temporary work-around, as I was expecting. ;)

Try it online or verify some more random outputs.

Explanation:

[                # Start an infinite loop:
 õ               #  (Re)set the result-string to an empty string ""
 0U              #  (Re)set variable `X` to 0
 .µ              #  Reset the counter_variable to 0
   [             #  Start an inner infinite loop:
    "BFSH"       #   Push string "BFSH"
          3ÝΩ    #   Push a random integer in the range [0,3]
             ©   #   Store this random integer in variable `r` (without popping)
              è  #   Index it into the string "BFSH"
               « #   Append it to the result-string
    ®Ā           #   If `r` is NOT 0:
      X+U        #    Increase `X` by 1
    ¼            #   Increase the counter_variable by 1
    X2›®+        #   Calculate `X`>2 (1 if truthy; 0 if falsey) + `r`
         3@      #   Check if this is larger than or equal to 3
    ¾X-          #   Calculate counter_variable - `X`
       3›        #   Check if this is larger than 3
    ~            #   If either of the two checks above is truhy:
     #           #    Stop the inner infinite loop
   }             #  After the inner infinite loop:
    I¾Q          #  If the input and counter_variable are equal:
       #         #   Stop the outer infinite loop
                 # (and output the result-string at the top of the stack implicitly)

Kevin Cruijssen

Posted 2019-02-27T02:14:09.727

Reputation: 67 575

1@Veskah I've done a straight-forward fix for now. I have the feeling xnor is able to do a shorter fix, so I'll probably port his fix to save some bytes later on. :) – Kevin Cruijssen – 2019-02-27T22:07:30.630

1You can fix it like I did by changing X/3 to X>2. – xnor – 2019-02-28T01:48:51.350

@xnor Thanks, back to 44 bytes again. I knew you'd find something shorter. ;p – Kevin Cruijssen – 2019-02-28T07:13:54.157

3

R, 148 bytes

function(n){`~`=paste0
`*`=sample
o=""
while(nchar(o)<n-1){a=c("B"[T<4],"F","S"[F<2])*1
F=F+(a>"E")
T=T+(a<"F")
o=o~a}
o~c("B"[T>3],"H","S"[F>1])*1}

Try it online!

Generates the string, using conditional inclusion in the sampling datasets to ensure that the result is a possible pitch sequence.

Possibly doing rejection sampling (as xnor's python answer does) is shorter.

function(n){`~`=paste0		# alias
`*`=sample			# alias
o=""				# empty string for output
while(nchar(o)<n-1){		# do n-1 times:
a=c("B"[T<4],"F","S"[F<2])*1	# sample 1 from the string "BFS", conditionally including B or S if the ball/strike count is 3/2	
F=F+(a>"E")			# increment F (strike count) if sampled character is F or S
T=T+(a<"F")			# increment T (ball count) if sampled character is B
o=o~a}				# append a to output

o~c("B"[T>3],"H","S"[F>1])*1}	# append the sampled "BHS", conditionally including B or S if the ball/strike count is 3/2.

Random "F and S" reference that kept playing in my head every time I typed one of those letters...

Giuseppe

Posted 2019-02-27T02:14:09.727

Reputation: 21 077

2

JavaScript (SpiderMonkey), 137 bytes

f=(n,o='',i=Math.random()*4|0,r=/^(?!.*((B.*){4}|([SF].*){2}S|H).)/)=>r.test(o)?n?f(n-1,o+'BSFH'[i%4])||i<8&&f(n,o,i+1):!r.test(o+1)&&o:0

Try it online!

tsh

Posted 2019-02-27T02:14:09.727

Reputation: 13 072

2

Pyth, 53 bytes

u+GO?H+W<K/G\B3+W<Jl@"SF"G2\F\S\B+WqK3+WgJ2\H\S\B_UQ[

Try it online here.

This feels way too long, I think another approach may be required.

u+GO?H+W<K/G\B3+W<Jl@"SF"G2\F\S\B+WqK3+WgJ2\H\S\B_UQ[   Implicit: Q=eval(input())
                                                 _UQ    Reversed range from Q-1 to 0
u                                                   [   Reduce the above, with initial value G=[], next value as H:
                    @"SF"G                                Keep elements of G which are in "SF"
                   l                                      Length of the above
                  J                                       Store in J - this is the number of strikes and fouls so far
          /G\B                                            Count number of "B"s in G
         K                                                Store in K - this is the number of balls so far
    ?H                                                    If H is not 0 (i.e. not final pitch):
                           \F                               Start with "F" (foul is always available in non-final pitch)
                W<J       2                                 If J<2...
               +             \S                             ... append "S"
       W<K    3                                             If K<3...
      +                        \B                           ... append "B"
                                                          Else:
                                           \H               Start with "H" (hit is always available in final pitch)
                                       WgJ2                 If J >= 2...
                                      +      \S             ... append "S"
                                  WqK3                      If K == 3...
                                 +             \B           ... append "B"
   O                                                      Choose one element at random from the available options
 +G                                                       Append the above to G
                                                        Implicit print the result of the reduce operation

Sok

Posted 2019-02-27T02:14:09.727

Reputation: 5 592

2

JavaScript (ES6),  107 106  99 bytes

f=(n,k=p=s=0,o='')=>p&&p>2|k-s>3|s>2&p<2?k-n?f(n):o:f(n,k+1,o+'FSBH'[p=Math.random()*4|0,s+=p<2,p])

Try it online!

Commented

f = (                       // f = recursive function taking:
  n,                        //   n = requested length
  k =                       //   k = pitch counter, initialized to 0
  p =                       //   p = last pitch
  s = 0,                    //   s = sum of strikes and fouls
  o = ''                    //   o = output string
) =>                        //
  p &&                      // if the last pitch was not a foul
  p > 2 |                   // AND the last pitch was a hit
  k - s > 3 |               //     OR we have 4 balls (or 3 balls + 1 hit)
  s > 2 & p < 2 ?           //     OR more than 2 strikes or fouls, ending with a strike:
    k - n ?                 //   if k is not equal to n:
      f(n)                  //     valid series but bad timing: try again from scratch
    :                       //   else:
      o                     //     success: return o
  :                         // else:
    f(                      //   do a recursive call:
      n,                    //     n is unchanged
      k + 1,                //     increment k
      o + 'FSBH'            //     append the pitch letter to o
        [ p = Math.random() //     pick a new random pitch
              * 4 | 0,      //     in [0..3]
          s += p < 2,       //     increment s if the pitch is a foul or a strike
          p ]               //     actual index in 'FSBH'
    )                       //   end of recursive call

Arnauld

Posted 2019-02-27T02:14:09.727

Reputation: 111 334

2

Ink, 120 119 116 117 bytes

=f(n)
->g(n,3,2)
=g(n,b,s)
~n--
{n:{~{b:b->g(n,b-1,s)}|{s:s->g(n,b,s-1)}|}f->g(n,b,s-(s>0))|{~{b:h|b}|{s:h|s}|h}}->->

Try it online!

Probably still golfable.

Ungolfed (mildly reformatted)

=f(length) // Define a stitch f, with one parameter which specifies the length of the created string. This is the intended entry point.
->g(length,3,2) // Instantly divert to g, defined below, with some extra parameters

=g(length,balls_left,strikes_left) // Define a stitch g, with three parameters.
~ length--                         // Decrement remaining length
{
    - length: // If this is not to be the last character in the string
              // randomly do one of the following:
              // 1. If balls_left is nonzero, print a b and recurse
              // 2. If strikes_left is nonzero, print an s and recurse
              // 3. Do nothing
              // If we did not divert earlier, print an f and recurse.
        {~{balls_left:b->g(length,balls_left-1,strikes_left)}|{strikes_left:s->g(length,balls_left,strikes_left-1)}|}f->g(length,balls_left,strikes_left-(strikes_left>0)) 
    - else: // Randomly do one of the following
            // 1. If a ball would result in a walk, print a b, otherwise an h.
            // 2. If a strike would result in a strikeout, print an s, otherwise an h.
            // 3. Just print an h.
            // And finally, halt.
        {~{balls_left:h|b}|{strikes_left:h|s}|h}}->->

Edits

  1. Saved a byte by finishing with ->-> instead of ->END.
  2. Saved three bytes by decrementing n earlier.
  3. Fixed a bug that caused strikeouts in incorrect places, thanks to @veskah for spotting it (+1 byte)

Sara J

Posted 2019-02-27T02:14:09.727

Reputation: 2 576

1Based on the write-up and outputs, it looks like it doesn't factor in fouls incrementing the strike-count correctly – Veskah – 2019-03-01T00:41:34.707

1@veskah Well spotted, should be fixed now, thanks – Sara J – 2019-03-01T08:01:54.153

1

APL (Dyalog Unicode), 77 bytesSBCS

{'HBSF'[{⍺←⍬⋄∨/((⊃⌽⍺)=⍳3)∧1,4 3≤(⊃s),+/1↓s←⍺∘(+/=)¨1+⍳3:⍺⋄∇⍨⍺,?4}⍣{i=≢⍺}i←⍵]}

Try it online!

voidhawk

Posted 2019-02-27T02:14:09.727

Reputation: 1 796

1

Charcoal, 57 bytes

≔⁰η≔⁰ζF⊖N«≔‽⁺²‹ζ³ι¿›ι¹≦⊕ζ≦⊕η§SFB∨ι›η²»⊞υHF›η¹⊞υSF›ζ²⊞υB‽υ

Try it online! Link is to verbose version of code. Explanation:

≔⁰η≔⁰ζ

Start with 0 balls and 0 strikes.

F⊖N«

Loop over all of the deliveries except the last.

≔‽⁺²‹ζ³ι

If the have been fewer than three balls, then generate a random number from 0 to 2, otherwise just coinflip between 0 and 1.

¿›ι¹≦⊕ζ≦⊕η

A random value of 2 is a ball otherwise it increases the strike count.

§SFB∨ι›η²»

The values 0 to 2 map to strike, foul and ball, except that if there would be three strikes then foul is printed instead. (Four balls are excluded above.)

⊞υHF›η¹⊞υSF›ζ²⊞υB‽υ

Determine whether a strike or ball would get the batter out and choose from those or a hit as appropriate.

Neil

Posted 2019-02-27T02:14:09.727

Reputation: 95 035

1

C (GCC) 164 145 142 bytes

-3 bytes ceilingcat

#define A(a)!i&&!r--?puts(#a),++a,--n:0;
b,s,f,h,i,r;p(n){srand(time(0));for(i=n;i--;){for(n=1;n;){r=rand()&3;b>2^!A(b)s+f>1^!A(s)!A(f)A(h)}}}

Try it online

rtpax

Posted 2019-02-27T02:14:09.727

Reputation: 411

Suggest &n instead of time(0) – ceilingcat – 2020-01-27T06:30:16.200

1

Perl 5, 122 bytes

map{$B=$S=$H=0;while($B<4&&$S<3&&!$H&&/./g){${$&}++;$S+=$&eq F&&$S<2}y///c>pos||push@a,$_}glob"{B,F,H,S}"x<>;say$a[rand@a]

Try it online!

Xcali

Posted 2019-02-27T02:14:09.727

Reputation: 7 671

1@Veskah I missed that part. Fixed it. – Xcali – 2019-07-17T19:24:41.613