17
\$\begingroup\$

Thanks to @ComradeSparklePony for the title.

This challenge should be very simple. You are given three lists.

The first is a list of first names, in title case.

The second is a list of adjectives, in lower case.

The third is a list of nouns, in lower case.

Please randomly select a name, optional adjective, and noun, and output <Name>'s <adjective> <noun>. However, each word must begin with the same letter. You can assume that all words begin with a letter. You can also assume (but note in your answer if you do):

  • that all words are composed solely of alphabetic characters
  • that there is at least one noun for each name
  • that there is at least one name for each noun

You cannot however assume that an adjective exists for a particular pair of name and noun, as the adjective is optional so the output will still be valid.

You do not have to select the shared letter uniformly, although all available letters must have a non-zero chance of occurring. You must however ensure that all outputs for a given letter have as near equal chance of occurring as possible within the limits of your language's random number generator. In the case of the adjective, this is equivalent to having an extra entry meaning "no adjective for this letter" which has the same chance as all of the other adjectives for that letter.

Example input lists:

Joan Neil Nicola Oswald Sherman Stephanie
new novel old original second silent
jeep noun novel output second sheep snake

Example outputs for these inputs (each line is a separate example):

Stephanie's second second
Sherman's silent snake
Oswald's original output
Nicola's novel novel
Neil's noun
Joan's jeep

Note no extra space between words in the last two examples.

This is , so the shortest code that breaks no standard loopholes wins!

In the unlikely event that it helps, you can input everything in upper case, but you still need to output in sentence case.

\$\endgroup\$
12
  • \$\begingroup\$ Are we correct to assume that the program should return: 1 name 1 adjective (if one matches the name) 1 noun ? Or are you asking to produce an output for each name? \$\endgroup\$
    – DavidC
    Commented May 12, 2019 at 11:26
  • 1
    \$\begingroup\$ Maybe you should add 'Joan' and 'jeep' in your example to illustrate the fact that there might be no adjective at all for a given letter? \$\endgroup\$
    – Arnauld
    Commented May 12, 2019 at 11:55
  • \$\begingroup\$ Given your example input is the chance of no adjective 1 in 3 (since all adjective "lists" are 2 long)? ...and if 'Joan' and 'Jeep' were also there with no j-adjective would the chance become 4 in 9? Might be worth placing probabilities against outputs, or enumerating all outputs -- as I understand it not only "all outputs for a given letter..." but also all distinct outputs should have equal likelihood (given distinct values within each list). \$\endgroup\$ Commented May 12, 2019 at 12:37
  • \$\begingroup\$ @DavidC Sorry, I realise adding extra examples has made that unclear; you only produce one line of output for each invocation. \$\endgroup\$
    – Neil
    Commented May 12, 2019 at 13:16
  • 1
    \$\begingroup\$ @JonathanAllan Adding "Joan" and "jeep" wouldn't affect the relative chances of "Neil's noun" being output compared with other options containing "Neil" and "noun". \$\endgroup\$
    – Neil
    Commented May 12, 2019 at 13:17

13 Answers 13

5
\$\begingroup\$

Jelly,  27 25  24 bytes

-1 thanks to Erik the Outgolfer (use a zero instead of a space character)

Ż€2¦Œpḟ€0ZḢŒuEƲƇXż“'s“”K

A full program accepting an argument in the form of a Python formatted list of lists of strings which prints the output to STDOUTt.

Try it online!

How?

Ż€2¦Œpḟ€0ZḢŒuEƲƇXż“'s“”K - Main Link: list of lists of lists of characters
 € ¦                     - sparse application...
  2                      - ...to indices: [2]
Ż                        - ...action: prepend a zero (place holder for no adjective)
    Œp                   - Cartesian product (all choices, including invalid ones)
       €                 - for each:
      ḟ 0                -   filter out any zeros
               Ƈ         - filter keep those for which:
              Ʋ          -   last four links as a monad:
         Z               -     transpose
          Ḣ              -     head
           Œu            -     upper-case
             E           -     all equal?
                X        - random (uniform) choice  e.g. [['B','o','b'],['b','l','u','e'],['b','a','g']]
                 ż       - zip with:
                  “'s“”  -   list [["'", 's'], []]       [[['B','o','b'],["'", 's']],[['b','l','u','e'],[]],['b','a','g']]
                       K - join with spaces              [['B','o','b'],["'", 's'],' ',['b','l','u','e'],[],' ','b','a','g']
                         - implicit (smashing) print     Bob's blue bag
\$\endgroup\$
2
5
\$\begingroup\$

05AB1E,  24 23  21 bytes

Assumes there is a noun for each name, as allowed by the challenge.

„'s«I¯ªâI‘ʒl€нË}Ωðý

Try it online!

Explanation

„'s«                    # append "'s" to all names in the name-list
    I¯ª                 # append an empty list to the adjective-list
       â                # cartesian product between the lists
        Iâ              # cartesian product with the noun-list
          €˜            # deep flatten each sublist
            ʒ    }      # filter, keep only lists that when
             l          # converted to lowercase
              €н        # with only heads kept
                Ë       # have all elements equal
                  Ω     # pick a valid list uniformly at random
                   ðý   # and join by spaces
\$\endgroup\$
2
  • \$\begingroup\$ Oh, the ¯ª and €˜ are smart! I had a 26 byte answer, but was having trouble fixing the double-space when there isn't an adjective.. \$\endgroup\$ Commented May 13, 2019 at 9:38
  • \$\begingroup\$ @KevinCruijssen: Yeah that was the part I had the most issues with as well. Took me a while to realize that I could use ¯ instead of filling with empty strings I had to manually clean up later. \$\endgroup\$
    – Emigna
    Commented May 13, 2019 at 9:40
4
\$\begingroup\$

R, 155 148 bytes

-7 bytes thanks to Giuseppe (using * for sample)

function(x,y,z){`*`=sample
while(T)T=length(unique(c(tolower(substr(c(a<-x*1,b<-c(y,"")*1,c<-z*1),1,1)),"")))-2
paste0(a,"'s ",b,if(nchar(b))" ",c)}

Try it online!

Uses rejection sampling: draw at random a name, an adjective (possibly the empty string) and a noun until the first letters match. This condition is checked by counting if the number of unique elements in the vector formed of the first letters, plus the empty string, is of length 2 - this allows for an empty adjective.

Then print the result, with an extra space if the adjective is non-empty.

The different possibilities starting with the same letter have equal occurrence probabilities, since sample draws from the uniform distribution. The easiest way to see this is to condition on the event that the name and noun start with the same letter (which is fine: if they don't, we would reject). Now condition on the event that we accept: this means we draw either the empty adjective, or an adjective starting with the same letter. Each of these possibilities still has equal probability.

Check the probabilities on \$10^5\$ replicates.

\$\endgroup\$
4
  • \$\begingroup\$ Does this have an equal chance of an empty adjective to each other possibility for any given first letter? \$\endgroup\$ Commented May 12, 2019 at 19:46
  • \$\begingroup\$ @NickKennedy Yes, since sample draws from the uniform distribution. The easiest way to see this is to condition on the event that the name and noun start with the same letter (which is fine: if they don't, we would reject). Now condition on the event that we accept: this means we draw either the empty adjective, or an adjective starting with the same letter. Each of these possibilities still has equal probability. \$\endgroup\$ Commented May 12, 2019 at 19:57
  • \$\begingroup\$ @NickKennedy Thanks, I'll add that explanation to the post along with a link to verify empirically that the probabilities are equal. \$\endgroup\$ Commented May 12, 2019 at 20:20
  • 1
    \$\begingroup\$ 148 bytes \$\endgroup\$
    – Giuseppe
    Commented May 13, 2019 at 14:20
3
\$\begingroup\$

JavaScript (ES6),  139 124 122  120 bytes

Save 2 bytes thanks to @Neil

Takes input as (names,adjectives)(nouns).

(N,a)=>F=n=>/^(.)\S+( \1\S+)+$/i.test(s=(g=a=>a[Math.random()*a.length|0])(N)+"'s "+[(o=g([,...a]))&&o+' ']+g(n))?s:F(n)

Try it online!

Or check the distribution on 5 million draws

How?

The helper function \$g\$ takes an array and returns a random element from this array, with a uniform distribution.

g = a => a[Math.random() * a.length | 0]

By invoking \$g\$ three times, we generate a random string \$s\$ with a valid format, but without taking the initial letters into account. For the adjective, we append an empty entry and make sure not to insert a trailing space if it's chosen.

s = g(N) + "'s " +
    [(o = g([, ...a])) && o + ' '] +
    g(n)

We then check if all initial letters are identical with the following regular expression:

/^(.)\S+( \1\S+)+$/i

It not, we simply try again until \$s\$ is valid.

\$\endgroup\$
2
  • \$\begingroup\$ +[(o=g([,...a]))&&o+' ']+ saves 2 bytes, I think? \$\endgroup\$
    – Neil
    Commented May 12, 2019 at 13:37
  • \$\begingroup\$ @Neil Ah, yes. Nice one. \$\endgroup\$
    – Arnauld
    Commented May 12, 2019 at 13:44
3
\$\begingroup\$

Python 3, 161 154 151 147 145 bytes

(Thanks ArBo, EmbodimentOfIgnorance, Neil who have contributed 2, 3 and 4 bytes to my first golf!)

from random import*
c=choice
def f(N,a,n):
 s=c(N);w=s[0].lower();o=N
 while o[0]!=w:o=c(n)
 print(s+"'s",c([x+" "for x in a if x[0]==w]+[""])+o)

Try it online! (with 500k executions)

  • Takes three lists as inputs.

  • Assumes at least one noun for each name.


Same score, more golf-y:

Python 3, 145 bytes

from random import*
c=choice
def f(N,a,n):
 s=c(N);y=lambda p,e=[]:c([x+" "for x in p if x[0]==s[0].lower()]+e);print(s+"'s",y(a,[""])+y(n)[:-1])

Try it online! (with 500k executions)

It's just 140 if trailing whitespaces are allowed (by removing square face [:-1])

\$\endgroup\$
11
  • 1
    \$\begingroup\$ Nice first answer! You can save a byte in the first while loop: while t>""<t[0]!=w. You can also replace the last line by print(s+"'s",t+(t and" ")+o), dropping the u= in the third line. \$\endgroup\$
    – ArBo
    Commented May 12, 2019 at 13:50
  • \$\begingroup\$ I ended up changing my solution because the previous didn't fit the requirements \$\endgroup\$
    – Nicola Sap
    Commented May 12, 2019 at 15:20
  • 1
    \$\begingroup\$ 152 bytes (Footer removed to fit URL in comment) \$\endgroup\$
    – Gymhgy
    Commented May 12, 2019 at 18:08
  • 1
    \$\begingroup\$ You're only using the variable t once so you can save 4 bytes by inlining the code. I think you can switch o to use a similar code pattern to t, and then save another 4 bytes by inlining that too. \$\endgroup\$
    – Neil
    Commented May 13, 2019 at 10:27
  • \$\begingroup\$ Thanks, you guys are really helping! @Neil, I haven't be able to refactor o: I reach this: from random import* c=choice def f(N,a,n): s=c(N);y=lambda p,e=[]:c([x for x in p if x[0]==s[0].lower()]+e);print(s+"'s",y(a,[""])+y(n)) (137) but adding the conditional whitespace, via an optional arg to y, costs me 11 bytes \$\endgroup\$
    – Nicola Sap
    Commented May 13, 2019 at 12:56
2
+100
\$\begingroup\$

Vyxal, 39 bytes

℅‛'s+:→a\ ?'h←a⇩h=;``J℅\ ?'h←a⇩h=;℅++++

Finally figured out what I'm doing.

Try it Online!

\$\endgroup\$
5
  • \$\begingroup\$ You might find using the register shorter than using variables \$\endgroup\$
    – lyxal
    Commented May 8, 2021 at 12:01
  • \$\begingroup\$ @lyxal I'm not entirely sure how to do that in the filter lambda. \$\endgroup\$
    – emanresu A
    Commented May 8, 2021 at 12:03
  • \$\begingroup\$ Oh well if it is in a lambda, you can simply use the context variable (n) \$\endgroup\$
    – lyxal
    Commented May 8, 2021 at 12:04
  • \$\begingroup\$ @lyxal But I need to check that the previous's first character is the same as the current first character, which I can't do without variables. The context is the current, but there's no way to get the previous. \$\endgroup\$
    – emanresu A
    Commented May 8, 2021 at 12:06
  • \$\begingroup\$ Ah, I see. Never mind me lol \$\endgroup\$
    – lyxal
    Commented May 8, 2021 at 12:07
0
\$\begingroup\$

Jelly, 28 bytes

1ịZḢXɓŒuḢ=ɗƇ€Ż€2¦X€ḟ0ż“'s“”K

Try it online!

Wrote this before I saw @JonathanAllan’s shorter answer, but thought it worth posting since it uses a different approach. Saved 3 bytes by @EriktheOutgolfer’s suggestion on that answer.

A full program taking a list of lists of strings and implicitly printing a randomly selected alliteration. Assumes at least one noun per name.

\$\endgroup\$
0
\$\begingroup\$

C# (Visual C# Interactive Compiler), 176 bytes

(a,b,c)=>(a=a[z.Next(a.Count)])+"'s "+b.Where(x=>(x[0]&95)==a[0]).Append("").OrderBy(x=>z.Next()).Last()+" "+c.OrderBy(x=>z.Next()).Last(x=>(x[0]&95)==a[0]);var z=new Random();

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ You can assume names begin with an uppercase letter, so you can just uppercase the other letters for the comparison, which should save you 10 bytes? \$\endgroup\$
    – Neil
    Commented May 12, 2019 at 23:19
  • \$\begingroup\$ @Neil Yep, exactly 10 bytes :) \$\endgroup\$
    – Gymhgy
    Commented May 13, 2019 at 1:30
0
\$\begingroup\$

Red, 179 bytes

func[a b c][random a random c
foreach k c[if k/1 = h: a/1/1 + 32[g: rejoin[sp k]]]collect/into[foreach
d b[if d/1 = h[keep rejoin[sp d]]]]e: copy[""]random e rejoin[a/1"'s"e/1 g]]

Try it online!

Explanation:

Red[]
f: func[a b c][                     ; a function with 3 arguments
    random a                        ; shuffle the list of names in place
    random c                        ; shuffle the list of nouns in place
    foreach k c [                   ; for each item in the shuffled list of nouns
        if k/1 = h: a/1/1 + 32 [    ; check if it begins with the same lowercase letter
                                    ; as the first name in the shuffled list of names
            g: rejoin [" " k]       ; if yes, then insert a " " in front of it save it as g
        ]                           ; thus I always get the last match
    ]
    collect/into [                  ; collect in a new list e
        foreach d b [               ; all items form the adjectives list
            if d/1 = h [            ; that start with the same lowercase letter as the 1st noun
                keep rejoin [" " d] ; insert a " " in form of the adjective
            ]
        ]
    ] e: copy[""]                   ; the list initially has a single item - the empty string
   random e                         ; shuffle the extracted adjectives list
   rejoin [a/1 "'s" e/1 g]          ; return the formatted string
]
\$\endgroup\$
0
\$\begingroup\$

Scala, 234 226 234 206 bytes

-28 due to the fact I thought it had to accept StdIn, it's a function now

def f(a:List[String],b:List[String],c:List[String])=scala.util.Random.shuffle(for(d<-a;e<-("" +: b);g<-c;if(d.head.toLower==g.head&&(e.isEmpty||e.head==g.head))) yield s"$d's $e $g".replace("  ", " ")).head

Try it online!

Ungolfed:

def f(names: List[String], adjectives: List[String], nouns: List[String]) = {
  val allPossible = for {
    name <- names
    adjective <- ("" +: adjectives) // Add the choice of no adjective
    noun <- nouns
    if (name.head.toLower == noun.head && (adjective.isEmpty || adjective.head == noun.head)) // Filter out so only matching entries remain
  } yield
    s"$name's $adjective $noun"
      .replace("  ", " ") // Get rid of artifact created by the empty adjective selection

  scala.util.Random.shuffle(allPossible.toList).head // Get a random element
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ 140 bytes. You can use a simple lambda instead of a proper method. There were also some other small modifications to be made (head can be (0) almost everywhere, you can use Seq instead of List (although that doesn't matter since it's not a method anymore), isEmpty can be .size<1, you can use infix syntax for replace) \$\endgroup\$
    – user
    Commented Nov 23, 2021 at 0:58
  • \$\begingroup\$ Actually, =="" can be used for isempty \$\endgroup\$
    – user
    Commented Nov 23, 2021 at 1:51
0
\$\begingroup\$

Ruby, 94 bytes

->a,b,c{"#{n=a.sample}'s #{s=[p,*b.grep(r=/^#{n[0]}/i)].sample;s+" "if s}#{c.grep(r).sample}"}

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Icon, 167 163 bytes

procedure f(a,b,c)
!a:=:?a&\x;!c:=:?c&\x;d:=[""]
e:=!b&e[1]==(t:=char(32+ord(a[1,1])))&put(d," "||e)&\x
!d:=:?d&\x;return(!a||"'s"||!d||" "||(k:=!c&t==k[1]&k))
end

Try it online!

Uses the same algorithm as my Red answer.

\$\endgroup\$
0
\$\begingroup\$

Excel, 201 bytes

=LET(a,INDEX(A:A,INT(RAND()*COUNTA(A:A))+1)&"'s",f,LEFT(a),b,FILTER(B:B,LEFT(B:B)=f,""),y,INT(RAND()*(ROWS(b)+1)),c,FILTER(C:C,LEFT(C:C)=f),a&IF(y," "&INDEX(b,y),"")&" "&INDEX(c,INT(RAND()*ROWS(c))+1))

Link to Spreadsheet

Explanation

=LET(
a,INDEX(A:A,INT(RAND()*COUNTA(A:A))+1)&"'s", 'a = rand name from first list & "'s"
f,LEFT(a),                                   'f = first letter of a
b,FILTER(B:B,LEFT(B:B)=f,""),                'b = all items in second list starting with f
y,INT(RAND()*(ROWS(b)+1)),                   'y = rand from {0 .. size b}
c,FILTER(C:C,LEFT(C:C)=f),                   'c = all items in third list starting with f
a                                            'result = a
&IF(y," "&INDEX(b,y),"")                     '    & if y = 0 then "" else " " & b(y)   
&" "&INDEX(c,INT(RAND()*ROWS(c))+1))         '    & " " & random item from c
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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