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

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);
PhiLho
источник
Правильный. 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.

mlchen850622
источник
4
В этом посте есть более старое решение, подобное этому.
JCalcines
7
Потому что это решение действительно неэффективно
András
Это создает дополнительную строку только для подсчета. Не знаю, почему кто-то предпочел бы это, а не StringUtils, если StringUtils является опцией. Если это не вариант, они должны просто создать простой цикл for в служебном классе.
раздавить
28
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();
Fubo
источник
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.

0xCAFEBABE
источник
Дарим немного любви этому, потому что он единственный делает один проход через строку. Я забочусь о производительности.
Джавадба
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;
}
Том Хотин - Tackline
источник
11

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

public static int numberOf(String target, String content)
{
    return (content.split(target).length - 1);
}
KannedFarU
источник
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
gil.fernandes
источник
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".

user496208
источник
5

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

int noOccurence=string.split("#",-1).length-1;
user3322553
источник
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) не похоже на лучший путь.

tcurdt
источник
Я думаю, что это самое элегантное решение. Почему вы использовали 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");

Saharcasm
источник
Проблема здесь в том, что массив должен быть размещен, что очень медленно.
Палек
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);
        }

    }
}
Shubham
источник
2
int count = (line.length() - line.replace("str", "").length())/"str".length();
Шабан
источник