13
\$\begingroup\$

Pangrams

The quick brown fox jumped over the lazy dog.

This is an example of a pangram - a sentence that contains every letter of the alphabet at least once.

A self-enumerating pangram is a sentence that fulfills the criteria to be a pangram by producing an analysis of its own letter count.

An example of achieving this is the sentence

This pangram contains four As, one B, two Cs, one D, thirty Es, six Fs, five Gs, seven Hs, eleven Is, one J, one K, two Ls, two Ms, eighteen Ns, fifteen Os, two Ps, one Q, five Rs, twenty-seven Ss, eighteen Ts, two Us, seven Vs, eight Ws, two Xs, three Ys, & one Z.


The Challenge

Produce a function with the input being the string leading into the letter inventory. In the example, the input would be "This pangram contains". The quantity of each letter must be in the written form and contribute to the displayed letter count.


Rules

  • Oxford comma is optional
  • Use an ampersand before Z (or, for hard mode, include the ability to switch between "&" and "and" in the function)
  • Every letter counts towards the total letter count
  • No non-written numbers
  • This is so the shortest code in bytes wins
  • In honour of the married couple this weekend, the numbers are to be written in the Queen's English. e.g. nine hundred and ninety-nine Gs for 999 occurrences of the letter G and nine hundred and nine Gs for 909.
  • Orders of magnitude are to be written in the standard short-scale naming convention

Edge cases

  • There are some cases where the code will get stuck in a loop - for example, if there are two Os the code will increase the count to three Os, which causes the code to count two Os again. If calculating every other letter before coming back to this can't resolve the issue, consider the input to be a false starter and output false, null or an empty string.
  • If a letter has over 999 occurrences, the input should be considered a false starter.

Test Cases

  • "This pangram contains" should output the example sentence
\$\endgroup\$
12
  • 8
    \$\begingroup\$ I haven't tested it, but I'm pretty sure there are some cases where you can't construct a valid solution, what should the output be in that case? For example, if you have a string that contains two o-s without the word for the count, then when you spell it out with two, the whole string now has three o-s, meaning the word is now incorrect, switching it makes it incorrect again, etc. \$\endgroup\$
    – Etheryte
    Commented May 20, 2018 at 15:00
  • 2
    \$\begingroup\$ What's the largest written number the solution has to support? A hundred, a thousand, a million? \$\endgroup\$
    – Etheryte
    Commented May 20, 2018 at 15:02
  • 7
    \$\begingroup\$ Nice first question! However, I'd personally recommend to limit the maximum number of letters to 999 or maybe even just 99. This would make the challenge more accessible to languages that don't have decimal to text built-ins, while preserving all the fun. (Also, it may not even be possible to test entries with millions of letters in some languages.) \$\endgroup\$
    – Arnauld
    Commented May 20, 2018 at 16:10
  • 1
    \$\begingroup\$ Related: Converting integers to English words \$\endgroup\$
    – Laikoni
    Commented May 20, 2018 at 16:28
  • 1
    \$\begingroup\$ Rob enjoys any sentence with two As, two Bs, two Cs, one D, thirty five Es, four Fs, three Gs, nine Hs, eight Is, two Js, one K, two Ls, one M, twenty two Ns, eighteen Os, one P, one Q, nine Rs, twenty three Ss, twenty three Ts, three Us, four Vs, eleven Ws, one X, seven Ys, & one Z. \$\endgroup\$
    – Chas Brown
    Commented May 22, 2018 at 22:53

1 Answer 1

7
\$\begingroup\$

Python 2, 615 bytes

def g(n):S=str.split;D=S('z one two three four five six seven eight nine');K=' fif six seven eigh nine';n,m=n/100,n%100;e,d=m/10,m%10;return' '.join([D[n],'hundred']*(n>0)+([S('ten eleven twelve thir four'+K)[d]+'teen'*(d>2)]if 9<m<20else[S('twen thir for'+K)[e-2]+'ty']*(e>0)+[D[d]]*(d>0)))
from random import*
X='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
def f(p):
 T=set();s='';i=0
 while 1:
	Q=s.upper().count;t=p+' '+', '.join(['& '*(c=='Z')+g(Q(c))+' '+c+'s'*(Q(c)!=1)for c in X])+'.'
        if s==t:return i,t
	if t in T:t=''.join(c*max(1,t.upper().count(c)+(randint(0,6)-3)*(random()<0.25))for c in X)
	T.add(t);s=t;i+=1

Try it online!

The function f takes p as the string prefix; and returns a tuple of an integer representing the number of steps taken, and the autogram.

The function g encodes a number 1<=n<=999 to its english string; it takes up 291 bytes, roughly half the total byte count. The code

Q=s.upper().count
t=p+' '+', '.join(['& '*(c=='Z')+g(Q(c))+' '+c+'s'*(Q(c)!=1)for c in X])+'.'

encodes the string s into a (potentially autogram-matic) pangram t.

We loop over the process, hoping for the situation where we find a t such that t is an autogram (i.e., where t==s). If When we get into a loop, we randomly nudge the counts of letters in a completely ad hoc manner.

For most values of p, this process will time out on TIO. Typically, millions of possibilities are examined before finding a 'winning' combination.

I don't have any proof at all, but my guess is: (a) apart from an exhaustive brute force search similar to what the originator Lee Sallows describes here and implements in hardware (!), this sort of random heuristic is about as good as you can get; and (b) there will be no solutions for many (most?) initial phrases p.

\$\endgroup\$
1
  • \$\begingroup\$ map(chr,range(65,91)) saves some bytes. \$\endgroup\$ Commented May 27, 2018 at 10:31

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.