Как подсчитать количество вхождений символа в строке?


547

У меня есть строка

a.b.c.d

Я хочу посчитать вхождения '.' идиоматическим образом, предпочтительно однострочник.

(Ранее я выражал это ограничение как «без цикла», на случай, если вам интересно, почему все пытаются ответить без использования цикла).


1
Домашнее задание? Потому что иначе я не вижу требования избегать петли.
— PhiLho

22
Не против цикла, а в поисках идиоматической однострочной.
— Барт

2
Циклы были сделаны для такой проблемы, напишите цикл в обычном классе Utility, а затем вызовите только что созданный вами лайнер.
— Че Джавара

Аналогичный вопрос для строк: stackoverflow.com/questions/767759/…
— koppor

Просто хочу отметить - я ценю поиск однострочников, это весело и (как настоящее преимущество) часто легко запомнить, но я хотел бы отметить, что отдельный метод и цикл лучше почти во всех отношениях - удобочитаемость и даже производительность. Большинство приведенных ниже «элегантных» решений не будут работать очень хорошо, поскольку они включают преобразование строк / копирование памяти, тогда как цикл, который просто сканирует строку и подсчитывает вхождения, будет быстрым и простым. Не то чтобы производительность обычно должна быть фактором, но не смотрите на одну строку в цикле и не предполагайте, что она будет работать лучше.
— Билл К

Ответы:


722

Моя «идиоматическая строчка» для этого:

int count = StringUtils.countMatches("a.b.c.d", ".");

Зачем писать самому, когда он уже в общем языке ?

Единственный подход Spring Framework для этого:

int occurance = StringUtils.countOccurrencesOf("a.b.c.d", ".");

44
Эквивалент гуавы : int count = CharMatcher.is('.').countIn("a.b.c.d");... Как ответил догбейн в дублирующем вопросе.
— Джоник

25
Хотя я не буду понижать это, это (а) требует сторонних библиотек и (б) дорого.
— Джавадба

Эта работа только с пружинной рамой должна импортироваться.
— Исуру Мадусанка,

1
если кому-то это нужно: grepcode.com/file/repo1.maven.org/maven2/commons-lang/…
— cV2

19
В каждой компании, в которой я работал, было дорого иметь много плохо написанных и плохо обслуживаемых классов "* Utils". Часть вашей работы - знать, что доступно в Apache Commons.
— AbuNassar

1016

Как насчет этого. Он не использует regexp, поэтому должен быть быстрее, чем некоторые другие решения, и не будет использовать цикл.

int count = line.length() - line.replace(".", "").length();

122
Самый простой способ. Умный И это работает на Android, где нет класса
— StringUtils

43
Это лучший ответ. Лучше всего потому, что вам не нужно импортировать другую библиотеку.
— Алекс Спенсер

27
Очень практично, но ужасно чертовски. Я не рекомендую это, поскольку это приводит к запутанному коду.
— Даниэль Сан

32
Уродливый код можно свести к минимуму, сделав его методом в своем собственном классе «StringUtils». Тогда уродливый код находится точно в одном месте, и везде хорошо читается.
— RonR

30
Метод цикла намного быстрее, чем этот. Особенно, когда нужно посчитать символ вместо String (поскольку нет метода String.replace (char, char)). На 15-символьной строке я получаю разницу 6049 нс против 26 739 нс (в среднем по 100 бегам). Необработанные цифры - огромная разница, но с точки зрения восприятия ... они складываются. Избегайте выделения памяти - используйте цикл!
— Бен

282

Резюмируйте другой ответ, и я знаю все способы сделать это с помощью одной строки:

   String testString = "a.b.c.d";

1) Использование Apache Commons

int apache = StringUtils.countMatches(testString, ".");
System.out.println("apache = " + apache);

2) Использование Spring Framework

int spring = org.springframework.util.StringUtils.countOccurrencesOf(testString, ".");
System.out.println("spring = " + spring);

3) Использование замены

int replace = testString.length() - testString.replace(".", "").length();
System.out.println("replace = " + replace);

4) Использование replaceAll (случай 1)

int replaceAll = testString.replaceAll("[^.]", "").length();
System.out.println("replaceAll = " + replaceAll);

5) Использование replaceAll (случай 2)

int replaceAllCase2 = testString.length() - testString.replaceAll("\\.", "").length();
System.out.println("replaceAll (second case) = " + replaceAllCase2);

6) Использование сплита

int split = testString.split("\\.",-1).length-1;
System.out.println("split = " + split);

7) Использование Java8 (случай 1)

long java8 = testString.chars().filter(ch -> ch =='.').count();
System.out.println("java8 = " + java8);

8) Использование Java8 (случай 2), может быть лучше для юникода, чем случай 1

long java8Case2 = testString.codePoints().filter(ch -> ch =='.').count();
System.out.println("java8 (second case) = " + java8Case2);

9) Использование StringTokenizer

int stringTokenizer = new StringTokenizer(" " +testString + " ", ".").countTokens()-1;
System.out.println("stringTokenizer = " + stringTokenizer);

Из комментария : Будьте осторожны с StringTokenizer, для abcd он будет работать, но для a ... bc ... d или ... abcd или a .... b ...... c ..... d ... или т. д. это не будет работать. Это просто будет иметь значение. между персонажами только один раз

Больше информации в github

Тест производительности (с использованием JMH , mode = AverageTime, тогда оценка 0.010выше 0.351):

Benchmark              Mode  Cnt  Score    Error  Units
1. countMatches        avgt    5  0.010 ±  0.001  us/op
2. countOccurrencesOf  avgt    5  0.010 ±  0.001  us/op
3. stringTokenizer     avgt    5  0.028 ±  0.002  us/op
4. java8_1             avgt    5  0.077 ±  0.005  us/op
5. java8_2             avgt    5  0.078 ±  0.003  us/op
6. split               avgt    5  0.137 ±  0.009  us/op
7. replaceAll_2        avgt    5  0.302 ±  0.047  us/op
8. replace             avgt    5  0.303 ±  0.034  us/op
9. replaceAll_1        avgt    5  0.351 ±  0.045  us/op

Печатные строки не совпадают с приведенными выше, и порядок сначала самый быстрый, что затрудняет поиск по крайней мере. Хороший ответ в любом случае!
— Maarten Bodewes

случай 2, обобщенный для кодовых точек, которым требуется более одной кодовой единицы UTF-16:"1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
— Том Блоджет

174

Рано или поздно что-то должно зацикливаться. Вам гораздо проще написать (очень простой) цикл, чем использовать что-то подобное, splitчто намного мощнее, чем вам нужно.

Обязательно инкапсулируйте цикл в отдельный метод, например

public static int countOccurrences(String haystack, char needle)
{
    int count = 0;
    for (int i=0; i < haystack.length(); i++)
    {
        if (haystack.charAt(i) == needle)
        {
             count++;
        }
    }
    return count;
}

Тогда вам не нужно иметь цикл в вашем основном коде - но цикл должен быть где-то там.


5
для (int i = 0, l = haystack.length (); i <l; i ++) будьте добры к вашему стеку
— Крис

12
(Я даже не уверен, откуда взялся бит «стека» комментария. Не похоже, чтобы этот ответ был моим рекурсивным, что действительно неприятно для стека.)
— Джон Скит

2
не только это, но, возможно, это антиоптимизация, не смотря на то, что делает jit. Например, если вы сделали вышеупомянутое с массивом for loop, вы могли бы ухудшить ситуацию.
— ShuggyCoUk

4
@sulai: беспокойство Криса безосновательно, IMO, перед тривиальной оптимизацией JIT. Есть ли какая-то причина, по которой этот комментарий привлек ваше внимание на данный момент, спустя три года? Просто интересно.
— Джон Скит

1
Вероятно, @sulai только что натолкнулся на вопрос, как и я (задаваясь вопросом, есть ли в Java встроенный метод для этого), и не заметил даты. Однако мне любопытно, как перемещение length()вызова за пределы цикла может ухудшить производительность , как упомянуто @ShuggyCoUk несколькими комментариями.
— JKillian

63

У меня была идея, похожая на Младена, но наоборот ...

String s = "a.b.c.d";
int charCount = s.replaceAll("[^.]", "").length();
println(charCount);

Правильный. ReplaceAll (".") Заменит любой символ, а не только точку. ReplaceAll ("\\.") Работало бы. Ваше решение более простое.
— VonC

jjnguy фактически предложил сначала заменить replaceAll ("[^.]"), увидев мое решение "abcd" .split ("\\."). length-1. Но после того, как меня ударили 5 раз, я удалил свой ответ (и его комментарий).
— VonC

«... теперь у вас две проблемы» (обязательно.) В любом случае, я бы поспорил, что в replaceAll()и length(). выполняются десятки циклов . Ну, если его не видно, его не существует; o)
— Писквор покинул здание

2
Я не думаю, что это хорошая идея, чтобы использовать регулярные выражения и создать новую строку для подсчета. я бы просто создал статический метод, который зацикливал бы каждый символ в строке для подсчета числа.
— Минфай

1
@mingfai: действительно, но первоначальный вопрос касается создания однострочного и даже без цикла (вы можете сделать цикл в одну строку, но это будет ужасно!). Вопрос вопрос, а не ответ ... :-)
— PhiLho

37
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

ReplaceAll (".") Заменит все символы.

Решение PhiLho использует ReplaceAll ("[^.]", ""), Который не нужно экранировать, поскольку [.] Представляет символ «точка», а не «любой символ».


Мне нравится этот. Конечно, там все еще есть петля, как и должно быть.
— Архетип Павел

Обратите внимание, что вам нужно разделить это число, если вы хотите найти подстроки длиной> 1
— rogerdpack

30

Мое «идиоматическое однострочное» решение:

int count = "a.b.c.d".length() - "a.b.c.d".replace(".", "").length();

Понятия не имею, почему принято решение, использующее StringUtils.


4
В этом посте есть более старое решение, подобное этому.
— JCalcines

7
Потому что это решение действительно неэффективно
— András

Это создает дополнительную строку только для подсчета. Не знаю, почему кто-то предпочел бы это, а не StringUtils, если StringUtils является опцией. Если это не вариант, они должны просто создать простой цикл for в служебном классе.
— раздавить

28
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();

1
Голосуйте + за нативное решение.
— Scadge

24

Более короткий пример

String text = "a.b.c.d";
int count = text.split("\\.",-1).length-1;

3
Похоже, что этот заголовок имеет относительно большие накладные расходы, поэтому имейте в виду, что он может создавать множество маленьких строк. Обычно это не имеет большого значения, но используйте с осторожностью.
— Maarten Bodewes

19

Вот решение без цикла:

public static int countOccurrences(String haystack, char needle, int i){
    return ((i=haystack.indexOf(needle, i)) == -1)?0:1+countOccurrences(haystack, needle, i+1);}


System.out.println("num of dots is "+countOccurrences("a.b.c.d",'.',0));

ну, есть петля, но она невидима :-)

- Йонатан


2
Если ваша строка не такая длинная, вы получите ошибку OutOfMemoryError.
— Спенсер Кормос

Проблема звучит достаточно искусно, чтобы быть домашней работой, и если да, то эта рекурсия, вероятно, является ответом, который вас просят найти.
— Эриксон

Это использует indexOf, который будет зацикливаться ... но хорошая идея. Публикация действительно «просто рекурсивного» решения за минуту ...
— Джон Скит

Если у вас будет больше вхождений, чем у вас в доступных слотах стека, у вас будет исключение переполнения стека;)
— Luca C.

15

Мне не нравится идея выделения новой строки для этой цели. И так как строка уже имеет массив char сзади, где она хранит свое значение, String.charAt () практически бесплатна.

for(int i=0;i<s.length();num+=(s.charAt(i++)==delim?1:0))

делает трюк, без дополнительных выделений, которые требуют сбора, в 1 строку или меньше, только с J2SE.


Дарим немного любви этому, потому что он единственный делает один проход через строку. Я забочусь о производительности.
— Джавадба

1
charAtперебирает 16-битные кодовые точки, а не символы! А charв Java это не символ. Таким образом, этот ответ подразумевает, что не должно быть символа Unicode с высоким суррогатом, равным точке кода delim. Я не уверен, правильно ли это для точки, но в целом это может быть не правильно.
— выступление

14

Хорошо, вдохновленный решением Йонатана, вот тот, который является чисто рекурсивным - используются только библиотечные методы, length()и charAt()ни один из которых не выполняет циклов:

public static int countOccurrences(String haystack, char needle)
{
    return countOccurrences(haystack, needle, 0);
}

private static int countOccurrences(String haystack, char needle, int index)
{
    if (index >= haystack.length())
    {
        return 0;
    }

    int contribution = haystack.charAt(index) == needle ? 1 : 0;
    return contribution + countOccurrences(haystack, needle, index+1);
}

То, считается ли рекурсия зацикливанием, зависит от того, какое именно определение вы используете, но это, вероятно, так близко, как вы получите.

Я не знаю, выполняет ли большинство JVM хвостовую рекурсию в наши дни ... если нет, то вы получите одноименное переполнение стека для подходящих длинных строк, конечно.


Нет, хвостовая рекурсия, вероятно, будет в Java 7, но она еще не получила широкого распространения. Эту простую прямую хвостовую рекурсию можно было бы преобразовать в цикл во время компиляции, но на самом деле Java 7 встроен в JVM для обработки цепочки с помощью различных методов.
— Эриксон

3
Вы бы с большей вероятностью получили бы хвостовую рекурсию, если бы ваш метод возвращал вызов самому себе (включая параметр промежуточного итога), а не возвращал результат выполнения сложения.
— Стивен Денн

12

Вдохновленный Джоном Скитом, не петлевой версией, которая не подорвет ваш стек. Также полезная отправная точка, если вы хотите использовать инфраструктуру fork-join.

public static int countOccurrences(CharSequeunce haystack, char needle) {
    return countOccurrences(haystack, needle, 0, haystack.length);
}

// Alternatively String.substring/subsequence use to be relatively efficient
//   on most Java library implementations, but isn't any more [2013].
private static int countOccurrences(
    CharSequence haystack, char needle, int start, int end
) {
    if (start == end) {
        return 0;
    } else if (start+1 == end) {
        return haystack.charAt(start) == needle ? 1 : 0;
    } else {
        int mid = (end+start)>>>1; // Watch for integer overflow...
        return
            countOccurrences(haystack, needle, start, mid) +
            countOccurrences(haystack, needle, mid, end);
    }
}

(Отказ от ответственности: не проверено, не скомпилировано, не имеет смысла.)

Возможно, лучший (однопоточный, без поддержки суррогатных пар) способ написать это:

public static int countOccurrences(String haystack, char needle) {
    int count = 0;
    for (char c : haystack.toCharArray()) {
        if (c == needle) {
           ++count;
        }
    }
    return count;
}

11

Не уверен насчет эффективности этого, но это самый короткий код, который я мог бы написать без привлечения сторонних библиотек:

public static int numberOf(String target, String content)
{
    return (content.split(target).length - 1);
}

4
Чтобы рассчитывать вхождения в конце строки вам придется вызвать раскол с отрицательным пределом аргументом , как это: return (content.split(target, -1).length - 1);. По умолчанию вхождения в конце строки опускаются в массиве, являющемся результатом split (). Смотрите Доку
— Vlz

10

С Вы также можете использовать потоки для достижения этой цели. Очевидно, что есть итерация за кулисами, но вам не нужно писать это явно!

public static long countOccurences(String s, char c){
    return s.chars().filter(ch -> ch == c).count();
}

countOccurences("a.b.c.d", '.'); //3
countOccurences("hello world", 'l'); //3

Использование .codePoints()вместо .chars()этого поддержало бы любое значение Юникода (включая те, которые требуют суррогатных пар)
— Люк Ашервуд

10

Также возможно использовать Reduce в Java 8 для решения этой проблемы:

int res = "abdsd3$asda$asasdd$sadas".chars().reduce(0, (a, c) -> a + (c == '$' ? 1 : 0));
System.out.println(res);

Вывод:

3

8

Полный образец:

public class CharacterCounter
{

  public static int countOccurrences(String find, String string)
  {
    int count = 0;
    int indexOf = 0;

    while (indexOf > -1)
    {
      indexOf = string.indexOf(find, indexOf + 1);
      if (indexOf > -1)
        count++;
    }

    return count;
  }
}

Вызов:

int occurrences = CharacterCounter.countOccurrences("l", "Hello World.");
System.out.println(occurrences); // 3

неправильный код не работает, когда я пытаюсь int вхождения = CharacterCounter.countOccurferences ("1", "101"); System.out.println (вхождения); // 1
— Джаеш

Я фиксирую исправление для кода, который работает с той же логикой
— MaanooAk

8

Самый простой способ получить ответ заключается в следующем:

public static void main(String[] args) {
    String string = "a.b.c.d";
    String []splitArray = string.split("\\.",-1);
    System.out.println("No of . chars is : " + (splitArray.length-1));
}

2
Этот фрагмент не возвращает правильное количество точек для заданного ввода «abc»
— dekaru

@dekaru Не могли бы вы вставить свой комментарий в комментарий, чтобы мы могли посмотреть.
— Амар Магар

5

Если вы используете Spring Framework, вы также можете использовать класс "StringUtils". Метод будет "countOccurferencesOf".


5

Вы можете использовать split()функцию только в одной строке кода

int noOccurence=string.split("#",-1).length-1;

Split действительно создает массив строк, который занимает много времени.
— Палек

Вы правы, это настоящая проблема. Другим способом это позволяет избежать добавления сторонней библиотеки в ваш проект (если это еще не сделано). Это зависит от того, что вы хотите сделать, и какова ожидаемая производительность.
— Бендж

3
Это решение НЕ будет включать в себя завершающие пустые попадания, поскольку limitв этом перегруженном вызове метода split аргумент устанавливается равным нулю. Пример: "1##2#3#####".split("#")выдаст только массив размером 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) вместо размера 9 ( [0:"1"; 1:""; 2:"2"; 3:"3"; 4:""; 5:""; 6:""; 7:""; 8:""]).
— Клар

4
public static int countOccurrences(String container, String content){
    int lastIndex, currIndex = 0, occurrences = 0;
    while(true) {
        lastIndex = container.indexOf(content, currIndex);
        if(lastIndex == -1) {
            break;
        }
        currIndex = lastIndex + content.length();
        occurrences++;
    }
    return occurrences;
}

4
import java.util.Scanner;

class apples {

    public static void main(String args[]) {    
        Scanner bucky = new Scanner(System.in);
        String hello = bucky.nextLine();
        int charCount = hello.length() - hello.replaceAll("e", "").length();
        System.out.println(charCount);
    }
}//      COUNTS NUMBER OF "e" CHAR´s within any string input

3

Хотя методы могут это скрыть, нет способа считать без цикла (или рекурсии). Вы хотите использовать char [] из соображений производительности.

public static int count( final String s, final char c ) {
  final char[] chars = s.toCharArray();
  int count = 0;
  for(int i=0; i<chars.length; i++) {
    if (chars[i] == c) {
      count++;
    }
  }
  return count;
}

Использование replaceAll (то есть RE) не похоже на лучший путь.


Я думаю, что это самое элегантное решение. Почему вы использовали toCharArray, а не charAt напрямую?
— Панайотис

Цикл с charAt, по крайней мере, раньше был медленнее. Может зависеть и от платформы. Единственный способ действительно узнать это - измерить разницу.
— tcurdt

3

Ну, с довольно похожей задачей я наткнулся на эту тему. Я не видел никаких ограничений в языке программирования, и поскольку groovy работает на Java vm: Вот как я смог решить мою проблему с помощью Groovy.

"a.b.c.".count(".")

сделанный.


3

Гораздо более простым решением было бы просто разбить строку на основе символа, с которым вы ее сопоставляете.

Например,

int getOccurences(String characters, String string) { String[] words = string.split(characters); return words.length - 1; }

Это вернет 4 в случае: getOccurences("o", "something about a quick brown fox");


Проблема здесь в том, что массив должен быть размещен, что очень медленно.
— Палек

2

Где-то в коде что-то должно зацикливаться. Единственным выходом из этого является полное развертывание цикла:

int numDots = 0;
if (s.charAt(0) == '.') {
    numDots++;
}

if (s.charAt(1) == '.') {
    numDots++;
}


if (s.charAt(2) == '.') {
    numDots++;
}

... и т. д., но тогда вы выполняете цикл вручную в редакторе исходного кода, а не на компьютере, который будет его запускать. Смотрите псевдокод:

create a project
position = 0
while (not end of string) {
    write check for character at position "position" (see above)
}
write code to output variable "numDots"
compile program
hand in homework
do not think of the loop that your "if"s may have been optimized and compiled to

2

Вот немного другое решение рекурсии стиля:

public static int countOccurrences(String haystack, char needle)
{
    return countOccurrences(haystack, needle, 0);
}

private static int countOccurrences(String haystack, char needle, int accumulator)
{
    if (haystack.length() == 0) return accumulator;
    return countOccurrences(haystack.substring(1), needle, haystack.charAt(0) == needle ? accumulator + 1 : accumulator);
}

2

Почему бы просто не разделить символ и затем получить длину полученного массива. длина массива всегда будет равна числу экземпляров + 1. Верно?


2

Следующий исходный код даст вам отсутствие вхождений данной строки в слове, введенном пользователем: -

import java.util.Scanner;

public class CountingOccurences {

    public static void main(String[] args) {

        Scanner inp= new Scanner(System.in);
        String str;
        char ch;
        int count=0;

        System.out.println("Enter the string:");
        str=inp.nextLine();

        while(str.length()>0)
        {
            ch=str.charAt(0);
            int i=0;

            while(str.charAt(i)==ch)
            {
                count =count+i;
                i++;
            }

            str.substring(count);
            System.out.println(ch);
            System.out.println(count);
        }

    }
}

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.