1

I am trying to write a simple function to crop a given message to a specific length but at the same time not to cut the words in between and no trailing spaces in the end.

Example:

Input String: The quick brown fox jumped over the fence, K: 11

Output: The quick

Here is what I have tried:

  function crop(message, K) {
  var originalLen = message.length;
  if(originalLen<K)
  {
      return message;
  }
  else
  {
      var words = message.split(' '),substr;

      for(var i=words.length;i > 0;i--)
       {

           words.pop();

            if(words.join(' ').length<=K)
            {
              return words.join(' ');
            }
       }


  }
}

This function works fine but I am not very happy with the implementation. Need suggestions on the performance aspects and will there be a case where this won't work?

Emma
  • 27,428
  • 11
  • 44
  • 69
beNerd
  • 3,314
  • 6
  • 54
  • 92
  • do you have an example of the unsplitted string? ... and the wanted result? – Nina Scholz May 09 '19 at 20:01
  • 3
    Possible duplicate of [Shorten string without cutting words in JavaScript](https://stackoverflow.com/questions/5454235/shorten-string-without-cutting-words-in-javascript) – Herohtar May 09 '19 at 20:02
  • @Herohtar yes but please read the lines below the question: ```This function works fine but I am not very happy with the implementation. Need suggestions on the performance aspects and will there be a case where this won't work?``` – beNerd May 09 '19 at 20:03
  • @NinaScholz Added an example in the question! – beNerd May 09 '19 at 20:08
  • I use this regex to crop text with word boundaries `'/^(.{1,300})(?!\w)\b\p{P}*/su'` - 300 being my length. Taken from this question: https://stackoverflow.com/questions/35023508/ellipsis-after-certain-number-or-characters-with-word-boundaries – dokgu May 09 '19 at 20:10
  • 3
    What do you want to know that isn't covered in the duplicate? There are multiple solutions listed there that are more performant than string splitting and loops. – Herohtar May 09 '19 at 20:12
  • You might get better answers from https://codereview.stackexchange.com/ – dokgu May 09 '19 at 20:26
  • @uom-pgregorio never knew something like that existed! Thanks. – beNerd May 09 '19 at 20:29

3 Answers3

2

This one gets the job done without using any for loops

function crop(message, K) {
  var originalLen = message.length;
  if(originalLen<K)
  {
      return message;
  }
  else
  {
    var words = message.split(' '); 
    var splitWords = message.substring(0, K).split(' ');

    if( words[splitWords.length - 1] != splitWords[splitWords.length - 1])
     return splitWords.slice(0, splitWords.length - 1).join(' ');
    else
     return splitWords.join(' ');
  }
}

var str = "The quick brown fox jumped over the fence";
console.log(crop(str, 11));
Rahul Vala
  • 695
  • 8
  • 17
  • 1
    This is pretty good, but there are some edge cases you're not considering. Ex.: str = "The quick brown fox jumped over the fence, bro"; and crop(str, 13) return "The quick bro". I know this isn't within the Question, but I'm assuming the provided test case isn't the only thing the OP is going use. – computercarguy May 09 '19 at 21:08
  • @computercarguy, thank you so much for finding the mistake, I have updated the code which now takes care of the edge case that you suggested – Rahul Vala May 09 '19 at 21:37
1

You could check the length of the new string and increment an index for slicing the array.

function crop(message, l) {
    var parts = message.split(/\s+/),
        i = 1;

    if (message.length < l) return message;
    while (parts.slice(0, i).join(' ').length < l) i++;
    return parts.slice(0, i - 1).join(' ');
}

console.log(crop('The quick brown fox jumped over the fence', 11)); // 'The quick'
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

Just reviewed your code!

Nina's answer is really great!


However, this expression might help you to do so:

([A-z0-9\s]{1,11})(\s)(.*)

This expression is relaxed from the right and has three capturing groups with just a list of chars that I have just added in the first capturing group and I'm sure you might want to change that list, maybe similar to:

([A-Za-z0-9\s]{1,11})(\s)(.*)

Graph

This graph shows how the expression would work and you can visualize other expressions in this link:

enter image description here

Performance Test

This JavaScript snippet shows the performance of that expression using a simple 1-million times for loop.

const repeat = 1000000;
const start = Date.now();

for (var i = repeat; i >= 0; i--) {
 const string = 'The quick brown fox jumped over the fence';
 const regex = /([A-z0-9\s]{1,11})(\s)(.*)/gm;
 var match = string.replace(regex, "$1");
}

const end = Date.now() - start;
console.log("YAAAY! \"" + match + "\" is a match  ");
console.log(end / 1000 + " is the runtime of " + repeat + " times benchmark test.  ");

Testing Code

const regex = /([A-z0-9\s]{1,11})(\s)(.*)/s;
const str = `The quick brown fox jumped over the fence`;
const subst = `$1`;

// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);

console.log('Substitution result: ', result);
Emma
  • 27,428
  • 11
  • 44
  • 69