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

Posted

技术标签:

【中文标题】如何计算字符串中字符的出现次数?【英文标题】:How do I count the number of occurrences of a char in a String? 【发布时间】:2010-09-21 12:22:02 【问题描述】:

我有字符串

a.b.c.d

我想计算 '.' 的出现次数以惯用的方式,最好是单行。

(之前我将此约束表达为“没有循环”,以防你想知道为什么每个人都试图在不使用循环的情况下回答)。

【问题讨论】:

家庭作业?因为否则我看不到避免循环的要求。 比起寻找惯用的单线,不反对循环。 循环是为这样的问题而设计的,在一个通用的实用程序类中编写循环,然后调用你新创建的一个班轮。 字符串的类似问题:***.com/questions/767759/… 只是指出——我很高兴找到单行,它很有趣并且(作为一个真正的优势)通常很容易记住,但我想指出一个单独的方法和一个循环在几乎所有方面都更好——可读性甚至性能。下面的大多数“优雅”解决方案都不会很好地执行,因为它们涉及重组字符串/复制内存,而仅扫描字符串并计算出现次数的循环会快速而简单。并不是说性能通常应该是一个因素,但不要在循环中查看单行并假设它会表现更好。 【参考方案1】:

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

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

【讨论】:

最简单的方法。聪明的一个。它适用于没有 StringUtils 类的 android 这是最好的答案。它是最好的原因是因为您不必导入另一个库。 非常实用但丑得要命。我不推荐它,因为它会导致代码混乱。 丑陋的代码可以通过在你自己的“StringUtils”类中作为一个方法来最小化。那么丑陋的代码就在一个地方,其他地方都可读性很好。 循环方法比这快很多。特别是当想要计算一个字符而不是一个字符串时(因为没有 String.replace(char, char) 方法)。在 15 个字符的字符串上,我得到 6049 ns 与 26,739 ns 的差异(平均超过 100 次运行)。原始数字差异很大,但从百分比来看……它加起来了。避免内存分配 - 使用循环!【参考方案2】:

我的“惯用单行”是:

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

既然已经在commons lang,为什么还要自己写呢?

Spring 框架的单线器是:

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

【讨论】:

Guava 等价物:int count = CharMatcher.is('.').countIn("a.b.c.d"); ...作为重复问题中的answered by dogbane。 虽然我不会对此投反对票,但它是 (a) 需要 3rd 方库和 (b) 昂贵。 这只适用于弹簧框架必须导入。 如果有人需要:grepcode.com/file/repo1.maven.org/maven2/commons-lang/… 在我工作过的每家公司中,代价高昂的是编写和维护不善的“*Utils”类。您的部分工作是了解 Apache Commons 中可用的内容。【参考方案3】:

总结其他答案以及我所知道的使用单行符的所有方法:

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

1) 使用 Apache Commons

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

2) 使用 Spring 框架的

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

Perfomance test(使用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

【讨论】:

打印的字符串与上面的不匹配,并且顺序是最快的,这至少使查找变得棘手。否则很好的答案! 案例 2,适用于需要多个 UTF-16 代码单元的代码点:"1?2?3 has 2".codePoints().filter((c) -> c == "?".codePointAt(0)).count() Apache Commons's StringUtils.countMatches 循环遍历索引并使用 charAt; Spring Framework's StringUtils.countOccurencesOf 反复使用 indexOf。 (OpenJDK's String.indexOf 基本上在循环中使用charAt。)【参考方案4】:

迟早,某事必须循环。编写(非常简单的)循环比使用像 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;

那么你不需要在你的主代码中有循环 - 但循环必须在某个地方。

【讨论】:

for (int i=0,l=haystack.length(); i (我什至不确定评论的“堆栈”位来自哪里。这不像 this 答案是我的递归答案,这确实令人讨厌堆栈。) 不仅如此,这可能是一种反优化,而无需查看 jit 的作用。例如,如果您在数组 for 循环上执行上述操作,您可能会使事情变得更糟。 @sulai:Chris 的担忧是毫无根据的,IMO,面对微不足道的 JIT 优化。三年多之后,这个评论现在引起你的注意有什么原因吗?只是感兴趣。 可能@sulai 只是像我一样遇到了这个问题(同时想知道 Java 是否为此提供了内置方法)并且没有注意到日期。但是,我很好奇将length() 调用移到循环之外会如何使性能更差,正如@ShuggyCoUk 提到的那样,提高了几个cmets。【参考方案5】:

我有一个和姆拉登类似的想法,但恰恰相反……

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

【讨论】:

正确。 ReplaceAll(".") 将替换任何字符,而不仅仅是点。 ReplaceAll("\\.") 会起作用的。您的解决方案更直接。 jjnguy 在看到我的 "a.b.c.d".split("\\.").length-1 解决方案后实际上首先建议了 replaceAll("[^.]") 。但在被击中 5 次后,我删除了我的答案(和他的评论)。 "...现在你有两个问题"(必须的。)无论如何,我敢打赌在 replaceAll()length() 中执行了数十个循环。好吧,如果它不可见,它就不存在;o) 我认为使用正则表达式并为计数创建一个新字符串不是一个好主意。我会创建一个静态方法,循环字符串中的每个字符来计算数字。 @mingfai:确实,但最初的问题是关于制作单行,甚至没有循环(你可以在一行中做一个循环,但这会很丑!)。质疑问题,而不是答案... :-)【参考方案6】:
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

ReplaceAll(".") 将替换所有字符。

PhiLho's solution 使用 ReplaceAll("[^.]",""),不需要转义,因为 [.] 表示字符“点”,而不是“任何字符”。

【讨论】:

我喜欢这个。当然,那里仍然有一个循环,因为必须有。 请注意,如果您想查找长度 > 1 的子字符串,您需要将此数字相除【参考方案7】:

我的“惯用单行”解决方案:

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

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

【讨论】:

这篇文章中有一个类似的旧解决方案。 因为这个方案实在是太低效了 这会创建一个额外的字符串来产生计数。如果 StringUtils 是一个选项,不知道为什么有人会喜欢它而不是 StringUtils。如果这不是一个选项,他们应该只在实用程序类中创建一个简单的 for 循环。【参考方案8】:
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();

【讨论】:

投票 + 支持原生解决方案。【参考方案9】:

一个简短的例子是

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

【讨论】:

这似乎有一个比较大的开销,请注意它可能会创建很多小字符串。通常这并不重要,但要小心使用。【参考方案10】:

这是一个没有循环的解决方案:

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

嗯,有一个循环,但它是不可见 :-)

-- 约纳坦

【讨论】:

除非你的字符串太长你得到一个 OutOfMemoryError。 这个问题听起来很做作,足以成为家庭作业,如果是这样,这个递归可能就是你被要求找到的答案。 这使用 indexOf,它将循环......但一个好主意。在一分钟内发布一个真正“只是递归”的解决方案...... 如果出现的次数超过了可用的堆栈槽位,则会出现堆栈溢出异常;) 循环并不危险。不受控制的深度递归真的很危险。【参考方案11】:

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

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

只用 J2SE 就可以做到这一点,无需额外的需要收集的分配,在 1 行或更短的时间内完成。

【讨论】:

对这个给予一些爱,因为它是唯一一个对字符串进行单次传递的人。我确实关心性能。 charAt 迭代 16 位代码点而不是字符! Java 中的 char 不是字符。所以这个答案意味着必须没有 Unicode 符号的高代理项等于delim 的代码点。我不确定点是否正确,但总的来说可能不正确。【参考方案12】:

好的,受 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 内置的,可以通过不同的方法处理链接。 如果您的方法返回对自身的调用(包括运行总参数),而不是返回执行加法的结果,您更有可能获得尾递归。【参考方案13】:

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

【讨论】:

【参考方案14】:

不确定它的效率,但这是我在不引入 3rd 方库的情况下可以编写的最短代码:

public static int numberOf(String target, String content)

    return (content.split(target).length - 1);

【讨论】:

要计算字符串末尾的出现次数,您必须使用负限制参数调用 split,如下所示:return (content.split(target, -1).length - 1);。默认情况下,字符串末尾的出现在 split() 生成的数组中被省略。见Doku【参考方案15】:

使用java-8,您还可以使用流来实现此目的。明明背后有一个迭代,但你不必显式写出来!

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 值(包括那些需要代理对的值)【参考方案16】:

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

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

输出:

3

【讨论】:

【参考方案17】:

得到答案的最简单方法如下:

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

【讨论】:

对于给定的输入“a.b.c.”,此 sn-p 不会返回正确的点数 @dekaru 能否请您在评论中粘贴您的刺痛,以便我们查看。【参考方案18】:

完整示例:

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 occurrences = CharacterCounter.countOccurrences("1", "101"); 时它不起作用System.out.println(出现次数); // 1 我对使用相同逻辑的代码进行了修复【参考方案19】:

如果您使用的是 Spring 框架,您还可以使用“StringUtils”类。 该方法将是“countOccurrencesOf”。

【讨论】:

【参考方案20】:

只需一行代码即可使用split()函数

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

【讨论】:

Split 确实创建了字符串数组,很耗时。 你说得对,这是一个真正的问题。以另一种方式,它避免在您的项目中引入第三方库(如果尚未完成)。这取决于您想做什么以及性能期望是什么。 此解决方案将不包括尾随的空命中,因为在此重载拆分方法调用中参数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:""]) 的数组。【参考方案21】:

一个更简单的解决方案是根据您匹配的字符拆分字符串。

例如,

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

这将在以下情况下返回 4: getOccurences("o", "something about a quick brown fox");

【讨论】:

这里的问题是必须分配一个数组,这非常慢。【参考方案22】:
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;

【讨论】:

【参考方案23】:

虽然方法可以隐藏它,但没有循环(或递归)就无法计数。不过,出于性能原因,您想使用 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 循环至少以前比较慢。也可能取决于平台。真正找出答案的唯一方法是衡量差异。【参考方案24】:
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

【讨论】:

【参考方案25】:

好吧,我偶然发现了这个线程。 我没有看到任何编程语言限制,因为 groovy 在 java vm 上运行: 以下是我使用 Groovy 解决问题的方法。

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

完成。

【讨论】:

【参考方案26】:

使用Eclipse Collections

int count = Strings.asChars("a.b.c.d").count(c -> c == '.');

如果要计算多个字符,可以使用CharBag,如下所示:

CharBag bag = Strings.asChars("a.b.c.d").toBag();
int count = bag.occurrencesOf('.');

注意:我是 Eclipse Collections 的提交者。

【讨论】:

【参考方案27】:

在代码的某个地方,有些东西必须循环。解决这个问题的唯一方法是完全展开循环:

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

【讨论】:

【参考方案28】:

这里是一个风格略有不同的递归解决方案:

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

【讨论】:

【参考方案29】:

为什么不直接分割字符然后得到结果数组的长度。数组长度总是实例数 + 1。对吗?

【讨论】:

【参考方案30】:

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

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

    

【讨论】:

以上是关于如何计算字符串中字符的出现次数?的主要内容,如果未能解决你的问题,请参考以下文章

如何计算 Oracle varchar 值中字符的出现次数?

Java 如何计算字符串中字符出现的最大次数

如何计算特定字母在字符串中出现的次数? (C++)

如何计算给定字符在一列字符串的每一行中出现的次数?

如何计算R中数据框中字符串中“c(\”)的出现次数?

如何计算numpy数组中字符串的出现次数? [复制]