2014-10-28 235 views
1

我正在爲計算機科學III級(Java編程)的分配,並在它我們必須編碼基於Huffman編碼的文件。For循環更換:For循環濾鏡

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循環相同,經過測試,我發現它的工作原理,但我想了解爲什麼以及如何。任何人都可以給我任何見解嗎?

+1

這些是使用lambda表達式的Java 8流 – 2014-10-28 14:09:20

回答

1

Netbeans做了什麼可以重構你的代碼來使用java 8流,但它實際上可以做得更好。例如,似乎prob應該包含一個不同的字符列表。在java中8,你可以做這樣的:

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

但是,你正在使用的概率也就是爲了然後計算每個字符出現了多少次的字符。隨着流,你可以做到這一點,必須首先進行概率列表:

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

我們採取要計數的字符映射,流入它的條目,按相反的順序排序它們的值,並將每一個輸出。

+0

這真是太棒了!我如何按頻率對它進行分類? – JonMcDev 2014-10-29 14:57:02

+0

@JonMcDev看到編輯 – Misha 2014-10-29 18:20:56

2

你的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()); 
    }); 

第二流map操作是有點怪。通常地圖用於將一種類型的流轉換爲另一種類型的流。它用於打印輸出,並返回相同的流。我不會使用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()); 
    }); 

其實,你並不需要爲甲流,因爲集合也有forEach方法的Java 8:

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

這對我來說看起來像NetBeans的重構你的代碼,使用Java 8的lambda或使用map的函數式編程操作 - 從Stream接口減少。

有關地圖()/減少()/流接口是指to this link

請仔細閱讀建議,IDE會爲您應用它們:)

0

首先,你應該閱讀有關java.util.Stream以前更多信息包,以獲得關於API設計和用途的第一印象。

這是你的第一個循環做什麼,在詞形:

  • 迭代從0值chars.size() - 1,相應的元素添加從charsprob,但只有當它的還沒有。

與該流API添加到Java與的Java 8級這樣的任務可以在其中重點介紹了功能的編程風格寫成「它怎麼辦」不上「什麼是IST做起來難「

chars.stream() 
    .filter(char1 -> !prob.contains(char1)) 
    .forEach(char1 -> { 
     prob.add(char1); 
    });  
  • ArrayList實現Collection<T>,因此該方法stream()
  • 這流(從收集管道中的所有元素)被過濾(由前if語句)
  • 在其餘的分量流,執行最後的操作prop.add

這可能有點吃不消了,但你可以改變過去的操作(.forEach)要更清楚:

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

爲了更好地瞭解或debuggin目的,你可能會發現Stream#peek有趣這讓。