@SuppressWarnings("unchecked")
  public static void main(String... args) throws Exception {

    Stream<String> scrabbleWordsStream = Files.lines(Paths.get("files", "ospd.txt"));
    Set<String> scrabbleWords =
        scrabbleWordsStream.map(String::toLowerCase).collect(Collectors.toSet());

    Stream<String> shakespeareWordsStream =
        Files.lines(Paths.get("files", "words.shakespeare.txt"));
    Set<String> shakespeareWords =
        shakespeareWordsStream.map(String::toLowerCase).collect(Collectors.toSet());

    System.out.println("# de mots autorisés au Scrabble : " + scrabbleWords.size());

    // mots utilisés par Shakespeare
    Long nWords1 = shakespeareWords.stream().count();
    System.out.println("# ofmots utilisés par Shakespeare  : " + nWords1);

    // number of words used by Shakespeare and allowed at Scrabble
    long count =
        shakespeareWords.stream().map(String::toLowerCase).filter(scrabbleWords::contains).count();
    System.out.println("# number of words used by Shakespeare and allowed at Scrabble = " + count);

    // words of Shakespeare grouped by their length
    Map<Integer, Long> map1 =
        shakespeareWords
            .stream()
            .collect(Collectors.groupingBy(String::length, Collectors.counting()));
    System.out.println("Words of Shakespeare grouped by their length = " + map1);

    // words of Shakespeare of 16 letters and more
    Map<Integer, List<String>> map2 =
        shakespeareWords
            .stream()
            .filter(word -> word.length() > 15)
            .collect(Collectors.groupingBy(String::length));
    System.out.println("Words of Shakespeare of 16 letters and more = " + map2);

    // words of Shakespeare grouped by their Scrabble score
    // in ascending order
    Function<String, Integer> score = word -> word.chars().map(scrabbleLetterValueEN).sum();
    Map<Integer, Long> map3 =
        shakespeareWords
            .stream()
            .map(String::toLowerCase)
            .filter(scrabbleWords::contains)
            .collect(Collectors.groupingBy(score, TreeMap::new, Collectors.counting()));
    System.out.println("Words of Shakespeare grouped by their Scrabble score = " + map3);

    // words of Shakespeare grouped by their Scrabble score, with a score greater than 29
    // in ascending order
    Predicate<String> scoreGT28 = word -> score.apply(word) > 28;
    Map<Integer, List<String>> map4 =
        shakespeareWords
            .stream()
            .map(String::toLowerCase)
            .filter(scrabbleWords::contains)
            .filter(scoreGT28)
            .collect(Collectors.groupingBy(score, TreeMap::new, Collectors.toList()));
    System.out.println("Words of Shakespeare grouped by their Scrabble score = " + map4);

    // histogram of the letters in a given word
    Function<String, Map<Integer, Long>> lettersHisto =
        word ->
            word.chars()
                .mapToObj(Integer::new)
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

    // score of a given word, taking into account that the given word
    // might contain blank letters
    Function<String, Integer> scoreWithBlanks =
        word ->
            lettersHisto
                .apply(word)
                .entrySet()
                .stream() // Map.Entry<letters, # used>
                .mapToInt(
                    entry ->
                        scrabbleENScore[entry.getKey() - 'a']
                            * (int)
                                Long.min(
                                    entry.getValue(), scrabbleENDistribution[entry.getKey() - 'a']))
                .sum();

    // number of blanks used for the given word
    Function<String, Integer> blanksUsed =
        word ->
            lettersHisto
                .apply(word)
                .entrySet()
                .stream() // Map.Entry<letters, # used>
                .mapToInt(
                    entry ->
                        (int)
                            Long.max(
                                0L,
                                entry.getValue() - scrabbleENDistribution[entry.getKey() - 'a']))
                .sum();

    System.out.println("Number of blanks in [buzzards] = " + blanksUsed.apply("buzzards"));
    System.out.println("Real score of [buzzards] = " + scoreWithBlanks.apply("buzzards"));
    System.out.println("Number of blanks in [whizzing] = " + blanksUsed.apply("whizzing"));
    System.out.println("Real score of [whizzing] = " + scoreWithBlanks.apply("whizzing"));

    // best words of Shakespeare and their scores
    Map<Integer, List<String>> map =
        shakespeareWords
            .stream()
            .filter(scrabbleWords::contains)
            .filter(word -> blanksUsed.apply(word) <= 2L)
            .filter(word -> scoreWithBlanks.apply(word) >= 24)
            .collect(Collectors.groupingBy(scoreWithBlanks, Collectors.toList()));
    System.out.println("Best words of Shakespeare : " + map);
  }