8

Thare are two input lists as follows:

inputA = [
            {
               name: "A",
               age: 20
            }, 
            {
               name: "B",
               age: 30
            },
            {  name: "C",
               age: 25
            },
            {  name: "D",
               age: 28
            }
          ]

inputB = ["D", "B"]

My preferred output list must be as follows:

expectedOutput = [
            {
               name: "D",
               age: 28
            }, 
            {
               name: "B",
               age: 30
            },
            {  name: "A",
               age: 20
            },
            {  name: "C",
               age: 25
            }
          ]

What I have done so far looks like below:

AtomicInteger count = new AtomicInteger();
Collections.sort(inputA, Comparator
    .comparing(a -> 
    if (inputB.indexOf(a.getName()) > -1) {
        return -1;
    }
    else {
        return count.incrementAndGet();
    })
    .thenComparingInt(a -> a.getAge()));

The output I am getting is as follows

actualOutput = [
            {
               name: "D",
               age: 28
            }, 
            {
               name: "B",
               age: 30
            },
            {  name: "C",
               age: 25
            },
            {  name: "A",
               age: 20
            }
          ]

Problem is with the elements that doesn't have their name in the list inputB. There order doesn't have the original order in inputA. For the original order to persist { name: "A", age: 20 } should come before { name: "C", age: 25 }

How can i solve this issue while using the comparator chaining strategy?

UPDATE Sorting logic is, if inputA has objects where the names are equal to the inputB list, those elements should come to the top of the inputA and then those elements must be sorted by their age while keeping the original order of the other elements in inputA which are not present in inputB

This is not a possible duplicate, because this question tries to compare two lists and also sort the common elements by a property of an object from the the first list while leaving the rest of the elements in their original order.

6
  • Should that be .thenComparingInt instead of just comparingInt? Commented Feb 6, 2019 at 18:08
  • I am curious to know what's the reasoning behind the processing with return count.incrementAndGet(); in the else case. That where the issue lies Commented Feb 6, 2019 at 18:21
  • What even is the rule by which you want to order your items? Commented Feb 6, 2019 at 18:29
  • 1
    Possible duplicate of Ordering with partial explicit and then another order? Commented Feb 8, 2019 at 10:51
  • @Joe How can this be a duplicate of the problem you are stating? Just read the question properly and also compare the two accepted answers of this and the question you are referring Commented Feb 8, 2019 at 20:08

2 Answers 2

3

As I see it, you need to sort elements by age if the name is contained in the inputB list and leave the rest of the elements as they are if they aren't contained in the inputB list. The elements sorted by age should appear at the top of the result, while the unsorted ones should appear at the bottom.

If this is what you need to do, you can use Comparator.comparingInt and let it return an integer that is either the age (for the first case) or Integer.MAX_VALUE (for the other case).

You should optimize the check over inputB, so that it is fast. For this, you could create a HashSet from inputB.

This is the code:

Set<String> set = new HashSet<>(inputB);

Collections.sort(inputA, Comparator.comparingInt(a -> set.contains(a.getName()) ? 
                                                      a.getAge() : 
                                                      Integer.MAX_VALUE));

This works, as long as you don't have an age that is equal to Integer.MAX_VALUE.

The idea is that you always compare by age, but if an element doesn't belong to inputB, you turn the age into Integer.MAX_VALUE. This will have two effects: first, it will make elements not contained in inputB appear at the bottom; second, as you always return Integer.MAX_VALUE, the order of the inputA list is preserved, because Collections.sort implements a stable sort.

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

Comments

1

The issue in your code is that the second comparator gets activated on all items in inputA. This means, even the items that are not in inputB will get sorted breaking the original order of inputA. You can avoid it like this:

Collections.sort(inputA, Comparator.comparing(a ->
{
    if (inputB.indexOf(a.getName()) > -1)
    {
        return 0;
    }
    return 1;
})
.thenComparingInt(a -> 
{
   if (inputB.indexOf(a.getName()) > -1)
   {
       return a.getAge();
   }
   return Integer.MAX_VALUE;
}));

However, I'm not sure if it's desirable to call IndexOf() twice specially if inputB is unsorted. May be worth looking at the bytecode generated to see if that gets optimized.

One thing is that, using a count doesn't have any impact on the comparison. You just need to return -1, 0, or 1 in a comparison operation. So, there's no value in doing a thread-safe increment on a count.

On the other hand, if you can, introduce a priority field in the class that defines the object in inputA. Then you can get around the issue of calling indexOf() multiple times like:

Collections.sort(inputA, Comparator.comparing(a ->
{
   if (inputB.indexOf(a.getName()) > -1)
   {
       a.setPriority(a.getAge());
       return 0;
   }

   a.setPriority(Integer.MAX_VALUE);
   return 1;
})
.thenComparingInt(a -> a.getPriority()));

Comments

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.