Як я можу підрахувати кількість виникнення знака в рядку?


547

У мене струна

a.b.c.d

Я хочу порахувати події "." ідіоматичним способом, переважно однолінійним.

(Раніше я висловлював це обмеження як "без циклу", на випадок, якщо вам цікаво, чому всі намагаються відповісти, не використовуючи цикл).


1
Домашнє завдання? Тому що в іншому випадку я не бачу вимоги уникати циклу.
— PhiLho

22
Не проти петлі настільки, як шукати ідіоматичну однолінійку.
— Барт

2
Петлі були зроблені для такої проблеми, запишіть цикл у загальний клас утиліти, а потім зателефонуйте вашому свіжовичавленому одному вкладишу.
— че явара

Схожий питання для рядків: 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
Хоча я не буду спростовувати це, це (а) вимагає сторонніх ліб та (б) дорогих.
— javadba

Цю лише роботу з пружинними рамними роботами доводиться імпортувати.
— Isuru Madusanka

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) Використання substituAll (випадок 1)

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

5) Використання substituAll (випадок 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) може бути кращим для unicode, ніж випадок 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 він буде працювати, але для ... bc ... d або ... abcd або a .... b ...... c ..... d ... або т. д. це не спрацює. Це просто буде рахуватися. між персонажами лише один раз

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

Тест на працездатність (використовуючи JMH , режим = 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
for (int i = 0, l = haystack.length (); i <l; i ++) будьте ласкаві до вашої стеки
— Chris

12
(Я навіть не знаю , де «стек» біт коментаря походить від Це не так. Цей відповідь мій рекурсивний один, який насправді огидно стеку.)
— Jon тарілочках

2
не тільки це, але це, можливо, анти-оптимізація, не дивлячись на те, що робить джит. Якщо ви, наприклад, зробили вище для масиву для циклу, ви можете погіршити ситуацію.
— 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 фактично запропонував спершу замінити все ("[^.]"), побачивши моє рішення "abcd" .split ("\\."). length-1. Але після удару 5 разів я видалив свою відповідь (і його коментар).
— VonC

"... тепер у вас є дві проблеми" (обов'язково.) Як би там не було, я б обміняв, що в replaceAll()і length(). виконуються десятки петель . Ну, якщо його не видно, його не існує; o)
— Пісквор покинув будівлю

2
Я не думаю, що це гарна ідея використовувати регулярний вираз і створити нову рядок для підрахунку. Я б просто створив статичний метод, який циклізує кожен символ у рядку для підрахунку числа.
— mingfai

1
@mingfai: справді, але оригінальне питання полягає в тому, щоб зробити однолінійку і навіть без циклу (ви можете зробити цикл в одному рядку, але це буде некрасиво!).
— Задайте

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
Тому що це рішення насправді неефективне
— Андрас

Це створює додатковий рядок просто для отримання рахунку. Не маю ідеї, чому хтось віддасть перевагу цьому над StringUtils, якщо StringUtils є варіантом. Якщо це не варіант, вони повинні просто створити простий цикл для класу утиліти.
— розчав

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

1
Голосуйте + за власне рішення.
— Скад

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.
— Спенсер Кормос

Проблема звучить надумано, щоб зробити домашнє завдання, і якщо так, то ця рекурсія, ймовірно, відповідь, яку ви просите знайти.
— erickson

Для цього використовується indexOf, який буде циклічно ... але хороша ідея. Публікація справді "просто рекурсивного" рішення за хвилину ...
— Джон Скіт

Якщо у вас більше випадків наявних слотів для стеків, у вас буде виняток переповнення стека;)
— Лука C.

15

Мені не подобається ідея виділяти для цього нову рядок. Оскільки рядок уже має масив char іззаду, де він зберігає його значення, String.charAt () практично безкоштовний.

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

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


Виявляючи деяку любов до цього, тому що це єдиний, хто робить один прохід через струну. Я дбаю про продуктивність.
— javadba

1
charAtповторюється через 16 бітових кодових очок, а не символів! A 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 для обробки ланцюжків за допомогою різних методів.
— erickson

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()цього підтримує будь-яке значення Unicode (включаючи ті, що вимагають сурогатних пар)
— Luke Usherwood

10

Для вирішення цієї проблеми також можливе використання скорочення в 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 eventss = CharacterCounter.countOccurrences ("1", "101"); System.out.println (події); // 1
— jayesh


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". Метод буде "countOccurrencesOf".


5

Ви можете використовувати split()функцію лише в одному кодовому рядку

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

Спліт дійсно створює масив рядків, який забирає багато часу.
— Палець

Ти маєш рацію, це справжня турбота. Іншим способом це дозволяє уникнути залучення сторонніх джерел у ваш проект (якщо цього ще не зроблено). Це залежить від того, що ви хочете зробити і яке очікування від ефективності.
— Бендж

3
Це рішення НЕ буде включати проміжні порожні звернення, оскільки аргумент limitвстановлений на нулі в цьому перевантаженому виклику методу розділеного доступу. Приклад: "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:""]).
— klaar

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;
}

Використання substituAll (тобто 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; }

У випадку: 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.