3

I have a List<List<String>> with sample data like:

("T1#R1", "T1#R1", "T1#R3", "T1#R4")
("T1#R1", "T1#R1", "T1#R3", "T1#R5")
("T1#R1", "T1#R1", "T1#R6", "T1#R4")
("T1#R1", "T1#R1", "T1#R6", "T1#R5")

And I need to assert, that a List<String> is present in the above sample, but without taking order into consideration.

For example, the following list ("T1#R1", "T1#R1", "T1#R4", "T1#R3") should be considered as present in the List<List<String>>, as it would contain the same items as the 1st list, but in different order.

On the other hand, ("T1#R1", "T1#R3", "T1#R4", "T1#R3") shouldn't be considered as present in the list, as it has the same items, but with a different count.

I know I could do this programmatically, but was wandering if there could be a Matcher for example that could help.

I've seen assertions like:

assertThat(myList, containsInAnyOrder(anotherList.toArray())

But that would just compare one list with another, and not a list inside a List of Lists.

PS: I'm using Java6, hamcrest-core-1.3, testng-5.14.1

juherr
  • 5,640
  • 1
  • 21
  • 63
Manuel S.
  • 411
  • 8
  • 21
  • 1
    I would iterate throug your list of lists, wrap each list into a `Set` as well as the list to check and compare the sets since there is no order specification – Stefan Warminski Jun 15 '17 at 13:09
  • I might need to change my example, as the lists could have repeated elements, so a Set wouldn't have into count number of repetitions of an element in the list. – Manuel S. Jun 15 '17 at 13:31

3 Answers3

3

I don't know of any matcher that can do what you want, so I'm afraid you'll have to program it.

I would simply sort the target list and then I'd iterate the sublists until a match is found:

List<String> target = new ArrayList<>(anotherList);
target.sort();

boolean result = myList.stream()
    .anyMatch(sublist -> equalsInAnyOrder(sublist, target));

Where method equalsInAnyOrder would be as follows:

public <T> boolean equalsInAnyOrder(List<T> sublist, List<T> target) {

    List<String> copy = new ArrayList<>(sublist);
    copy.sort();

    return copy.equals(target);
}

This sorts each sublist and compares it with the target sorted list, so it's not performance-wise, but at least it's simple and succint code.


EDIT as per OP's need to target Java 6:

The logic is exactly the same as in the Java 8 version. First sort the target list and then compare each sublist until a match is found:

List<String> target = new ArrayList<>(anotherList);
Collections.sort(target);

The stream() with anyMatch has now become a while loop:

boolean match = false;
Iterator<List<String>> it = myList.iterator();
while (it.hasNext() && !match) {
    List<String> sublist = it.next();
    match = equalsInAnyOrder(sublist, target);
}

And now method equalsInAnyOrder looks like this:

public <T> boolean equalsInAnyOrder(List<T> sublist, List<T> target) {

    List<String> copy = new ArrayList<>(sublist);
    Collections.sort(copy);

    return copy.equals(target);
}
fps
  • 33,623
  • 8
  • 55
  • 110
  • nice idea, less overhead than my solution i think – Stefan Warminski Jun 15 '17 at 14:09
  • @StefanWarminski Your solution might have more overhead because you're streaming each sublist, but it's `O(n x m)`, while mine is `O(n x m log m)`, so your solution is better for larger lists. – fps Jun 15 '17 at 14:11
  • 1
    This solutions looks more "readable" to me, and is also adapted to Java 6, that's why I mark it as the correct one. – Manuel S. Jun 15 '17 at 15:41
2

I don't get a one-line-solution but my tests passes.

I iterate through the list of lists, wrapping each list to a map (entry is the key, count the value) as well as for the list to check. Now I can check for equality:

public void listOfLists() throws Exception {
    List<List<String>> myList = Arrays.asList(
        Arrays.asList("T1#R1", "T1#R1", "T1#R3", "T1#R4"),
        Arrays.asList("T1#R1", "T1#R1", "T1#R3", "T1#R5"),
        Arrays.asList("T1#R1", "T1#R1", "T1#R6", "T1#R4"),
        Arrays.asList("T1#R1", "T1#R1", "T1#R6", "T1#R5"));
    List<String> easy = Arrays.asList("T1#R1", "T1#R1", "T1#R4", "T1#R3");
    List<String> duplicate = Arrays.asList("T1#R1", "T1#R5", "T1#R1", "T1#R6");
    List<String> noMatch = Arrays.asList("T1#R1", "T1#R5", "T1#R6");

    Map<String, Integer> easyCount = countEntries(easy);
    Map<String, Integer> duplicateCount = countEntries(duplicate);
    Map<String, Integer> noCount = countEntries(noMatch);

    for (List<String> l : myList) {
        Map<String, Integer> countedEntries = countEntries(l);
        if (countedEntries.equals(easyCount)) {
            System.out.println("easy matches");
        }
        if (countedEntries.equals(duplicateCount)) {
            System.out.println("duplicate matches");
        }
        if (countedEntries.equals(noCount)) {
            System.out.println("Damn!");
        }
    }
}

private Map<String, Integer> countEntries(List<String> original) {
    return original.stream()
        .collect(Collectors.toMap(Function.identity(), s -> 1, Integer::sum));
}

This prints

easy matches
duplicate matches
Stefan Warminski
  • 1,845
  • 1
  • 9
  • 18
0

I don't know any Matcher that does that, do you care about dupiclates or not? (will you have duplicate values in your strings?)

I would go for somthing like that:

private boolean containsInAnyOrder(List<List<String>> container, List<String> list)
{
  for (List<String> list1 : container)
  {
     if(list.size()==list1.size())
     {
        if(list1.containsAll(list))
        {
           return true;
        }
     }
  }
  return false;
}

This would work only if you don't care about duplicate elements and there count.

("T1#R1", "T1#R1", "T1#R1", "T1#R2")
("T1#R1", "T1#R2", "T1#R2", "T1#R1")

would return true for example. Would you have duplicates? (same for Set solution, it would not work for the same reason)

In case of duplicates you need to count each items:

private boolean containsInAnyOrder(List<List<String>> container, List<String> list)
{
  for (List<String> list1 : container)
  {
     if(list.size()==list1.size())
     {
        boolean found = true;
        for(String string : list)
        {
           if (list.stream().filter(pS -> pS.equals(string)).count() != list1.stream().filter(pS -> pS.equals(string)).count())
           {
              found = false;
           }
        }
        if(found)
        {
           return true;
        }
     }
  }
  return false;
}

Note that it's not optimised, inner for loop could be stop before in case of mismatch.

B. Bri
  • 546
  • 2
  • 7
  • 23
  • I can have duplicates, and the number of occurrences should match. I'll update my question. – Manuel S. Jun 15 '17 at 13:33
  • In that case you will need to count and compare counts. I don't think the hasItems will work it will stop after the first match if @Jorn Vernee is talking about the hamcrest matcher. – B. Bri Jun 15 '17 at 13:41
  • I'll try this out, with the necessary changes to adapt it to Java6, as that's the version I'm using. – Manuel S. Jun 15 '17 at 14:02