如何计算字符串中char出现的次数?


547

我有琴弦

a.b.c.d

我想计算“。”的出现。以惯用方式,最好是单线。

(以前,我曾将此约束表示为“无循环”,以防您想知道为什么每个人都试图不使用循环而回答)。


1
家庭作业?因为否则我看不到避免循环的要求。
PhiLho

22
不喜欢循环,而是寻找惯用的单线。
巴特

2
针对这样的问题制作了循环,请在通用的Utility类中编写循环,然后调用您刚铸造的一个衬套。
che javara


只是要指出-我很高兴找到一个内衬,这很有趣,并且(作为一个真正的优势)通常很容易记住,但是我想指出一个单独的方法和一个循环在几乎所有方面都更好-可读性甚至性能。下面的大多数“优雅”解决方案都无法很好地执行,因为它们涉及到重新格式化字符串/复制内存,而仅扫描字符串并计算出现次数的循环将是快速而简单的。并不是说性能通常应该成为一个因素,而是不要看循环中的单行,并认为它会更好。
比尔K'5

Answers:


722

我对此的“惯用一语”是:

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

当它已经成为公共语言时,为什么还要自己编写呢?

Spring Framework的oneliner为此:

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

44
番石榴等效品:int count = CharMatcher.is('.').countIn("a.b.c.d");... 由狗狗在重复的问题中回答。
Jonik 2013年

25
尽管我不会对此表示反对,但这是(a)需要第三方库和(b)昂贵的。
javadba 2014年

这仅与弹簧框架一起使用时必须导入。
Isuru Madusanka 2015年


19
在我工作过的每家公司中,昂贵的是,有很多写得不好并且维护不好的“ * Utils”类。您的一部分工作是了解Apache Commons中可用的功能。
阿布纳萨尔(AbuNassar)2016年

1016

这个怎么样。它在下面不使用正则表达式,因此应该比其他一些解决方案更快,并且不会使用循环。

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

122
最简单的方法。聪明的一个。它可以在没有StringUtils类的Android上运行
Jose_GD 2012年

43
这是最好的答案。最好的原因是因为您不必导入其他库。
Alex Spencer

27
非常实用,但很难看。我不建议这样做,因为它会导致代码混乱。
Daniel San

32
丑陋的代码可以通过使其成为您自己的“ StringUtils”类中的方法来最小化。然后,丑陋的代码恰好位于一个位置,其他所有地方都很好读。
RonR 2014年

30
循环方法是很多比这更快。特别是当要计算一个char而不是一个String时(因为没有String.replace(char,char)方法)。在15个字符串上,我得到6049 ns与26,739 ns的差(平均超过100游程)。原始数字相差很大,但是明智的选择……加起来。避免分配内存-使用循环!
2014年

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),对于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它将起作用,但对于a ... 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()
Tom Blodget '18年

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 ++)对您的堆栈很友善
Chris

12
(我甚至不知道从哪里注释的“堆栈”位来自这不像。这个答案是我的递归之一,这的确是讨厌到堆栈。)
乔恩斯基特

2
不仅如此,而且这可能是一个反优化,而无需了解jit的功能。例如,如果对循环数组执行上述操作,则可能会使情况更糟。
ShuggyCoUk,2009年

4
@sulai:面对微不足道的 JIT优化,克里斯对IMO的关注毫无根据。三年后,此刻有什么理由引起您的关注?只是感兴趣而已。
乔恩·斯基特

1
大概@sulai就像我一样遇到了问题(同时想知道Java是否为此提供了内置方法)并且没有注意到日期。但是,我很好奇如何将length()调用移动到循环外会使性能变差,正如@ShuggyCoUk提到的一些评论。
JKillian 2014年

63

我有一个类似于姆拉登的主意,但是相反。

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

正确。ReplaceAll(“。”)将替换任何字符,而不仅仅是点。ReplaceAll(“ \\。”)会起作用。您的解决方案更加简单。
VonC

在看到我的“ abcd” .split(“ \\。”)。length-1解决方案时,jjnguy实际上首先建议了replaceAll(“ [^。]”)。但是被打了5次后,我删除了我的答案(和他的评论)。
VonC

“ ...现在您有两个问题”(必须)。无论如何,我敢打赌,replaceAll()and中执行了数十个循环length()。好吧,如果它不可见,那么它就不存在; o)
Piskvor于

2
我认为使用正则表达式并创建新的字符串进行计数不是一个好主意。我只是创建一个静态方法来循环字符串中的每个字符以计算数字。
mingfai 2011年

1
@mingfai:的确如此,但是最初的问题是关于制作单线,甚至没有循环(您可以在一行中循环,但这会很丑!)。质疑问题,而不是答案... :-)
PhiLho 2011年

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

ReplaceAll(“。”)将替换所有字符。

PhiLho的解决方案使用ReplaceAll(“ [^。]”,“”),由于[。]代表字符“点”,而不是“任何字符”,因此不需要转义。


我喜欢这一个。当然,仍然必须存在一个循环。
原型保罗,

注意,如果要查找长度大于1的子字符串,则需要除以该数字
rogerdpack '04

30

我的“惯用一线”解决方案:

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

不知道为什么接受使用StringUtils的解决方案。


4
这篇文章中有一个与此类似的旧解决方案。
JCalcines

7
由于该解决方案是非常低效
安德拉斯

这将创建一个额外的字符串,仅用于产生计数。不知道如果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
这似乎有一个相对较大的开销,请注意可能会创建很多小的字符串。通常,不要紧,但要小心使用。
马丁·波德维斯

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。
Spencer Kormos

这个问题听起来很人性化,无法做功课,如果是这样,那么递归可能就是您要找到的答案。
erickson

那使用indexOf,它将循环...但是一个好主意。一分钟内发布真正的“公正递归”解决方案……
乔恩·斯基特

如果出现更多可用堆栈插槽的情况,则将出现堆栈溢出异常;)
Luca

15

我不喜欢为此目的分配新字符串的想法。并且由于该字符串在其存储值的后面已经有一个char数组,因此String.charAt()实际上是免费的。

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

仅需J2SE,就可以在1行以内或更少的时间内完成需要的操作,而无需进行需要收集的其他分配。


给它一个爱,因为它是唯一在字符串上传递一次的人。我确实很在意性能。
javadba 2014年

1
charAt遍历16位代码点而不是字符!charJava中的A 不是字符。因此,此答案表示必须没有Unicode代码等于的代码点的Unicode符号delim。我不确定该点是否正确,但总的来说可能不正确。
2014年

14

好的,受Yonatan解决方案的启发,这是一个纯粹的递归方法-唯一使用的库方法是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
如果您的方法返回了对自身的调用(包括正在运行的total参数),而不是返回执行加法的结果,则更有可能获得尾递归。
Stephen Denne

12

受到Jon Skeet的启发,这是一个非循环版本,不会破坏您的堆栈。如果要使用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
要还计算字符串末尾的出现次数,您必须使用负限制参数调用split,如下所示:return (content.split(target, -1).length - 1);。默认情况下,由split()导致的字符串末尾在Array中的出现被忽略。见数独
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

然后,使用using .codePoints()代替.chars()将支持任何Unicode值(包括那些需要代理对的值)
Luke Usherwood 2014年

10

也可以在Java 8中使用reduce来解决此问题:

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.countOccurrences(“ 1”,“ 101”); System.out.println(出现); // 1
jayesh

我提交了适用于相同逻辑的代码的修复程序
MaanooAk '17

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能否将您的ing钉粘贴在评论中,以便我们看看。
阿玛玛格

5

如果您使用的是Spring框架,则还可以使用“ 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:""])的数组。
克拉

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?
Panayotis

使用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");


这里的问题是必须分配一个数组,这非常慢。
Palec

2

代码中的某些地方必须循环。解决此问题的唯一方法是完全展开循环:

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

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


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

... etc,但是您是在源代码编辑器中手动执行循环的人-而不是将运行该循环的计算机。参见伪代码:

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

以下源代码将给您用户输入的单词中出现给定字符串的次数:-

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

    }
}

2
int count = (line.length() - line.replace("str", "").length())/"str".length();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.