2010-11-01 29 views
4

假设我有一个列表(EG:LinkedList<SomeObject>,其中包含由某个属性排序的元素(EG:SomeObject.someValue())。此属性通常可以并且通常经常重复/它不是是不是唯一的,但是从来不是空的。Java Collections:如何将已排序的列表划分为子列表

有没有一种方便的方法将它分成多个列表,每个列表只包含其基数次序相等?此外,这可以只用一次迭代列表完成?例如,原始列表:

1, 1, 1, 2, 2, 3, 3, 3 

从该所期望的列表:

1, 1, 1 
2, 2, 
3, 3, 3 

回答

10

不太方便,但是:

  • 开始一个循环。存储前一个项目,并将其与当前值进行比较。
  • 如果先前与当前不同(使用equals(..),并小心null),然后创建一个新的List,或使用list.subList(groupStart, currentIdx)
+1

+1 - 到目前为止唯一正确的答案,IMO :-) – 2010-11-01 23:05:38

+1

+1,但如果它是一个数组,修改的二进制搜索(找到最后一个实例,而不是第一个)会更好大量数据) – st0le 2010-11-02 05:35:45

1

这应该工作(未经测试,但我敢肯定一切好吧,这也假设列表的内容是可排序的):

public static List[] getEquivalentSubLists(List parent) 
{ 

    List cloneList = parent.clone(); 
    Collections.sort(cloneList); 

    ArrayList<List> returnLists; 
    int end; 
    while (cloneList.size() > 0) 
    { 
     end = cloneList.lastIndexOf(cloneList.get(0)); 

     returnLists.add(cloneList.subList(0, end)); 
     cloneList.removeAll(cloneList.subList(0, end)); 
    } 

    return returnList.toArray(); 
} 
3

很确定没有一个java API的方法。然而,你可以写:

// This assumes your list is sorted according to someValue() 
// SomeValueType is the type of SomeObject.someValue() 
public Map<SomeValueType, List<SomeObject>> partition(List<SomeObject> list) { 
    Object currValue = null; 
    HashMap<SomeValueType, LinkedList<SomeObject>> result = new HashMap<SomeValueType, LinkedList<SomeObject>>(); 
    LinkedList<SomeObject> currList = null; 

    for (SomeObject obj : list) { 
     if (!obj.someValue().equals(currValue()) { 
      currValue = obj.someValue(); 
      currList = new LinkedList<SomeObject>(); 
      result.put(currValue, currList); 
     } 
     currList.add(obj); 
    } 
} 

这将返回子列表,一个HashMap其中关键是someValue和值是与之相关的分区列表。请注意,我没有测试这个,所以不要复制代码。

编辑:使这个返回hashmap而不是arraylist。

+0

您应该返回Map而不是HashMap,并且需要List而不是LinkedList。任何你在返回值中遗漏了一个'>'。 – whiskeysierra 2010-11-01 22:45:56

+0

@Willi editted。正如我所说我没有测试任何这一点。 > _ < – shoebox639 2010-11-01 22:49:34

+0

Hashmap作为输出是必需的,因为通过键更容易查找整个列表。它与输入是否排序无关。如果你真的看看代码,我会利用它被分类的事实。 – shoebox639 2010-11-02 00:36:33

4

你可以使用Apache CollectionUtils要做到这一点,在“名单”是最初的名单,而“价值”是要提取的子列表中的对象的当前值:

Collection<SomeObject> selectedObjects = CollectionUtils 
    .select(list, 
      new Predicate() { 
       boolean evaluate(Object input) { 
        return ((SomeObject) input).someValue().equals(value);  
       } 
      }); 

这种方法手段使用一个众所周知的,经过充分测试的库(这总是一件好事),但缺点是,您将为每个需要的子列表遍历列表一次。

+1

要将此应用于OP的陈述问题,您需要用代码片段循环选择值。结果是一个“O(N ** 2)”解决方案,它不使用输入列表排序的事实。 Ughh。 – 2010-11-01 22:58:41

+0

@Stephen C:是的,这正是我想要在最后一句中表达的意思。如果输入列表相对较小,这不是问题。但是如果它变大,另一种单循环方法会更好。我尽量避免预先优化(因为它只是邪恶的根源)。 – crunchdog 2010-11-02 12:02:31

3

如果你想使用谷歌Guava-libaries

import com.google.common.collect.HashMultiset; 
import com.google.common.collect.Lists; 

public class Example { 
    public static void main(String[] args) { 
     HashMultiset<Integer> ints = HashMultiset.create(); 
     ints.addAll(Lists.newArrayList(1, 1, 1, 2, 2, 3, 3, 3)); 
     System.out.println(ints); 
    } 
} 

输出:

[1 x 3, 2 x 2, 3 x 3] 

如果你需要计算的X,你必须使用ints.count(x);,如果你有值类型,你没有多少个元素需要更多,然后才算数。

+0

你用这个得到3个不同的列表吗? – zengr 2010-11-02 05:12:46

+0

这是一组'Entry '而不是'Integer',你得到3个不同的Entry。 'ints.entrySet();'可以得到一组'Entry '。 – Margus 2010-11-02 05:33:15