Policz liczbę wystąpień znaku w ciągu w JavaScript


525

Muszę policzyć liczbę wystąpień znaku w ciągu.

Załóżmy na przykład, że mój ciąg zawiera:

var mainStr = "str1,str2,str3,str4";

Chcę znaleźć liczbę ,znaków przecinka , która wynosi 3. I liczbę pojedynczych ciągów po podziale wzdłuż przecinka, czyli 4.

Muszę również sprawdzić, czy każdy z łańcuchów, tj. Str1 lub str2 lub str3 lub str4, nie powinien przekraczać, powiedzmy, 15 znaków.

Odpowiedzi:


764

Zaktualizowałem tę odpowiedź. Podoba mi się pomysł lepszego dopasowania, ale jest wolniejszy:

console.log(("str1,str2,str3,str4".match(/,/g) || []).length); //logs 3

console.log(("str1,str2,str3,str4".match(new RegExp("str", "g")) || []).length); //logs 4

jsfiddle

Użyj literału wyrażenia regularnego, jeśli wiesz, czego szukasz, jeśli nie, możesz użyć RegExpkonstruktora i przekazać gflagę jako argument.

matchzwraca nullbez żadnych wyników, więc|| []

Oryginalna odpowiedź, którą udzieliłem w 2009 roku, jest poniżej. Tworzy tablicę niepotrzebnie, ale użycie podziału jest szybsze (od września 2014 r.). Jestem ambiwalentny, gdybym naprawdę potrzebował prędkości, nie byłoby wątpliwości, że użyłbym podziału, ale wolałbym użyć dopasowania.

Stara odpowiedź (z 2009 r.):

Jeśli szukasz przecinków:

(mainStr.split(",").length - 1) //3

Jeśli szukasz str

(mainStr.split("str").length - 1) //4

Zarówno w odpowiedzi @ Lo, jak i w moim głupim podziale testu jsperf pojawia się szybko, przynajmniej w Chrome, ale znowu tworzenie dodatkowej tablicy po prostu nie wydaje się rozsądne.


8
test pokazuje, że Firefox dzieli się szybciej niż jakakolwiek inna przeglądarka. jsperf.com/count-the-number-of-occurances-in-string
vsync

4
Właśnie testowałem jsperf vsync, a regex działał wolniej w Chrome, Firefox i IE. Odpowiednio 68%, 100% i 14%. Mam i7 2600.
Moss

56
Naprawdę nie podoba mi się pomysł użycia wyrażenia regularnego, ponieważ „bardziej ci się podoba”. Regeksy mają swój cel, ale ogólnie rzecz biorąc, gdy istnieje proste rozwiązanie nieregexowe, jest to lepszy wybór. Zauważ też, że obie metody tworzą tablicę, więc nie jest to również powód do używania wyrażenia regularnego.
Jasper

4
Z tego powodu bardziej mi się podoba w tym przypadku . Podział łańcucha na tablicę w celu uzyskania wielu wystąpień jest dobrym sposobem na uzyskanie tej informacji. Podział tablicy jest szybszy tylko ze względu na szczegóły implementacji, coś, co można zmienić, podczas gdy uzyskanie liczby dopasowań poprawia czytelność, zamiar jest oczywisty i nie tworzy i nie wypełnia nieużywanej struktury danych.
Bjorn,

30
split () to podstawowe narzędzie w javascript, koncepcyjnie proste, a liczenie podziałów daje wyraźną intencję i jest całkowicie czytelne.
bradw2k,

217

Istnieją co najmniej cztery sposoby. Najlepsza opcja, która powinna być również najszybsza - dzięki natywnemu silnikowi RegEx - - znajduje się na górze. Witryna jsperf.com jest obecnie niedostępna, w przeciwnym razie przedstawiłbym statystyki wydajności.

Aktualizacja : znajdź tutaj testy wydajnościi sam je uruchom, aby przyczynić się do uzyskania wyników. Szczegóły wyników zostaną podane później.

1.

 ("this is foo bar".match(/o/g)||[]).length
 //>2

2)

"this is foo bar".split("o").length-1
 //>2

podział nie jest zalecany. Głodny zasobów. Przypisuje nowe wystąpienia „Array” dla każdego dopasowania. Nie próbuj tego w przypadku pliku> 100 MB przez FileReader. Możesz naprawdę łatwo obserwować zużycie zasobów DOKŁADNIE, korzystając z opcji profilera Chrome .

3)

var stringsearch = "o"
   ,str = "this is foo bar";
for(var count=-1,index=-2; index != -1; count++,index=str.indexOf(stringsearch,index+1) );
 //>count:2

4

szukanie jednego znaku

var stringsearch = "o"
   ,str = "this is foo bar";
for(var i=count=0; i<str.length; count+=+(stringsearch===str[i++]));
 //>count:2

Aktualizacja:

5

mapowanie i filtrowanie elementów, niezalecane ze względu na ogólną wstępną alokację zasobów zamiast używania Pythonowskich „generatorów”

var str = "this is foo bar"
str.split('').map( function(e,i){ if(e === 'o') return i;} )
             .filter(Boolean)
//>[9, 10]
[9, 10].length
//>2

Podziel się: Zrobiłem to sens , ze obecnie 8 sposobów liczenia znaków, więc możemy bezpośrednio połączyć i podzielić się naszymi pomysłami - tylko dla zabawy, a może kilka interesujących punktów odniesienia :)

https://gist.github.com/2757250


27
Zajęło mi trochę czasu, aby zrozumieć, co się ||[]dzieje, ale ta odpowiedź jest świetna! Każdy, kto podrapie się po głowach, match()zwraca, nulljeśli nie zostaną znalezione żadne dopasowania, i ||[]zwraca tablicę o długości 0, jeśli match()zwraca null, co oznacza, length()że zwróci 0 zamiast produkować błąd typu.
Nathan

1
Nathan, w mojej obronie, rozwinąłem to przed napisaniem powyższego kodu: gist.github.com/2757164 . Chcę unikać wpisów w blogu zawierających małe fragmenty kodu, które jednak umożliwiłyby ci natychmiastowy dostęp za pośrednictwem wyszukiwarki Google. Gist jako repozytorium urywków jest bardzo słabo indeksowane i mniej niż idealne. PS: Ja też nienawidzę niejasnych składniowych osobliwości.
Lorenz Lo Sauer,

2
Lo Sauer, nie musisz się bronić, kod jest solidny i sam nauczyłem się czegoś, zastanawiając się, jak to działa :) Wolę tę metodę od tego, co faktycznie jest oznaczone jako odpowiedź. Nie powinno być potrzeby dzielenia łańcucha, jeśli nie będziemy wykorzystywać wyników.
Nathan

3
Twoja trzecia metoda (również niestety najszybsza) nie trafi w żaden mecz o indeksie 0 w stogu siana. Można to naprawić za pomocą pętli do ... while zamiast: var strsearch = "o", str = "othis is foo bar", index = -1, count = -1; do {index = str.indexOf (strsearch, index + 1); liczyć ++; } while (indeks! = -1); hrabia
August

1
Wystarczy ustawić początek index = -2, ale wielkie dzięki @Augustus
Lorenz Lo Sauer

18

Dodaj tę funkcję do prototypu żądła:

String.prototype.count=function(c) { 
  var result = 0, i = 0;
  for(i;i<this.length;i++)if(this[i]==c)result++;
  return result;
};

stosowanie:

console.log("strings".count("s")); //2

co "stringsstringstrings".count("str")?
Toskan

12

Szybka wyszukiwarka Google ma to (z http://www.codecodex.com/wiki/index.php?title=Count_the_number_of_occurrences_of_a_specific_character_in_a_string#JavaScript )

String.prototype.count=function(s1) { 
    return (this.length - this.replace(new RegExp(s1,"g"), '').length) / s1.length;
}

Użyj tego w ten sposób:

test = 'one,two,three,four'
commas = test.count(',') // returns 3

4
błąd na *char ( SyntaxError: nothing to repeat)

1
argument musi być wyrażeniem regularnym. Więc jeśli chcesz policzyć , musisz wysłać „[* ]”
Gerard ONeill

8

Wystarczy użyć podziału, aby sprawdzić liczbę wystąpień znaku w ciągu.

mainStr.split(',').length // daje 4, która jest liczbą ciągów znaków po rozdzieleniu za pomocą przecinka

mainStr.split(',').length - 1 // // daje 3, która jest liczbą przecinków


Jest to w zasadzie wymagana odpowiedź tutaj. Jestem zszokowany, nikt jeszcze nie zauważył.
Rohit Gupta

7

Oto podobne rozwiązanie, ale wykorzystuje Array.prototype.reduce

function countCharacters(char, string) {
  return string.split('').reduce((acc, ch) => ch === char ? acc + 1: acc, 0)
}

Jak wspomniano, String.prototype.splitdziała znacznie szybciej niż String.prototype.replace.


6

Odkryłem, że najlepszym podejściem do wyszukiwania znaku w bardzo dużym ciągu (na przykład o długości 1 000 000 znaków) jest użycie tej replace()metody.

window.count_replace = function (str, schar) {
    return str.length - str.replace(RegExp(schar), '').length;
};

Możesz zobaczyć jeszcze jeden pakiet JSPerf do testowania tej metody wraz z innymi metodami znajdowania znaku w ciągu.


Oczywiste jest, że jeśli twój kod w jakiś sposób iteruje ponad milion znaków 500000 razy na sekundę, mój procesor działa co najmniej 100 GHz (zakładając, że nie ma SIMD; nawet wtedy będzie to co najmniej 40 GHz). Dlatego nie wierzę, że ten test porównawczy jest poprawny.
mój zaimek jest monicareinstate

5

Możesz także spocząć na swoim ciągu i pracować z nim jak z wykorzystaniem szeregu elementów

const mainStr = 'str1,str2,str3,str4';
const commas = [...mainStr].filter(l => l === ',').length;

console.log(commas);

Lub

const mainStr = 'str1,str2,str3,str4';
const commas = [...mainStr].reduce((a, c) => c === ',' ? ++a : a, 0);

console.log(commas);


1
Drugi jest przydatny, dzięki!
AlexGera

4

Wprowadziłem niewielką poprawę w stosunku do przyjętej odpowiedzi, pozwala ona sprawdzać z rozróżnianiem wielkości liter / bez rozróżniania wielkości liter i jest metodą dołączoną do obiektu łańcucha:

String.prototype.count = function(lit, cis) {
    var m = this.toString().match(new RegExp(lit, ((cis) ? "gi" : "g")));
    return (m != null) ? m.length : 0;
}

lit jest ciągiem do wyszukania (takim jak „ex”), a cis nie rozróżnia wielkości liter, domyślnie false, umożliwi wybór dopasowania bez rozróżniania wielkości liter.


Aby wyszukać ciąg pod 'I love StackOverflow.com'kątem małej litery 'o', użyj:

var amount_of_os = 'I love StackOverflow.com'.count('o');

amount_of_osbyłoby równe 2.


Gdybyśmy ponownie przeszukali ten sam ciąg znaków, używając dopasowywania bez rozróżniania wielkości liter, użyłbyś:

var amount_of_os = 'I love StackOverflow.com'.count('o', true);

Tym razem amount_of_osbyłby równy 3, ponieważ kapitał Oz ciągu zostaje włączony do wyszukiwania.


4

ok, inny z regexp - prawdopodobnie nie szybki, ale krótki i lepiej czytelny niż inne, w moim przypadku po prostu '_'liczyć

key.replace(/[^_]/g,'').length

po prostu usuń wszystko, co nie wygląda jak twój char, ale nie wygląda ładnie z ciągiem jako wejściem


4

Wydajność Split vs RegExp

var i = 0;

var split_start = new Date().getTime();
while (i < 30000) {
  "1234,453,123,324".split(",").length -1;
  i++;
}
var split_end = new Date().getTime();
var split_time = split_end - split_start;


i= 0;
var reg_start = new Date().getTime();
while (i < 30000) {
  ("1234,453,123,324".match(/,/g) || []).length;
  i++;
}
var reg_end = new Date().getTime();
var reg_time = reg_end - reg_start;

alert ('Split Execution time: ' + split_time + "\n" + 'RegExp Execution time: ' + reg_time + "\n");


4

Najłatwiejszy sposób, żeby się dowiedzieć ...

Przykład-

str = 'mississippi';

function find_occurences(str, char_to_count){
    return str.split(char_to_count).length - 1;
}

find_occurences(str, 'i') //outputs 4

zwięzły! Dzięki!
LeOn - Han Li

3

Pracowałem nad małym projektem, który wymagał licznika podciągów. Poszukiwanie niewłaściwych fraz nie dało mi rezultatów, jednak po napisaniu własnej implementacji natknąłem się na to pytanie. W każdym razie, oto moja droga, prawdopodobnie jest wolniejsza niż większość tutaj, ale może być komuś pomocna:

function count_letters() {
var counter = 0;

for (var i = 0; i < input.length; i++) {
    var index_of_sub = input.indexOf(input_letter, i);

    if (index_of_sub > -1) {
        counter++;
        i = index_of_sub;
    }
}

http://jsfiddle.net/5ZzHt/1/

Daj mi znać, jeśli ta implementacja zakończy się niepowodzeniem lub nie spełnia niektórych standardów! :)

AKTUALIZACJA Możesz zastąpić:

    for (var i = 0; i < input.length; i++) {

Z:

for (var i = 0, input_length = input.length; i < input_length; i++) {

Ciekawe lektury omawiające powyższe: http://www.erichynds.com/blog/javascript-length-property-is-a-stored-value


1
Tak, i działałoby w przypadku podciągów, a nie tylko subcharów. Musisz jednak dodać parametry do funkcji :)
Nico

2

Jeśli używasz lodash, metoda _.countBy zrobi to:

_.countBy("abcda")['a'] //2

Ta metoda działa również z tablicą:

_.countBy(['ab', 'cd', 'ab'])['ab'] //2

2

Oto moje rozwiązanie. Wiele rozwiązań już zostało opublikowanych przede mną. Ale uwielbiam dzielić się tutaj moim poglądem.

const mainStr = 'str1,str2,str3,str4';

const commaAndStringCounter = (str) => {
  const commas = [...str].filter(letter => letter === ',').length;
  const numOfStr = str.split(',').length;

  return `Commas: ${commas}, String: ${numOfStr}`;
}

// Run the code
console.log(commaAndStringCounter(mainStr)); // Output: Commas: 3, String: 4

Tutaj znajdziesz moją REPL


2

Najszybszą metodą wydaje się być operator indeksu:

function charOccurances (str, char)
{
  for (var c = 0, i = 0, len = str.length; i < len; ++i)
  {
    if (str[i] == char)
    {
      ++c;
    }
  }
  return c;
}

console.log( charOccurances('example/path/script.js', '/') ); // 2

Lub jako funkcja prototypowa:

String.prototype.charOccurances = function (char)
{
  for (var c = 0, i = 0, len = this.length; i < len; ++i)
  {
    if (this[i] == char)
    {
      ++c;
    }
  }
  return c;
}

console.log( 'example/path/script.js'.charOccurances('/') ); // 2


1

Poniżej zastosowano wyrażenie regularne do przetestowania długości. Testex zapewnia, że ​​nie masz 16 lub więcej kolejnych znaków bez przecinków. Jeśli przejdzie test, przechodzi do podziału łańcucha. liczenie przecinków jest tak proste, jak liczenie tokenów minus jeden.

var mainStr = "str1,str2,str3,str4";
var testregex = /([^,]{16,})/g;
if (testregex.test(mainStr)) {
  alert("values must be separated by commas and each may not exceed 15 characters");
} else {
  var strs = mainStr.split(',');
  alert("mainStr contains " + strs.length + " substrings separated by commas.");
  alert("mainStr contains " + (strs.length-1) + " commas.");
}

1
s = 'dir/dir/dir/dir/'
for(i=l=0;i<s.length;i++)
if(s[i] == '/')
l++

1

Co z string.split (pożądanyCharecter) .length-1

Przykład:

var str = "hellow how is life"; var len = str. podświetlony („h”). długość-1; da licznik 2 dla znaku „h” w powyższym ciągu;


1

Używam Node.js v.6.0.0, a najszybszy jest ten z indeksem (trzecia metoda w odpowiedzi Lo Sauera).

Drugi to:

function count(s, c) {
  var n = 0;
  for (let x of s) {
    if (x == c)
      n++;
  }
  return n;
}


1

Oto jeden prawie tak szybki, jak metody dzielenia i zastępowania, które są nieco szybsze niż metoda regex (w chrome).

var num = 0;
for (ch of "str1,str2,str3,str4")
{
    if (ch === ',') num++;
}

1

Właśnie zrobiłem bardzo szybki i brudny test na repl.it przy użyciu Node v7.4 . W przypadku pojedynczego znaku standard pętli jest najszybszy:

Jakiś kod :

// winner!
function charCount1(s, c) {
    let count = 0;
    c = c.charAt(0); // we save some time here
    for(let i = 0; i < s.length; ++i) {
        if(c === s.charAt(i)) {
            ++count;
        }
    }
    return count;
}

function charCount2(s, c) {
    return (s.match(new RegExp(c[0], 'g')) || []).length;
}

function charCount3(s, c) {
    let count = 0;
    for(ch of s) {
        if(c === ch) {
            ++count;
        }
    }
    return count;
}

function perfIt() {
    const s = 'Hello, World!';
    const c = 'o';

    console.time('charCount1');
    for(let i = 0; i < 10000; i++) {
        charCount1(s, c);
    }
    console.timeEnd('charCount1');

    console.time('charCount2');
    for(let i = 0; i < 10000; i++) {
        charCount2(s, c);
    }
    console.timeEnd('charCount2');

    console.time('charCount3');
    for(let i = 0; i < 10000; i++) {
        charCount2(s, c);
    }
    console.timeEnd('charCount3');
}

Wyniki z kilku przebiegów :

 perfIt()
charCount1: 3.843ms
charCount2: 11.614ms
charCount3: 11.470ms
=> undefined
   perfIt()
charCount1: 3.006ms
charCount2: 8.193ms
charCount3: 7.941ms
=> undefined
   perfIt()
charCount1: 2.539ms
charCount2: 7.496ms
charCount3: 7.601ms
=> undefined
   perfIt()
charCount1: 2.654ms
charCount2: 7.540ms
charCount3: 7.424ms
=> undefined
   perfIt()
charCount1: 2.950ms
charCount2: 9.445ms
charCount3: 8.589ms

1

I jest:

function character_count(string, char, ptr = 0, count = 0) {
    while (ptr = string.indexOf(char, ptr) + 1) {count ++}
    return count
}

Działa również z liczbami całkowitymi!


0

Moje rozwiązanie:

function countOcurrences(str, value){
   var regExp = new RegExp(value, "gi");
   return str.match(regExp) ? str.match(regExp).length : 0;  
}

To nie będzie działać jako String.prototype.matchzwroty nullbez dopasowań. Oznacza to brak odniesienia do obiektu z lengthatrybutem. Innymi słowy:String.prototype.match.call('willnotwork', /yesitwill/) === null
Lorenz Lo Sauer

0

Piąta metoda w odpowiedzi Leo Sauersa kończy się niepowodzeniem, jeśli znak znajduje się na początku ciągu. na przykład

var needle ='A',
  haystack = 'AbcAbcAbc';

haystack.split('').map( function(e,i){ if(e === needle) return i;} )
  .filter(Boolean).length;

da 2 zamiast 3, ponieważ funkcja filtru Boolean daje wartość false dla 0.

Inne możliwe funkcje filtra:

haystack.split('').map(function (e, i) {
  if (e === needle) return i;
}).filter(function (item) {
  return !isNaN(item);
}).length;

0

Wiem, że to może być stare pytanie, ale mam proste rozwiązanie dla początkujących na niskim poziomie w JavaScript.

Jako początkujący mogłem zrozumieć tylko niektóre rozwiązania tego pytania, więc użyłem dwóch zagnieżdżonych pętli FOR, aby sprawdzić każdy znak względem każdego innego znaku w ciągu, zwiększając zmienną count dla każdego znalezionego znaku, który jest równy temu znakowi.

Utworzyłem nowy pusty obiekt, w którym każdy klucz właściwości jest znakiem, a wartością jest to, ile razy każdy znak pojawił się w ciągu (liczba).

Przykładowa funkcja: -

function countAllCharacters(str) {
  var obj = {};
  if(str.length!==0){
    for(i=0;i<str.length;i++){
      var count = 0;
      for(j=0;j<str.length;j++){
        if(str[i] === str[j]){
          count++;
        }
      }
      if(!obj.hasOwnProperty(str[i])){
        obj[str[i]] = count;
      }
    }
  }
  return obj;
}

0

Uważam, że poniższe rozwiązanie będzie bardzo krótkie, bardzo szybkie, zdolne do pracy z bardzo długimi ciągami, zdolne do obsługi wyszukiwania wielu znaków, odporne na błędy i zdolne do obsługi wyszukiwania pustych ciągów.

function substring_count(source_str, search_str, index) {
    source_str += "", search_str += "";
    var count = -1, index_inc = Math.max(search_str.length, 1);
    index = (+index || 0) - index_inc;
    do {
        ++count;
        index = source_str.indexOf(search_str, index + index_inc);
    } while (~index);
    return count;
}

Przykładowe użycie:

console.log(substring_count("Lorem ipsum dolar un sit amet.", "m "))

function substring_count(source_str, search_str, index) {
    source_str += "", search_str += "";
    var count = -1, index_inc = Math.max(search_str.length, 1);
    index = (+index || 0) - index_inc;
    do {
        ++count;
        index = source_str.indexOf(search_str, index + index_inc);
    } while (~index);
    return count;
}

Powyższy kod naprawia główny błąd wydajności w Jakubie Wawszczyku, że kod szuka dopasowania nawet po tym, jak indexOf mówi, że nie ma, a jego wersja nie działa, ponieważ zapomniał podać parametry wejściowe funkcji.


0
var a = "acvbasbb";
var b= {};
for (let i=0;i<a.length;i++){
    if((a.match(new RegExp(a[i], "g"))).length > 1){
        b[a[i]]=(a.match(new RegExp(a[i], "g"))).length;
    }
}
console.log(b);

W javascript możesz użyć powyższego kodu, aby uzyskać wystąpienie znaku w ciągu.


0

Moje rozwiązanie z ramda js:

const testString = 'somestringtotest'

const countLetters = R.compose(
  R.map(R.length),
  R.groupBy(R.identity),
  R.split('')
)

countLetters(testString)

Link do REPL.


0

Funkcja przyjmuje ciąg znaków jako parametr i liczy wystąpienie każdego unikalnego znaku w ciągu. Wynik występuje w parze klucz-wartość dla każdego znaku.

var charFoundMap = {};//object defined
    for (var i = 0; i < str.length; i++) {

       if(!charFoundMap[ str[i] ])  {
        charFoundMap[ str[i] ]=1;
       } 
       else
       charFoundMap[ str[i] ] +=1;
       //if object does not contain this 
    }
    return charFoundMap;

} 

Zapomniałeś drugiej części pytania: „Muszę również potwierdzić, że każdy z łańcuchów, tj. Str1 lub str2 lub str3 lub str4, nie powinien przekraczać, powiedzmy, 15 znaków.”
Maxime Launois,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.