【问题标题】:How to 'Slice' a Collection in Groovy如何在 Groovy 中“切片”集合
【发布时间】:2011-05-03 17:53:33
【问题描述】:

我有一组对象,我想将它们分解为一组集合,其中每个连续的 3 个元素组都在一个集合中。 例如,如果我有

def l = [1,4,2,4,5,9]

我想把它变成:

def r = [[1,4,2], [4,5,9]]

我现在通过迭代集合并将其分解来做到这一点。但是我需要将这些“组”传递给处理它们的并行函数。消除这个 O(n) 会很好预处理工作,然后说类似

l.slice(3).collectParallel { subC -> process(subC) }

我在 Range 类上找到了 step 方法,但它看起来只对索引起作用。有什么聪明的主意吗?

更新: 我不认为这是引用链接的副本,尽管它非常接近。正如下面所建议的,它更像是我正在寻找的迭代器类型的东西.. 然后子集合将被传递到 GPars collectParallel 中。理想情况下,我不需要分配整个新集合。

【问题讨论】:

标签: groovy


【解决方案1】:

查看 groovy 1.8.6。 List 上有一个新的 collat​​e 方法。

def list = [1, 2, 3, 4]
assert list.collate(4) == [[1, 2, 3, 4]] // gets you everything   
assert list.collate(2) == [[1, 2], [3, 4]] //splits evenly
assert list.collate(3) == [[1, 2, 3], [4]] // won't split evenly, remainder in last list.

查看Groovy List documentation 了解更多信息,因为还有一些其他参数可以为您提供一些其他选项,包括删除其余部分。

就您的并行处理而言,您可以使用gpars 浏览列表。

def list = [1, 2, 3, 4, 5]
GParsPool.withPool {
  list.collate(2).eachParallel {
     println it
  }
}

【讨论】:

    【解决方案2】:

    如果我理解正确,您当前正在将原始集合中的元素复制到子集合中。有关这些方面的更多建议,请查看以下问题的答案:Split collection into sub collections in Groovy

    听起来您正在寻找的是一种让子集合有效地成为原始集合视图的方法。如果是这种情况,请查看List.subList() method。您可以以 3 为增量(或您选择的任何切片大小)循环从 0 到 size() 的索引,或者您可以变得更漂亮并构建一个 Iterable/List 来隐藏调用者的详细信息。这是后者的一个实现,灵感来自Ted's answer

    class Slicer implements Iterator {
      private List backingList
      private int sliceSize
      private int index
    
      Slicer(List backingList, int sliceSize) {
        this.backingList = backingList
        this.sliceSize = sliceSize
      }
    
      Object next() {
        if (!hasNext()) {
          throw new NoSuchElementException()
        }
    
        def ret
        if (index + sliceSize <= backingList.size()) {
          ret = backingList.subList(index, index+sliceSize)
        } else if (hasNext()) {
          ret = backingList.subList(index, backingList.size())
        }
        index += sliceSize
        return ret
      }
    
      boolean hasNext() {
        return index < backingList.size()
      }
    
      void remove() {
        throw new UnsupportedOperationException() //I'm lazy ;)
      }
    }
    

    【讨论】:

    • 感谢您的信息--您说得对,我想创建更多的“视图”.. 无需重新分配任何新对象。上面迈克尔链接中的方法工作得很好......就像我现有的代码一样。这个“Iterable”实现听起来很正确——想知道它需要多少代码——需要定义一个新的 Iterable 实现?子类迭代器?
    • 我认为您在实现 hasNext() 时可能会遇到一个错误。尝试使用 0、1、2、3、4、5 个元素和 sliceSize 为 4 的列表。
    • @jabley 你是对的!我不确定是什么导致我把它放在那里。我去删除它。
    【解决方案3】:

    我喜欢这两种解决方案,但这里是我非常喜欢的第一个解决方案的略微改进版本:

    class Slicer implements Iterator {
    private List backingList
    private int sliceSize
    private int index
    
    Slicer(List backingList, int sliceSize) {
      this.backingList = backingList;
      int ss = sliceSize;
    
      // negitive sliceSize = -N means, split the list into N equal (or near equal) pieces  
      if( sliceSize < 0) {
          ss = -sliceSize;
          ss = (int)((backingList.size()+ss-1)/ss);
      }
      this.sliceSize = ss
    }
    
    Object next() {
      if (!hasNext()) {
        throw new NoSuchElementException()
      }
    
      def ret = backingList.subList(index, Math.min(index+sliceSize , backingList.size()) );
      index += sliceSize
      return ret
      }
    
      boolean hasNext() {
        return index < backingList.size() - 1
      }
    
      void remove() {
        throw new UnsupportedOperationException() //I'm lazy ;)
      }
    
      List asList() {
        this.collect { new ArrayList(it) }
      }
    
      List flatten() {
        backingList.asImmutable()
      }
    
    }
    
    // ======== TESTS
    
        def a = [1,2,3,4,5,6,7,8];
        assert  [1,2,3,4,5,6,7,8] == a;
        assert [[1, 2], [3, 4], [5, 6], [7, 8]] ==  new Slicer(a,2).asList(); 
        assert [[1,2,3], [4,5,6], [7,8]] == (new Slicer(a,3)).collect { it } // alternative to asList but inner items are subList
        assert [3, 2, 1, 6, 5, 4, 8, 7] == ((new Slicer(a,3)).collect { it.reverse() } ).flatten()
    
        // show flatten iterator
        //new Slicer(a,2).flattenEach { print it }
        //println ""
    
        // negetive slice into N pieces, in this example we split it into 2 pieces
        assert [[1, 2, 3, 4], [5, 6, 7, 8]] ==  new Slicer(a,-2).collect { it as List }  // same asList
        assert [[1, 2, 3], [4, 5, 6], [7, 8]] == new Slicer(a,-3).asList()
        //assert a == (new Slicer(a,3)).flattenCollect { it } 
        assert [9..10, 19..20, 29..30] == ( (new Slicer(1..30,2)).findAll { slice -> !(slice[1] % 10) } )
        assert [[9, 10], [19, 20], [29, 30]] == ( (new Slicer(1..30,2)).findAll { slice -> !(slice[1] % 10) }.collect { it.flatten() } )
    
        println( (new Slicer(1..30,2)).findAll { slice -> !(slice[1] % 10) } )
        println( (new Slicer(1..30,2)).findAll { slice -> !(slice[1] % 10) }.collect { it.flatten() } )
    

    【讨论】:

      【解决方案4】:

      没有任何内置功能可以完全按照您的要求进行操作,但是如果我们 @Delegate 调用本机列表的迭代器,我们可以编写自己的类,该类的工作方式与返回您正在查找的块的迭代器一样为:

      class Slicer {
          protected Integer sliceSize 
          @Delegate Iterator iterator
      
          Slicer(objectWithIterator, Integer sliceSize) {
              this.iterator = objectWithIterator.iterator()
              this.sliceSize = sliceSize
          }
      
          Object next() {
              List currentSlice = []
              while(hasNext() && currentSlice.size() < sliceSize) {
                  currentSlice << this.iterator.next()
              }
              return currentSlice
          }
      }
      
      assert [[1,4,2], [4,5,9]] == new Slicer([1,4,2,4,5,9], 3).collect { it }
      

      因为它具有普通迭代器所具有的所有方法,所以您可以免费获得 groovy 语法糖方法,并对任何具有 iterator() 方法的东西(例如范围)进行惰性求值:

      assert [5,6] == new Slicer(1..100, 2).find { slice -> slice.first() == 5 }
      
      assert [[9, 10], [19, 20], [29, 30]] == new Slicer(1..30, 2).findAll { slice -> !(slice[1] % 10) }
      

      【讨论】:

      • 我喜欢您的解决方案,但它仍然感觉像是制作副本而不是拥有作为原始列表视图的子列表。我试图弄清楚是否有某种方法可以将您的方法与 List.subList() 结合起来。
      • 我可能不理解您的评论,但上面的代码并没有复制,它创建了一个惰性列表迭代器,它委托给原始列表本地迭代器。没有发生列表复制。我不确定如何创建一个惰性版本的列表,像 collectParallel 这样的东西可以使用。很多非懒惰的版本(有类似注入的东西,但不是懒惰的。如果你想出一些东西,我很想看看:)。
      • 我的思绪终于合二为一。当您对支持对象的所有了解都知道它是一个 Iterable 时,您的解决方案就是完美的。但是,如果您知道它是一个 RandomAccessList(例如 ArrayList),您可以改为在 Slicer 中维护一个正在运行的索引,并让您的下一个方法返回 backingList.subList(index, index+sliceSize)。当 sliceSize 为 3 时不会有太大区别,但是对于 1000,您将避免为每个切片分配空间以及将项目添加到切片中所花费的时间。更有意义?
      • 是的,我明白你现在在说什么。您不是在谈论担心列表的完整副本,而是在切片部分中的子集项目的副本。您要求进行优化,一次只将一个项目返回给视图中的调用者。您需要返回一个代理对象,该对象还将 getAt 委托给原始列表。
      • 这基本上就是我的意思。有关具体示例,请参阅我编辑的答案。
      猜你喜欢
      • 2011-08-25
      • 2012-10-03
      • 1970-01-01
      • 1970-01-01
      • 2014-08-31
      • 2017-05-30
      • 2019-04-09
      • 2019-06-26
      • 2012-01-30
      相关资源
      最近更新 更多