【问题标题】:Mixing two generic lists java混合两个通用列表java
【发布时间】:2018-06-27 19:07:01
【问题描述】:

我有一个任务,我需要编写一个方法来混合两个通用列表并返回一个新的混合列表。

混合列表应该有 l1 的第一个元素在新混合列表的第一个位置,l2 的第一个元素应该在第二个位置,然后 l1 的第二个元素应该在第三个位置,依此类推。

如果一个列表比另一个长,则应按原始顺序添加其余列表。

一个例子是:l1 = (1,2,3) and l2=(9,8) --> 混合列表 = (1,9,2,8,3)

public <S, T> List<T> listeMischen(List<S> l1, List<T> l2) {
    List<T> newlist = new ArrayList<T>();
    for(int i = 0; i < l1.size(); i++)
    { 
        for(int j = 0; j < l2.size(); j++) {
            newlist.add(charAt(i));
            newlist.add(charAt(j));
        }           
    }
    return newlist;
}

附:我不知道如何正确添加元素,因为它们是通用的。我输入了绝对错误的“charAt”方法,只是为了展示如果类型不是通用的而是字符,我会尝试做什么。 然而,由于这些元素可能是通用的,我非常不确定该怎么做。

【问题讨论】:

  • 您的退货清单必须是 T 型吗?在那种情况下,这意味着 S 是 T 的子类。如果不是这种情况,我想最好的办法是返回一个 Object 列表(或者如果存在,则返回 S 和 T 的超类)
  • 这两个列表是否总是具有相同的类型?
  • 另外,请注意您的算法不会达到您想要的效果。您应该有一个从 0 到较小列表大小的第一个循环。然后你可以有两个循环从那个大小到每个列表的末尾,用剩余的元素完成你的结果列表(不知道我是否清楚:-P)
  • 我建议您使用“zip”作为方法名称的一部分...合并的方式就像“拉上/拉下拉链”。
  • 肯定是个好名字。但是我住在德国,我的讲师希望我使用这个名字:)

标签: java list generics arraylist


【解决方案1】:

这将返回两个参数列表的泛型类型的公共超类型列表:

public <R, S extends R, T extends R> List<R> listeMischen(List<S> l1, List<T> l2) {
    List<R> newList = new ArrayList<>(l1.size() + l2.size());
    int sizeOfLargerList = Math.max(l1.size(), l2.size());
    for (int i = 0; i < sizeOfLargerList; i++) {
        if (i < l1.size())
            newList.add(l1.get(i));
        if (i < l2.size())
            newList.add(l2.get(i));
    }
    return newList;
}

用法:

public static void main(String[] args) {
    List<Number> list = listeMischen(Arrays.asList(1, 2, 3), Arrays.asList(4.5, 5.5, 6.5, 7.5, 8.5));
    System.out.println(list);
}

预期输出 == 实际输出:

[1, 4.5, 2, 5.5, 3, 6.5, 7.5, 8.5]


更新:添加考虑到 cmets 的优化方法

下面显示了该方法的两个重载:一个用于随机访问列表(例如 ArrayList),另一个用于任何旧的可迭代类型,并针对每种类型优化了主体。

static <R> List<R> listeMischen(List<? extends R> l1, List<? extends R> l2) {
    if (!(l1 instanceof RandomAccess && l2 instanceof RandomAccess))
        return listeMischen((Iterable<? extends R>) l1, (Iterable<? extends R>) l2);

    // Preallocate with known exact required capacity
    List<R> newList = new ArrayList<>(l1.size() + l2.size());
    int sizeOfSmallerList = Math.min(l1.size(), l2.size());
    int i;
    // Zip the lists up to common maximum index
    for (i = 0; i < sizeOfSmallerList; i++) {
        newList.add(l1.get(i));
        newList.add(l2.get(i));
    }
    // Add any remaining items from one or the other list
    for (; i < l1.size(); i++)
        newList.add(l1.get(i));
    for (; i < l2.size(); i++)
        newList.add(l2.get(i));
    return newList;
}

static <R> List<R> listeMischen(Iterable<? extends R> l1, Iterable<? extends R> l2) {
    List<R> newList = new ArrayList<>();
    Iterator<? extends R> it1 = l1.iterator();
    Iterator<? extends R> it2 = l2.iterator();
    // Zip the lists up to common maximum index
    while (it1.hasNext() && it2.hasNext()) {
        newList.add(it1.next());
        newList.add(it2.next());
    }
    // Add any remaining items from one or the other lists
    it1.forEachRemaining(newList::add);
    it2.forEachRemaining(newList::add);
    return newList;
}

【讨论】:

  • 首先在最小列表长度上循环可能会更有效一些,没有 if 和在循环内重复调用 lx.size(),然后使用 if { for 添加任一列表的其余部分(如果有) () } -else-if { for () }
  • @ValentinRuano 是的。或者,我可以使用.hasNext() 而不是使用List.size()(就像OldCurmudgeon 的回答一样)。
  • @DodgyCodeException 我发现在实践中,如果您使用 ArrayLists,for-get 循环比使用迭代器更快,我认为由于编译器能够更好地优化代码。但是,当人们不知道 get 操作是否像 LinkedList 那样效率低下时,迭代器版本更可取。在任何情况下,hasNext 仍然是一个 if 和对可变对象(迭代器)的重复调用,所以我认为在实践中,只要您使用 ArrayList,for 循环就会更快。
  • 很好地使用有界类型参数 (+1)。
  • 另一个观察...实际上您不需要声明类型参数 S 和 T...您可以将 l1 和 l2 参数声明为 List。我想这不会造成任何伤害,但我认为? 更可取(更简约)。
【解决方案2】:

唯一明智的做法是返回Object 的列表。泛型不会有帮助,因为您的列表是非同质的(并且不保证您的列表的两种对象类型之间的关系,无论是暗示的还是明确的)。

public List<Object> mixedList(List<?> a, List<?> b) {
    List<Object> result = new ArrayList<>();
    for(int i = 0, j = 0; i < a.size() && j < b.size(); i++, j++) {
        result.add(a.get(i));
        result.add(b.get(j));
    }

    return result;
}

我将按照您规定的方式编织元素的实际问题留给读者作为练习。这应该满足两个列表长度相同的情况。

【讨论】:

    【解决方案3】:

    您必须为您的ST 找到一些共同点,ST,或者相反,或者两者都是 some U 的子类型:

    public <S extends T, T> List<T> listeMischen(List<S> l1, List<T> l2) {
        List<T> newlist = new ArrayList<>();
        // i will leave the "adding" logic to you, just for the sake of demonstration that you can add elements of both lists
        newlist.add(l1.get(0));
        newlist.add(l2.get(0));
    
        return newlist;
    }
    
    public <U, S extends U, T extends U> List<U> listeMischen2(List<S> l1, List<T> l2) {
        List<U> newlist = new ArrayList<>();
        // i will leave the "adding" logic to you, just for the sake of demonstration that you can add elements of both lists
        newlist.add(l1.get(0));
        newlist.add(l2.get(0));
    
        return newlist;
    } 
    

    【讨论】:

    • 啊,好的。我重新观看了有关仿制药的视频,您所说的很有道理。我应该记住这一点
    【解决方案4】:

    假设 S 和 T 之间有一个共同的类型,您可以使用 Iterators 轻松做到这一点。

    public <S extends T, T> List<T> listeMischen(Iterable<S> l1, Iterable<T> l2) {
        List<T> newlist = new ArrayList<T>();
        Iterator<S> l1i = l1.iterator();
        Iterator<T> l2i = l2.iterator();
        // Consume both while we can.
        while (l1i.hasNext() && l2i.hasNext()) {
            newlist.add(l1i.next());
            newlist.add(l2i.next());
        }
        // Consume whatever remains of the remaining list.
        while (l1i.hasNext()) {
            newlist.add(l1i.next());
        }
        while (l2i.hasNext()) {
            newlist.add(l2i.next());
        }
        return newlist;
    }
    

    如果没有通用类型或接口,那么您将不得不使用List&lt;Object&gt;

    【讨论】:

      【解决方案5】:

      其他答案表明不能使用泛型,或者结果的类型必须是List&lt;Object&gt;。更好的答案表明两个输入列表的元素与输出列表的元素之间必须存在某种类型关系。其中最好的是DodgyCodeException's answer (+1),它使用泛型类型边界要求结果列表的元素是两个输入列表的元素类型的超类型。

      我将提供一个替代方案,即结果列表的类型是从输入列表转换元素的结果,并且输入列表的元素类型不需要有任何彼此之间有直接关系,也不与结果列表的元素类型有直接关系。相反,提供了执行转换的函数参数。

      这是一个从 DodgyCodeException 派生的示例,它使用转换函数而不是要求输入和输出之间的类型关系。

      static <T, U, R> List<R> listMerge(List<T> l1, List<U> l2,
                                         Function<? super T, ? extends R> f1,
                                         Function<? super U, ? extends R> f2) {
          int size1 = l1.size();
          int size2 = l2.size();
          int max = Math.max(size1, size2);
          List<R> result = new ArrayList<>(size1 + size2);
      
          for (int i = 0; i < max; i++) {
              if (i < size1)
                  result.add(f1.apply(l1.get(i)));
              if (i < size2)
                  result.add(f2.apply(l2.get(i)));
          }
      
          return result;
      }
      

      这允许列表混合/转换,如下所示:

      List<String> list = listMerge(List.of(1, 2, 3), List.of(4.5, 5.5, 6.5, 7.5, 8.5),
                                    i -> String.format("0x%x", i),
                                    d -> String.format("%.1f", d));
      
      [0x1, 4.5, 0x2, 5.5, 0x3, 6.5, 7.5, 8.5]
      

      各种流压缩方法,例如 Guava 的 Streams.zip,也采用函数转换方法而不是类型绑定方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-10-19
        • 2019-04-27
        • 2019-11-22
        • 2019-04-24
        • 1970-01-01
        • 2017-08-22
        • 2016-03-16
        • 1970-01-01
        相关资源
        最近更新 更多