【问题标题】:Java: split a List into two sub-Lists?Java:将一个列表拆分为两个子列表?
【发布时间】:2010-09-27 15:08:38
【问题描述】:

在 Java 中将 List 拆分为两个子 List 的最简单、最标准和/或最有效的方法是什么?改变原始列表是可以的,所以不需要复制。方法签名可以是

/** Split a list into two sublists. The original list will be modified to
 * have size i and will contain exactly the same elements at indices 0 
 * through i-1 as it had originally; the returned list will have size 
 * len-i (where len is the size of the original list before the call) 
 * and will have the same elements at indices 0 through len-(i+1) as 
 * the original list had at indices i through len-1.
 */
<T> List<T> split(List<T> list, int i);

[EDIT] List.subList 返回原始列表上的视图,如果修改原始列表,则该视图无效。所以split 不能使用subList,除非它也省略了原始参考(或者,如Marc Novakowski 的回答,使用subList 但立即复制结果)。

【问题讨论】:

  • “最有效”不是取决于列表的具体类型吗?
  • 你的评论应该以“/**”开头,因为它是一个方法评论。
  • Hemal,可能。所以给我最简单最标准的。
  • @Hemal:幸运的是,使用 subList() 每个列表实现都可以做最快的事情。

标签: java list


【解决方案1】:

快速半伪代码:

List sub=one.subList(...);
List two=new XxxList(sub);
sub.clear(); // since sub is backed by one, this removes all sub-list items from one

它使用标准的 List 实现方法,避免了所有的循环运行。 clear() 方法还将对大多数列表使用内部removeRange(),并且效率更高。

【讨论】:

  • list.subList(first, Math.min(list.size(), first + pagesize));
  • @shanyangqu:不,因为子列表由原始支持,它不会从原始中清除子列表中的项目。基本上,子列表只是一个视图进入一个更大的列表。
【解决方案2】:

您可以使用常用的实用程序,例如 Guava 库:

import com.google.common.collect.Lists;
import com.google.common.math.IntMath;
import java.math.RoundingMode;

int partitionSize = IntMath.divide(list.size(), 2, RoundingMode.UP);
List<List<T>> partitions = Lists.partition(list, partitionSize);

结果是一个包含两个列表的列表 - 不完全符合您的规范,但如果需要,您可以轻松调整。

【讨论】:

    【解决方案3】:

    基于Marc's solution,此解决方案使用for 循环来保存对list.size() 的一些调用:

    <T> List<T> split(List<T> list, int i) {
        List<T> x = new ArrayList<T>(list.subList(i, list.size()));
        // Remove items from end of original list
        for (int j=list.size()-1; j>i; --j)
            list.remove(j);
        return x;
    }
    

    【讨论】:

      【解决方案4】:

      使用 subList 方法获取返回的数组非常简单,但据我所知没有简单的方法可以从 List 中删除一系列项目。

      这是我所拥有的:

      <T> List<T> split(List<T> list, int i) {
          List<T> x = new ArrayList<T>(list.subList(i, list.size()));
          // Remove items from end of original list
          while (list.size() > i) {
              list.remove(list.size() - 1);
          }
          return x;
      }
      

      【讨论】:

      • 一个澄清 - 我创建一个新的 ArrayList 是因为 subList 返回的列表是由原始列表支持的,并且由于我们稍后会对其进行修改,所以它会弄乱返回的列表。
      • 完全正确。感谢您指出我(现已删除)尝试中的错误 - 这就是我发布半睡半醒的结果......
      • .subList(i, list.Size()).clear() 应该从基本列表中删除子列表中包含的项目(如子列表的JavaDoc中所述)。请参阅下面的答案以获取示例。
      【解决方案5】:

      我需要类似的东西,所以这是我的实现。它允许调用者指定应该返回 List 的哪个实现:

      package com.mrojas.util;
      
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.List;
      
      public class ListUtils {
      
      /**
       * Splits a list into smaller sublists.
       * The original list remains unmodified and changes on the sublists are not propagated to the original list.
       *
       *
       * @param original
       *            The list to split
       * @param maxListSize
       *            The max amount of element a sublist can hold.
       * @param listImplementation
       *            The implementation of List to be used to create the returned sublists
       * @return A list of sublists
       * @throws IllegalArgumentException
       *             if the argument maxListSize is zero or a negative number
       * @throws NullPointerException
       *             if arguments original or listImplementation are null
       */
      public static final <T> List<List<T>> split(final List<T> original, final int maxListSize,
              final Class<? extends List> listImplementation) {
          if (maxListSize <= 0) {
              throw new IllegalArgumentException("maxListSize must be greater than zero");
          }
      
          final T[] elements = (T[]) original.toArray();
          final int maxChunks = (int) Math.ceil(elements.length / (double) maxListSize);
      
          final List<List<T>> lists = new ArrayList<List<T>>(maxChunks);
          for (int i = 0; i < maxChunks; i++) {
              final int from = i * maxListSize;
              final int to = Math.min(from + maxListSize, elements.length);
              final T[] range = Arrays.copyOfRange(elements, from, to);
      
              lists.add(createSublist(range, listImplementation));
          }
      
          return lists;
      }
      
      /**
       * Splits a list into smaller sublists. The sublists are of type ArrayList.
       * The original list remains unmodified and changes on the sublists are not propagated to the original list.
       *
       *
       * @param original
       *            The list to split
       * @param maxListSize
       *            The max amount of element a sublist can hold.
       * @return A list of sublists
       */
      public static final <T> List<List<T>> split(final List<T> original, final int maxListSize) {
          return split(original, maxListSize, ArrayList.class);
      }
      
      private static <T> List<T> createSublist(final T[] elements, final Class<? extends List> listImplementation) {
          List<T> sublist;
          final List<T> asList = Arrays.asList(elements);
          try {
              sublist = listImplementation.newInstance();
              sublist.addAll(asList);
          } catch (final InstantiationException e) {
              sublist = asList;
          } catch (final IllegalAccessException e) {
              sublist = asList;
          }
      
          return sublist;
      }
      

      }

      还有一些测试用例:

      package com.mrojas.util;
      
      import static org.junit.Assert.assertEquals;
      import static org.junit.Assert.assertTrue;
      
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.LinkedList;
      import java.util.List;
      
      import org.junit.Test;
      
      public class ListUtilsTest {
      
      @Test
      public void evenSplitTest() {
          final List<List<Object>> sublists = ListUtils.split(getPopulatedList(10), 2, LinkedList.class);
          assertEquals(5, sublists.size());
          for (final List<Object> sublist : sublists) {
              assertEquals(2, sublist.size());
              assertTrue(sublist instanceof LinkedList<?>);
          }
      }
      
      @Test
      public void unevenSplitTest() {
          final List<List<Object>> sublists = ListUtils.split(getPopulatedList(10), 3, LinkedList.class);
          assertEquals(4, sublists.size());
      
          assertEquals(3, sublists.get(0).size());
          assertEquals(3, sublists.get(1).size());
          assertEquals(3, sublists.get(2).size());
          assertEquals(1, sublists.get(3).size());
      }
      
      @Test
      public void greaterThanSizeSplitTest() {
          final List<List<Object>> sublists = ListUtils.split(getPopulatedList(10), 20, LinkedList.class);
          assertEquals(1, sublists.size());
          assertEquals(10, sublists.get(0).size());
      }
      
      @Test
      public void emptyListSplitTest() {
          final List<List<Object>> sublists = ListUtils.split(Collections.emptyList(), 10, LinkedList.class);
          assertEquals(0, sublists.size());
      }
      
      @Test(expected=IllegalArgumentException.class)
      public void negativeChunkSizeTest() {
          ListUtils.split(getPopulatedList(5), -10, LinkedList.class);
      }
      
      @Test
      public void invalidClassTest() {
          final List<List<Object>> sublists = ListUtils.split(getPopulatedList(10), 2, LinkedList.class);
          assertEquals(5, sublists.size());
          for (final List<Object> sublist : sublists) {
              assertEquals(2, sublist.size());
              assertTrue(sublist instanceof LinkedList<?>);
          }
      }
      
      private List<Object> getPopulatedList(final int size) {
          final List<Object> list = new ArrayList<Object>(10);
          for (int i = 0; i < 10; i++) {
              list.add(new Object());
          }
      
          return list;
      }
      

      }

      【讨论】:

        【解决方案6】:
        import java.util.Collection;
        
        public class CollectionUtils {
        
          /**
           * Will split the passed collection so that the size of the new collections
           * is not greater than maxSize
           * @param t
           * @param maxSize
           * @return a List containing splitted collections
           */
        
          @SuppressWarnings("unchecked")
          public static <T> List<Collection<T>>split(Collection<T> t, int maxSize) {
            int counter = 0;
            List<Collection<T>> ret = new LinkedList<Collection<T>>();
            Iterator<T>itr = t.iterator();
            try {
              Collection<T> tmp = t.getClass().newInstance();
              ret.add(tmp);
              while(itr.hasNext()) {
                tmp.add(itr.next());
                counter++;
                if(counter>=maxSize && itr.hasNext()) {
                  tmp = t.getClass().newInstance();
                  ret.add(tmp);
                  counter=0;
                }
              }
            } catch(Throwable e) {
              Logger.getLogger(CollectionUtils.class).error("There was an error spliting "+t.getClass(),e);
            }
            return ret;
          }
        
        }
        
        // JUnit test cases
        
        import java.util.ArrayList;
        
        /**
         *
         * $Change$
         * @version $Revision$
         * Last modified date & time $DateTime$
         */
        public class CollectionUtilsTest {
        
          @Test
          public void testSplitList() {
            List<Integer>test = new ArrayList<Integer>(100);
            for (int i=1;i<101;i++) {
              test.add(i);
            }
            List<Collection<Integer>> tests = CollectionUtils.split(test, 10);
            Assert.assertEquals("Size mismatch", 10,tests.size());
        
            TreeSet<Integer> tmp = new TreeSet<Integer>();
            for(Collection<Integer> cs:tests) {
              for(Integer i:cs) {
                Assert.assertFalse("Duplicated item found "+i,tmp.contains(i));
                tmp.add(i);
              }
              System.out.println(cs);
            }
            int why = 1;
            for(Integer i:tmp) {
              Assert.assertEquals("Not all items are in the collection ",why,i.intValue());
              why++;
            }
          }
          @Test
          public void testSplitSet() {
            TreeSet<Integer>test = new TreeSet<Integer>();
            for (int i=1;i<112;i++) {
              test.add(i);
            }
            List<Collection<Integer>> tests = CollectionUtils.split(test, 10);
            Assert.assertEquals("Size mismatch", 12,tests.size());
        
            TreeSet<Integer> tmp = new TreeSet<Integer>();
            int cI = 0;
            for(Collection<Integer> cs:tests) {
              for(Integer i:cs) {
                Assert.assertFalse("Duplicated item found "+i,tmp.contains(i));
                tmp.add(i);
              }
        //      if(cI>10) {
                System.out.println(cs);
        //      }
              cI++;
            }
            int why = 1;
            for(Integer i:tmp) {
        
              Assert.assertEquals("Not all items are in the collection ",why,i.intValue());
              why++;
            }
          }
        }
        

        【讨论】:

          【解决方案7】:

          我使用“Apache Commons Collections 4”库。它在 ListUtils 类中有一个分区方法:

          ...
          int targetSize = 100;
          List<Integer> largeList = ...
          List<List<Integer>> output = ListUtils.partition(largeList, targetSize);
          

          此方法改编自http://code.google.com/p/guava-libraries/

          【讨论】:

            【解决方案8】:
            <T> List<T> split(List<T> list, int i) {
               List<T> secondPart = list.sublist(i, list.size());
               List<T> returnValue = new ArrayList<T>(secondPart());
               secondPart.clear(),
               return returnValue;
            }
            

            【讨论】:

              【解决方案9】:

              将列表拆分为特定大小列表的通用函数。我在 java 集合中错过了很长时间。

              private List<List<T>> splitList(List<T> list, int maxListSize) {
                      List<List<T>> splittedList = new ArrayList<List<T>>();
                      int itemsRemaining = list.size();
                      int start = 0;
              
                      while (itemsRemaining != 0) {
                          int end = itemsRemaining >= maxListSize ? (start + maxListSize) : itemsRemaining;
              
                          splittedList.add(list.subList(start, end));
              
                          int sizeOfFinalList = end - start;
                          itemsRemaining = itemsRemaining - sizeOfFinalList;
                          start = start + sizeOfFinalList;
                      }
              
                      return splittedList;
                  }
              

              【讨论】:

              • 你的代码有错误,while块的第一行一定是这样的:int end = itemsRemaining &gt;= maxListSize ? (start + maxListSize) : **(start + itemsRemaining)**;
              【解决方案10】:

              同样抄袭 Marc 的列表,我们将使用 List.removeAll() 从第二个列表中删除重复的条目。请注意,严格来说,这仅在原始列表不包含重复项时才遵循规范:否则,原始列表可能会丢失项。

              <T> List<T> split(List<T> list, int i) {
                      List<T> x = new ArrayList<T>(list.subList(i, list.size()));
                      // Remove items from end of original list
                      list.removeAll(x);
                      return x;
              }
              

              【讨论】:

              • 我认为这个解决方案在 .equals() 元素同时出现在两个分区中的情况下会失败。
              【解决方案11】:

              我的解决方案:

              List<X> listToSplit = new ArrayList<X>();
              
              List<X> list1 = new ArrayList<X>();
              List<X> list2 = new ArrayList<X>();
              
              for (X entry : listToSplit)
              {
                  if (list1.size () > list2.size ())
                      list2.add (entry);
                  else
                      list1.add( entry );
              }
              

              应该工作:)

              【讨论】:

                【解决方案12】:
                //Here is my list
                    ArrayList<T> items=new ArrayList<T>();
                    Integer startIndex = 0;
                            int j = 0;
                            for (; j < items.size() - 1; j++) {//always start with next index not again 0
                                for (int i = 0; i < 4; i++) { // divide into equal part with 4 item
                                    startIndex = startIndex + 1;
                                    j = startIndex;
                                }
                            }
                

                【讨论】:

                  【解决方案13】:

                  使用流的解决方案,对每个项目使用切换/翻转布尔值进行分区:

                  Collection<String> big = ...;
                  AtomicBoolean switcheroo = new AtomicBoolean();
                  Map<Boolean, List<String>> subLists = big.stream()
                          .collect(Collectors.partitioningBy(o -> switcheroo.getAndSet(!switcheroo.get())));
                  

                  您最终会得到一个包含两个条目的映射,键是 truefalse,值是子列表。

                  这并不能完全回答最初的问题,但您可以使用每次递增的 AtomicInteger,而不是 AtomicBoolean

                  【讨论】:

                    【解决方案14】:

                    用于拆分列表的示例 java 代码

                    public List<List<Long>> split(List<Long> list, int i ){
                    
                        List<List<Long>> out = new ArrayList<List<Long>>();
                    
                        int size = list.size();
                    
                        int number = size/i;
                        int remain = size % i; 
                        if(remain != 0){
                            number++;
                        }
                    
                        for(int j=0; j < number; j++){
                            int start  = j * i;
                            int end =  start+ i;
                            if(end > list.size()){
                                end = list.size();
                            }
                            out.add(list.subList(start, end));
                        }
                    
                        return out;
                    }
                    

                    【讨论】:

                    • 问题要求在索引 i 处拆分的 两个 个子列表,而不是长度为 in 个子列表。