【问题标题】:Printing character occurrence in string - Java打印字符串中出现的字符 - Java
【发布时间】:2016-11-02 16:19:49
【问题描述】:

我正在尝试计算每个字符在字符串中出现的次数,但是程序打印出不同的大小写计数以及 打印出一个不显示的字符计数为 0。

所需的输出应该类似于...

A = 0

B = 1

C = 2

...

X = 0

Y = 1

Z = 2

以及列出 0-9 出现或不出现的次数。我已经找到了一种防止大写/小写字符不被计算在一起的工作方法(p = 1,P = 1),但不确定它是否最有效。但主要问题是让程序打印字符串中没有出现的字符。我遇到的大多数其他帖子都涉及用户检查某个字符而不是所有字符出现的次数。

我目前的代码是

import java.util.*;
import java.io.*;

public class LetterCount  {
public static void main(String[] args) throws FileNotFoundException{
    String myInput;

    Scanner fileIn = new Scanner(new File("input_test"));
    myInput = fileIn.nextLine();

    myInput=charAdjust(myInput);
    charCounter(myInput);
    fileIn.close();

}

public static String charAdjust(String s) throws FileNotFoundException {
    String str;
    Scanner fileIn = new Scanner(new File("input_test"));
    str=fileIn.nextLine();

    System.out.println("-----------");
    fileIn.close();
    return (str.toUpperCase());

}
public static String charCounter(String str) {
    int[]counter = new int[(int) Character.MAX_VALUE];

    for (int i=0; i<str.length(); i++){
        char charAt = str.charAt(i);
        counter[(int) charAt]++;
    }

        for (int i=0; i<counter.length; i++) {
            if (counter[i] > 0)
                System.out.println((char)i + " = " + counter[i]);

        }
        return str;
    }   
}

【问题讨论】:

  • 您是否只查找某些特定字符?否则,65536 个字符位置中的大多数将打印零。
  • 如果只有 a-z,您可以使用 26 的数组并从 str.charAt(i) 中减去 'a' 或 'A'。

标签: java arrays string


【解决方案1】:
    /* Most common string occurrence related solutions using java 8 */
    
    //find all character occurrences in a string
    String myString = "test";
    List<Character> list = myString.chars().mapToObj(c -> (char)c).collect(Collectors.toList());
    list.stream().distinct().forEach(c -> System.out.println(c + " = " + Collections.frequency(list, c)));

    //find one specific character occurrence in a string
    String myString = "test";
    char search = 't';
    long count = myString.chars().filter(c -> c == search).count();
    System.out.println(count);

    //find all unique characters in a string
    String myString = "test";
    List<Character> list = myString.chars().mapToObj(c -> (char)c).collect(Collectors.toList());
    list.stream().filter(c -> Collections.frequency(list,c) == 1).forEach(System.out::println);

    //find first unique character in a string
    String myString = "test";
    List<Character> list = myString.chars().mapToObj(c -> (char)c).collect(Collectors.toList());
    char firstUniqueChar = list.stream().filter(c -> Collections.frequency(list,c) == 1).findFirst().get();
    System.out.println(firstUniqueChar);

【讨论】:

    【解决方案2】:

    没有简单的方法可以做到这一点。严格来说,要确定字母表中所有字母的计数,您必须知道字母表是什么。

    例如,您显然是一个会说英语的人,因此根据您在帖子中定义的规则,它将是 [A-Za-z0-9]。但是想象一下你是俄罗斯人;你的字母表会改变,你的程序需要以不同的方式运行。要知道这些字母是什么,唯一的方法就是定义它们。

    如果您只是在谈论英语和[A-Za-z0-9],那么它会变得更简单一些。 [A-Za-z0-9] 用 ASCII 表示,可以表示为数值,从你的代码 sn-p 中你显然已经知道了。

    您从代码 sn-p 尝试此操作的方式是您可以采用的最有效的方式 - 有一两个警告。如果您以任何方式、形状或形式了解 C,那么您就知道我将要解释的内容,但为了完整起见,无论如何我都会描述它。基本上,你是说你有一段内存从内存地址 N 开始。地址是一个字符的大小(在 Java 中应该是 2 个字节,而不是 C 中的 1 个字节 - 尽管这在 Java 中被抽象掉了并且不是严格的 2 个字节,据我所知,它通常是乘以数组的索引数(在您的情况下,它是 2^16 或 65536 个索引)。当您说array[N] 时,它很聪明,知道准确跳转到 N*size_of_char 的内存地址并获取该地址的值。这是最快的。

    您的逻辑稍有落后的部分是,您的缓冲区(如上一节中所暗示的)是 65536 个索引,根据您所描述的要求,这显然比您关心的要多。但是,这可以通过一些简单的逻辑来缓解,以便在您尝试处理指标时仅读取您关心的索引。换句话说,只读取位于48-57 (0-9)、65-90 (A-Z) 和97-122 (a-z) 的索引。

    下一个最好的方法是使用 Java 集合,例如 HashMap。这比你使用的数组效率低得多,所以我不推荐它。

    编辑:我不清楚您是否尝试将 a-zA-Z 的字符数汇总在一起,或者您是否尝试分别计算它们。将它们一起或分开并不难。正如有人在对您的问题的评论中提到的那样,您可以使用算术从 ASCII 字符中获取大写/小写字母,这非常快(而且非常快,我的意思是这是一个 CPU 周期)。

    编辑 2:在审查时,看来我不一定回答您的所有问题。要打印出未出现在字符串中的字符,您只需从数组中读取索引 48-57 (0-9)、65-90 (A-Z) 和 97-122 (a-z) 并打印出价值观。初始化数组时,它会初始化为空值。在您的情况下,您使用 int 数组。 int 是原语,原语版本的空值转换为零值。对于intshortbytechar 表示0,对于boolean 表示假,对于long 表示0L,对于doublefloat 表示0F。换句话说,您不必做任何明确的事情来获取未出现在您的字符串中的字符。只需阅读与您关心的字母相关的所有索引,如果它不是您的字符串的一部分,它将给您一个“空”(或零)值。

    【讨论】:

      【解决方案3】:

      一些提示:


      暂时不要太担心进一步的效率——您只需循环字符串中的字符一次就可以了。一些初学者会为他们正在搜索的每个字符通读一次字符串,这会使其非常慢,而您已经避免了这种情况。

      还有一些其他技术可以遍历字符串,例如StringReader,但你很快就会遇到它们,charAt() 现在还可以。 (确实,String.charAt() 非常快——在幕后它是一种廉价的数组查找。其他选项的速度大致相同,但可能更具表现力,并且更适合其他字符源,例如文件或网络流)。

      您无法避免有两个循环:一次通过输入字符串收集您的计数,然后另一个通过counter 数组输出结果。


      您首先使用toUpperCase 的策略是可以的,而且很常见。对于非常长的输入,最好在遇到每个字符时将其大写:

        char charAt = Character.toUpperCase(str.charAt(i));
      

      这是因为在幕后,String.toUpperCase()(当然)循环输入字符串,所以您要添加第三个循环,其中两个就足够了。但是,如果您知道输入量不大,那真的没关系。


      由于您只对 A-Z 和 0-9 感兴趣,因此您可以使用大小为 36 的 counter 数组,如果您为“其他”保留一个插槽,则可以使用 37。您需要编写一个将 char 转换为其索引的方法:

        int charToIndex(char c) {
             char upper = Character.toUpperCase(c);
             if(upper >= 'A' && upper <= 'Z') {
                 // returns 1 for 'A' ... 26 for 'Z'
                 return (upper - 'A') + 1;
             }
             if(upper >= '0' && upper <= '9') {
                 // returns 27 for '0' ... 37 for '9'
                 return 27 + (upper - '0');
             }
             return 0; // meaning 'other'
        }
      

      现在要输出您的计数,您只需遍历这个小数组,输出每个项目。您需要编写另一种方法将索引转换回可打印的字母/数字。


      请注意,这只适用于 ASCII 字母和数字。 Unicode 世界中的情况变得更加复杂,其中还有数百个其他字符,包括表情符号、非罗马字母,甚至看起来与拉丁字母中的字母完全相同但代码不同的符号。

      【讨论】:

      • 好答案。但我唯一不喜欢的是你说“不要太担心效率”。客观地说,我明白了——Java 有意将所有的微观管理抽象出来,但这是一条危险的道路,你从一开始就忽略效率,然后到最后,你有一个程序,你的经理强迫你用 Java 编写,以及决定时间的要求,现在您不知道如何编写快速的 Java 应用程序。常说的一句话是“如果需要快速,就不要用 Java 编写”,但我们并不总是拥有这种级别的控制。
      • 修改为“提高效率”。已经是 O(x) 了,已经是最好的了。您可能会疯狂地尝试最小化x:对于字符串上的操作,这通常是一种浪费,因为x 已经很小了——而且OP 显然是一个初学者。 (另见:blog.codinghorror.com/…
      【解决方案4】:

      创建一个容量为123个元素的int数组(从'0''9''A''Z''a''z',最大值为'z',即122) .

      迭代每个字符并将其用作计数器中的索引。 不要大写任何东西 - 这是浪费时间。

      打印字母时,只需从索引 65 到 90(那些是大写字母)。你知道大写字母和小写字母之间的间隔是 32 吗?换句话说,您可以通过类似counter['A'] + counter['A' + 32] 的操作将'a''A' 放在一起。不需要大写任何东西。

      打印数字时,只需从索引 48 到 57。

      public static void main(String[] args) {
      
          String input            = "sjdSaaASDB12bbBBB555BbbbjsdajdasJDa51hkajsdJASDHKjasd2233haksjdDAKSJD!!!!";
      
          int[] letters           = new int[123]; // Because from '0' to 'z', the highest is 122.
      
          for (int i = 0; i < input.length(); ++i) {
              char c = input.charAt(i);
              if (c <= 122) letters[c] += 1;
          }
      
          System.out.println("LETTER COUNT:");
          for (int i = 'A'; i < 'Z'; ++i) {
              if ((letters[i] + letters[i + 32]) > 0) System.out.println((char)i + " -> " + (letters[i] + letters[i + 32]));
          }
      
          System.out.println("NUMBER COUNT:");
          for (int i = '0'; i < '9'; ++i) {
              System.out.println((char)i + " -> " + letters[i]);
          }
      
      }
      

      【讨论】:

        【解决方案5】:

        HashMap 可能不是最便宜的解决方案,但如果有人不太关心它,至少只有一个循环是可能的,而且它是直截了当的。希望这对某人有所帮助。

            public static void charOccurence(String string) {
                // create a char array out of your string
                char[] chs = string.toCharArray();
                // create a map for storing your character and count pairs
                HashMap<Character, Integer> map = new HashMap<>();      
                // loop trough using the string length
                for (int i = 0; i < string.length() ; i++) {
                    // if you already have the letter stored simply add one to the count
                    if (map.containsKey(chs[i])) {
                        int count = map.get(chs[i]) + 1;
                        map.put(chs[i], count);
                    // else add your letter for the first time with the count of 1
                    } else {
                        map.put(chs[i], 1);
                    }
                }
                // print results
                System.out.println(map);
            }
        

        【讨论】:

          【解决方案6】:

          HashMap 可能是在 Java 中解决这个问题的最简单的实现。

          public static String charCounter(String str) {
              // Initialize counter HashMap with 0 value counts for desired characters
              HashMap<Character, Integer> counter = new HashMap<Character, Integer>();
              String indexes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
          
              for (int i=0; i<indexes.length;i++){
                  counter.put(indexes.charAt(i), 0);
              }
          
              // Update HashMap by incrementing for each character in the string
              for (int i=0; i<str.length(); i++){
                  char charAt = Character.toUpperCase(str.charAt(i));
                  int count = counter.containsKey(charAt) ? counter.get(charAt) : 0;
                  counter.put(charAt, count+1)
              }
          
              // Print out the counts
              for (int i=0; i<indexes.length; i++) {
                  char index = Character.toUpperCase(indexes.charAt(i));
                  int count = counter.get(index);
                  if (counter.get(index) >= 0)
                      System.out.println(index + " = " + count);
              }
              return str;
          
          }
          

          【讨论】:

          • 没有。 HashMap 的效率远低于 OP 使用的数组。我建议找一篇博文或类似的东西来了解 HashMaps 如何在后台工作以及数组如何在 Java 中工作以了解原因。在 OPs 用例中使用 HashMap 所获得的收益很少。
          • 很公平。更改为“最简单的实现” HashMap 可能会比数组慢一点,但是,就字符串大小而言,算法仍然是 O(n),并且没有迹象表明 OP
          • 不幸的是,这对我来说仍然是一票否决。 OP 的问题是关于效率的,这比 OP 已经完成的效率要低。
          • 相当肯定 OP 的主要关注点是正确性而不是效率,因为他无法显示 0 个计数,但没关系。
          • 我想你是对的。我将取消投票,因为这似乎解决了这个问题并且不正确(尽管我只是略读并没有亲自验证 - 乍一看它看起来是正确的)
          猜你喜欢
          • 2017-04-15
          • 2017-05-21
          • 2015-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-26
          • 2013-02-26
          • 1970-01-01
          相关资源
          最近更新 更多