11
\$\begingroup\$

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, and 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
3 => [S,S,F]     #Fouls will not cause a strike-out
4 => [S,S,S,H]   #Batter is already struck out
5 => [B,B,B,B,B] #Batter has already walked
\$\endgroup\$
  • 1
    \$\begingroup\$ So we have to be able to produce anywhere from 1 to infinity F's? \$\endgroup\$ – Quintec Feb 27 at 2:34
  • \$\begingroup\$ 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 \$\endgroup\$ – Veskah Feb 27 at 2:37
  • \$\begingroup\$ Oh, got it, missed that \$\endgroup\$ – Quintec Feb 27 at 2:38
  • \$\begingroup\$ @Quintec Reworded it to be a bit more explicit just in case \$\endgroup\$ – Veskah Feb 27 at 2:40

11 Answers 11

4
\$\begingroup\$

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!

\$\endgroup\$
  • \$\begingroup\$ 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 \$\endgroup\$ – Veskah Feb 27 at 21:45
  • 2
    \$\begingroup\$ @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. \$\endgroup\$ – xnor Feb 28 at 1:47
4
\$\begingroup\$

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)
\$\endgroup\$
  • 1
    \$\begingroup\$ @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. :) \$\endgroup\$ – Kevin Cruijssen Feb 27 at 22:07
  • 1
    \$\begingroup\$ You can fix it like I did by changing X/3 to X>2. \$\endgroup\$ – xnor Feb 28 at 1:48
  • \$\begingroup\$ @xnor Thanks, back to 44 bytes again. I knew you'd find something shorter. ;p \$\endgroup\$ – Kevin Cruijssen Feb 28 at 7:13
3
\$\begingroup\$

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...

\$\endgroup\$
2
\$\begingroup\$

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!

\$\endgroup\$
2
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

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)
\$\endgroup\$
  • 1
    \$\begingroup\$ Based on the write-up and outputs, it looks like it doesn't factor in fouls incrementing the strike-count correctly \$\endgroup\$ – Veskah Mar 1 at 0:41
  • 1
    \$\begingroup\$ @veskah Well spotted, should be fixed now, thanks \$\endgroup\$ – Sara J Mar 1 at 8:01
1
\$\begingroup\$

APL (Dyalog Unicode), 77 bytesSBCS

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

Try it online!

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

C (GCC) 164 145 bytes

#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

\$\endgroup\$
1
\$\begingroup\$

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!

\$\endgroup\$
  • 1
    \$\begingroup\$ @Veskah I missed that part. Fixed it. \$\endgroup\$ – Xcali Jul 17 at 19:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.