Fix my Basic Orthography

While I am excellent at following the English capitalisation rules when writing, when I am typing I have a habit a capitalising almost everything (as you will see from the titles of my challenges). I need a program to fix that, but because I use so many capitals, and capitals are big letters, your program must be as short as possible.


  • Your program should take each word, and if it is the first alphanumeric set of characters after a full stop or the beginning of the input, capitalize the first letter. All the rest of the letters until the next full stop will be lowercase. At the full stop, capitalize the first letter and continue.
  • Punctuation must be preserved.
  • In a sentence without a full stop at the end, that should be added.
  • Spaces, where missing, should be inserted. A space is 'missing' if there is nothing between a full stop and the character following it.

(Note for americanish speakers: full stop = period = .)

Test Cases

tHIS SenteNce HAS veRy bAd GrammAr. ==> This sentence has very bad grammar.


PuncTuation must-be PReserved. ==> Punctuation must-be preserved.


full StOps ShoulD Be inserted ==> Full stops should be inserted.


MultiPLe sEntEnceS Are possible. thEY Are, yoU KNOW. ==> Multiple sentences are possible. They are, you know.


spaces. are.inserted. ==> Spaces. Are. Inserted.


  • Your program should take input as an argument or from STDIN (or the closest alternative in your language).
  • Standard loopholes are forbidden.


When is the tag, it's the least bytes to win!


To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

If there you want to include multiple numbers in your header (e.g. because your score is the sum of two files or you want to list interpreter flag penalties separately), make sure that the actual score is the last number in the header:

# Perl, 43 + 2 (-p flag) = 45 bytes

You can also make the language name a link which will then show up in the leaderboard snippet:

# [><>](http://esolangs.org/wiki/Fish), 121 bytes


/* Configuration */

var QUESTION_ID = 79842; // Obtain this from the url
// It will be like https://XYZ.stackexchange.com/questions/QUESTION_ID/... on any question page
var ANSWER_FILTER = "!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe";
var COMMENT_FILTER = "!)Q2B_A2kjfAiU78X(md6BoYk";
var OVERRIDE_USER = 53406; // This should be the user ID of the challenge author.

/* App */

var answers = [], answers_hash, answer_ids, answer_page = 1, more_answers = true, comment_page;

function answersUrl(index) {
  return "https://api.stackexchange.com/2.2/questions/" +  QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER;

function commentUrl(index, answers) {
  return "https://api.stackexchange.com/2.2/answers/" + answers.join(';') + "/comments?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + COMMENT_FILTER;

function getAnswers() {
    url: answersUrl(answer_page++),
    method: "get",
    dataType: "jsonp",
    crossDomain: true,
    success: function (data) {
      answers.push.apply(answers, data.items);
      answers_hash = [];
      answer_ids = [];
      data.items.forEach(function(a) {
        a.comments = [];
        var id = +a.share_link.match(/\d+/);
        answers_hash[id] = a;
      if (!data.has_more) more_answers = false;
      comment_page = 1;

function getComments() {
    url: commentUrl(comment_page++, answer_ids),
    method: "get",
    dataType: "jsonp",
    crossDomain: true,
    success: function (data) {
      data.items.forEach(function(c) {
        if (c.owner.user_id === OVERRIDE_USER)
      if (data.has_more) getComments();
      else if (more_answers) getAnswers();
      else process();


var SCORE_REG = /<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/;

var OVERRIDE_REG = /^Override\s*header:\s*/i;

function getAuthorName(a) {
  return a.owner.display_name;

function process() {
  var valid = [];
  answers.forEach(function(a) {
    var body = a.body;
    a.comments.forEach(function(c) {
        body = '<h1>' + c.body.replace(OVERRIDE_REG, '') + '</h1>';
    var match = body.match(SCORE_REG);
    if (match)
        user: getAuthorName(a),
        size: +match[2],
        language: match[1],
        link: a.share_link,
  valid.sort(function (a, b) {
    var aB = a.size,
        bB = b.size;
    return aB - bB

  var languages = {};
  var place = 1;
  var lastSize = null;
  var lastPlace = 1;
  valid.forEach(function (a) {
    if (a.size != lastSize)
      lastPlace = place;
    lastSize = a.size;
    var answer = jQuery("#answer-template").html();
    answer = answer.replace("{{PLACE}}", lastPlace + ".")
                   .replace("{{NAME}}", a.user)
                   .replace("{{LANGUAGE}}", a.language)
                   .replace("{{SIZE}}", a.size)
                   .replace("{{LINK}}", a.link);
    answer = jQuery(answer);

    var lang = a.language;
    if (/<a/.test(lang)) lang = jQuery(lang).text();
    languages[lang] = languages[lang] || {lang: a.language, user: a.user, size: a.size, link: a.link};

  var langs = [];
  for (var lang in languages)
    if (languages.hasOwnProperty(lang))

  langs.sort(function (a, b) {
    if (a.lang > b.lang) return 1;
    if (a.lang < b.lang) return -1;
    return 0;

  for (var i = 0; i < langs.length; ++i)
    var language = jQuery("#language-template").html();
    var lang = langs[i];
    language = language.replace("{{LANGUAGE}}", lang.lang)
                       .replace("{{NAME}}", lang.user)
                       .replace("{{SIZE}}", lang.size)
                       .replace("{{LINK}}", lang.link);
    language = jQuery(language);

body { text-align: left !important}

#answer-list {
  padding: 10px;
  width: 290px;
  float: left;

#language-list {
  padding: 10px;
  width: 290px;
  float: left;

table thead {
  font-weight: bold;

table td {
  padding: 5px;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b">
<div id="answer-list">
  <table class="answer-list">
    <tbody id="answers">

<div id="language-list">
  <h2>Winners by Language</h2>
  <table class="language-list">
    <tbody id="languages">

<table style="display: none">
  <tbody id="answer-template">
    <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr>
<table style="display: none">
  <tbody id="language-template">
    <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr>

  • 9
    \$\begingroup\$ "and capitals are big letters, your program must be as short as possible." ....... \$\endgroup\$ – Bálint May 13 '16 at 6:56
  • 1
    \$\begingroup\$ We only get 1 sentence? \$\endgroup\$ – Bálint May 13 '16 at 6:57
  • 7
    \$\begingroup\$ Why exclude Matlab (I know I can use Octave), Mathematica and the like if it can be shown that they work? It makes sense in CnR challenges, but I don't like it in code golf. Sorry, but -1 until that rule is changed. \$\endgroup\$ – Stewie Griffin May 13 '16 at 7:35
  • 1
    \$\begingroup\$ The extra requirements (or even the restating of them) for function vs program are unnecessary, we have defaults for that. \$\endgroup\$ – Rɪᴋᴇʀ May 13 '16 at 14:53
  • 3
    \$\begingroup\$ None of this question has anything to do with grammar. You also need to fix your basic vocabulary: the word for how things should be written is orthography. \$\endgroup\$ – Peter Taylor May 13 '16 at 17:49

12 Answers 12


Pyke, 16 bytes


Try it here!

(15 bytes after a bugfix)



Q\.c             -  input().split(".")
    F            - for i in ^:
     l8l5        -  i = i.lstrip().capitalise()
          I      -  if i:
           \.+   -   i += "."
               J - " ".join(^)

Vim, 40 39 keystrokes

VgugUl:s/\. *\(\a\)/\. \U\1/g<cr>:s/\.$<cr>A.

This assumes that input will not take multiple lines.


Vgu                                            "Make the whole line lowercase
   gUl                                         "Make the first character uppercase
       :s/\. *\(\a\)/\. \U\1/g<cr>             "Replace a (.) followed by any number of
                                               "spaces, followed by a character, with
                                               "with a (.), one space, and that character
                                               "In lowercase
                                  :s/\.$<cr>   "Remove a dot, if it's at the end of this line
                                            A. "Add a dot to the end of this line.

JavaScript (ES6), 76 95 bytes

s=>s.replace(/ +/g,' ').replace(/(\w)([^.]*)\.?/g,(_,c,r)=>c.toUpperCase()+r.toLowerCase()+'.')
<input oninput=o.value=f(this.value)><input id=o>

  • \$\begingroup\$ @Bálint I can do better than that: my function can be tested on this site! \$\endgroup\$ – Neil May 13 '16 at 8:20
  • \$\begingroup\$ Edit the byte count \$\endgroup\$ – Bálint May 13 '16 at 8:26
  • \$\begingroup\$ Please update so multiple spaces are replaced with one. \$\endgroup\$ – George Gibson May 13 '16 at 16:04

Python 3, 86 80 bytes

print('. '.join(x.strip().capitalize() for x in input().split('.') if x!='')+'.')

This works for all of the current test cases, but not for much beyond that.

Thanks @muddyfish for 6 bytes.


  • 1
    \$\begingroup\$ print('. '.join(x.strip().capitalize() for x in input().split('.')if x!='')+'.') is shorter \$\endgroup\$ – Blue May 13 '16 at 17:24
  • \$\begingroup\$ print('. '.join(x.strip().capitalize()for x in input().split('.')if x)+'.') is shorter still and print'. '.join(x.strip().capitalize()for x in input().split('.')if x)+'.' if you're willing to go python 2 \$\endgroup\$ – Blue May 13 '16 at 17:37
  • \$\begingroup\$ I think you can also save 4 more by dropping the !=''. \$\endgroup\$ – RootTwo May 13 '16 at 21:36
  • \$\begingroup\$ Isn't there an useless whitespace before the if statement? \$\endgroup\$ – Yytsi May 21 '16 at 17:24

Ruby, 50 bytes

Basic split/map/join solution.

->s{s.split(?.).map{|e|e.strip.capitalize+?.}*' '}

Retina, 41 bytes

S-`\. *

Try it online!

Test suite.

  • \$\begingroup\$ Please update to comply with the revised rules. \$\endgroup\$ – George Gibson May 13 '16 at 16:02

V, 25 bytes (non-competing)

guGgUló® *¨á©/® Õ±

Try it online!

This isn't super creative, since it's just a direct port of my vim answer. However, it's a lot shorter, so I'm happy with it.

Due to an oversight, this is one or two bytes longer than it needs to be. That should be fixed soon.


PHP, 123 103 102 95 92 bytes

<?foreach(explode('.',trim(fgets(STDIN)))as$b)echo!$b?!1:ucfirst(strtolower(trim($b))).'. ';

Takes a newline-terminated line from STDIN and prints the fixed version to STDOUT.


Lua, 149 141 bytes

u=function(l)return l:upper()end print(io.read():lower():gsub("%s+"," "):gsub("%.([^%s])",". %1"):gsub("^(%w)",u):gsub("(%.[^%w]*%w)",u).."")

Lua lacks some common features of regular expressions, and so it had to get a little sloppier than I would have liked. I might try to fix it up later though.


Perl, 40 + 12 (flags) = 52 bytes

#!perl -paF\.\s*
map{s/\s*$/. /;$\.=ucfirst lc}@F}{chop$\


echo "foo.baz. bar" | perl -paF'\.\s*' -e 'map{s/\s*$/. /;$\.=ucfirst lc}@F}{chop$\'


while (<>) {
    my @F = split(/\.\s*/, $_);
# code above added by -paF\.\s*
    # $\ has undef (or '')
    for (@F) {
        $_ =~ s/\s*$/. /;
        $\ .= ucfirst(lc($_));
} {
# code below added by -p
    print;  # prints $_ (undef here) and $\



Q 78 Bytes

(76 if anonymous function)

f:{(". "/:{{@[(" "=*x)_x;0;"c"$-32+"i"$]}'"."\:_-1_x}x),$["."=u:*|x;u;u,"."]}


f"tHIS SenteNce HAS veRy bAd GrammAr."                  /This sentence has very bad grammar.
f"PuncTuation must-be PReserved."                       /Punctuation must-be preserved.
f"full StOps ShoulD Be inserted"                        /Full stops should be inserted.
f"MultiPLe sEntEnceS Are possible. thEY Are, yoU KNOW." /Multiple sentences are possible. They are, you know.
f"spaces. are.inserted."                                /Spaces. Are. Inserted.

Q has lower and upper functions (conversion to lowercase and to uppercase), but more primitive sub-language k (concise, used in the example) only has lower (operator _), so part of the text (12 bytes "c"$-32+"i"$) implements upper.

Split the original string using ". " as separator, processes each line, and join again

For each line we discard first character if is a blank (join inserts a blanck after each .), and capitalice first character.

Finally, insert a final "." if last char is not "."


Perl 5, 42 bytes

say map{$_=ucfirst lc.'. '}split/\.\s*/,<>

Try it online!


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.