Skróć ciąg bez wycinania słów w JavaScript

102

Nie jestem zbyt dobry w manipulowaniu ciągami znaków w JavaScript i zastanawiałem się, jak skrócić ciąg bez obcinania żadnego słowa. Wiem, jak używać podciągów, ale nie indexOf ani niczego naprawdę dobrego.

Powiedzmy, że mam następujący ciąg:

text = "this is a long string I cant display"

Chcę skrócić go do 10 znaków, ale jeśli nie kończy się spacją, zakończ słowo. Nie chcę, aby zmienna łańcuchowa wyglądała tak:

„To jest długi ciąg, którego nie mogę znaleźć”

Chcę, żeby kończyło słowo, aż pojawi się spacja.

Josh Bedo
źródło
masz na myśli przyciąć sznurek? spróbuj" too many spaces ".trim()
Anurag
1
Niektóre przykładowe dane wejściowe i oczekiwane wyniki bardzo pomogłyby w odpowiedzi na to pytanie.
deceze
w porządku, przepraszam, powiedz, że mam ciąg tekstowy = "to jest długi ciąg i nie mogę go wyświetlić" Chcę go skrócić do 10 znaków, ale jeśli nie kończy się spacją, zakończ słowo Nie chcę, aby zmienna typu string wyglądała jak to „to jest długi ciąg, którego nie mogę”
Josh Bedo

Odpowiedzi:

180

Jeśli dobrze rozumiem, chcesz skrócić ciąg do określonej długości (np. Skrócić "The quick brown fox jumps over the lazy dog"do powiedzmy 6 znaków bez odcinania żadnego słowa).

W takim przypadku możesz wypróbować coś takiego:

var yourString = "The quick brown fox jumps over the lazy dog"; //replace with your string.
var maxLength = 6 // maximum number of characters to extract

//Trim and re-trim only when necessary (prevent re-trim when string is shorted than maxLength, it causes last word cut) 
if(yourString.length > trimmedString.length){
    //trim the string to the maximum length
    var trimmedString = yourString.substr(0, maxLength);

    //re-trim if we are in the middle of a word and 
    trimmedString = trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" ")))
}
NT3RP
źródło
9
@josh, to absolutnie nieprawda, że ​​„.replace” nie działa w „funkcjach jQuery”. Nie ma nawet czegoś takiego jak „funkcja jQuery”.
Pointy
3
czy to nie powinno być „maxLength + 1”. A jeśli maxLength jest większa lub równa całkowitej długości zdania, to ostatnie słowo nie jest uwzględniane. ale dzięki za rozwiązanie.
Beytan Kurt
4
Jeśli użyjesz tego na ciągu, który jest krótszy niż maxLength, ostatnie słowo zostanie obcięte. Może @AndrewJuniorHoward już podał poprawkę dla tego ( maxLength + 1), ale naprawiłem to, dodając po prostu tę linię u góry:var yourString += " ";
tylerl
3
Niestety, jeśli odbierzesz fox jumps over the lazy dogczęść, wynik będzie taki The quick brown , jak powinien The quick brown fox.
Andrey Gordeev
2
To zawsze kończy ostatnie słowo.
Chris Cinelli
108

Można to zrobić na wiele sposobów, ale wyrażenie regularne jest przydatną metodą jednowierszową:

"this is a longish string of text".replace(/^(.{11}[^\s]*).*/, "$1"); 
//"this is a longish"

To wyrażenie zwraca pierwszych 11 (dowolnych) znaków oraz wszelkie kolejne znaki niebędące spacjami.

Przykładowy skrypt:

<pre>
<script>
var t = "this is a longish string of text";

document.write("1:   " + t.replace(/^(.{1}[^\s]*).*/, "$1") + "\n");
document.write("2:   " + t.replace(/^(.{2}[^\s]*).*/, "$1") + "\n");
document.write("5:   " + t.replace(/^(.{5}[^\s]*).*/, "$1") + "\n");
document.write("11:  " + t.replace(/^(.{11}[^\s]*).*/, "$1") + "\n");
document.write("20:  " + t.replace(/^(.{20}[^\s]*).*/, "$1") + "\n");
document.write("100: " + t.replace(/^(.{100}[^\s]*).*/, "$1") + "\n");
</script>

Wynik:

1:   this
2:   this
5:   this is
11:  this is a longish
20:  this is a longish string
100: this is a longish string of text
Hamish
źródło
Wspaniale, dosłownie wyszukałem w Google to pytanie na milion sposobów i mogłem znaleźć tylko działającą wersję dla php, nic podobnego do tego i obejmujących pętle.
Josh Bedo
1
Odnosi się do pierwszego (i jedynego w tym przypadku) dopasowania wyrażenia podrzędnego - rzeczy w nawiasach. $ 0 odnosiłoby się do całego dopasowania, które w tym przypadku jest całym ciągiem.
Hamish
3
@josh Powinieneś być w stanie ustawić maksymalną długość jako zmienną za pomocą obiektu regexp:t.replace(new RegExp("^(.{"+length+"}[^\s]*).*"), "$1")
rjmackay
1
@ Hamish twoja opcja działa dobrze, ale obejmuje ostatnie słowo również wtedy, gdy długość przekracza. Próbowałem zmienić wyrażenie regex, aby wykluczyć ostatnie słowo, jeśli maksymalny limit słów przekracza, ale nie działało. Jak możemy to osiągnąć?
Shashank Agrawal
1
Cóż, to naprawdę nie działa poprawnie, czasami mijam maksymalną wartość, na przykład jeśli ostatnie słowo miało już 30 znaków, będzie miało już długość większą niż 60! nawet jeśli ustawiono długość na{30}
Al-Mothafar
65

Jestem trochę zdziwiony, że na taki prosty problem jest tak wiele odpowiedzi, które są trudne do odczytania, a niektóre, w tym ta wybrana, nie działają.

Zwykle chcę, aby wynikowy ciąg składał się z maksymalnie maxLen znaków. Używam również tej samej funkcji, aby skrócić informacje o błędach w adresach URL.

str.lastIndexOf(searchValue[, fromIndex]) przyjmuje drugi parametr, który jest indeksem, od którego należy rozpocząć wyszukiwanie wstecz w ciągu, dzięki czemu wszystko jest wydajne i proste.

// Shorten a string to less than maxLen characters without truncating words.
function shorten(str, maxLen, separator = ' ') {
  if (str.length <= maxLen) return str;
  return str.substr(0, str.lastIndexOf(separator, maxLen));
}

Oto przykładowe dane wyjściowe:

for (var i = 0; i < 50; i += 3) 
  console.log(i, shorten("The quick brown fox jumps over the lazy dog", i));

 0 ""
 3 "The"
 6 "The"
 9 "The quick"
12 "The quick"
15 "The quick brown"
18 "The quick brown"
21 "The quick brown fox"
24 "The quick brown fox"
27 "The quick brown fox jumps"
30 "The quick brown fox jumps over"
33 "The quick brown fox jumps over"
36 "The quick brown fox jumps over the"
39 "The quick brown fox jumps over the lazy"
42 "The quick brown fox jumps over the lazy"
45 "The quick brown fox jumps over the lazy dog"
48 "The quick brown fox jumps over the lazy dog"

A dla ślimaka:

for (var i = 0; i < 50; i += 10) 
  console.log(i, shorten("the-quick-brown-fox-jumps-over-the-lazy-dog", i, '-'));

 0 ""
10 "the-quick"
20 "the-quick-brown-fox"
30 "the-quick-brown-fox-jumps-over"
40 "the-quick-brown-fox-jumps-over-the-lazy"
Chris Cinelli
źródło
1
Zupełnie zapomniałem o lastIndexOf (). Dobry chwyt!
Tici
2
To ulega awarii, jeśli z jakiegoś powodu strjest undefined. Dodałemif (!str || str.length <= maxLen) return str;
Silvain
nie obsługuje to przypadku krawędzi, w którym separator nie występuje w ciągu znaków
shrewquest
@shrewquest To działa. Jeśli separator nie znajduje się w ciągu, zwraca sam łańcuch if str.length <= maxLen. W przeciwnym razie zwraca pusty ciąg.
Chris Cinelli,
20

Wydaje się, że wszyscy zapominają, że indexOf przyjmuje dwa argumenty - ciąg znaków do dopasowania i indeks znaku, od którego ma się rozpocząć wyszukiwanie. Możesz przerwać ciąg przy pierwszej spacji po 10 znakach.

function cutString(s, n){
    var cut= s.indexOf(' ', n);
    if(cut== -1) return s;
    return s.substring(0, cut)
}
var s= "this is a long string i cant display";
cutString(s, 10)

/*  returned value: (String)
this is a long
*/
kennebec
źródło
Zwróć uwagę, że indexOf można zastąpić lastIndexOf, jeśli potrzebne są twarde granice.
Scheintod
14

Lodash ma specjalnie napisaną funkcję: _.truncate

const truncate = _.truncate
const str = 'The quick brown fox jumps over the lazy dog'

truncate(str, {
  length: 30, // maximum 30 characters
  separator: /,?\.* +/ // separate by spaces, including preceding commas and periods
})

// 'The quick brown fox jumps...'
Leon Li
źródło
7

W oparciu o odpowiedź NT3RP, która nie obsługuje niektórych przypadków narożnych, stworzyłem ten kod. Gwarantuje, że nie zwróci tekstu ze zdarzeniem size> maxLength, ...na końcu którego dodano wielokropek .

Obsługuje to również niektóre przypadki narożne, takie jak tekst, w którym jedno słowo jest> maxLength

shorten: function(text,maxLength,options) {
    if ( text.length <= maxLength ) {
        return text;
    }
    if ( !options ) options = {};
    var defaultOptions = {
        // By default we add an ellipsis at the end
        suffix: true,
        suffixString: " ...",
        // By default we preserve word boundaries
        preserveWordBoundaries: true,
        wordSeparator: " "
    };
    $.extend(options, defaultOptions);
    // Compute suffix to use (eventually add an ellipsis)
    var suffix = "";
    if ( text.length > maxLength && options.suffix) {
        suffix = options.suffixString;
    }

    // Compute the index at which we have to cut the text
    var maxTextLength = maxLength - suffix.length;
    var cutIndex;
    if ( options.preserveWordBoundaries ) {
        // We use +1 because the extra char is either a space or will be cut anyway
        // This permits to avoid removing an extra word when there's a space at the maxTextLength index
        var lastWordSeparatorIndex = text.lastIndexOf(options.wordSeparator, maxTextLength+1);
        // We include 0 because if have a "very long first word" (size > maxLength), we still don't want to cut it
        // But just display "...". But in this case the user should probably use preserveWordBoundaries:false...
        cutIndex = lastWordSeparatorIndex > 0 ? lastWordSeparatorIndex : maxTextLength;
    } else {
        cutIndex = maxTextLength;
    }

    var newText = text.substr(0,cutIndex);
    return newText + suffix;
}

Myślę, że możesz łatwo usunąć zależność jquery, jeśli ci to przeszkadza.

Sebastien Lorber
źródło
3
Podoba mi się to rozwiązanie, ale czy $.extendpodane argumenty nie powinny być odwrócone?
JKesMc9tqIQe9M
6

Oto rozwiązanie w jednej linii.

text = "this is a long string I cant display"

function shorten(text,max) {
    return text && text.length > max ? text.slice(0,max).split(' ').slice(0, -1).join(' ') : text
}


console.log(shorten(text,10));

Joakim Poromaa Helger
źródło
3

Spóźniłem się na imprezę, ale oto małe i łatwe rozwiązanie, które wymyśliłem, aby zwrócić pewną liczbę słów.

Nie jest to bezpośrednio związane z twoimi wymaganiami dotyczącymi postaci , ale służy temu samemu rezultatowi, o którym myślę, że chciałeś.

function truncateWords(sentence, amount, tail) {
  const words = sentence.split(' ');

  if (amount >= words.length) {
    return sentence;
  }

  const truncated = words.slice(0, amount);
  return `${truncated.join(' ')}${tail}`;
}

const sentence = 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.';

console.log(truncateWords(sentence, 10, '...'));

Zobacz działający przykład tutaj: https://jsfiddle.net/bx7rojgL/

Michael Giovanni Pumo
źródło
Napisałeś funkcję JS, która skraca ciąg znaków do kilku słów. Przeczytaj ponownie pytanie.
ChristoKiwi
1
eeehm. myślę, że to jedyna prawidłowa odpowiedź na to pytanie. Zapytał, nie przerywając słowa.
Mike Aron
2

To wyklucza ostatnie słowo, zamiast je włączać.

function smartTrim(str, length, delim, appendix) {
    if (str.length <= length) return str;

    var trimmedStr = str.substr(0, length+delim.length);

    var lastDelimIndex = trimmedStr.lastIndexOf(delim);
    if (lastDelimIndex >= 0) trimmedStr = trimmedStr.substr(0, lastDelimIndex);

    if (trimmedStr) trimmedStr += appendix;
    return trimmedStr;
}

Stosowanie:

smartTrim(yourString, 11, ' ', ' ...')
"The quick ..."
klimat
źródło
2

Przyjąłem inne podejście. Chociaż potrzebowałem podobnego wyniku, chciałem, aby moja wartość zwracana była mniejsza niż określona długość.

function wordTrim(value, length, overflowSuffix) {
    value = value.trim();
    if (value.length <= length) return value;
    var strAry = value.split(' ');
    var retString = strAry[0];
    for (var i = 1; i < strAry.length; i++) {
        if (retString.length >= length || retString.length + strAry[i].length + 1 > length) break;
        retString += " " + strAry[i];
    }
    return retString + (overflowSuffix || '');
}

Edytuj Poprawiłem to trochę tutaj: Przykład JSFiddle . Łączy ponownie oryginalną tablicę zamiast konkatenacji.

function wordTrim(value, length, overflowSuffix) {
    if (value.length <= length) return value;
    var strAry = value.split(' ');
    var retLen = strAry[0].length;
    for (var i = 1; i < strAry.length; i++) {
        if(retLen == length || retLen + strAry[i].length + 1 > length) break;
        retLen+= strAry[i].length + 1
    }
    return strAry.slice(0,i).join(' ') + (overflowSuffix || '');
}
Pete
źródło
2
function shorten(str,n) {
  return (str.match(RegExp(".{"+n+"}\\S*"))||[str])[0];
}

shorten("Hello World", 3); // "Hello"

Roko C. Buljan
źródło
1

Możesz użyć truncatejednej linijki poniżej:

const text = "The string that I want to truncate!";

const truncate = (str, len) => str.substring(0, (str + ' ').lastIndexOf(' ', len));

console.log(truncate(text, 14));

Viktor Vlasenko
źródło
1
shorten(str, maxLen, appendix, separator = ' ') {
if (str.length <= maxLen) return str;
let strNope = str.substr(0, str.lastIndexOf(separator, maxLen));
return (strNope += appendix);

}

var s = "to jest długi ciąg i nie mogę wyjaśnić wszystkiego"; skrócić (s, 10, '...')

/* "to jest .." */

vivi margaretha
źródło
1

Oto kolejny fragment kodu, który skraca się wzdłuż znaków interpunkcyjnych (szukałem tego, a Google znalazło to pytanie tutaj). Musiałem sam wymyślić rozwiązanie, więc włamałem się do tego w 15 minut. Znajduje wszystkie wystąpienia. ! ? i obcina w dowolnej pozycji tych, które są <niżlen

function pos(str, char) {
    let pos = 0
    const ret = []
    while ( (pos = str.indexOf(char, pos + 1)) != -1) {
        ret.push(pos)
    }
    return ret
}

function truncate(str, len) {
    if (str.length < len)
        return str

    const allPos = [  ...pos(str, '!'), ...pos(str, '.'), ...pos(str, '?')].sort( (a,b) => a-b )
    if (allPos.length === 0) {
        return str.substr(0, len)
    }

    for(let i = 0; i < allPos.length; i++) {
        if (allPos[i] > len) {
            return str.substr(0, allPos[i-1] + 1)
        }
    }
}

module.exports = truncate
Stefan
źródło
1

Maszynopis iz elipsami :)

export const sliceByWord = (phrase: string, length: number, skipEllipses?: boolean): string => {
  if (phrase.length < length) return phrase
  else {
    let trimmed = phrase.slice(0, length)
    trimmed = trimmed.slice(0, Math.min(trimmed.length, trimmed.lastIndexOf(' ')))
    return skipEllipses ? trimmed : trimmed + '…'
  }
}
doublejosh
źródło
1

`` Makaron z pomidorami i szpinakiem ''

jeśli nie chcesz przeciąć słowa na pół

pierwsza iteracja:

acc: 0 / acc + cur.length = 5 / newTitle = ['Pasta'];

druga iteracja:

acc: 5 / acc + cur.length = 9 / newTitle = ['Pasta', 'with'];

trzecia iteracja:

acc: 9 / acc + cur.length = 15 / newTitle = ['Pasta', 'with', 'tomato'];

czwarta iteracja:

acc: 15 / acc + cur.length = 18 (limit bound) / newTitle = ['Pasta', 'with', 'tomato'];

const limitRecipeTitle = (title, limit=17)=>{
    const newTitle = [];
    if(title.length>limit){
        title.split(' ').reduce((acc, cur)=>{
            if(acc+cur.length <= limit){
                newTitle.push(cur);
            }
            return acc+cur.length;
        },0);
    }

    return `${newTitle.join(' ')} ...`
}

wyjście: Makaron z pomidorami ...

Lord
źródło
Nie uwzględnia to znaków „join („ ”), które mogą spowodować, że ciąg będzie dłuższy niż limit. Jeśli zmienisz funkcję redukcji () na (acc, cur, idx), a if na (acc + cur.length <= limit - idx), to uwzględni dodatkowe spacje, gdy słowa zostaną ponownie połączone. Jeśli wymagane jest ściśle określone ograniczenie.
PSaul
0

Na co warto napisałem to, aby obciąć do granicy słowa bez pozostawiania znaków interpunkcyjnych lub białych znaków na końcu ciągu:

function truncateStringToWord(str, length, addEllipsis)
{
    if(str.length <= length)
    {
        // provided string already short enough
        return(str);
    }

    // cut string down but keep 1 extra character so we can check if a non-word character exists beyond the boundary
    str = str.substr(0, length+1);

    // cut any non-whitespace characters off the end of the string
    if (/[^\s]+$/.test(str))
    {
        str = str.replace(/[^\s]+$/, "");
    }

    // cut any remaining non-word characters
    str = str.replace(/[^\w]+$/, "");

    var ellipsis = addEllipsis && str.length > 0 ? '&hellip;' : '';

    return(str + ellipsis);
}

var testString = "hi stack overflow, how are you? Spare";
var i = testString.length;

document.write('<strong>Without ellipsis:</strong><br>');

while(i > 0)
{
  document.write(i+': "'+ truncateStringToWord(testString, i) +'"<br>');
  i--;
}

document.write('<strong>With ellipsis:</strong><br>');

i = testString.length;
while(i > 0)
{
  document.write(i+': "'+ truncateStringToWord(testString, i, true) +'"<br>');
  i--;
}

bbeckford
źródło
0

Głosowane rozwiązania nie zostały uznane za satysfakcjonujące. Więc napisałem coś, co jest rodzajowe i działa zarówno jako pierwsza, jak i ostatnia część twojego tekstu (coś w rodzaju substr, ale dla słów). Możesz także ustawić, czy chcesz, aby spacje zostały pominięte w liczbie znaków.

    function chopTxtMinMax(txt, firstChar, lastChar=0){
        var wordsArr = txt.split(" ");
        var newWordsArr = [];

        var totalIteratedChars = 0;
        var inclSpacesCount = true;

        for(var wordIndx in wordsArr){
            totalIteratedChars += wordsArr[wordIndx].length + (inclSpacesCount ? 1 : 0);
            if(totalIteratedChars >= firstChar && (totalIteratedChars <= lastChar || lastChar==0)){
                newWordsArr.push(wordsArr[wordIndx]);
            }
        }

        txt = newWordsArr.join(" ");
        return txt;
    }
Vasili Paspalas
źródło
0

Spóźniłem się na to, ale myślę, że ta funkcja robi dokładnie to, czego żąda OP. Możesz łatwo zmienić wartości SENTENCE i LIMIT dla różnych wyników.

function breakSentence(word, limit) {
  const queue = word.split(' ');
  const list = [];

  while (queue.length) {
    const word = queue.shift();

    if (word.length >= limit) {
      list.push(word)
    }
    else {
      let words = word;

      while (true) {
        if (!queue.length ||
            words.length > limit ||
            words.length + queue[0].length + 1 > limit) {
          break;
        }

        words += ' ' + queue.shift();
      }

      list.push(words);
    }
  }

  return list;
}

const SENTENCE = 'the quick brown fox jumped over the lazy dog';
const LIMIT = 11;

// get result
const words = breakSentence(SENTENCE, LIMIT);

// transform the string so the result is easier to understand
const wordsWithLengths = words.map((item) => {
  return `[${item}] has a length of - ${item.length}`;
});

console.log(wordsWithLengths);

W wyniku tego fragmentu LIMIT 11 to:

[ '[the quick] has a length of - 9',
  '[brown fox] has a length of - 9',
  '[jumped over] has a length of - 11',
  '[the lazy] has a length of - 8',
  '[dog] has a length of - 3' ]
Ian Calderon
źródło
0

Z warunkami brzegowymi, takimi jak puste zdanie i bardzo długie pierwsze słowo. Ponadto nie używa specyficznego dla języka interfejsu API / biblioteki.

function solution(message, k) {
    if(!message){
        return ""; //when message is empty
    }
    const messageWords = message.split(" ");
    let result = messageWords[0];
    if(result.length>k){
        return ""; //when length of first word itself is greater that k
    }
    for(let i = 1; i<messageWords.length; i++){
        let next = result + " " + messageWords[i];

        if(next.length<=k){
            result = next;
        }else{
            break;
        }
    }
    return result;
}

console.log(solution("this is a long string i cant display", 10));

Shishir Arora
źródło
-1

Możesz przycinać spacje za pomocą tego:

var trimmedString = flabbyString.replace(/^\s*(.*)\s*$/, '$1');
Spiczasty
źródło
-1

Zaktualizowano z @ NT3RP Odkryłem, że jeśli ciąg uderzy w spację za pierwszym razem, spowoduje to usunięcie tego słowa, czyniąc ciąg o jedno słowo krótszym, niż może być. Dlatego właśnie dodałem instrukcję if else, aby sprawdzić, czy wartość maxLength nie przypada na spację.

codepen.io

var yourString = "The quick brown fox jumps over the lazy dog"; //replace with your string.
var maxLength = 15 // maximum number of characters to extract

if (yourString[maxLength] !== " ") {

//trim the string to the maximum length
var trimmedString = yourString.substr(0, maxLength);

alert(trimmedString)

//re-trim if we are in the middle of a word
trimmedString = trimmedString.substr(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" ")))
}

else {
  var trimmedString = yourString.substr(0, maxLength);
}

alert(trimmedString)
Landon Call
źródło