【问题标题】:Java - How can I merge 2 lists based on a third?Java - 如何根据第三个列表合并 2 个列表?
【发布时间】:2020-03-10 12:07:44
【问题描述】:

我从 2 天开始就尝试解决一个问题,但我的算法似乎太复杂了。 我需要生成 1 个合并 2 个有序列表的数据列表(在“列表 A”和“列表 B”下面命名) 此合并必须基于参考有序列表来确定每个元素的放置位置。 如果列表 A 和列表 B 包含相同的排名值,我只需要保留其中一个

例如:

参考列表:1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
列表 A : 2 | 4 | 6
清单 B : 1 | 4

预期结果:1 | 2 | 4 | 6

代码

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;
}

现在我必须复杂化最初的问题。主要困难来自于在每个列表中多次具有相同值的可能性。 这意味着找出允许重复值的位置。

让我们想象一些例子来说明这一点(其中 4 出现在参考列表中两次)

参考列表:1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
列表 A : 2 | 3 | 5 | 4 | 7
列表 B:4 | 7 | 8

预期结果:2 | 3 | 5 | 4 | 7 | 8

说明:
在列表 A 中:关于参考列表,4 介于 5 和 7 之间,因此 4 只能放在第二位
在列表 B 中:关于参考列表,我无法确定 4 在哪里(第一或第二级)
所以,List A 确定了 4 在第二名的位置


参考列表:1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
列表 A : 2 | 3 | 4 | 5 | 7
列表 B:4 | 7 | 8

预期结果:2 | 3 | 4 | 5 | 7 | 8

说明:
在列表 A 中:关于参考列表,4 介于 3 和 5 之间,因此 4 只能放在第一排
在列表 B 中:关于参考列表,我无法确定 4 在哪里(第一或第二级)
所以,List A 确定 4 的排名第一


参考列表:1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
列表 A : 2 | 3 | 4 | 5 | 7
清单 B : 6 | 4 | 7 | 8

预期结果:2 | 3 | 4 | 5 | 6 | 4 | 7 | 8

说明:
在列表 A 中:关于参考列表,4 介于 3 和 5 之间,因此 4 只能放在第一排
在列表 B 中:关于参考列表,4 介于 6 和 7 之间,因此 4 只能放在第二位
所以,4必须放在第一和第二位


参考列表:1 | 2 | 3 | 4 | 5 | 6 | 4 | 7 | 8
列表 A : 2 | 3 | 4 | 5 | 7
清单 B : 6 | 7 | 8

预期结果:2 | 3 | 4 | 5 | 6 | 7 | 8

说明:
在列表 A 中:关于参考列表,4 介于 3 和 5 之间,因此 4 只能放在第一排
在列表 B 中:0 次出现 4 所以,List A 确定 4 的排名第一

我以前的代码无法处理这些困难的情况。 有谁知道如何帮助我推进其实施? :)

编辑: 用于验证实现的主要 Java :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 + " | ");
        }
    }

【问题讨论】:

  • 我不明白在参考列表中多次出现相同值的情况下的规则是什么。
  • 看起来像引用列表与列表 A 和列表 B 的交集,如果是这种情况,您可以将 HashSet 与 RetainAll 方法一起使用
  • 是的,将两个列表相交(参考列表根本不相关?!)......也许使用集合。是的,我同意:我不清楚您的要求。尝试找到参考列表真正重要的示例。
  • 我假设元素的顺序在这里也很重要。
  • 为什么前 4 个在 EX 1 中被抑制,而在 EX 3 中没有?我不明白这个逻辑。我无法从你所说的推断出规则。

标签: java algorithm list collections merge


【解决方案1】:

您的代码工作正常!

但是有两个问题:

  • 您的逻辑不需要空值检查。

    由于equals(null)required 以返回false,您可以安全地将if (a != null &amp;&amp; ref.equals(a)) 减少为if (ref.equals(a))

  • 您对 EX 1 的期望不正确。

    这些值是这样匹配的:

    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"
    

    但你期待

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

    如您所见,您也应该期待第一个 4

【讨论】:

    【解决方案2】:

    前两个测试失败,因为您预期的列表缺少第二个 4。要合并的列表中有两个 4。

    不管怎样,我刚刚浏览了 ref 并从尝试合并的列表中删除了所有匹配项。

    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;
    }
    

    完整示例

    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;
        }
    }
    

    这是莫里斯·佩里的回应的一个版本,但它仍然没有保持秩序。

    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;
    }
    

    示例输出

    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
    

    【讨论】:

      【解决方案3】:

      我认为这个解决方案会起作用 - 它保留了列表的顺序,并为上面显示的所有示例提供了正确的答案。通过将引用列表保留为索引的 hashMap,它允许快速查询任意两个整数,如果一个整数可以通过引用出现在另一个之前。我在 main 中添加了一个打印输出以进行测试。

       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();
          }
      

      【讨论】:

      • @antoinecaron 还有什么我可以做的吗?您似乎接受了答案,但几个小时后又删除了勾号。
      • 打错了,我再打勾!我对您的解决方案进行了一些更改,因为如果您切换 listA 和 listB,则几乎没有问题。
      【解决方案4】:
      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;
      }
      

      如果参考列表与两个列表相比都很大:

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

      这将保持参考列表的顺序。这里的主要问题是 listA 和 listB 都不是集合,因此不可能进行快速包含检查。

      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());
      }
      

      不幸的是,Stream.distinct() 在无序集合上不稳定, 所以上面应该做。

      【讨论】:

      • 由于第二个块使用流,您应该包括如何使用流构建common,例如:Set&lt;String&gt; common = Stream.concat(listA.stream(), listB.stream()).collect(Collectors.toSet());
      • @Andreas Stream 使用可能天真地使用次优数据结构 List 而不是 Set。
      • @antoinecaron 我错误地假设参考列表来确定顺序并且是唯一的。我会修复我的答案。
      • @JoopEggen 嗯?流使用在哪里可以使用List 而不是SettoSet() 调用要求使用 Set
      • @Andreas listA 和 listB 是 List,List.contains 不如 Set.contains。如果您确实打算将 listA 和 listB 转换为每个 Stream 的集合,那将使代码不是一个简单的stream()-chain。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-11
      • 2013-06-07
      • 2023-02-10
      相关资源
      最近更新 更多