29
\$\begingroup\$

Taken straight from the ACM Winter Programming Contest 2013. You are a person who likes to take things literally. Therefore, for you, the end of The World is ed; the last letters of "The" and "World" concatenated.

Make a program which takes a sentence, and output the last letter of each word in that sentence in as little space as possible (fewest bytes). Words are separated with anything but letters from the alphabet (65 - 90, 97 - 122 on the ASCII table.) That means underscores, tildes, graves, curly braces, etc. are separators. There can be more than one seperator between each word.

asdf jkl;__zxcv~< vbnm,.qwer| |uiop -> flvmrp
pigs, eat dogs; eat Bob: eat pigs -> ststbts
looc si siht ,gnitirw esreveR -> citwR
99_bottles_of_beer_on_the_wall -> sfrnel

\$\endgroup\$
4
  • \$\begingroup\$ Could you add a test case including digits and underscores? \$\endgroup\$
    – grc
    Mar 12, 2013 at 4:56
  • 17
    \$\begingroup\$ The world ends in ed? I knew vim and Emacs couldn't measure up! \$\endgroup\$
    – Joe Z.
    Mar 13, 2013 at 3:29
  • 2
    \$\begingroup\$ Well, the “real men use ed” essay has been part of the Emacs distribution for as long as I can remember. \$\endgroup\$
    – J B
    Apr 11, 2013 at 15:07
  • \$\begingroup\$ Will the inputs be ASCII only? \$\endgroup\$
    – Phil H
    Mar 26, 2018 at 14:21

55 Answers 55

23
\$\begingroup\$

ed, 35 characters

s/[a-zA-Z]*\([a-zA-Z]\)\|./\1/g
p
Q

So, the world ends in ed. As I like to be too literal, I decided to write to write the solution with ed - and apparently it is actually a programming language. It's surprisingly short, even considering many shorter solutions already exist in this thread. It would be nicer if I could use something other than [a-zA-Z], but considering ed isn't a programming language, it's actually good enough.

First, I would like to say this only parses the last line in file. It would be possible to parse more, just type , at beginning of two first lines (this specified "everything" range, as opposed to standard last line range), but that would increase code size to 37 characters.

Now for explanations. The first line does exactly what Perl solution does (except without support for Unicode characters). I haven't copied the Perl solution, I just invented something similar by coincidence.

The second line prints last line, so you could see the output. The third line forces quit - I have to do it, otherwise ed would print ? to remind you that you haven't saved the file.

Now for how to execute it. Well, it's very simple. Just run ed with the file containing test case, while piping my program, like that.

ed -s testcase < program

-s is silent. This prevents ed from outputing ugly file size at beginning. After all, I use it as a script, not editor, so I don't need metadata. If I wouldn't do that, ed would show file size that I couldn't prevent otherwise.

\$\endgroup\$
2
  • \$\begingroup\$ I installed ed just to try this. \$\endgroup\$
    – primo
    Jun 14, 2013 at 14:45
  • \$\begingroup\$ 32 bytes... Try it Online! \$\endgroup\$
    – roblogic
    Nov 11, 2020 at 22:50
17
\$\begingroup\$

Perl 5, 18 bytes

s/\pL*(\pL)|./$1/g

Requires a -p command line switch. The named property L matches only letter characters A-Za-z. There are several hundred such named properties, but when dealing with ASCII text, very few of them are interesting. Besides \pL, the only other one of any real note is \pP, which matches punctuation.

Try it online!


Perl 5, 17 bytes

A one byte improvement by Dom Hastings

print/\pL*(\pL)/g

Requires -n (and -l to support multiple inputs).

Try it online!


Sample usage

$ more in.dat
asdf jkl;__zxcv~< vbnm,.qwer| |uiop
pigs, eat dogs; eat Bob: eat pigs
looc si siht ,gnitirw esreveR
99_bottles_of_beer_on_the_wall

$ perl -p ends-in-ed.pl < in.dat
flvmrp
ststbts
citwR
sfrnel
\$\endgroup\$
5
  • \$\begingroup\$ I think \w also matches digits and underscores. \$\endgroup\$
    – grc
    Mar 11, 2013 at 5:20
  • \$\begingroup\$ Hmm, indeed. That will need to be updated. \$\endgroup\$
    – primo
    Mar 11, 2013 at 5:28
  • 2
    \$\begingroup\$ Brilliant. Regex was an obvious solution, but |. was not obvious (to me, at least). \$\endgroup\$ Mar 11, 2013 at 9:29
  • 1
    \$\begingroup\$ Just noticed a -1 in print/\pL*(\pL)/g, seems to output the same for your test cases! \$\endgroup\$ Mar 16, 2018 at 10:33
  • 4
    \$\begingroup\$ s/(\pL)*|./$1/g and print/(\pL)*/g for 15 and 14 bytes since the capturing group captures here one char at a time so it store the last char of the word \$\endgroup\$
    – Jakque
    Jul 21, 2021 at 11:49
6
\$\begingroup\$

Javascript, 49

alert(prompt().replace(/.(?=[a-z])|[^a-z]/gi,''))

It uses a regular expression to remove all characters that come before a letter, as well as all non-letter characters. Then we're left with the last letter of each word.

Thanks to tomsmeding for a nice improvement.

\$\endgroup\$
1
  • 4
    \$\begingroup\$ You can probably improve this by making the regex case-insensitive, like in: alert(prompt().replace(/.(?=[a-z])|[^a-z]/gi,'')) \$\endgroup\$
    – tomsmeding
    Mar 11, 2013 at 5:52
6
\$\begingroup\$

C, 78

Golfed:

main(int c,char**s){for(;c=*s[1]++;)isalpha(c)&&!isalpha(*s[1])?putchar(c):0;}

With whitespace:

main(int c,char**s)
{
  for(;c=*s[1]++;)
    isalpha(c)&&!isalpha(*s[1])?putchar(c):0;
}

Output:

enter image description here

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can save 4 bytes by using K&R declaration and defaulting c: main(c,s)char**s;{for \$\endgroup\$ Mar 21, 2018 at 16:10
5
\$\begingroup\$

GNU Sed, 40 38 37

s/[a-z]\b/&\n/g; s/[^\n]*\(.\)\n/\1/g

Testing

cat << EOF > data.txt
asdf jkl;__zxcv~< vbnm,.qwer| |uiop
pigs, eat dogs; eat Bob: eat pigs
looc si siht ,gnitirw esreveR
EOF

Run sed:

sed 's/[A-Za-z]\b/&\n/gi; s/[^\n]*\(.\)\n/\1/g' data.txt

Output:

flvmrp
ststbts
citwR

Explanation

The first substitution replaces all word boundaries, that are preceded by the desired match group, with a new-line. This makes it easy to remove all extraneous characters in the second substitution.

Edit

  • Use case-insensitive flag (-2), thanks manatwork.
  • Don't count whitespace (-1).
\$\endgroup\$
3
  • \$\begingroup\$ sed's s command has i flag for cases insensitive matching: s/[a-z]\b/&\n/gi. \$\endgroup\$
    – manatwork
    Mar 11, 2013 at 9:29
  • \$\begingroup\$ @manatwork: good point, this would make it GNU sed only, but it seems it already is, thanks. \$\endgroup\$
    – Thor
    Mar 11, 2013 at 9:38
  • \$\begingroup\$ \b considers _s to be letters, so if any words in the test END with _, that word's last letter is not included in the output \$\endgroup\$
    – Marty Neal
    Apr 16, 2014 at 17:41
3
\$\begingroup\$

Grep and Paste, 36 34 28

> echo 'asdf jkl;__zxcv~< vbnm,.qwer| |uiop' | grep -io '[a-z]\b' | tr -d \\n
flvmrp

> echo 'pigs, eat dogs; eat Bob: eat pigs'   | grep -io '[a-z]\b' | tr -d \\n
ststbts

echo 'looc si siht ,gnitirw esreveR'         | grep -io '[a-z]\b' | tr -d \\n
citwR

If a final new-line is needed, replace tr -d \\n with paste -sd ''.

Edit

  • Use case-insensitive grep (-2), thanks manatwork.
  • Use tr instead of paste (-4), thanks manatwork.
  • Don't count whitespace around pipe (-2).
\$\endgroup\$
4
  • \$\begingroup\$ Quite creative with that paste -sd '', but tr -d \\n is shorter. Regarding grep, it has -i switch meaning “ignore case”, which can make it shorter: grep -io '[a-z]\b'. \$\endgroup\$
    – manatwork
    Mar 11, 2013 at 9:24
  • \$\begingroup\$ @manatwork, tr also delete the final newline. Case insensitive mode is of course shorter, thanks. \$\endgroup\$
    – Thor
    Mar 11, 2013 at 9:29
  • \$\begingroup\$ there is no rule requiring final newline. \$\endgroup\$
    – manatwork
    Mar 11, 2013 at 9:32
  • \$\begingroup\$ @manatwork: I can agree with that, updated answer. \$\endgroup\$
    – Thor
    Mar 11, 2013 at 9:44
3
\$\begingroup\$

sed, 37 chars

Equal length to Thor's answer, but, I think, simpler.

s/[a-z]*\([a-z]\)/\1/ig;s/[^a-z]*//ig

The logic is quite trivial - replace letter sequences with their last letter, then delete all non-letters.

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

Mathematica, 39

""<>StringCases[#,(__~~x_)?LetterQ:>x]&

Test:

""<>StringCases[#,(__~~x_)?LetterQ:>x]& /@
 {"asdf jkl;__zxcv~< vbnm,.qwer| |uiop",
  "pigs, eat dogs; eat Bob: eat pigs",
  "looc si siht ,gnitirw esreveR",
  "99_bottles_of_beer_on_the_wall"}
{"flvmrp", "ststbts", "citwR", "sfrnel"}
\$\endgroup\$
2
  • \$\begingroup\$ Good one. LetterQ should be called LettersQ :) I haven't thought of it for testing whole strings. \$\endgroup\$ Mar 27, 2013 at 11:47
  • \$\begingroup\$ @belisarius Actually, with this construct it is applied character-wise, so it could be a literal "LetterQ" and still work. \$\endgroup\$
    – Mr.Wizard
    Mar 27, 2013 at 20:08
3
\$\begingroup\$

Nibbles, 4.5 bytes (9 nibbles)

.%~$\$a/\
.%~$\$a/\       # 9 nibble program
.%~$\$a/\$$     # with implicit variables added:
 $~             # split
   $            # the input
    \           # by whether the character class
     $          # of each element
      a         # is alphabetic;
.               # now, map over this
        \       # reversing
         $      # each group
       /        # and folding across these
          $     # returning the first (left-hand) argument
                # (in other words, return the first character of each group)

enter image description here

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

Zsh (builtins only), 40

for j (${=@//[^a-zA-Z]/ });printf $j[-1]

Try it Online...

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

K, 49

{last'f@&"b"$#:'f:"|"\:@[x;&~x in,/.Q`a`A;:;"|"]}

.

k){last'f@&"b"$#:'f:"|"\:@[x;&~x in,/.Q`a`A;:;"|"]}"asdf jkl;__zxcv~< vbnm,.qwer| |uiop"
"flvmrp"
k){last'f@&"b"$#:'f:"|"\:@[x;&~x in,/.Q`a`A;:;"|"]}"pigs, eat dogs; eat Bob: eat pigs"
"ststbts"
k){last'f@&"b"$#:'f:"|"\:@[x;&~x in,/.Q`a`A;:;"|"]}"looc si siht ,gnitirw esreveR"
"citwR"
\$\endgroup\$
2
\$\begingroup\$

Scala, 59 (or 43)

Assuming the string in already in s:

s.split("[^a-zA-Z]+").map(_.last).mkString

If you need to read from a prompt and print rather than using the REPL output, convert s to readLine and wrap in println() for 59.

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

x86: 54 bytes

Assume a cdecl routine with the signature void world_end(char *input, char *output):

60 8b 74 24 24 8b 7c 24 28 33 d2 8a 0e 8a c1 24
df 3c 41 72 08 3c 5a 77 04 8a d1 eb 09 84 d2 74
05 88 17 47 33 d2 46 84 c9 75 e0 84 d2 74 03 88
17 47 88 0f 61 c3
\$\endgroup\$
1
  • 1
    \$\begingroup\$ By the way, I realize the question asks for a program and not a routine, but I wanted to do something different. Contrary to the problem statement, I guess I'm not a "person who likes to take things literally" after all. :P \$\endgroup\$ Mar 11, 2013 at 18:18
2
\$\begingroup\$

Xi, 32

println$ @{=>.-1}<>input re"\W+"

Xi is a language still in its beta phase, but it seems to work well with code golf so I figured I might as well show yet another short and functional solution (and advertise the language a little :-)).

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

Mathematica 62 57 52

Row@StringTake[StringCases[#,LetterCharacter..],-1]&

Testing

l = {"asdf jkl;__zxcv~<vbnm,.qwer| |uiop", 
     "pigs,eat dogs;eat Bob:eat pigs", 
     "looc si siht,gnitirw esreveR"}

Row@StringTake[StringCases[#,LetterCharacter..],-1]&/@ l
(*{flvmrp,ststbts,citwR}*)
\$\endgroup\$
1
  • \$\begingroup\$ I mistakenly edited yours, but then rolled it back. Ooops. \$\endgroup\$
    – DavidC
    Mar 13, 2013 at 21:01
2
\$\begingroup\$

Python3, 59 chars

import re;print(re.sub('.(?=[a-z])|[^a-z]','',input(),0,2))

Correctly deals with capital letters and underscores. The 2 is to pass re.sub the re.IGNORECASE flag without having to use re.I.

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

Python, 76 chars

import re;print "".join(re.findall("([a-zA-Z])(?=$|[^a-zA-Z])",raw_input()))

\$\endgroup\$
2
  • \$\begingroup\$ You can remove the space after print. \$\endgroup\$
    – flornquake
    Mar 29, 2013 at 0:28
  • \$\begingroup\$ Shorten by porting to Python 3: import re;print(*re.findall("([a-zA-Z])(?=$|[^a-zA-Z])",input()),sep='') \$\endgroup\$ Mar 29, 2013 at 20:38
2
\$\begingroup\$

R, 126

My R solution

function(x){cat(paste(unlist(sapply(strsplit(unlist(strsplit(gsub("
[^[:alpha:]]"," ",x)," ")),""),tail,1)),collapse=""),"\n")}
\$\endgroup\$
1
2
\$\begingroup\$

Pip, 8 bytes

DQ*a@+XA

Verify all test cases: Try it online!

Explanation

      XA  Built-in regex variable: `[A-Za-z]`
     +    Apply regex + quantifier: `[A-Za-z]+`
   a@     Find all matches in the input
DQ*       Dequeue the last character from each match
          Concatenate together and autoprint (implicit)
\$\endgroup\$
2
\$\begingroup\$

Haskell, 62 60 bytes

f s=last<$>words[last$' ':[c|'@'<c,c<'{','`'<c||c<'[']|c<-s]

Try it online! Thanks to Wheat Wizard for a version without the import, saving two bytes!


Haskell, 62 bytes

import Data.Char
f s=last<$>words[last$' ':[c|isAlpha c]|c<-s]

Try it online!

Given an input string s, [last$' ':[c|isAlpha c]|c<-s] converts all non-alphabetic characters to spaces. words splits the resulting string on white space and last<$> takes the last character of each of the resulting strings.

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

Japt v2.0a0 -P, 16 9 6 bytes

q\L mÌ

Try it or run all test cases

q\L mÌ     :Implicit input of string U
q          :Split on
 \L        :  Regex /[^a-z]/gi
    m      :Map
     Ì     :  Last character
           :Implicitly join & output
\$\endgroup\$
1
  • \$\begingroup\$ I think you can just do f"%l(?!%l)" q (doesn't work in v2 because the parser doesn't like the (?) \$\endgroup\$ Feb 1, 2018 at 20:05
2
\$\begingroup\$

Pyth, 27 19 bytes

seMc:z"[^A-Za-z]+"d

Test suite

Explanation:
seMc:z"[^A-Za-z]+"d | Full code
--------------------+---------------------------------------------
    :z"[^A-Za-z]+"d | In the input, replace non-letters with space
   c                | Split on spaces
 eM                 | Replace each word with its last letter
s                   | Concatenate each letter
\$\endgroup\$
1
\$\begingroup\$

Python 3.x, 64 bytes

import re;print(''.join(a[-1] for a in re.split('\W+',input())))
\$\endgroup\$
2
  • 2
    \$\begingroup\$ The last example is not working. Also, an error occurs if the line begins or ends with a separator \$\endgroup\$
    – AMK
    Mar 12, 2013 at 13:32
  • \$\begingroup\$ You can remove the space before for. \$\endgroup\$
    – Bakuriu
    Apr 2, 2013 at 10:55
1
\$\begingroup\$

Lua, 42

print(((...):gsub('.-(.)%f[%A]%A*','%1')))

Usage example: lua script.lua "asdf jkl;__zxcv~< vbnm,.qwer| |uiop"

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

Mathematica 71 47 45 61

Back to the drawing board, after @belisarius found an error in the code.

StringCases[#, RegularExpression["[A-Za-z](?![A-Za-z])"]] <> "" &

Testing

l = {"asdf jkl;__zxcv~<vbnm,.qwer| |uiop", "asdf jkl__zxcv~<vbnm,.qwer| |uiop", 
"pigs,eat dogs;eat Bob:eat pigs", "looc si siht,gnitirw esreveR"};

StringCases[#, RegularExpression["[A-Za-z](?![A-Za-z])"]] <> "" & /@ l

{"flvmrp", "flvmrp", "ststbts", "citwR"}

\$\endgroup\$
5
  • \$\begingroup\$ \\w matches _, so it doesn't work for (for example) "asdf jkl__zxcv~<vbnm,.qwer| |uiop" \$\endgroup\$ Mar 13, 2013 at 12:23
  • \$\begingroup\$ Wait Row@StringTake[ StringCases[#, LetterCharacter ..], -1] &@"asdf jkl__zxcv~<vbnm,.qwer| |uiop" gives me flvmrp, but #~StringCases~RegularExpression@"\\w\\b" <> "" &@"asdf jkl__zxcv~<vbnm,.qwer| |uiop" returns fvmrp here. Are we getting the same results?? \$\endgroup\$ Mar 13, 2013 at 13:39
  • \$\begingroup\$ @belisarius You were right about the error in my earlier version. I was testing it with the wrong string! \$\endgroup\$
    – DavidC
    Mar 14, 2013 at 1:34
  • \$\begingroup\$ Hehe , +1 again \$\endgroup\$ Mar 14, 2013 at 2:59
  • \$\begingroup\$ @belisarius guys, please see the answer I posted. If it is correct it's shorter. \$\endgroup\$
    – Mr.Wizard
    Mar 27, 2013 at 6:34
1
\$\begingroup\$

Python 2, 88 80 75 69 68

s=p=''
for c in raw_input()+' ':a=c.isalpha();s+=p[a:];p=c*a
print s

Input: 435_ASDC__uio;|d re;fG o55677jkl..f

Output: CodeGolf


This solution can be shortened to 67 characters if you allow the output to include backspace characters (ASCII code 8) at the beginning. The output will be visually identical.

s=p='<BS>'
for c in raw_input()+p:a=c.isalpha();s+=p[a:];p=c*a
print s

Same input, (visually) same output. <BS> is meant to be the backspace character.

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

Smalltalk, Squeak/Pharo flavour
122 char with traditional formatting for this method added to String:

endOfWords
    ^(self subStrings: (CharacterSet allCharacters select: #isLetter) complement) collect: #last as: String

62 chars in Pharo 1.4, with regex and weird formatting

endOfWords^''join:(self regex:'[a-zA-Z]+'matchesCollect:#last)
\$\endgroup\$
1
\$\begingroup\$

J: 60 characters (or 38 characters for a less correct version)

(#~e.&(,26&{.&(}.&a.)"0(97 65))){:&>;:]`(' '"_)@.(e.&'_:')"0

If we're willing let the program break whenever there are words ending in a colon or an underscore, then we can simplify this to 38 characters.

(#~e.&(,26&{.&(}.&a.)"0(97 65))){:&>;:

Sample run:

    (#~e.&(,26&{.&(}.&a.)"0(97 65))){:&>;:]`(' '"_)@.(e.&'_:')"0'asdf jkl;__zxcv~< vbnm,.qwer| |uiop'
flvmrp
    (#~e.&(,26&{.&(}.&a.)"0(97 65))){:&>;:]`(' '"_)@.(e.&'_:')"0'pigs, eat dogs; eat Bob: eat pigs'
ststbts
    (#~e.&(,26&{.&(}.&a.)"0(97 65))){:&>;:]`(' '"_)@.(e.&'_:')"0'99_bottles_of_beer_on_the_wall'
sfrnel
\$\endgroup\$
4
  • 1
    \$\begingroup\$ 38 Bytes (for a correct version): (#~[:2&|64 90 96 122&I.@(u:inv)){:&>;:, or 43 bytes for a non-explicit version: (#~[:2&|64 90 96 122&I.@(u:inv))@:({:@>)@;:. This uses the interval index verb, I., which interprets 64 90 96 122 as the set of intervals (__, 64] (64, 90], (90, 96], (96, 122], (122, _), and returns the index of the iterval to which its argument, the ascii code of the char, belongs. If this index is odd, it's not alphabetical. \$\endgroup\$ Mar 14, 2018 at 23:32
  • \$\begingroup\$ @BolceBussiere doesn’t work with underscores for some reason (last test case). \$\endgroup\$
    – FrownyFrog
    Mar 16, 2018 at 14:43
  • \$\begingroup\$ @FrownyFrog ah, I see why, ;: interprets abc_ as one word since variable names can contain underscores. +10 bytes to add (#~~:&'_'), probably an inefficient fix \$\endgroup\$ Mar 17, 2018 at 2:13
  • \$\begingroup\$ @BolceBussiere that’s just '_'-.~ or something similar. \$\endgroup\$
    – FrownyFrog
    Mar 17, 2018 at 11:52
1
\$\begingroup\$

C#

Method, 105 bytes: (assumes usings for System, System.Text.RegularExpressions and System.Linq)

string R(string i){return string.Concat(Regex.Split(i,"[^a-zA-Z]").Where(x=>x!="").Select(n=>n.Last()));}

Program, 211 bytes:

using System;using System.Text.RegularExpressions;using System.Linq;class A{static void Main(){Console.WriteLine(string.Concat(Regex.Split(Console.ReadLine(),"[^a-zA-Z]").Where(x=>x!="").Select(n=>n.Last())));}}
\$\endgroup\$
1
\$\begingroup\$

VBA, 147 161

Sub a(s)
For n=0 To 255:m=Chr(n):s=Replace(s,IIf(m Like"[A-Za-z]","",m)," "):Next
For Each r In Split(s," "):t=t & Right(r,1):Next
MsgBox t
End Sub
\$\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.