2

I'm trying to get the items difference between two lists, but, sometimes one list is bigger than other, sometimes one is smaller and sometimes they are equal. Also, missing objects can happen in both lists.

I found good solutions to get the difference between two lists, like How can I return the difference between two lists?, but no one considers 2 lists.

I saw Find common and different elements between two list in java too, but it isn't exactly what I'm asking.

I elaborate one example to illustrate what I want to do.

Consider one list of ordered items, and one list of sent items. They must be equal because client must receive the items that he ordered.

So I did this way:

public class Main {
    public static void main(String[] args) {
        // Case 1
        List<String> order1 = Arrays.asList(new String[] { "A", "B" });
        List<String> sended1 = Arrays.asList(new String[] { "A", "B" });

        // No difference
        System.out.println("Case 1: " + getDifference(order1, sended1));

        // Case 2
        List<String> order2 = Arrays.asList(new String[] { "A", "B", "C" });
        List<String> sended2 = Arrays.asList(new String[] { "B", "C" });

        // "1 item(s) missing in sended: A"
        System.out.println("Case 2: " + getDifference(order2, sended2));

        // Case 3
        List<String> order3 = Arrays.asList(new String[] { "A", "D" });
        List<String> sended3 = Arrays.asList(new String[] { "A", "B", "C", "D" });

        // "2 item(s) missing in order: B, C"
        System.out.println("Case 3: " + getDifference(order3, sended3));

        // Case 4
        List<String> order4 = Arrays.asList(new String[] { "A", "D", "F" });
        List<String> sended4 = Arrays.asList(new String[] { "A", "B", "C", "D" });

        // 1 item(s) missing in sended: "F" & 2 item(s) missing in order: "B", "C"
        System.out.println("Case 4: " + getDifference(order4, sended4));
    }

    private static String getDifference(List<String> order, List<String> sended) {
        StringBuilder output = new StringBuilder();

        if (order.equals(sended)) {
            output.append("No difference");

        } else {
            List<String> auxOrder = new ArrayList<>(order);
            auxOrder.removeAll(sended);

            if (auxOrder.size() > 0) {
                output.append(auxOrder.size()).append(" item(s) missing in sended: ");
                output.append("\"").append(String.join("\", \"", auxOrder)).append("\"");
            }

            List<String> auxSended = new ArrayList<>(sended);
            auxSended.removeAll(order);

            if (auxSended.size() > 0) {
                if (output.length() > 0) {
                    output.append(" & ");
                }

                output.append(auxSended.size()).append(" item(s) missing in order: ");
                output.append("\"").append(String.join("\", \"", auxSended)).append("\"");
            }
        }
        return output.toString();
    }
}

It works, but I'm not sure that it is the best way to do it, so I ask for your help!

Diego Borba
  • 1,282
  • 8
  • 22
  • What do you mean by "volatile"? Also, list equality means that both lists contain the same items in the same order. Why should the list of items ordered be equal to the list of items sent/received? I think they should contain all items the same number of times, but the order should not matter. – Bernhard Stadler Aug 14 '23 at 13:26
  • 1) I removed the "volatile". 2) You're right, they dont need to have the same order. – Diego Borba Aug 14 '23 at 13:42

8 Answers8

2

Using Java 8 streams:

Logic Here:

I have used the java 8 stream operation to get the difference between two given lists and created a output message using StringBuilder as mentioned in the problem statement.

Code:

public class Test {
    public static void main(String[] args) {
        // Case 1
        List<String> order1 = Arrays.asList("A", "B");
        List<String> sended1 = Arrays.asList("A", "B");
        System.out.println("Case 1: " + getMissingItems(order1,sended1));

        // Case 2
        List<String> order2 = Arrays.asList("A", "B", "C");
        List<String> sended2 = Arrays.asList("B", "C");
        System.out.println("Case 2: " + getMissingItems(order2,sended2));

        // Case 3
        List<String> order3 = Arrays.asList("A", "D");
        List<String> sended3 = Arrays.asList("A", "B", "C", "D");
        System.out.println("Case 3: " + getMissingItems(order3,sended3));

        // Case 4
        List<String> order4 = Arrays.asList("A", "D", "F");
        List<String> sended4 = Arrays.asList("A", "B", "C", "D");
        System.out.println("Case 4: " + getMissingItems(order4,sended4));
    }

    private static String getMissingItems(List<String> order, 
                                          List<String> sended){
        StringBuilder sb = new StringBuilder();
        List<String> missingInOrder = getMissingInList(order, sended);
        createMessage(sb, missingInOrder," item(s) missing in order: ");
        List<String> missingInSended = getMissingInList(sended, order);
        if(!missingInOrder.isEmpty() && !missingInSended.isEmpty()){
            sb.append(" && ");
        }
        createMessage(sb, missingInSended," item(s) missing in sended: ");
        return sb.isEmpty() ? "No difference":sb.toString();
    }

    private static void createMessage(StringBuilder sb, 
                                      List<String> diffInList,
                                      String msg) {
        if(!diffInList.isEmpty()){
            sb.append(diffInList.size())
              .append(msg)
              .append(String.join(",", diffInList));
        }
    }

    private static List<String> getMissingInList(List<String> list1, 
                                                 List<String> list2) {
        return list2.stream()
                    .filter(e -> !list1.contains(e))
                    .collect(Collectors.toList());
    }
}

Output:

Case 1: No difference
Case 2: 1 item(s) missing in sended: A
Case 3: 2 item(s) missing in order: B,C
Case 4: 2 item(s) missing in order: B,C && 1 item(s) missing in sended:F
GD07
  • 1,117
  • 1
  • 7
  • 9
1

Generally speaking I think it is the correct approach, you need to subtract one from the other. I can only think of slight improvements here and there:

  • when "No difference" you can return the String directly, no need for the StringBuilder. Also returning directly removes one level of nesting (no else)
  • The Stream joining collector allows to set a prefix and a suffix so you et the " around the items for free.
  • You can factor the formatting of "x item(s) missing in list: "X", "Y"
private static String getDifference(List<String> order, List<String> sent) {
    if (new HashSet<>(order).equals(new HashSet<>(sent))) {
        return "No difference";
    }

    StringBuilder output = new StringBuilder();

    List<String> auxOrder = new ArrayList<>(order);
    auxOrder.removeAll(sent);
    if (!auxOrder.isEmpty()) {
        output.append(format(auxOrder, "sent"));
    }

    List<String> auxSent = new ArrayList<>(sent);
    auxSent.removeAll(order);
    if (!auxSent.isEmpty()) {
        output.append(auxOrder.isEmpty() ? "" : " & ");
        output.append(format(auxSent, "order"));
    }

    return output.toString();
}

private static String format(List<String> missing, String listName) {
    return "%s item(s) missing from %s : %s".formatted(
            missing.size(),
            listName,
            missing.stream().collect(Collectors.joining("\", \"", "\"", "\""))
    );
}

If they are big lists and the removeAll is costly, you can improve performance by checking the size of the lists beforehand and only do the removeAll if the first list is longer than the second

Bentaye
  • 9,403
  • 5
  • 32
  • 45
  • 2
    First, thanks for the help. Second, what if the 2 lists have the same size but diferent items? – Diego Borba Aug 14 '23 at 18:05
  • 1
    @DiegoBorba is correct, your solution is fundamentally broken because 2 lists could have the same size, but different elements – davidalayachew Aug 14 '23 at 22:20
  • @DiegoBorba then I would use a `Set`, so that order and duplication does not matter, and check if they are equals. If the elements are `String`, fine, if they are objects you have to make sure their `equals` method is correctly implemented. Same for the `removeAll` too anyways – Bentaye Aug 15 '23 at 09:18
  • 1
    @Bentaye That's at least a workable solution, but that is not what your answer currently says. Fix your answer, because right now, as it is written, it is plain wrong. – davidalayachew Aug 15 '23 at 12:54
  • @Bentaye I see the update. Now what you are saying matches your answer. – davidalayachew Aug 19 '23 at 18:00
1

First, it's not clear enough if there are duplicates or not. Secondly, if you don't care about order then don't use the list equals() method as it will fail in many cases where both lists contains the same items but in different order.

Besides that, the algorithm should work but it could be optimized. I believe the main problem is that using the removeAll() method between a list of size n and a list of size m has a worst time complexity of O(n*m) (quadratic) when you could actually achieve a time complexity of O(n+m) (lineal). If you don't care about duplicates you could slightly modify your solution to use HashSet to achieve this goal in the following way:

// If duplicates are irrelevant
private static String getDifference(List<String> ordered, List<String> sent) {
    Set<String> orderedSet = new HashSet<>(ordered);
    Set<String> sentSet = new HashSet<>(sent);
    if (orderedSet.equals(sentSet)) {
        return "No difference";
    } else {
        StringBuilder output = new StringBuilder();
        orderedSet.removeAll(sent);

        if (!orderedSet.isEmpty()) {
            output.append(orderedSet.size()).append(" item(s) missing in sent: ");
            output.append("\"").append(String.join("\", \"", orderedSet)).append("\"");
        }

        sentSet.removeAll(ordered);

        if (!sentSet.isEmpty()) {
            if (output.length() > 0) {
                output.append(" & ");
            }

            output.append(sentSet.size()).append(" item(s) missing in ordered: ");
            output.append("\"").append(String.join("\", \"", sentSet)).append("\"");
        }

        return output.toString();
    }
}

If you care about duplicates then you aren't actually comparing Lists but Multisets. For a Multiset implementation you could use the Guava library. A possible implementation with Multisets looks like this:

// If duplicates are relevant, with Guava
private static String getDifferenceWithDuplicates(List<String> ordered, List<String> sent) {
    Multiset<String> orderedSet = HashMultiset.<String>create(ordered);
    Multiset<String> sentSet = HashMultiset.<String>create(sent);
    if (orderedSet.equals(sentSet)) {
        return "No difference";
    } else {
        StringBuilder output = new StringBuilder();

        for (String item : Sets.union(orderedSet.elementSet(), sentSet.elementSet())) {
            int orderedCount = orderedSet.count(item);
            int sentCount = sentSet.count(item);

            if (orderedCount != sentCount) {
                if (output.length() > 0) {
                    output.append(" & ");
                }

                output.append(Math.abs(orderedCount - sentCount)).append(" item(s) missing in ");
                output.append(orderedCount > sentCount ? "sent" : "ordered").append(": ");
                output.append("\"").append(item).append("\"");
            }
        }
        return output.toString();
    }
}

If for some reason you can't use Guava, then you can implement the same behavior of a HashMultiset using a HashMap of type Map<String, Integer> where the key is the item and the value is the amount of appearances in the original list (this is an exercise for the reader).

1

Your solution is pretty good, but there's a little room for improvement.

First off, you used .equals(). Most of the time, this is wise, but doing .equals() will iterate through both lists until it finds the first difference. This is bad because, you don't just want to know if the 2 lists are different, but you also want to know what those differences are. That means that, after you have iterated through these lists once just to confirm that there is a difference, finding those differences will require you to iterate through both lists again, which slows down your solution considerably.

Instead, I am going to do an optimistic == check here, just to handle the chance that someone passed in the same list. I'm also adding null checks by using Objects.requireNonNull(object, message). I'm also removing your else clause by just returning early instead.

None of the above changes are really necessary, but will eliminate the easy edge cases. Feel free to leave them out if you feel it is unnecessary or complicates the solution needlessly.

(Speaking of complexity, once Valhalla comes out, this == might not be a good idea anymore. I can't predict the future of what will and won't be good advice for features that haven't come out yet, but putting this note for future readers working in a post-Valhalla world).

private static String getDifference(final List<String> list1, final List<String> list2)
{

    Objects.requireNonNull(list1, "list1 cannot be null");
    Objects.requireNonNull(list2, "list2 cannot be null");

    if (list1== list2)
    {
        return "No difference";
    } 

    //in progress

}

Next, let's find a quick way to find the missing elements in both. I don't want to make any destructive changes to either list passed in. Plus, Java has some lists that are Unmodifiable Lists. In general, it is safer to just make a copy of what you are given and go from there.

Please note though, if it is part of your contract that you are allowed to make destructive changes to the incoming lists, then not making a copy would be more efficient. Regardless, I will make a copy to be safe. Let's do that now.

private static String getDifference(final List<String> list1, final List<String> list2)
{

    Objects.requireNonNull(list1, "list1 cannot be null");
    Objects.requireNonNull(list2, "list2 cannot be null");

    if (list1 == list2)
    {
        return "No difference";
    } 

    final List<String> copy1 = new ArrayList<>(list1);
    final List<String> copy2 = new ArrayList<>(list2);

    //in progress

}

Ok, now that we have created our copies, let's remove the elements that are common to both lists. To do that, we will make use of the List.removeAll() method.

private static String getDifference(final List<String> list1, final List<String> list2)
{

    Objects.requireNonNull(list1, "list1 cannot be null");
    Objects.requireNonNull(list2, "list2 cannot be null");

    if (list1 == list2)
    {
        return "No difference";
    } 

    final List<String> copy1 = new ArrayList<>(list1);
    final List<String> copy2 = new ArrayList<>(list2);

    //Yes, we are removing the OTHER SOURCE LIST from the COPY LIST
    copy1.removeAll(list2);
    copy2.removeAll(list1);

    //in progress

}

Ok, we found the differences now. All that is left to do is to return the results. I will go ahead and do some basic formatting, since that seemed important to you.

private static String getDifference(final List<String> list1, final List<String> list2)
{

    Objects.requireNonNull(list1, "list1 cannot be null");
    Objects.requireNonNull(list2, "list2 cannot be null");

    if (list1 == list2)
    {
        return "No difference";
    } 

    final List<String> copy1 = new ArrayList<>(list1);
    final List<String> copy2 = new ArrayList<>(list2);

    //Yes, we are removing the OTHER SOURCE LIST from the COPY LIST
    copy1.removeAll(list2);
    copy2.removeAll(list1);

    if (copy1.isEmpty() && copy2.isEmpty())
    {
        return "No difference";
    }
    else if (copy1.isEmpty())
    {
        return "items missing in parameter 1 = " + copy2.toString();
    }
    else if (copy2.isEmpty())
    {
        return "items missing in parameter 2 = " + copy1.toString();
    }
    else
    {
        return "items missing in parameter 1 = " + copy2.toString() + " and items missing in parameter 2 = " + copy1.toString();
    }

}

That should solve your question. Here are some sample outputs.

final List<String> list1 = List.of("A", "B", "C", "D");
final List<String> list2 = List.of("A", "B", "C", "E");
final List<String> list3 = List.of("A", "B", "C");

getDifference(list1, list1) // "No difference"
getDifference(list1, list2) // "items missing in parameter 1 = [E] and items missing in parameter 2 = [D]"
getDifference(list1, list3) // "items missing in parameter 2 = [D]"
getDifference(list2, list1) // "items missing in parameter 1 = [D] and items missing in parameter 2 = [E]"
getDifference(list2, list2) // "No difference"
getDifference(list2, list3) // "items missing in parameter 2 = [E]"
getDifference(list3, list1) // "items missing in parameter 1 = [D]"
getDifference(list3, list2) // "items missing in parameter 1 = [E]"
getDifference(list3, list3) // "No difference"

davidalayachew
  • 1,279
  • 1
  • 11
  • 22
1

Below code iterates both lists while updating two, separate lists that contain differences. I think the advantage is that you iterate both lists once only. However, for the algorithm to work, both lists must be sorted (in ascending order). If you have a precondition whereby both lists are always sorted, then no need to perform the sort. Such a precondition would exist if, for example, both lists contained results of database queries because each query could have an ORDER BY clause.

More explanations after the code.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

    private static List<List<String>> diffs(List<String> lst1, List<String> lst2) {
        // For algorithm to work, both lists must be sorted.
        Collections.sort(lst1);
        Collections.sort(lst2);
        int ndx1 = 0;
        int ndx2 = 0;
        int size1 = lst1.size();
        int size2 = lst2.size();
        List<String> diffs1 = new ArrayList<>(size1); // strings in 'lst1' only
        List<String> diffs2 = new ArrayList<>(size2); // strings in 'lst2' only

        // Get first element in each list. Consider empty lists also.
        String str1 = size1 == 0 ? "" : lst1.get(ndx1);
        String str2 = size2 == 0 ? "" : lst2.get(ndx2);

        while (ndx1 < size1) {
            int difference = str1.compareTo(str2);
            // Always increment index of lower string
            if (difference > 0) {
                diffs2.add(str2);
                ndx2++;
                if (ndx2 < size2) {
                    str2 = lst2.get(ndx2);
                }
                else {
                    // if reach last element of 'lst2' add remaining elements of 'lst1'
                    while (ndx1 < size1) {
                        diffs1.add(lst1.get(ndx1++));
                    }
                    break;
                }
            }
            else if (difference < 0) {
                diffs1.add(str1);
                ndx1++;
                if (ndx1 < size1) {
                    str1 = lst1.get(ndx1);
                }
            }
            else {
                // strings are equal, increment both indexes
                ndx1++;
                if (ndx1 < size1) {
                    str1 = lst1.get(ndx1);
                }
                ndx2++;
                if (ndx2 < size2) {
                    str2 = lst2.get(ndx2);
                }
                else {
                    // if reach last element of 'lst2' add remaining elements of 'lst1'
                    while (ndx1 < size1) {
                        diffs1.add(lst1.get(ndx1++));
                    }
                    break;
                }
            }
        }
        // add remaining elements of 'lst2'
        while (ndx2 < size2) {
            diffs2.add(lst2.get(ndx2++));
        }
        return List.of(diffs1, diffs2);
    }

    private static void test(List<String> ordered, List<String> sent) {
        List<List<String>> diffs = diffs(ordered, sent);
        List<String> diffs0 = diffs.get(0);
        List<String> diffs1 = diffs.get(1);
        int count0 = diffs0.size();
        int count1 = diffs1.size();
        if (count0 == 0  &&  count1 == 0) {
            System.out.println("No differences.");
        }
        else {
            if (count1 > 0) {
                System.out.println("Missing in 'ordered': " + diffs1);
            }
            if (count0 > 0) {
                System.out.println("Missing in 'sent': " + diffs0);
            }
        }
        System.out.println("====================================================================");
    }

    public static void main(String[] args) {
        test(new ArrayList<>(List.of("A", "B")), new ArrayList<>(List.of("A", "B")));
        test(new ArrayList<>(List.of("A", "B", "C")), new ArrayList<>(List.of("B", "C")));
        test(new ArrayList<>(List.of("A", "D")), new ArrayList<>(List.of("A", "B", "C", "D")));
        test(new ArrayList<>(List.of("A", "D", "F")), new ArrayList<>(List.of("A", "B", "C", "D")));
    }
}

Note that method of (in interface java.util.List) was added in JDK 9. It creates an immutable list which means that the lists cannot be sorted. Hence I wrap each list in an ArrayList which creates a mutable list which can then be sorted.

Basically the algorithm works as follows (assuming that both lists are sorted).

  1. Get first element of both lists.
  2. Compare them.
  3. If they are not equal, add the lower one to its differences list and get the next element in the list containing the lower one.
  4. If they are equal, add nothing to either of the differences list and get the next element of both lists.
  5. Once you have iterated all the elements of one list, add any remaining elements of the other list to its differences list.
  6. First differences list contains all elements in first list that are not in second list and vice-versa.

Here is the output I got when I ran the above code:

No differences.
====================================================================
Missing in 'sent': [A]
====================================================================
Missing in 'ordered': [B, C]
====================================================================
Missing in 'ordered': [B, C]
Missing in 'sent': [F]
====================================================================
Abra
  • 19,142
  • 7
  • 29
  • 41
0

I think your problem isn't well defined. I will try to specify it in this way that your solution is correct.

There are two lists which represent ordered things and sent things. Missing objects can happen only in one list. The assumption of missing objects is important because otherwise your solution is incorrect.

Your solution can be improved using (Hash)Set data structure. removeAll method from ArrayList has quadratic time complexity, for HashSet it is linear (on average).

If your list can contain duplicates you would need to handle it when using HashSet ( for example by adding to objects some unique identifier ).

gryxon
  • 46
  • 4
0

User set to find mismatch

import java.util.Collections;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.lang.String;
import java.util.*;



public class Main {

  

    public static void main(String[] args) {
        
         // Case 1
        List<String> order1 = Arrays.asList(new String[] { "A", "B" });
        List<String> sended1 = Arrays.asList(new String[] { "A", "B" });

        // No difference
        System.out.println("Case 1: " + getMissingItemsDescription(order1, sended1));

        // Case 2
        List<String> order2 = Arrays.asList(new String[] { "A", "B", "C" });
        List<String> sended2 = Arrays.asList(new String[] { "B", "C" });

        // "1 item(s) missing in sended: A"
        System.out.println("Case 2: " + getMissingItemsDescription(order2, sended2));

        // Case 3
        List<String> order3 = Arrays.asList(new String[] { "A", "D" });
        List<String> sended3 = Arrays.asList(new String[] { "A", "B", "C", "D" });

        // "2 item(s) missing in order: B, C"
        System.out.println("Case 3: " + getMissingItemsDescription(order3, sended3));

        // Case 4
        List<String> order4 = Arrays.asList(new String[] { "A", "D", "F" });
        List<String> sended4 = Arrays.asList(new String[] { "A", "B", "C", "D" });

        // 1 item(s) missing in sended: "F" & 2 item(s) missing in order: "B", "C"
        System.out.println("Case 4: " + getMissingItemsDescription(order4, sended4));


    }

      public static <T> String getMissingItemsDescription(List<T> order, List<T> sent) {
        Set<T> orderSet = new HashSet<>(order);
        Set<T> sentSet = new HashSet<>(sent);
        
        Set<T> missingInOrder = new HashSet<>(orderSet);
        missingInOrder.removeAll(sentSet);
        
        Set<T> missingInSent = new HashSet<>(sentSet);
        missingInSent.removeAll(orderSet);

        StringBuilder description = new StringBuilder();
        
        if (!missingInOrder.isEmpty()) {
            description.append("Missing in order: ").append(missingInOrder).append("\n");
        }
        
        if (!missingInSent.isEmpty()) {
            description.append("Missing in sent: ").append(missingInSent);
        }
        
        if (description.length() == 0) {
            description.append("No missing items found");
        }

        return description.toString();
    }
}
divyang4481
  • 1,584
  • 16
  • 32
0

You could use Java 8 Streams:

public static String differences(List<String> ordered, List<String> sent) {
    //look for ordered items missing in sent
    List<String> orderedButNotSent = ordered.stream()
        .filter(el -> sent.stream().noneMatch(el::equals))
        .collect(Collectors.toList());

    //look for sent items missing in ordered
    List<String> sentButNotOrdered = sent.stream()
        .filter(el -> ordered.stream().noneMatch(el::equals))
        .collect(Collectors.toList());

    //build report
    StringBuilder report = new StringBuilder();
    report.append("orderedButNotSent: " + orderedButNotSent + "\n")
          .append("sentButNotOrdered: " + sentButNotOrdered + "\n");

    //return report
    return report.toString();
}
public static void main(String args[]) {
    List<String> ordered = Arrays.asList(new String[] { "A", "D", "F" });
    List<String> sent = Arrays.asList(new String[] { "A", "B", "C", "D" });
    
    String differences = differences(ordered,sent);
    //=> orderedButNotSent: [F]
    //=> sentButNotOrdered: [B, C]
}
Moob
  • 14,420
  • 1
  • 34
  • 47