用于环路更换:用于环路至过滤器



我正在为一门计算机科学III课程(Java编程)做作业,其中我们必须基于霍夫曼编码对文件进行编码。

import java.util.Scanner;
import java.util.ArrayList;
import java.io.*;
import java.util.Collections;
import java.util.StringTokenizer;
public class Client {
public static void main(String[] args) throws IOException {
    // TODO code application logic here
    Scanner in = new Scanner(System.in);
    System.out.println("Enter a filename to read from.");
    String filename = in.nextLine();
    File file = new File(filename);
    Scanner inputFile = new Scanner(file);
    String line, word;
    StringTokenizer token;
    ArrayList<Character> chars = new ArrayList<>();
    while(inputFile.hasNext()){
        line = inputFile.nextLine();
        ArrayList<Character> lineChar = new ArrayList<>();
        for (int i=0; i<line.length(); i++){
            if (line.charAt(i)!=' '){
                lineChar.add(line.charAt(i));
            }
        }
        chars.addAll(lineChar);
    }
    ArrayList<Character> prob = new ArrayList<Character>();
    for (int i=0; i<chars.size(); i++){
        if (!prob.contains(chars.get(i))){
            prob.add(chars.get(i));
        }
    }    
    for (int i=0; i<prob.size(); i++){
        System.out.print("Frequency of " + prob.get(i));
        System.out.println(": " + ((double)Collections.frequency(chars, prob.get(i)))/chars.size());
    }

我在我的NetBeans IDE中进行了这项工作,并遵循了一些建议。它将最后两个for循环更改为:

    chars.stream().filter((char1) -> (!prob.contains(char1))).forEach((char1) -> {
        prob.add(char1);
    });    
    prob.stream().map((prob1) -> {
        System.out.print("Frequency of " + prob1);
        return prob1;
    }).forEach((prob1) -> {
        System.out.println(": " + ((double) Collections.frequency(chars, prob1)) / chars.size());
    });

我真的,真的,非常感兴趣,但我发现很难追踪所有的东西。很明显,它的运行方式与我的for循环相同,经过测试,我发现它确实有效,但我想了解为什么以及如何工作。有人能给我什么见解吗?

您的IDE用新的Java 8功能(流和lambda表达式)替换了一些代码。你应该读一下它们。

流允许您对管道中的集合执行操作,其中只有最终(终端)操作对元素进行实际迭代(仅针对所需数量的元素)。

Lambda表达式允许您在将实现函数接口(=具有单个方法的接口)的匿名类实例传递给方法时编写更少的代码。

以下是解释新代码的作用:

chars.stream() // creates a Stream<Character> from your chars List
     .filter((char1) -> (!prob.contains(char1))) // keeps only Characters not contained
                                                 // in prob List
     .forEach((char1) -> {prob.add(char1);}); // iterates over all the elements of
                                              // the Stream (i.e. those that weren't
                                              // filtered out) and adds them to prob   
prob.stream() // creates a Stream<Character> of the prob List
    .map((prob1) -> {
      System.out.print("Frequency of " + prob1);
      return prob1;
     }) // prints "Frequency of " + character for the current Character in the Stream
    .forEach((prob1) -> { // prints the frequency of each character in the Stream
        System.out.println(": " + ((double) Collections.frequency(chars, prob1)) /  chars.size());
     });

第二个Stream上的map操作有点奇怪。通常map用于将一种类型的Stream转换为另一种类型。在这里,它用于打印输出,并返回相同的Stream。我不会为此使用map。您可以简单地将打印移动到forEach

prob.stream() // creates a Stream<Character> of the prob List
    .forEach((prob1) -> { // prints the frequency of each character in the Stream
        System.out.print("Frequency of " + prob1);
        System.out.println(": " + ((double) Collections.frequency(chars, prob1)) /  chars.size());
     });

实际上,您不需要Stream,因为Collections在Java8:中也有一个forEach方法

prob.forEach((prob1) -> { // prints the frequency of each character in the Stream
        System.out.print("Frequency of " + prob1);
        System.out.println(": " + ((double) Collections.frequency(chars, prob1)) /  chars.size());
     });

Netbeans尽其所能重构代码以使用java8流,但实际上可以做得更好。例如,prob似乎应该包含一个不同的字符列表。在java8中,您可以这样做:

List<Character> prob = chars.stream()
    .distinct()
    .collect(Collectors.toList());

但您所使用的prob-for只是计算每个字符在字符中出现的次数。使用流,您可以在不首先列出问题列表的情况下完成:

Map<Character, Long> freq = chars.stream()
   .collect(
        Collectors.groupingBy(
            x->x, 
            Collectors.counting()
        )
   );

Collections类中的静态方法通常只是静态导入的,因此上面的内容可以写成:

Map<Character, Long> freq = chars.stream()
   .collect(groupingBy(x->x, counting());

这意味着,取我的字符流,制作一张地图。映射的关键是字符本身(这就是x->x所做的),映射的值是该字符在字符中出现的次数。

但这还不是全部!方法的前半部分遍历文件的行并收集字符。也可以用流重写:

Stream<Character> charStream = Files.lines(Paths.get(filename))
        .flatMap(line -> line.chars().mapToObj(i->(char) i));

File.lines(..)给了我们一连串的行。flatMap部分有点神秘,但它将每个字符串展开为一个单独的字符流,并连接这些流,这样我们就有了一个大的字符流。

现在我们把它放在一起:

public static void main(String[] args) throws IOException {
    Scanner in = new Scanner(System.in);
    System.out.println("Enter a filename to read from.");
    String filename = in.nextLine();
    Map<Character, Long> freq = Files.lines(Paths.get(filename))
            .flatMap(line -> line.chars().mapToObj(i -> (char) i))
            .collect(groupingBy(x -> x, counting()));
    long total = freq.values().stream().mapToLong(x->x).sum();
    freq.forEach((chr, count) ->
            System.out.format("Frequency of %s: %s%n", chr, ((double) count) / total)
    );
}

编辑:

要按排序顺序输出频率,请执行以下操作(使用import static java.util.Comparator.*):

freq.entrySet().stream()
        .sorted(comparing(e->e.getValue(), reverseOrder()))
        .forEach(e -> System.out.format("Frequency of %s: %s%n", e.getKey(), (double) e.getValue() / total));

我们对Character的映射进行计数,对其条目进行流式处理,按值按相反顺序排序,然后打印出每个条目。

在我看来,这就像NetBeans重构了您的代码,以使用Java 8的lambda或使用Stream接口的map-reduce进行函数式编程操作。

有关map()/reduce()/流接口的更多信息,请参阅此链接

在应用之前,请阅读IDE提供的建议:)

首先,您应该阅读java.util.Stream包,以便对API的设计方式和用途有第一印象。

以下是你的第一个循环的作用,以单词的形式:

  • 对从0到chars.size()-1的值进行迭代,并将相应的元素从chars添加到prob,但前提是该元素尚未存在

通过将Stream API添加到带有Java 8的Java中,可以以函数式编程风格编写此类任务,该风格侧重于"如何完成",而不是的"完成了什么"

chars.stream()
    .filter(char1 -> !prob.contains(char1))
    .forEach(char1 -> {
        prob.add(char1);
    });    
  • ArrayList实现了Collection<T>,因此方法stream()也实现了
  • 此流(管道中Collection中的所有元素)正在进行筛选(由您以前的if语句进行筛选)
  • 在剩余元素的流上,执行最终操作prop.add

目前这可能有点太多了,但您可以将最后一个操作(.forEach)更改为更清晰:

//...
.forEach(prop::add);

为了更好地了解或调试,您可能会发现Stream#peek很有趣。

相关内容

  • 没有找到相关文章