2

I'm considering how to sort an ArrayList with two parameters. First by occurences of some char in string, then by natural order. Here is the code:

  ArrayList<String> words;
    words=getWords(sentence);//return all words from sentence
    words.sort(Comparator.comparing(o -> countChar(c,  o))
                     .thenComparing(Comparator::naturalOrder));

Method getWords(sentence) return an ArrayList<String> of words from sentence.

Method countChar(c,o) counts number of char c in word o.

When adding .thenComparing(Comparator::naturalOrder)) IDE shows that o should be cast to String and that it can't resolve method thenComparing().

What might be the problem?

1

3 Answers 3

4

Two mistakes in your code.

  1. You'll need to provide the generic parameters to comparing
  2. naturalOrder returns a comparator; invoke it, rather than passing a reference

Try:

        words.sort(Comparator.<String, Integer>comparing(o -> countChar(c,  o))
                         .thenComparing(Comparator.naturalOrder()));

Sign up to request clarification or add additional context in comments.

3 Comments

It compiles fine. Could you please tell me why are we providing Comparator.<> than Comparator<>.. I am poor in generics :(
Great answer! Added .reversed() as elements with occurences of char c should go first and than elements in natural order: words.sort(Comparator.<String, Integer>comparing(o -> countChar(c, o)).reversed().thenComparing(Comparator.naturalOrder()));
1

My solution is to add an object with count(c) and impl the Comparable.

class StringWithChar implements Comparable<StringWithChar> {
    private String s;
    private char c;
    private long count;
    public StringWithChar(String s, char c) {
         this.s = s;
         this.c = c;
         count = s.chars().filter(ch -> ch == c).count();
     }

     public String getS() {
         return s;
     }

     public void setS(String s) {
         this.s = s;
     }

     public char getC() {
         return c;
     }

     public void setC(char c) {
         this.c = c;
     }

     public long getCount() {
         return count;
     }

     public void setCount(long count) {
         this.count = count;
     }

     @Override
     public int compareTo(StringWithChar s2) {
         int res = Long.compare(this.getCount(), s2.getCount());
         if (res == 0) {
             return this.getS().compareTo(s2.getS());
         }
         return res;
     }
 }

// then you can easier stream 
words.stream().map(s -> new StringWithChar(s, c)).sort().collect(Collectors.toList());

I hope it helps!

1 Comment

class StringWithChar implements Comparator<StringWithChar> are you possibly confusing Comparable and Comparator?
0

My only concern with that approach is a potential performance bottle neck as it could potentially loop through all elements to sort by the first comparator then loop again to sort by the 2nd comparator.
Perhaps try creating a single comparator that does both comparisons and use that instead?

2 Comments

list will be sort by a single - combined - comparator. No need to worry about list being traversed twice...
This approach works, but I expected usage of Java 8 will make it look better :) words.sort((s1, s2) -> { int charCount; charCount = Integer.compare(countChar(c, s2), countChar(c, s1)); if (charCount != 0) { return charCount; } return s1.compareTo(s2); });

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.