30
\$\begingroup\$

According to this site a general rule recommended by The U.S. Government Printing Office Style Manual is

Capitalize all words in titles of publications and documents, except a, an, the, at, by, for, in, of, on, to, up, and, as, but, or, and nor.

This might not be true as I'm unable to find such a recommendation in the Style Manual, but let's use this rule anyway.


The Challenge

Given an input string consisting of lower case words delimited by spaces, output the capitalization of the string according to the following rules

  • The first and last word is capitalized.
  • All other words are capitalized, except a, an, the, at, by, for, in, of, on, to, up, and, as, but, or, and nor.

The input string will contain at least one word and each word contains at least one letter and only characters from a to z.

This is a code golf challenge, so try to use as few bytes as possible in the language of your choice. You may write a full program or a function to accomplish the task.

Testcases

"the rule of thumb for title capitalization" -> "The Rule of Thumb for Title Capitalization"
"programming puzzles and code golf" -> "Programming Puzzles and Code Golf"
"the many uses of the letter a" -> "The Many Uses of the Letter A"
"title" -> "Title"
"and and and" -> "And and And"
"a an and as at but by for in nor of on or the to up" -> "A an and as at but by for in nor of on or the to Up"
"on computable numbers with an application to the entscheidungsproblem" -> "On Computable Numbers With an Application to the Entscheidungsproblem"
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Should beginning/ending words be capitalized even if they are in the exclusion list? Your examples say yes, but the spec just says capitalize words unless they are in the list, and nothing about first/last word. Note that the two possibilities are distinctly different, one being a simple filter and the second requiring special behavior in (literal) edge cases. \$\endgroup\$
    – CAD97
    Oct 26, 2016 at 16:01
  • 3
    \$\begingroup\$ @CAD97 The rules for the capitalization are the two bullet points, not the Quote. And the first bullet point says "The first and last word is capitalized." and the second one says "All other words are capitalized, except ..." meaning first and last words are always capitalized. \$\endgroup\$
    – Laikoni
    Oct 26, 2016 at 16:16
  • \$\begingroup\$ I missed that, somehow. Still, thanks for clarifying. \$\endgroup\$
    – CAD97
    Oct 26, 2016 at 16:39
  • \$\begingroup\$ I'm not sure it's really necessary to specify that each word contains at least one letter. :) \$\endgroup\$ Oct 27, 2016 at 7:41

22 Answers 22

11
\$\begingroup\$

Python 2, 118 bytes

Look ma, no regex!

for w in`input()`.split():print[w.title(),w][`w`in"'a'an'and'as'at'the'by'but'for'nor'in'of'on'or'to'up'"].strip("'"),

Input must be wrapped in quotes. Output has a trailing space and no trailing newline (I assume that's okay). Verify all test cases on Ideone.

Explanation

Let's take the input a or an as our example.

Using Python 2's `x` shortcut for repr, we wrap the input in single quotes: 'a or an'. Then we split on whitespace and iterate over the words.

Inside the loop, we take the repr again. For the first and last words, this gives "'a" and "an'". For other words, it gives 'or'. We want to avoid capitalizing words if they fit the latter pattern and are in the short-words list. So we can represent the word list as the string "'a'an'...'up'" and know that the repr of any short word will be a substring.

`w` in "..." gives a boolean value, which we can treat as 0 or 1 for the purposes of indexing into the list [w.title(), w]. In short, we title-case the word if it is at the beginning, at the end, or not in the list of short words. Otherwise, we leave it alone. Fortunately, title() still works as expected with input like 'a.

Finally, we strip any single quotes from the word and print it with a trailing space.

\$\endgroup\$
8
\$\begingroup\$

05AB1E, 68 61 bytes

Saved 7 bytes thanks to Adnan

™ð¡Dg<UvyN__NXQ_“a€¤€€€›€‹€‡€†€‚€‰€„€¾€ƒ€œ€³€—š¯“#™yå&&il})ðý

Try it online!

Explanation

“a€¤€€€›€‹€‡€†€‚€‰€„€¾€ƒ€œ€³€—š¯“ is a dictionary string translated as a an the at by for in of on to up and as but or nor.

™                          # title case input string
ð¡                         # split on spaces
Dg<U                       # store index of last word in X

vy                         # for each word
  N__                      # is it not first index?
     NXQ_                  # is it not last index
         “...“             # the compressed string 
              #            # split on spaces
               ™           # convert to title case
                yå         # is current word in this list?
                  &&       # and the 3 previous conditions together
                    il     # if all are true, convert to lower case
                      }    # end loop
)ðý                        # wrap stack in list and join by spaces
\$\endgroup\$
5
  • 2
    \$\begingroup\$ It Never Ceases to Amaze Me What You Manage to Achieve With a Short String of Totally Unrecognisible Characters. Looks like it works then :) +1 \$\endgroup\$
    – ElPedro
    Oct 26, 2016 at 16:18
  • \$\begingroup\$ Bah! I'm so close, and I can't find a way to shave off a character. \$\endgroup\$
    – mbomb007
    Oct 26, 2016 at 17:28
  • \$\begingroup\$ @mbomb007: Better hurry up before Jelly, MATL or some other language that can apply functions to indices comes and beat this :) I seem to remember a language with compressed regex as well, but can't remember what it was called. This is long enough that it might still be golfable as well. \$\endgroup\$
    – Emigna
    Oct 26, 2016 at 17:37
  • 1
    \$\begingroup\$ For 62 bytes :) \$\endgroup\$
    – Adnan
    Oct 26, 2016 at 22:43
  • \$\begingroup\$ @Adnan: I started like that but only with the 3-char words (which ended up longer), but I didn't consider taking the 2-char words as well... a instead of €… saves an additional byte as well if lead of with it :) Thanks! \$\endgroup\$
    – Emigna
    Oct 27, 2016 at 6:23
7
\$\begingroup\$

GNU sed 81 74 73 Bytes

Includes +1 for -r

s/\b./\u&/g
:;s/.(And?|A[st]?|The|By|But|[FN]or|In|O[fnr]|To|Up) /\L&/;t

The first line capitalizes the first letter of every word. The second switches all of the required words back to lowercase.

Try it Online!

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

Retina, 69 66 bytes

Capitalize the first letter of every word, then change the selected words to lowercase if they're not the first or last word. There's a space at the end of the last line.

T`l`L`\b.
+T`L`l` (And?|A[st]?|The|By|But|[FN]or|In|O[fnr]|To|Up) 

Try it online

This also works with a . instead of the first space.

There are a lot of regexes with the same length, but I can't find a way to trim it anymore...

\$\endgroup\$
2
  • \$\begingroup\$ (This approach is also 69 bytes in Pip, but I can't use the + trick to shorten it.) \$\endgroup\$
    – DLosc
    Oct 26, 2016 at 18:44
  • \$\begingroup\$ @DLosc Thanks. Idk why I didn't see that. I was close. \$\endgroup\$
    – mbomb007
    Oct 26, 2016 at 19:16
3
\$\begingroup\$

JavaScript (ES6), 141 138 135 133 bytes

Saved 3 bytes thanks to mbomb007

s=>s.replace(/(\w+)( ?)/g,(a,w,n,i)=>i&&n&&/^(a[nst]?|the|by|in|of|on|to|up|and|but|[fn]?or)$/.exec(w)?a:a[0].toUpperCase()+a.slice(1))

Test cases

let f =

s=>s.replace(/(\w)(\w*)( ?)/g,(a,l,w,n,i)=>i&&n&&/^(a[nst]?|the|by|in|of|on|to|up|and|but|[fn]?or)$/.exec(l+w)?a:l.toUpperCase()+w+n)

console.log(f("the rule of thumb for title capitalization"));
console.log(f("programming puzzles and code golf"));
console.log(f("the many uses of the letter a"));
console.log(f("title"));
console.log(f("and and and"));
console.log(f("a an and as at but by for in nor of on or the to up"));
console.log(f("on computable numbers with an application to the entscheidungsproblem"));

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

Jelly, 58 bytes

“Ð/ṃƇ¬þṄẊƙ€,⁽ṙƬ®OṪJ"ɦ3×kf3Ṙç%ġu’b26ịØaṣ”z
e€¢¬T;2Ḷ¤
ḲŒtǦK

TryItOnline! or run all tests

How?

A compressed string with spaces separating the words would be 47 bytes, splitting it costs 1 byte, for 48 bytes.

Two unseparated compressed strings of the words of length 2 and 3 (with an 'a' on the end of one) respectively would be 40 bytes plus 2 to split each and 1 to join them, for 45 bytes.

One base 250 number as described below is 32 bytes, then 3 to convert to base 26, 3 to index into the lowercase alphabet and 3 to split it on the unused character, 'z', for 41 bytes.

So, the lookup for the words not to capitalise:
“Ð/ṃƇ¬þṄẊƙ€,⁽ṙƬ®OṪJ"ɦ3×kf3Ṙç%ġu’
was formed like so:

Take those words and join them with a separator:
s="a an the at by for in of on to up and as but or nor"

Next label 'a' as 1, 'b' as 2 with the separator as 0:

alpha = ' abcdefghijklmnopqrstuvwxyz'
x = [alpha.index(v) for v in s]
x
[1,0,1,14,0,20,8,5,0,1,20,0,2,25,0,6,15,18,0,9,14,0,15,6,0,15,14,0,20,15,0,21,16,0,1,14,4,0,1,19,0,2,21,20,0,15,18,0,14,15,18]

Convert this into a base 26 number (the last letter used is 'y' plus a digit for the separator, Python code for this is:
n=sum(v*26**i for i,v in enumerate(x[::-1]))

Convert that into a base 250 number (using a list for the digits):

b=[]
while n:
    n,d = divmod(n,250)
    b=[d]+b
b
[16,48,220,145,8,32,202,209,162,13,45,142,244,153,9,80,207,75,35,161,52,18,108,103,52,205,24,38,237,118]

Lookup the characters at those indexes in jelly's codepage:

codepage = '''¡¢£¤¥¦©¬®µ½¿€ÆÇÐÑ×ØŒÞßæçðıȷñ÷øœþ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQR TUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¶°¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ƁƇƊƑƓƘⱮƝƤƬƲȤɓƈɗƒɠɦƙɱɲƥʠɼʂƭʋȥẠḄḌẸḤỊḲḶṂṆỌṚṢṬỤṾẈỴẒȦḂĊḊĖḞĠḢİĿṀṄȮṖṘṠṪẆẊẎŻạḅḍẹḥịḳḷṃṇọṛṣṭụṿẉỵẓȧḃċḋėḟġḣŀṁṅȯṗṙṡṫẇẋẏż«»‘’“”'''
r=''.join(codepage[i-1] for i in b)
r
'Ð/ṃƇ¬þṄẊƙ€,⁽ṙƬ®OṪJ"ɦ3×kf3Ṙç%ġu'

(note: since the actual implementation is bijective, if b had any 0 digits one would need to carry down first)

The rest:

ḲŒtǦK - Main link: title string
Ḳ      - split on spaces
    ¦  - apply to indexes
   Ç   -     given by calling the last link (1) as a monad (with the split title string)
 Œt    -     title case (first letter of each (only) word to upper case)
     K - join on spaces

e€¢¬T;2Ḷ¤ - Link 1, find indexes to capitalise: split title string
e€        - is an element of, for €ach
  ¢       - the result of calling the last link (2) as a nilad
   ¬      - logical not
    T     - get the truthy indexes (indexes of words that are not in the list)
     ;    - concatenate with
        ¤ - nilad followed by link(s) as a nilad
      2Ḷ  - range(2) -> [0,1]
                (we always want to capitalise the first index, 1, and the last index, 0)

“Ð/ṃƇ¬þṄẊƙ€,⁽ṙƬ®OṪJ"ɦ3×kf3Ṙç%ġu’b26ịØaṣ”z - Link 2, make the word list: no arguments
“Ð/ṃƇ¬þṄẊƙ€,⁽ṙƬ®OṪJ"ɦ3×kf3Ṙç%ġu’          - the base 250 number
                                b26       - convert to base 26
                                   ị      - index into
                                    Øa    - lowercase alphabet
                                      ṣ   - split on
                                       ”z - literal 'z' (the separator 0 indexes into `z`)
\$\endgroup\$
2
\$\begingroup\$

PHP, 158 Bytes

10 Bytes saved by @Titus

foreach($w=explode(" ",$argv[1])as$k=>$v)echo" "[!$k],$k&&$k+1<count($w)&&preg_match("#^(a[snt]?|and|[fn]or|up|by|but|the|to|in|o[rnf])$#",$v)?$v:ucfirst($v);

Previous version PHP, 174 Bytes

foreach($w=explode(" ",$argv[1])as$k=>$v)$k&&$k+1<count($w)&&in_array($v,[a,an,the,at,by,"for",in,of,on,to,up,"and","as",but,"or",nor])?:$w[$k]=ucfirst($v);echo join(" ",$w);
\$\endgroup\$
1
  • \$\begingroup\$ Echoing in the loop saves 10 bytes: foreach(...)echo" "[!$k],(condition)?$v:ucfirst($v); \$\endgroup\$
    – Titus
    Oct 28, 2016 at 14:16
2
\$\begingroup\$

TI-Basic, 295 + 59 + 148 = 502 bytes

Now you can capitalize on your calculator. Great for school :)

Main Program, 295 bytes

Basically, the trick to matching words so all A don't become a is to enclose with spaces, such as replace " A " with " a ". This also automatically makes it so that the first and last words stay capitalized, because they do not have a space on both sides and will thus not match any of the words. (Genius, right? And super long because lowercase letters are two bytes each...)

"("+Ans+")→Str1
"@A ~ a@An ~ an@The ~ the@At ~ at@By ~ by@For ~ for@In ~ in@Of ~ of@On ~ on@To ~ to@Up ~ up@And ~ and@As ~ as@But ~ but@Or ~ or@Nor ~ nor@→Str2
For(I,2,length(Ans
If "@"=sub(Str2,I-1,1
Then
" "+Str1+"~"+sub(Str2,I,inString(Str2,"@",I)-I)+" "
prgmQ
Ans→Str1
End
End

Subprogram (prgmQ), 59 bytes:

Ans→Str9
inString(Ans,"~
sub(Str9,Ans,length(Str9)-Ans+1→Str8
Str9
prgmR
Repeat Str9=Ans+Str8
Ans+Str8→Str9
prgmR
End

Subprogram (prgmR), 148 bytes:

Ans→Str0
inString(Ans,"~→Z
inString(Str0,"~",Ans+1→Y
inString(sub(Str0,1,Z-1),sub(Str0,Z+1,Ans-Z-1→X
sub(Str0,1,-1+inString(Str0,"~
If X
sub(Str0,1,X-1)+sub(Str0,Y+1,length(Str0)-Y)+sub(Str0,X+length(sub(Str0,Z+1,Y-Z-1)),Z-X-length(sub(Str0,Z+1,Y-Z-1

P.S. ~ represents token 0x81 and @ represents token 0x7F, learn more here.

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

Java 7, 271 259 258 bytes

String c(String x){String a[]=x.split(" "),s=" ",r=w(a[0])+s;for(int i=0,l=a.length-1;i<l;r+=(!s.matches("^(a[nst]?|the|by|in|of|on|to|up|and|but|[fn]?or)$")|i==l?w(s):s)+" ")s=a[++i];return r;}String w(String w){return(char)(w.charAt(0)-32)+w.substring(1);}

Ungolfed & test code:

Try it here.

class M{
  static String c(String x){
    String a[] = x.split(" "),
           s = " ",
           r = w(a[0]) + s;
    for(int i = 0, l = a.length-1; i < l; r += (!s.matches("^(a[nst]?|the|by|in|of|on|to|up|and|but|[fn]?or)$") | i == l
                                                 ? w(s)
                                                 : s)   + " "){
      s = a[++i];
    }
    return r;
  }

  static String w(String w) {
    return (char)(w.charAt(0) - 32) + w.substring(1);
  }

  public static void main(String[] a){
    System.out.println(c("the rule of thumb for title capitalization"));
    System.out.println(c("programming puzzles and code golf"));
    System.out.println(c("the many uses of the letter a"));
    System.out.println(c("title"));
    System.out.println(c("and and and"));
    System.out.println(c("a an and as at but by for in nor of on or the to up"));
    System.out.println(c("on computable numbers with an application to the entscheidungsproblem"));
  }
}

Output:

The Rule of Thumb for Title Capitalization 
Programming Puzzles and Code Golf 
The Many Uses of the Letter A 
Title 
And and And 
A an and as at but by for in nor of on or the to Up 
On Computable Numbers With an Application to the Entscheidungsproblem 
\$\endgroup\$
1
\$\begingroup\$

Groovy, 131 129

Two bytes saved thanks to carusocomputing

{it.split()*.with{a->a in "a an the at by for in of on to up and as but or nor".split()?a:a.capitalize()}.join(" ").capitalize()}
\$\endgroup\$
5
  • \$\begingroup\$ Nice, I was at 137; you win. Remove it i-> and use it to save 2 bytes. {it.split()*.with{a->a in "a an the at by for in of on to up and as but or nor".split()?a:a.capitalize()}.join(" ").capitalize()} \$\endgroup\$ Oct 26, 2016 at 17:33
  • 1
    \$\begingroup\$ I don't know Groovy but does this really capitalize the first and last word? \$\endgroup\$
    – Emigna
    Oct 26, 2016 at 17:40
  • \$\begingroup\$ @Emigna the final capitalize covers starting with one of the words. \$\endgroup\$ Oct 26, 2016 at 17:47
  • \$\begingroup\$ @Emigna not really, I missed that requirement (that last word needs to be capitalized). I would need to adjust my anwser. \$\endgroup\$ Oct 26, 2016 at 17:49
  • \$\begingroup\$ The two uses of .capitalize() take up a lot of bytes. Is there a short way you can make an alias to .capitalize()? \$\endgroup\$
    – Cyoce
    Nov 2, 2016 at 23:13
1
\$\begingroup\$

C#, 305 bytes

Lots of room for improvement still but here you go:

s=>{;var b=s.Split(' ');b[0]=((char)(b[0][0]-32))+b[0].Substring(1);int i=0,n=b.Length;for(;++i<n;)if(!"a,an,the,at,by,for,in,of,on,to,up,and,as,but,or,nor".Split(',').Contains(b[i]))b[i]=((char)(b[i][0]-32))+b[i].Substring(1);b[n-1]=((char)(b[n-1][0]-32))+b[n-1].Substring(1);return string.Join(" ",b);};
\$\endgroup\$
1
\$\begingroup\$

Ruby, 123 117 111 102 bytes

->s{s.gsub(/ .|^./,&:upcase).gsub(/ (A[nts]?|The|By|In|To|Up|And|But|[NF]or|O[rnf])(?= )/,&:downcase)}

Sorry for all the edits - this should be the last one.

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

Python, 177 bytes

Delivered in function format for byte saving purposes. This is not an especially competitive answer, but it is one that doesn't require repr() or regex trickery. It is also version-agnostic; it works with Python 2 or 3.

Though it is perhaps a very by-the-rules solution.

def t(s):
 w="a an the the at by for in of on to up and as but or nor".split()
 l=[(s.title(),s)[s in w]for s in s.split()]
 for x in(0,-1):l[x]=l[x].title()
 return' '.join(l)
\$\endgroup\$
1
\$\begingroup\$

PHP, 109 142 bytes

<?=preg_replace_callback("# (A[snt]?|And|[FN]or|Up|By|But|The|To|In|O[rnf])(?= )#",function($m){return strtolower($m[0]);},ucwords($argv[1]));

A merger of user59178´s and mbomb007´s answer.

uppercases the first letter of every word, then lowercases all words from the list surrounded by spaces.
Unfortunately, the callback has to operate on the complete set; this costs 29 bytes.

\$\endgroup\$
1
  • \$\begingroup\$ it works not for a an and as at but by for in nor of on or the to up \$\endgroup\$ Oct 28, 2016 at 20:15
1
\$\begingroup\$

Racket 353 bytes

(define(cap i)(set! i(string-append i))(define c(string-ref i 0))(string-set! i 0(if(char-upper-case? c)c(integer->char(-(char->integer c)32))))i)
(let*((ex(list"a""an""the""at""by""for""in""of""on""to""up""and""as""but""or""and""nor"))(sl(string-split s)))
(string-join(for/list((i sl)(n(in-naturals)))(cond[(= n 0)(cap i)][(member i ex)i][(cap i)]))))

Ungolfed:

(define (f s)

  (define (cap i)                 ; sub-fn to capitalize first letter of a word
    (set! i (string-append i))
    (define c (string-ref i 0))
    (string-set! i 0
                 (if (char-upper-case? c)
                     c
                     (integer->char (-(char->integer c)32))))
    i)

  (let* ((ex (list "a""an""the""at""by""for""in""of""on""to""up""and""as""but""or""and""nor"))
         (sl (string-split s)))
    (string-join
     (for/list
         ((i sl)
          (n (in-naturals)))
       (cond
         [(= n 0) (cap i)]
         [(member i ex) i]
         [(cap i)]
         )))))

Testing:

(f "the rule of thumb for title capitalization")

Output:

"The Rule of Thumb for Title Capitalization"
\$\endgroup\$
1
\$\begingroup\$

Java 7, 431 317 311 bytes

Thanks to @KevinCruijssen for 114 bytes.
Thanks to @RosLup for saving 6 bytes.

String c(String s){String v="",x,l[]=s.split(" "),b[]={"a","an","the","at","but,"by","for","in","of","on","to","‌​up","as","or","and","nor"};int i=0,f=0,z=0;for(String c:l){for(f=0;f<b.length;z=c.equals(b[f++])|z>0?1:0);x=(char)(c.charAt(0)-32)+c.substring(1);v+=(z>0?i<1|i>l.length-2?x:c:x)+" ";i++;}return v;}

ungolfed

first answer above 250 bytes

 static String c(String s) {
      String v = "", x, l[] = s.split(" "),
b[]={"a","an","the","at","by","for","in","of","on","to",
                                         "‌​up","and","as","or","nor","but"};
    int i , f , z = i = f = 0;
    for (String c : l) {

   for (f = 0; f < b.length; z = c.equals( b[f++] ) | z > 0 ? 1 : 0);
        x = (char)(c.charAt(0) - 32) + c.substring(1);

        v += (z > 0 ? i < 1 | i > l.length - 2 ? x : c : x) + " ";
        i++;
   }
    return v;
    }
\$\endgroup\$
6
  • 1
    \$\begingroup\$ It was too much to summarize in a comment, but you can golf it to this: String f(String s){String v="",x,l[]=s.split(" "),b[]={"a","an","the","at","by","for","in","of","on","to","up","and","as","but","or","and","nor"};int i=0,f=0,z=0;for(String c:l){for(f=0;f<b.length;z=c.equals(b[f++])|z>0?1:0);x=(char)(c.charAt(0)-32)+c.substring(1);v+=z>0?i<1|i++==l.length-1?x:c:x)+" ";}return v;} (314 bytes) I suggest taking a look at what I changed as tips for next time. :) PS: I've posted an answer with a different approach (259 bytes). \$\endgroup\$ Oct 27, 2016 at 12:28
  • 1
    \$\begingroup\$ Especially things like c.substring(0,1).toUpperCase()+c.substring(1,c.length())+" " which you did twice should make you think about re-using it somehow. And combined initializations like you did correctly with the int, but for some reason not with the String. Also, no need for the extra boolean when you can store at as an int 0 or 1 and then check it >0. And I would try to avoid brackets and break as much as possible; usually there is a trick to get rid of them, like the for(f=0;f<b.length;z=c.equals(b[f++])|z>0?1:0); I've showed. :) \$\endgroup\$ Oct 27, 2016 at 12:32
  • 1
    \$\begingroup\$ So much to learn and thanks for being helpful always (long live Nederland ;) \$\endgroup\$
    – Numberknot
    Oct 27, 2016 at 12:38
  • 1
    \$\begingroup\$ Oh, I've made a copy-paste error.. It should be this String c(String s){String v="",x,l[]=s.split(" "),b[]={"a","an","the","at","by","for","in","of","on","to","up","and","as","but","or","and","nor"};int i=0,f=0,z=0;for(String c:l){for(f=0;f<b.length;z=c.equals(b[f++])|z>0?1:0);x=(char)(c.charAt(0)-32)+c.substring(1);v+=(z>0?i<1|i++>l.length-2?x:c:x)+" ";}return v;} And no problem. :) I also learned a lot when I was new to code-golfing. I just make a list with every general codegolf tip I learn and look/update it sometimes. But my code still gets golfed by others a lot. \$\endgroup\$ Oct 27, 2016 at 12:49
  • 1
    \$\begingroup\$ In the string b[] there are 2 'and' is that ok? \$\endgroup\$
    – user58988
    Oct 29, 2016 at 11:30
1
\$\begingroup\$

PHP, 117 118 112 bytes

<?=strtr(ucwords(preg_replace("# (?=(a[snt]?|and|[fn]or|up|by|but|the|to|in|o[rnf]) )#","!",$argv[1])),'!',' ');

Uses the behaviour of ucwords() and escapes the relevant words that are surrounded by spaces then deletes the escape characters.

I copied the (a[snt]?|and|[fn]or|up|by|but|the|to|in|o[rnf]) from Jörg Hülsermann's answer but as the approach is completely different I'm posting it as a separate answer.

edit: bug noticed by Titus, fixing it cost 1 byte. also: 6 bytes saved thanks to his helpful comment about strtr

\$\endgroup\$
4
  • \$\begingroup\$ Save 6 bytes with strtr instead of str_replace. Or prepend the words with <> and drop the str_replace and use HTML output. \$\endgroup\$
    – Titus
    Oct 28, 2016 at 14:08
  • \$\begingroup\$ In some cases you can use preg_filter instead of preg_replace. I have not try it with your solution \$\endgroup\$ Oct 28, 2016 at 14:31
  • \$\begingroup\$ The regex will not work for two words from the list in a row; test nice try for a start. Replacing one of the spaces with an assertion solves that (+4 bytes). \$\endgroup\$
    – Titus
    Oct 29, 2016 at 11:20
  • \$\begingroup\$ Unfortunately preg_filter would fail on the title test case, returning nothing. \$\endgroup\$
    – user59178
    Nov 1, 2016 at 9:30
1
\$\begingroup\$

Pure bash - 253

(no external programs called) - needs bash v4

declare -A b;for x in A An The At By For In Of On To Up And As But Or Nor;do b[$x]=1;done
while read -a w;do
n=${#w[@]};o[0]=${w[0]^}
for((i=1;i<n-1;i++)){
g=${w[$i]^};((${b[$g]}))&&o+=(${g,,})||o+=($g);}
((n>1))&&o[$n]=${w[-1]^}
echo ${o[@]};o=()
done

normal view with comments

#create the "blacklist"
declare -A b
for w in A An The At By For In Of On To Up And As But Or Nor
do
    b[$x]=1
done

# logic:
# read each line (split by words) into array
# and each word is assigned capitalized to the new output array
# but the blacklisted ones

#read each line to array w (split on spaces)
while read -a w
do
    n=${#w[@]}         # get the number of words
    o[0]=${w[0]^}          # copy the capitalized word1
    for((i=1 ; i<n-1 ; i++)) { # loop over 2 up to last -1 words

        g=${w[$i]^}    # for the given word
        # check if it is in the blacklisted ones
        # if yes - convert to lowercase, if not leave as it is
        # and append to the output array
        (( ${b[$g]} )) && o+=(${g,,}) || o+=($g)
    }
    # capitalize the last word if here is more words
    (( n>1 )) && o[$n]=${w[-1]^}
    # make a line from the words
    echo ${o[@]}
    o=() #cleanup
done

output

Title
And and And
The Rule of Thumb for Title Capitalization
Programming Puzzles and Code Golf
The Many Uses of the Letter A
A an and as at but by for in nor of on or the to Up
On Computable Numbers With an Application to the Entscheidungsproblem
\$\endgroup\$
1
\$\begingroup\$

Japt, 71 bytes

£`a  e  by f     up d  ¿t  n`¸aX >0©Y¦0©YĦZl ?X:Xg u +XÅ}S

Try it online!

Explanation:

£`a  e  by f     up d  ¿t  n`¸aX >0©Y¦0©YĦZl ?X:Xg u +XÅ}S
£`...`qS aX >0&&Y!=0&&Y!=UqS l -1?X:Xg u +Xs1}S

£                                            }S   // Split at spaces and map each item X by this function:
 `...`                                            //  Backticks are used to decompress strings
      qS                                          //  Split the decompressed string at spaces.
         aX >J                                    //  If this contains X
              &&Y!=0                              //  and the index is non-zero (it's not the first word)
                    &&Y!=UqS l -1                 //  and the index is not the length of the input -1 (it's not the last word),
                                 ?X               //  return X.
                                   :Xg u +Xs1     //  Else, return X capitalized. (Literally X[0].toUpperCase() + X.slice(1))
                                             }S   // Rejoin with spaces

One of my favorite Japt features is its string compression, which uses the shoco library.

You can compress a string by wrapping it in Oc"{string}"Oc"a an the at by for in of on to up and as but or nor"

Then decompressing it with backticks or Od"{compressed string}"Od"a e by f up d ¿t n"

\$\endgroup\$
3
  • \$\begingroup\$ The -S flag was added after this challenge was posted, so your current solution is non-competing. However, I think you can do £...+XÅ}S, which would be competing for the same byte-count (Try it online!) \$\endgroup\$ Mar 23, 2017 at 20:30
  • \$\begingroup\$ How does shoco compare with Jelly's dictionary compression in your opinion? \$\endgroup\$ Mar 24, 2017 at 20:37
  • \$\begingroup\$ @RobertFraser Compared to Jelly, it's not very good at compressing strings of English words, but it is very good at compressing strings of arbitrary lowercase letters, which comes in handy sometimes. \$\endgroup\$ Mar 24, 2017 at 20:40
1
\$\begingroup\$

Pure bash - 205 192 181 bytes

tc(){
while read -a x
do x=(${x[@]^})
for ((i=1;i<${#x[@]}-1;i++))
do
case "${x[i]}" in
A|A[nts]|The|By|[FN]or|In|O[fnr]|To|Up|And|But)x[i]=${x[i],};;
esac
done
echo ${x[@]}
done
}

Like jm66's answer tc accepts standard input.

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

Actually, 79 bytes

' ,ÿsd@p@`;0"A0An0The0At0By0For0In0Of0On0To0Up0And0As0But0Or0Nor"síu'ù*ƒ`Moq' j

Try it online!

Explanation:

' ,ÿsd@p@`;0"longstring"síu'ù*ƒ`Moq' j
' ,ÿs                                   title case input, split on spaces
     d@p@                               pop first and last words to stack
         `;0"longstring"síu'ù*ƒ`M       for every word except the first and last:
          ;0"longstring"s                 duplicate word, split the long string on 0s
                         íu               1-based index of word in list (0 if not found)
                           'ù*            "ù"*(index)
                              ƒ           execute the resulting string as a function (lowercases word if it's in the list)
                                 oq' j  put the first and last word back in the list, join with spaces
\$\endgroup\$
0
\$\begingroup\$

Batch, 323 bytes

@echo off
set s=
for %%w in (@%*@)do call:w %%w
echo%s%
exit/b
:w
for %%s in (a an the at by for in of on to up and as but or nor)do if %%s==%1 set s=%s% %1&exit/b
set w=%1
set w=%w:@=%
set f=%w:~0,1%
for %%c in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)do call set f=%%f:%%c=%%c%%
set s=%s% %f%%w:~1%

With comments:

@echo off
rem Start with an empty output string
set s=
rem Wrap the parameters in @ signs to identify the first and last words 
for %%w in (@%*@) do call :w %%w
rem Ignore the leading space when printing the result
echo%s%
exit/b
:w
rem Check whether this is a word that we don't change
for %%s in (a an the at by for in of on to up and as but or nor) do if %%s==%1 set s=%s% %1&exit/b
set w=%1
rem Delete any @ signs from the first and last words
set w=%w:@=%
rem Get the first character
set f=%w:~0,1%
rem Case insensitively replace each upper case letter with itself
for %%c in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do call set f=%%f:%%c=%%c%%
rem Concatenate with the rest of the word
set s=%s% %f%%w:~1%
\$\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.