3

I try to solve a problem since 2 days and my algorithm seems too complex. I need to produce 1 list of data that merge 2 ordered lists (named below "list A" and "list B") This merge must be based on a reference ordered list to identify where each element will be placed. If List A and List B contain same value for a rank, I need to keep only one of them

Ex :

Reference list : 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
List A : 2 | 4 | 6
List B : 1 | 4

Expected result : 1 | 2 | 4 | 6

Code

private static List<String> mergeListsUsingRef(final List<String> listRef, final List<String> listA, final List<String> listB) { final List<String> res = new ArrayList<>(); final Iterator<String> listRefIterator = listRef.iterator(); final Iterator<String> listAIterator = listA.iterator(); final Iterator<String> listBIterator = listB.iterator(); String a = listAIterator.hasNext() ? listAIterator.next() : null; String b = listBIterator.hasNext() ? listBIterator.next() : null; while (listRefIterator.hasNext() && (a != null || b != null)) { final String ref = listRefIterator.next(); if (a != null && ref.equals(a)) { res.add(a); if (b != null && ref.equals(b)) { b = listBIterator.hasNext() ? listBIterator.next() : null; } a = listAIterator.hasNext() ? listAIterator.next() : null; } else if (b != null && ref.equals(b)) { res.add(b); b = listBIterator.hasNext() ? listBIterator.next() : null; } } return res; } 

Now I have to complexify the initial problem. The main difficulty comes from the possibility of having same values several times into each list. This means finding which positions are allowed for duplicate values.

Let's imagine some examples to illustrate that (with 4 that appear two times into the reference list)

Reference list : 1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
List A : 2 | 3 | 5 | 4 | 7
List B : 4 | 7 | 8

Expected result : 2 | 3 | 5 | 4 | 7 | 8

Explaination :
In List A : regarding to reference list, 4 is between 5 and 7 so 4 can be placed only at the second rank
In List B : regarding to reference list, I can't determine where 4 is (first or second rank)
So, List A determine the 4's position at the second rank


Reference list : 1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
List A : 2 | 3 | 4 | 5 | 7
List B : 4 | 7 | 8

Expected result : 2 | 3 | 4 | 5 | 7 | 8

Explaination :
In List A : regarding to reference list, 4 is between 3 and 5 so 4 can be placed only at the first rank
In List B : regarding to reference list, I can't determine where 4 is (first or second rank)
So, List A determine the 4's position at the first rank


Reference list : 1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
List A : 2 | 3 | 4 | 5 | 7
List B : 6 | 4 | 7 | 8

Expected result : 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8

Explaination :
In List A : regarding to reference list, 4 is between 3 and 5 so 4 can be placed only at the first rank
In List B : regarding to reference list, 4 is between 6 and 7 so 4 can be placed only at the second rank
So, 4 must be placed at the first and second rank


Reference list : 1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
List A : 2 | 3 | 4 | 5 | 7
List B : 6 | 7 | 8

Expected result : 2 | 3 | 4 | 5 | 6 | 7 | 8

Explaination :
In List A : regarding to reference list, 4 is between 3 and 5 so 4 can be placed only at the first rank
In List B : 0 occurrence of 4 So, List A determine the 4's position at the first rank

My previous code does not work to deal with these difficult cases. Does anyone know how to help me to advance its implementation ? :)

EDIT : Main Java to validate the implementation :D

public static void main(String[] args) { System.out.println("EX 1 : "); List<String> refList = Arrays.asList("1", "2", "3", "4", "5", "6", "4", "7", "8"); List<String> listA = Arrays.asList("2", "3", "5", "4", "7"); List<String> listB = Arrays.asList("4", "7", "8"); List<String> mergeListsUsingRef = mergeListsUsingRef(refList, listA, listB); System.out.println("Expected : 2 | 3 | 5 | 4 | 7 | 8 "); System.out.print("Actual : "); for (String res : mergeListsUsingRef) { System.out.print(res + " | "); } System.out.println(""); System.out.println("----------------------------------"); System.out.println("EX 2 : "); refList = Arrays.asList("1", "2", "3", "4", "5", "6", "4", "7", "8"); listA = Arrays.asList("2", "3", "4", "5", "7"); listB = Arrays.asList("4", "7", "8"); mergeListsUsingRef = mergeListsUsingRef(refList, listA, listB); System.out.println("Expected : 2 | 3 | 4 | 5 | 7 | 8 "); System.out.print("Actual : "); for (String res : mergeListsUsingRef) { System.out.print(res + " | "); } System.out.println(""); System.out.println("----------------------------------"); System.out.println("EX 3 : "); refList = Arrays.asList("1", "2", "3", "4", "5", "6", "4", "7", "8"); listA = Arrays.asList("2", "3", "4", "5", "7"); listB = Arrays.asList("6", "4", "7", "8"); mergeListsUsingRef = mergeListsUsingRef(refList, listA, listB); System.out.println("Expected : 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8 "); System.out.print("Actual : "); for (String res : mergeListsUsingRef) { System.out.print(res + " | "); } System.out.println(""); System.out.println("----------------------------------"); System.out.println("EX 4 : "); refList = Arrays.asList("1", "2", "3", "4", "5", "6", "4", "7", "8"); listA = Arrays.asList("2", "3", "4", "5", "7"); listB = Arrays.asList("6", "7", "8"); mergeListsUsingRef = mergeListsUsingRef(refList, listA, listB); System.out.println("Expected : 2 | 3 | 4 | 5 | 6 | 7 | 8 "); System.out.print("Actual : "); for (String res : mergeListsUsingRef) { System.out.print(res + " | "); } } 
6
  • 3
    I don't understand what's the rule for cases where same value occurs multiple times in the reference list. Commented Nov 14, 2019 at 13:59
  • Looks Like intersection of Reference List with List A and List B, IF that is the case you can use HashSet with RetainAll method Commented Nov 14, 2019 at 14:03
  • Yes, intersect the two lists (reference list isn't relevant at all?!) ... maybe using sets. And yes, I agree: your requirements arent clear to me. Try to find example where the reference list actually matters. Commented Nov 14, 2019 at 14:42
  • I am assuming order of elements also matter here. Commented Nov 14, 2019 at 15:26
  • Why is the first 4 suppressed in EX 1, but not in EX 3? I don't get the logic. I can't infer the rules from what you've said. Commented Nov 14, 2019 at 15:46

4 Answers 4

1

I think this solution will work - it preserves the order of the lists, and gave the correct answers to all the samples shown above. By keeping the reference list as a hashMap of indexes, it allows to quickly query for any two integers if one can appear before the other by the reference. I added a printout in a main to test.

 public class ListMergeByRef { public Map<Integer, List<Integer>> refMap = new HashMap<Integer,List<Integer>>(); public ListMergeByRef(List<Integer> reference) { int elementIndex = 0; for (Integer element:reference) { List<Integer> refListPerElement = refMap.get(element); if (refListPerElement == null) { refListPerElement = new ArrayList<Integer>(); } refListPerElement.add(elementIndex); elementIndex++; refMap.put(element, refListPerElement); } } public List<Integer> mergeLists (List<Integer> first, List<Integer> second) { int firstIndex = 0; int secondIndex = 0; List<Integer> merged = new ArrayList<Integer>(); while (firstIndex < first.size() || secondIndex < second.size()) { if (firstIndex == first.size()) { merged.addAll(second.subList(secondIndex, second.size())); return merged; } else if (secondIndex == second.size()) { merged.addAll(first.subList(firstIndex, first.size())); return merged; } if (first.get(firstIndex).equals(second.get(secondIndex))){ merged.add(first.get(firstIndex)); firstIndex++; secondIndex++; } else if (isElementAllowedBeforeOther(first.get(firstIndex), second.get(secondIndex))) { merged.add(first.get(firstIndex)); firstIndex++; } else { merged.add(second.get(secondIndex)); secondIndex++; } } return merged; } public boolean isElementAllowedBeforeOther(Integer firstElement, Integer secondElement) { List<Integer> firstElementIndexes = refMap.get(firstElement); List<Integer> secondElementIndexes = refMap.get(secondElement); if (firstElementIndexes == null || firstElementIndexes.isEmpty()) return false; if (secondElementIndexes == null || secondElementIndexes.isEmpty()) return true; if (firstElementIndexes.get(0) < secondElementIndexes.get(secondElementIndexes.size()-1)) return true; return false; } public static void main(String[] args) { List<Integer> ref = Arrays.asList(new Integer[] {1,2,3,4,5,6,4,7,8}); List<Integer> first = Arrays.asList(new Integer[] {2,3,4,5,7}); List<Integer> second = Arrays.asList(new Integer[] {4,7,8}); ListMergeByRef merger = new ListMergeByRef(ref); List<Integer> mergedList = merger.mergeLists(first, second); for (Integer element: mergedList) { System.out.print(element+" "); } System.out.println(); } 
Sign up to request clarification or add additional context in comments.

2 Comments

@antoinecaron is there anything else I could do? It seems that you accepted the answer, but then removed the tick a few hours later.
it s a mistake, I tick it again! I made some changes to your solution because if you switch listA and listB, there is few issues.
1
private static List<String> mergeListsUsingRef(List<String> listRefRaw, List<String> listA, List<String> listB) { List<String> listRef = uniques(listRefRaw); Set<String> common = new HashSet<>(); common.addAll(listA); common.addAll(listB); List<String> result = new ArrayList<>(listRef); result.retainAll(common); return result; } 

If the reference list is huge compared to both lists:

 return listRef.stream() .filter(common::contains) .collect(Collectors.toList()); 

This will keep the order of the reference list. The main problem here is that both listA and listB are not sets, so there is no fast contains-check possible.

private static List<String> uniques(List<String> listRefRaw) { Set<String> uniqueValues = new HashSet<>(); return listRefRaw.stream() .filter(s -> !uniqueValues.contains(s)) .peek(uniqueValues::add) .collect(Collectors.toList()); } 

The Stream.distinct() unfortunately is not stable on unordered collections, so the above should do.

10 Comments

Since the second block uses streams, you should include how to build common with streams too, e.g.: Set<String> common = Stream.concat(listA.stream(), listB.stream()).collect(Collectors.toSet());
@Andreas Stream usage could naively use a suboptimal data structure List instead of Set.
@antoinecaron I wrongly assumed assumed the reference list to determine the order and be unique. I'll repair my answer.
@JoopEggen Huh? Where could stream usage use List instead of Set? The toSet() call mandates the use of Set.
@Andreas the listA and listB are Lists, and a List.contains is inferior to Set.contains. If you do mean to convert listA and listB into sets per Stream, that would make the code not one simple stream()-chain.
|
1

The first two tests fail, because your expected lists are missing the second 4. There are two 4s present in the lists to be merged.

Well anyways, I just went through the ref and removed any matching items from the lists that are attempting to be merged.

public static <E extends Comparable<E>>List<E> merge(List<E> refList, List<E>... lists) { List<E> result = new ArrayList<E>(); Iterator<E> ref = refList.iterator(); boolean found = false; while (ref.hasNext()) { E curr = ref.next(); found = false; // Reset for (List<E> list : lists) { if (found) continue; // If already found, skip the next list for (Iterator<E>it = list.iterator(); !found && it.hasNext();) { E term = it.next(); boolean equal = term.equals(curr); if (equal) { result.add(term); list.remove(term); found = true; } } } } return result; } 

Full Example

import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class ListReferenceJoiner { public static final boolean DEBUG = true; public static void main(String[] args) { System.out.printf("Equal? %b%n", test1()); System.out.println("==================="); System.out.printf("Equal? %b%n", test2()); System.out.println("==================="); System.out.printf("Equal? %b%n", test3()); System.out.println("==================="); System.out.printf("Equal? %b%n", test4()); } private static boolean test1() { List<Integer> refList = asArrayList(1, 2, 3, 4, 5, 6, 4, 7, 8); List<Integer> listA = asArrayList(2, 3, 5, 4, 7); List<Integer> listB = asArrayList(4, 7, 8); List<Integer> expected = asArrayList(2, 3, 5, 4, 7, 8); return testRunner(refList, listA, listB, expected); // 2, 3, 4, 5, 4, 7, 8 } private static boolean test2() { List<Integer> refList = asArrayList(1, 2, 3, 4, 5, 6, 4, 7, 8); List<Integer> listA = asArrayList(2, 3, 4, 5, 7); List<Integer> listB = asArrayList(4, 7, 8); List<Integer> expected = asArrayList(2, 3, 4, 5, 7, 8); return testRunner(refList, listA, listB, expected); // 2, 3, 4, 5, 4, 7, 8 } private static boolean test3() { List<Integer> refList = asArrayList(1, 2, 3, 4, 5, 6, 4, 7, 8); List<Integer> listA = asArrayList(2, 3, 4, 5, 7); List<Integer> listB = asArrayList(6, 4, 7, 8); List<Integer> expected = asArrayList(2, 3, 4, 5, 6, 4, 7, 8); return testRunner(refList, listA, listB, expected); // 2, 3, 4, 5, 6, 4, 7, 8 } private static boolean test4() { List<Integer> refList = asArrayList(1, 2, 3, 4, 5, 6, 4, 7, 8); List<Integer> listA = asArrayList(2, 3, 4, 5, 7); List<Integer> listB = asArrayList(6, 7, 8); List<Integer> expected = asArrayList(2, 3, 4, 5, 6, 7, 8); return testRunner(refList, listA, listB, expected); // 2, 3, 4, 5, 6, 7, 8 } private static boolean testRunner(List<Integer> ref, List<Integer> listA, List<Integer> listB, List<Integer> expected) { if (DEBUG) { System.out.printf("Expecting: %s%n", expected); } List<Integer> actual = merge(ref, listA, listB); // 2, 3, 4, 5, 6, 7, 8 if (DEBUG) { System.out.printf("Actual: %s%n", actual); } return expected.equals(actual); } @SuppressWarnings("unchecked") public static <E extends Comparable<E>>List<E> merge(List<E> refList, List<E>... lists) { List<E> result = new ArrayList<E>(); Iterator<E> ref = refList.iterator(); boolean found = false; while (ref.hasNext()) { if (DEBUG) { printLists(lists); } E curr = ref.next(); found = false; // Reset for (List<E> list : lists) { if (found) continue; // If already found, skip the next list for (Iterator<E>it = list.iterator(); !found && it.hasNext();) { E term = it.next(); boolean equal = term.equals(curr); if (equal) { if (DEBUG) { System.out.printf("Found '%s'%n", term); } result.add(term); list.remove(term); found = true; } } } if (DEBUG && !found) { System.out.printf("Could not find '%s'%n", curr); } } return result; } private static <E> void printLists(List<E>... lists) { for (ListIterator< List<E>>it = Arrays.asList(lists).listIterator(); it.hasNext();) { printList(it.next(), getCharForNumber(it.nextIndex())); } } private static <E> void printList(List<E> list, String label) { System.out.printf("%s: ", label); for (Iterator< E>it = list.iterator(); it.hasNext();) { System.out.print(it.next()); if (it.hasNext()) { System.out.print(" | "); } } System.out.print(System.lineSeparator()); } @SuppressWarnings("unchecked") private static <E> List<E> asArrayList(E... values) { return new ArrayList<E>(Arrays.asList(values)); } protected static String getCharForNumber(int i) { return i > 0 && i < 27 ? String.valueOf((char) (i + 64)) : null; } } 

Here is a version of Maurice Perry's response, but it still does not preserve order.

private static <E> List<E> merge(final List<E> listRef, final List<E>... lists) { final List<E> res = new ArrayList<E>(); final List<List<E>> copies = Arrays.stream(lists).map(ArrayList::new).collect(Collectors.toList()); for (E ref : listRef) { boolean found = false; for (Iterator<List<E>> it = copies.iterator(); !found && it.hasNext();) { if (it.next().remove(ref)) { res.add(ref); found = true; } } if (found) { continue; } } return res; } 

Example Output

Testing: [2, 3, 5, 4, 7, 8] A: 2 | 3 | 5 | 4 | 7 B: 4 | 7 | 8 Could not find '1' A: 2 | 3 | 5 | 4 | 7 B: 4 | 7 | 8 Found '2' A: 3 | 5 | 4 | 7 B: 4 | 7 | 8 Found '3' A: 5 | 4 | 7 B: 4 | 7 | 8 Found '4' A: 5 | 7 B: 4 | 7 | 8 Found '5' A: 7 B: 4 | 7 | 8 Could not find '6' A: 7 B: 4 | 7 | 8 Found '4' A: 7 B: 7 | 8 Found '7' A: B: 7 | 8 Found '8' Actual: [2, 3, 4, 5, 4, 7, 8] Equal? false =================== Testing: [2, 3, 4, 5, 7, 8] A: 2 | 3 | 4 | 5 | 7 B: 4 | 7 | 8 Could not find '1' A: 2 | 3 | 4 | 5 | 7 B: 4 | 7 | 8 Found '2' A: 3 | 4 | 5 | 7 B: 4 | 7 | 8 Found '3' A: 4 | 5 | 7 B: 4 | 7 | 8 Found '4' A: 5 | 7 B: 4 | 7 | 8 Found '5' A: 7 B: 4 | 7 | 8 Could not find '6' A: 7 B: 4 | 7 | 8 Found '4' A: 7 B: 7 | 8 Found '7' A: B: 7 | 8 Found '8' Actual: [2, 3, 4, 5, 4, 7, 8] Equal? false =================== Testing: [2, 3, 4, 5, 6, 4, 7, 8] A: 2 | 3 | 4 | 5 | 7 B: 6 | 4 | 7 | 8 Could not find '1' A: 2 | 3 | 4 | 5 | 7 B: 6 | 4 | 7 | 8 Found '2' A: 3 | 4 | 5 | 7 B: 6 | 4 | 7 | 8 Found '3' A: 4 | 5 | 7 B: 6 | 4 | 7 | 8 Found '4' A: 5 | 7 B: 6 | 4 | 7 | 8 Found '5' A: 7 B: 6 | 4 | 7 | 8 Found '6' A: 7 B: 4 | 7 | 8 Found '4' A: 7 B: 7 | 8 Found '7' A: B: 7 | 8 Found '8' Actual: [2, 3, 4, 5, 6, 4, 7, 8] Equal? true =================== Testing: [2, 3, 4, 5, 6, 7, 8] A: 2 | 3 | 4 | 5 | 7 B: 6 | 7 | 8 Could not find '1' A: 2 | 3 | 4 | 5 | 7 B: 6 | 7 | 8 Found '2' A: 3 | 4 | 5 | 7 B: 6 | 7 | 8 Found '3' A: 4 | 5 | 7 B: 6 | 7 | 8 Found '4' A: 5 | 7 B: 6 | 7 | 8 Found '5' A: 7 B: 6 | 7 | 8 Found '6' A: 7 B: 7 | 8 Could not find '4' A: 7 B: 7 | 8 Found '7' A: B: 7 | 8 Found '8' Actual: [2, 3, 4, 5, 6, 7, 8] Equal? true 

Comments

1

Your code works correctly!

There are however 2 issues:

  • Your logic doesn't need the null checks.

    Since equals(null) is required to return false, you can safely reduce if (a != null && ref.equals(a)) to just if (ref.equals(a))

  • Your expectation for EX 1 is incorrect.

    The values match up like this:

    ref: "1", "2", "3", "4", "5", "6", "4", "7", "8" --------------------------------------------------- A: "2", "3", "5", "4", "7" B: "4", "7", "8" =================================================== result: "2", "3", "4", "5", "4", "7", "8" 

    But you're expecting

     "2", "3", "5", "4", "7", "8" 

    As you can see, you should be expecting the first 4 too.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.