有没有办法用类似于常规数组的 ArrayList 来计算字符频率?

Is there a way to count character frequency with an ArrayList similar to a regular array?

通常当你想得到一个字符的频率时,你可以这样做:

int[] count = new int[256];
for(int i: arr) count[arr.charAt(i)]++;
// Example if you have 2 'a' characters count[97] (97 being the ascii value of 'a') will return 2

有没有办法用 ArrayList 代替?

tl;博士

让我们开始使用以下代码,或者继续阅读下面的代码以获得更简单的代码。

"Hello"
        .codePoints()       // Returns `IntStream` of the code point for each character in the string.
        .boxed()            // Converts `int` primitives to `Integer` objects.
        .collect(
                Collectors.groupingBy(
                        Function.identity() ,   // Classification function.
                        TreeMap :: new ,        // Map factory.
                        Collectors.counting()   // Downstream collector.
                )
        )
        .forEach(
                ( codePoint , count ) ->
                        System.out.println( "Code point: " + codePoint + " | Character: " + Character.toString( codePoint ) + " | Count: " + count )
        );
Code point: 72 | Character: H | Count: 1
Code point: 101 | Character: e | Count: 1
Code point: 108 | Character: l | Count: 2
Code point: 111 | Character: o | Count: 1
Code point: 128075 | Character:  | Count: 1

列表

作为 ,您可以使用 List 实现,例如 ArrayList。但可能没有好处。代码会更复杂,会使用更多内存,并且可能会降低性能。您将不得不使用 Integer 个对象而不是 int 个基元。

顺便说一句,你不应该使用 char。该类型自 Java 2 以来一直是旧类型。作为 16 位值,char 在物理上无法表示大多数字符。

而且您的 256 限制太小了。 Java 支持 Unicode 中定义的所有超过 140,000 个字符。这些字符被分配给范围超过一百万的代码点整数。使用常量 Character.MAX_CODE_POINT 作为限制。

List< Integer > counts = new ArrayList<>( Character.MAX_CODE_POINT ) ;   

为每个元素填充一个零。列表在每个元素中初始化为 null,这与 int 的数组在每个元素中初始化为零不同。

for( int i = 0 ; i <= Character.MAX_CODE_POINT ; i ++ ) 
{
    counts.add( 0 ) ;  
}

处理您的输入。

String input = "Hello" ; 
int[] codePoints = input.codePoints().toArray() ;

for( int i = 0 ; i < codePoints.length ; i ++ ) 
{
    int codePoint = codePoints[ i ] ;
    int count = counts.get( codePoint ) ;
    counts.set( codePoint , count + 1 ) ;
}

我们可以通过将 counts 列表转储到控制台来报告结果。但是如果有超过一百万个元素,那将很麻烦。相反,让我们过滤掉计数为零的所有元素。

for ( int index = 0 ; index < counts.size() ; index++ )
{
    if ( counts.get( index ) != 0 )
    {
        System.out.println(
                index + " ➣ " + counts.get( index )
        );
    }
}

或者,使用流和 lambda 语法执行相同的操作。保存效果。

IntStream.range( 0 , counts.size() )
        .filter( index -> counts.get( index ) != 0 )
        .mapToObj( index -> index + " ➣ " + counts.get( index ) )
        .forEach( System.out :: println );

当运行:

72 ➣ 1
101 ➣ 1
108 ➣ 2
111 ➣ 1
128075 ➣ 1

地图

如果使用对象和集合,使用 Map 而不是 List 会更有意义。地图中的条目数与输入中的不同字母数相同,而不是列表中超过一百万个。

映射跟踪键和值对。在我们手头的问题中,我们将使用代码点编号作为我们的键,而计数将是我们的值。

如果我们想保留我们的密钥,我们的代码点,我们将使用 NavigableMap.

String input = "Hello";

NavigableMap < Integer, Long > codePointFrequency =
        input
                .codePoints()                       // Returns `IntStream` of the code point for each character in the string.
                .boxed()                            // Converts `int` primitives to `Integer` objects.
                .collect(                           
                        Collectors.groupingBy( 
                            Function.identity() ,   // Classification function.
                            TreeMap :: new ,        // Map factory.
                            Collectors.counting()   // Downstream collector. 
                        )
                );

将地图转储到控制台。

System.out.println( "codePointFrequency = " + codePointFrequency );

当运行.

codePointFrequency = {72=1, 101=1, 108=2, 111=1, 128075=1}

报告每个字符。

codePointFrequency.forEach(
        ( codePoint , count ) ->
                System.out.println( "Code point: " + codePoint + " | Character: " + Character.toString( codePoint ) + " | Count: " + count )
);

当运行.

Code point: 72 | Character: H | Count: 1
Code point: 101 | Character: e | Count: 1
Code point: 108 | Character: l | Count: 2
Code point: 111 | Character: o | Count: 1
Code point: 128075 | Character:  | Count: 1