loading...

Daily Challenge #180 - SMS Shortener

thepracticaldev profile image dev.to staff ・1 min read

Setup

SMS messages are limited to 160 characters. It tends to be irritating, especially when freshly written message is 164 characters long. Implement a function to shorten the message to 160 characters, starting from the end, by replacing spaces with camelCase, as much as necessary.

Example

Original Message:
No one expects the Spanish Inquisition! Our chief weapon is surprise, fear and surprise; two chief weapons, fear, surprise, and ruthless efficiency! And that will be it.

Shortened Message:
No one expects the Spanish Inquisition! Our chief weapon is surprise, fear and surprise; two chief weapons, fear,Surprise,AndRuthlessEfficiency!AndThatWillBeIt.

Tests

"SMS messages are limited to 160 characters. It tends to be irritating, especially when freshly written message is 164 characters long. SMS messages are limited to 160 characters. It tends to be irritating, especially when freshly written message is 164 characters long."

"This message is already short enough!"

"ThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoon"

Good luck!


This challenge comes from Bugari on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Discussion

pic
Editor guide
Collapse
avaq profile image
Aldwin Vlasblom

A recursive solution in JavaScript:

const pattern = /\s+(\S)(\S*)$/;
const minify = sms => 
  sms.length <= 160 || sms.match (pattern) === null ? sms :
  minify (sms.replace (pattern, (_, c, w) => c.toUpperCase() + w))

With comments:

// A regular expression that matches any white-space followed by a captured 
// non-white-space character, and any number of non white-space characters
// separately captured, all at the end of the input.
const pattern = /\s+(\S)(\S*)$/;

const minify = sms =>
  // If the input SMS is already short enough, or has no white-space
  // characters, we just return it.
  sms.length <= 160 || sms.match (pattern) === null ? sms :

  // If it does have white-space, we remove the last occurrence and
  // uppercase the captured character. Then we minify the resulting
  // SMS recursively.
  minify (sms.replace (pattern, (_, c, w) => c.toUpperCase() + w))
Collapse
jehielmartinez profile image
Jehiel Martinez

js recursive

function shortener(str) {
    const regex = /\s+(\S*)$/g;

    if(str.length <= 160){
        return str;
    };

    let match = str.match(regex);
    if(!match){
        return 'No place to trim'
    }

    let trimmed = match[0].trim();
    let capital = trimmed.replace(/^./, trimmed[0].toUpperCase());

    return shortener(str.replace(regex, capital));
};
Collapse
exts profile image
Lamonte

This one almost stumped me ngl lol

Dart

String smsShortener(String message) {
  if(message.length <= 160) return message;

  List<String> spaces = message.split(" ");
  var result = message;
  for(var idx = spaces.length-1; idx > 0; idx--) {
    var start = spaces.take(idx).join(" ");
    var end = spaces.skip(idx).map((word) => "${word[0].toUpperCase()}${word.substring(1)}").join("");
    result = "$start$end";
    if(result.length <= 160) return result;
  }
  return result.substring(0, 160);
}
Collapse
candidateplanet profile image
lusen / they / them 🏳️‍🌈🥑

in Python

Note the tricky case about a tweet that starts with a space but is truncated -- first letter, even following spaces and punctuation, should not be capitalized.

MAX = 160
LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"
UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def first_letter_idx(msg):
  for idx in range(0, len(msg)):
    if msg[idx] in LOWERCASE_LETTERS or msg[idx] in UPPERCASE_LETTERS:
      return idx
  return None

def shorten(tweet):
  if len(tweet) < MAX:
    return tweet

  # how many spaces we need to delete
  delete_count = len(tweet) - MAX

  # build it backwards and then reverse when return
  result = []
  # idx is index in tweet, read right to left
  for idx in range(0, len(tweet))[::-1]:
    if tweet[idx] == ' ' and delete_count > 0:
      if len(result) > 0 and result[-1] in LOWERCASE_LETTERS:
        result[-1] = result[-1].upper()
      delete_count -= 1
    else:
      result.append(tweet[idx])

  result = result[::-1]
  result = result[:160]

  # special case: first letter in tweet is preceeded by a space. that letter -- as the _start_ of camel-case -- should not be capitalized. however all other letters following spaces can be  capitlized.
  r_fl_idx = first_letter_idx(result)
  t_fl_idx = first_letter_idx(tweet)
  if r_fl_idx is not None and result[r_fl_idx].lower() == tweet[t_fl_idx].lower():
    result[r_fl_idx] = tweet[t_fl_idx]

  return ''.join(result)

def test(result, expected):
  print('-'*80)
  print(len(result) == len(expected))
  print(result == expected)
  #print(result, len(result))
  #print(expected,  len(expected))

result = shorten("No one expects the Spanish Inquisition! Our chief weapon is surprise, fear and surprise; two chief weapons, fear, surprise, and ruthless efficiency! And that will be it.")
expected = "No one expects the Spanish Inquisition! Our chief weapon is surprise, fear and surprise; two chief weapons, fear,Surprise,AndRuthlessEfficiency!AndThatWillBeIt."
test(result, expected)

result = shorten("SMS messages are limited to 160 characters. It tends to be irritating, especially when freshly written message is 164 characters long. SMS messages are limited to 160 characters. It tends to be irritating, especially when freshly written message is 164 characters long.")
expected = "SMSMessagesAreLimitedTo160Characters.ItTendsToBeIrritating,EspeciallyWhenFreshlyWrittenMessageIs164CharactersLong.SMSMessagesAreLimitedTo160Characters.ItTendsTo"
test(result, expected)

result = shorten("This message is already short enough!")
expected = "This message is already short enough!"
test(result, expected)

result = shorten("ThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoon")
expected = "ThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereIsNoSpoonThereI"
test(result, expected)

result = shorten(" dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
expected = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
test(result, expected)

result = shorten("    dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
expected = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
test(result, expected)
print(result)
print(expected)

result = shorten("    Dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
expected = "Dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
test(result, expected)
print(result)
print(expected)

result = shorten(" !d Eddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
expected = " !d Eddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
test(result, expected)
print(result)
print(expected)

result = shorten(" !d Edddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeeedddddddddddddddddddddddddddddddddddddddddddddddd")
expected = "!dEdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddeeeddddddddddddddddddddddddddddddddddddddddddddddd"
test(result, expected)
print(result)
print(expected)
Collapse
korguy profile image
KORguy

python solution

def SMS_shortner(msg):
    while len(msg) > 160:
        try:
            lst = msg.split(" ")
            lst.append(lst.pop(-2) + lst.pop().title())
            msg = " ".join(lst)
        except IndexError:
            print("Text can not be shortened.")
    return msg
Collapse
cipharius profile image
Valts Liepiņš

This one ended up being less readable, will give a compact version and expanded version with commentary. Once again, Ruby:

def shortenSMS str
    [str.length - 160, str.count(' ')].min.times do
        str.sub!(/\s(\S*)$/) { $1[0].upcase + $1[1..] }
    end
    str[0...160]
end

Explained version:

def shortenSMS str
    # Get basic message statistics
    excessChars = str.length - 160
    spacesCount = str.count(' ')
    # How many substitutions should be done
    subCount = [excessChars, spacesCount].min

    subCount.times do
        # Substitutes with mutation first space from end, capturing the tail
        str.sub!(/\s(\S*)$/) do
            # Upcase first character of tail and append rest
            # $1.capitalize won't do, because it lowercases rest of the string
            $1[0].upcase + $1[1..]
        end
    end
    # Ensure that message is 160 chars long
    str[0...160]
end