# Truncate words in a sentence

Inspired by wezl.

Your challenge is to take words (sequences of [a-zA-Z]) and truncate them to length 3. For example, never gonna give you up would become nev gon giv you up.

Words will not necessarly be delimited by spaces - for example, youtube.com/watch?v=dQw4w9WgXcQ will become you.com/wat?v=dQw4w9WgX. Input will be printable ASCII.

This is , shortest wins!

## Testcases

never gonna give you up -> nev gon giv you up
code golf -> cod gol
happy_birthday_to_you -> hap_bir_to_you
*hello world* -> *hel wor*
abcd1234fghi -> abc1234fgh

• What's the rule regarding _ underscore? stackoverflow.com/questions/47182265/… Jun 20 at 8:26
• @jubilatious1 Not a letter, as evidenced in the question Jun 20 at 8:43

# K (ngn/k), 21 20 bytes

-1 byte thanks to coltim!

{5>1(1+*)\~"a{"'_x}#


Try it online!

{ ... }# Filter the input using the boolean mask returned by the left function.
_x Convert the string to lowercase.
~"a{"' For each character, is it in ["a", "{")?
1(1+*)\ A scan that converts each 1 to the number of consecutive 1s up to it, incremented by 1. 0s are mapped to 1.
5> Keep the indices where this is at most 4.

• I think one byte can be trimmed with {5>1(1+*)\~"a{"'_x}# Jun 20 at 13:39
• @coltim yes that works, thanks! Also saved two bytes on another answer
– ovs
Jun 20 at 13:47
• What's the { in the "a{" string for? Jun 20 at 13:56
• @thejonymyster It marks the end of the interval of the alphabetical characters ({ is the character after z). Without it characters with a higher codepoint than z would be counted as alphabetical. "a{"' sorts each char in its input into 3 categories: characters that appear before a in ASCII get -1, before { get 0 and everything after that gets 1. ~ is boolean inversion on that result.
– ovs
Jun 20 at 14:10

# x86-64 machine code, 29 bytes

83 C9 FF AC 88 07 2C 41 72 06 24 1F 3C 1A 72 02 31 C9 83 F9 FC 7E EC B0 00 AE E0 E7 C3


Try it online!

Following the standard calling convention for Unix-like systems (from the System V AMD64 ABI), this takes in RDI an address at which to place the result, as a null-terminated byte string; and the address of the input, as a null-terminated byte string, in RSI.

In assembly:

f:
or ecx, -1
startloop:
lodsb
mov [rdi], al
sub al, 0x41
jb notletter
and al, 0x1F
cmp al, 26
jb isletter
notletter:
xor ecx, ecx
isletter:
cmp ecx, -4
jle startloop
mov al, 0
scasb
loopnz startloop
ret


# Python, 52 bytes

lambda s,c=0:[i for i in s if(c:=-~c*i.isalpha())<4]

Attempt This Online!

Outputs as a list of characters.

If that's not allowed:

## Python, 58 bytes

lambda s,c=0:"".join(i*((c:=-~c*i.isalpha())<4)for i in s)

Attempt This Online!

# Zsh, 39 bytes

<<<${1//(#m)[a-zA-Z](#c3,)/$MATCH[1,3]}

Attempt This Online!

# JavaScript (V8), 39 bytes

x=>x.replace(/(?<=[a-z]{3})[a-z]/gi,"")


Try it online!

Pretty straightforward.

x=>x.replace(/(?<=[a-z]{3})[a-z]/gi,"")

x=>x.replace(                      ,  )  // standard regex replace setup
/                  /gi      // case insensitive, replace all occurences of
[a-z]         // an alphabetical character
""   // with the empty string
(?<=        )              // given that it is immediately following
[a-z]{3}               // 3 consecutive alphabet characters


# BQN, 22 bytes

⊣/˜3≥·(×+⊢)2|"A[a{"⊸⍋


Try it here!

-5 bytes thanks to @ovs!

## Explanation

• ⊣/˜... filter input by constructing this binary array...
• 2|"A[a{"⊸⍋ for each char, 1 if alphabetical and 0 otherwise
• more literally: check if ordering the char into A[a{ will yield an odd index
• (×+⊢) count consecutive runs of 1s
• 3≥· for each element, check if >= 3

Example of how the input is manipulated under the hood:

never gonna give you up  # input
11111011111011110111011  # 2|"A[a{"⊸⍋
12345012345012340123012  # (×+⊢)
11100111100111101111111  # 3≥·
nev██ gon██ giv█ you up  # ⊣/˜

• ∘∊⟜3‿1 can be shortened to 2|. And one more byte can be saved with (...)⊸/ -> ⊢/˜...
– ovs
Jun 18 at 20:38
• @ovs thanks, very clever! That filter trick is very handy Jun 19 at 4:50

# brev, 81 bytes

(print (strse (read-line) '(+ alpha) (as-list (fn (take x (min (length x) 3))))))


I would've wanted:

(print (strse (read-line) 'word (as-list (fn (take x (min (length x) 3))))))


but that YouTube example looks differently.

Reading and writing stdin/out adds 13 bytes, it's just 68 as a function:

((over (strse x '(+ alpha) (as-list (fn (take x (min (length x) 3))))))
'("never gonna give you up"
"code golf"
"happy_birthday_to_you"
"*hello world*"
"abcd1234fghi"))


# R, 38 bytes

\(x)gsub('([a-z]{3})[a-z]+','\\1',x,T)

Attempt This Online!

-2 bytes thanks to pajonk!

Direct application of gsub.

• You can omit the curly braces for -2 bytes. Jun 18 at 18:49

# Perl 5-p, 17 bytes

s/\pL{3}\K\pL*//g


Try it online!

# Vyxals, 7 bytes

øWƛL3∵Ẏ


Try it Online!

Would be 5 bytes if not for modular indexing. Undone by something otherwise useful, I say!

## Explained

øWƛL3∵Ẏ
øW      # Split the input on words (/[A-z]+/). Numbers and punctuation are left as single character strings.
ƛ     # To each "word":
L3∵  #   get the minimum of the length of the word and the number 3. This is needed to account for modular indexing. This would otherwise just be 3
Ẏ #   get the first amount of characters of the word, where the number of characters is the number we just calculated.
# the s flag joins everything into a single string before printing it.


# Retina 0.8.2, 21 bytes

i([a-z]{3})[a-z]+
$1  Try it online! Link includes test cases. Explanation: Case-insensitively matches runs of three letters followed by at least one more letter and keeps only the first three. # Python, 84 bytes lambda s:''.join([w,w[:3]][w.isalpha()]for w in re.split('([^a-zA-Z])',s)) import re  Attempt This Online! # Factor, 5850 48 bytes [ R/ [a-z]+/i [ 3 short head ] re-replace-with ]  Try it online! # Python, 57 bytes lambda s:re.sub('([a-z]{3})[a-z]+',r'\1',s,0,2) import re Attempt This Online! A straight port of my regex R solution. re.IGNORECASE is 2. Framework taken from solid.py's answer. # C (clang), 78 65 bytes c;*p;f(*s){for(c=0,p=s;*s;p+=c<4)c=iswalpha(*p=*s++)?c+1:0;*p=0;}  Try it online! Saved a whopping 13 bytes thanks to Neil!!! Inputs a wide character string. Performs the word truncations in place. • I fiddled around with your loop a bit and ended up with for(c=0,p=s;*s;p+=c<4)c=iswalpha(*p=*s++)?c+1:0; which saves 13 bytes. – Neil Jun 18 at 21:19 • @Neil That's awesome, hold the pointer at the same place until it has a valid char - thanks! :D Jun 18 at 22:03 # Ruby, 31 bytes ->s{s.gsub(/[a-z]+/i){$&[0,3]}}


Try it online!

• Nice one! Sad \pl doesn't work in Ruby... You can save a few bytes using -p too of you're that way inclined! Try it online! Jun 20 at 19:48

# Stax, 7 bytes

îªZ╛▲?↕


Run and debug it

# Charcoal, 15 bytes

ΦθΦ⁴¬№α↥§⁺×_μθκ


Try it online! Link is to verbose version of code. Explanation: Generates all overlapping substrings of up to four characters ending at each character and keeps those containing at least one non-letter.

 θ              Input string
Φ               Filtered by
⁴            Literal integer 4
Φ             Any value satisfies
α         Predefined variable uppercase alphabet
¬№          Does not contain
_    Literal string _
×     Repeated by
λ   Current value
⁺      Concatenated with
θ  Input string
§       Indexed by
κ Current index
↥        Uppercase
Implicitly print


Note that I use Filter instead of Any because the latter does not implicitly convert an integer to a range.

# Pip, 38 25 bytes

Simple regex solution, matches runs of letters and captures the first 3, replacing matches with the capture group.

aR-([A-Z]{3})[A-Z]*\1


Attempt This Online!

# Pyth, 15 bytes

sf<=Z*hZ}rT0G4Q


Try it online!

# Julia, 46 bytes 42 bytes

f(s)=replace(s,r"([\p{L}]{3})[\p{L}]*"=>s"\1")

We do not need the square brackets, thx to @thejonymyster:

f(s)=replace(s,r"(\p{L}{3})\p{L}*"=>s"\1")

Attempt This Online!

• Are the []s around each \p{L} necessary? I don't know the language, but it seemed to not error when I removed them. Jun 19 at 17:54
• yes. i'm weak on regex-es, it was just a simple change the [a-zA-z] to the \p stuff. i suspect that the \p syntax can represent a character class w/o the square brackets :-) Jun 19 at 19:47
• you can save 2 bytes by using an anonymous function s->... or an unary operator !s=... Jun 23 at 18:08
• @MarcMush: i leave this for the more ambitious golfers :-) Jun 23 at 20:30
• common, you can be ambitious too ^^ It's not that hard Jun 24 at 15:43

# sed -E, 24 bytes

s/([A-Z]{3})[A-Z]*/\1/gi


Try it online!

# 05AB1E, 12 bytes

.γa}DaÅÏ3£}J


Explanation:

.γ }          # Adjacent group the (implicit) input-string by:
a           #  Letters
D         # Duplicate this list of groups
a        # Check which groups only consist of letters
ÅÏ  }   # Apply on the truthy indices:
3£    #  Keep up to the first 3 characters
J  # Join all groups back together to a string
# (which is output implicitly as result)


# Raku -p, 20 bytes

### (formerly known as Perl_6)

s:g/<:L>**3<(<:L>*//


FYI, Raku regexes tolerate whitespace by default, so a more readable (exploded) version would be:

s:g/ <:L>**3 <( <:L>* //


# Rust, 109 bytes

fn f(s:&str)->String{let mut n=0;s.chars().filter(|c|if c.is_alphabetic(){n+=1;n<4}else{n=0;true}).collect()}


Try it online!

• Idea: .filter(|c|{n+=1;if!c.is_alphabetic(){n=0};n<4})
– Lynn
Jun 22 at 18:21
• After looking at the thread on Rust code golfing, a couple easy things: write the answer as a closure |s|->String{...}. And maybe, instead of the let mut write |s|->String((|mut n|...)(0)). (And before Lynn's suggestion, I was thinking: use 0<1 in place of true.) Jun 22 at 23:14
• So, down to 96 bytes: tio.run/… Jun 22 at 23:33
• OK, so it turned out in this case changing the let to a call of a closure didn't save any bytes: tio.run/… Jun 23 at 0:26

# Javascript 62 bytes#

t=(s)=>s.split(" ").map(n=>n.slice(0, n.length - 2)).join(" ")

• This fails most of the test cases because word delimiters are not always spaces. Jun 18 at 8:40
• You don't really need to bother about delimiters. You can just look for runs of consecutive letters and ignore everything else. This should take you to around 40 bytes. Jun 18 at 10:43