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


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.