0

I made this quick code for a class and I got everything to work fine as far as reading the text file and printing it out, but I can't figure out how to get it to print out in ascending order. The goal is to read a file and print out the number of times that word appears and sort it by the number of times it appears.

public class Analyser {
    public static void main(String[] args) throws IOException {


        File file = new File("src/txt.txt");
        BufferedReader br = new BufferedReader(new FileReader(file));
        String line;
        Map<String, Long> counts = new HashMap<>();
        while ((line = br.readLine()) != null) {


            String[] words = line.split("[\\s.;,?:!()\"]+");
            for (String word : words) {
                word = word.trim();
                if (word.length() > 0) {
                    if (counts.containsKey(word)) {
                        counts.put(word, counts.get(word) + 1);
                    } else {
                        counts.put(word, 1L);
                    }
                }
            }
        }
        for (Map.Entry<String, Long> entry : counts.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
        br.close();
    }
}
justjenn
  • 1
  • 1
  • I'm not sure whether you intend to sort the map alphabetically, or by the number of times each word appears. If you want it sorted alphabetically, then your best bet is to use a map that keeps its data always sorted, such as a `TreeMap`. So if you change `HashMap` to `TreeMap` on the fourth line of the method, this should just work. – Dawood ibn Kareem Sep 11 '22 at 01:03

2 Answers2

1

A HashMap has no defined order. Maps which implement the SortedMap interface, such as TreeMap, do have an order defined by a Comparator, but they're ordered by keys, not values. And then there's LinkedHashMap, which maintains insertion order (or access order, if you want that instead). You could make use of LinkedHashMap if you make sure to insert the elements in the desired order. That, of course, means you still need to iterate your original map in the desired order somehow.

But I think, in your case, it does not matter so much that the map has the order you want. You only want order when you print the results. And you only do this once, so there's no need for some kind of caching of the order. So, I would probably just use an intermediate list or a stream.

Example using list:

List<Map.Entry<String, Long>> list = new ArrayList<>(counts.entrySet());
list.sort(Map.Entry.comparingByValue());

for (Map.Entry<String, Long> entry : list) {
  System.out.println(entry.getKey() + " : " + entry.getValue());
}

Example using stream:

counts.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByValue())
    .forEachOrdered(entry -> System.out.println(entry.getKey() + " : " + entry.getValue());

The Map.Entry.comparingByValue() call returns a Comparator which is used to sort the elements. Specifically, it will sort by the natural order of the values of the entries. A Long's natural order is from smallest to largest. If you want a different order, then pass whatever Comparator you need to get that order (which may involve writing your own implementation). Though if you simply want to sort from largest to smallest (i.e., reversed natural order), just use Map.Entry.comparingByValue(Comparator.reverseOrder()) instead.


I also recommend, at least in "real" code, that you use try-with-resources instead of manually closing the reader. For example:

File file = new File("src/txt.txt");
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
  // use 'br' here (the remainder of you code, minus the 'br.close()' call
}

When that try block exits, the resource referenced by br will be automatically closed for you, and in a safer way than simply calling br.close().

Slaw
  • 37,820
  • 8
  • 53
  • 80
0

Since a TreeMap only sorts on the key and not the values, it won't work as you want to sort based on the count which is a value. Since you have already created your map, the easiest ways is to sort it and print as follows:

  • stream the entrySet() and then sort using the Entry's comparingByValue comparator.
counts.entrySet().stream()
        .sorted(Entry.comparingByValue())
        .forEach(System.out::println);

If you want to retain the sorted order in a different map, you can do it like this and then print the new map. Using a LinkedHashMap preserves the order.

  • sort as before
  • but collect in a map
    • first argument is the entry's key
    • second is the entry's value.
    • third is a BinaryOperator (Not used here but syntactically required)
    • fourth is a supplier which makes the map a LinkedHashMap
Map<String, Long> sortedMap = 
   counts.entrySet().stream().sorted(Entry.comparingByValue())
        .collect(Collectors.toMap(Entry::getKey,
                Entry::getValue, (a, b) -> a,
                LinkedHashMap::new));
// now print the map
sortedMap.entrySet().forEach(System.out::println);
WJS
  • 36,363
  • 4
  • 24
  • 39