分区收集到“K”近于相等的块(斯卡拉,但语言无关)(Partition a collection i

2019-07-30 09:26发布

这个代码块前的定义:

  • dataset可以是一个VectorList
  • numberOfSlices是一种Int ,表示很多“次”怎么给数据集

我要拆分的数据集插入到numberOfSlices片,为均匀分布成为可能。 通过“拆分”我想我的意思是“分区”(所有的交集应该是空的,所有的工会应原)使用集理论来看,虽然这并不一定是一套,只是一个任意集合。

dataset = List(1, 2, 3, 4, 5, 6, 7)
numberOfSlices = 3
slices == ListBuffer(Vector(1, 2), Vector(3, 4), Vector(5, 6, 7))

有没有更好的办法做到这一点比我有以下? (我甚至不能确定是最优的...)或者,也许这不是一个可行的算法的努力,在这种情况下,任何已知良好的启发?

val slices = new ListBuffer[Vector[Int]]
val stepSize = dataset.length / numberOfSlices
var currentStep = 0
var looper = 0
while (looper != numberOfSlices) {
  if (looper != numberOfSlices - 1) {
    slices += dataset.slice(currentStep, currentStep + stepSize)
    currentStep += stepSize
  } else {
    slices += dataset.slice(currentStep, dataset.length)
  }
  looper += 1
}

Answer 1:

如果行为xs.grouped(xs.size / n)不为你工作,这是很容易正是你想要的定义。 商是较小的块的尺寸,其余是更大的片数:

def cut[A](xs: Seq[A], n: Int) = {
  val (quot, rem) = (xs.size / n, xs.size % n)
  val (smaller, bigger) = xs.splitAt(xs.size - rem * (quot + 1))
  smaller.grouped(quot) ++ bigger.grouped(quot + 1)
}


Answer 2:

典型的“最佳”的分区在切割之后,计算一个精确的分数的长度,然后舍入到找到的实际数量取:

def cut[A](xs: Seq[A], n: Int):Vector[Seq[A]] = {
  val m = xs.length
  val targets = (0 to n).map{x => math.round((x.toDouble*m)/n).toInt}
  def snip(xs: Seq[A], ns: Seq[Int], got: Vector[Seq[A]]): Vector[Seq[A]] = {
    if (ns.length<2) got
    else {
      val (i,j) = (ns.head, ns.tail.head)
      snip(xs.drop(j-i), ns.tail, got :+ xs.take(j-i))
    }
  }
  snip(xs, targets, Vector.empty)
}

这样,你的较长和较短块将被穿插,这往往是更理想的均匀度:

scala> cut(List(1,2,3,4,5,6,7,8,9,10),4)
res5: Vector[Seq[Int]] = 
  Vector(List(1, 2, 3), List(4, 5), List(6, 7, 8), List(9, 10))

你甚至可以切割更多的时间比你的元素:

scala> cut(List(1,2,3),5)
res6: Vector[Seq[Int]] = 
  Vector(List(1), List(), List(2), List(), List(3))


Answer 3:

这里有一个一行,没有工作对我来说,使用返回一个递归函数的熟悉斯卡拉招Stream 。 注意使用(x+k/2)/k到舍入块大小,嵌入在最终列表中的较小和较大的块,所有的尺寸的差异的至多一个元件。 如果上舍入,而不是与(x+k-1)/k ,则移动至更小的块到结束,和x/k它们移动到开始处。

def k_folds(k: Int, vv: Seq[Int]): Stream[Seq[Int]] =
    if (k > 1)
        vv.take((vv.size+k/2)/k) +: k_folds(k-1, vv.drop((vv.size+k/2)/k))
    else
        Stream(vv)

演示:

scala> val indices = scala.util.Random.shuffle(1 to 39)

scala> for (ff <- k_folds(7, indices)) println(ff)
Vector(29, 8, 24, 14, 22, 2)
Vector(28, 36, 27, 7, 25, 4)
Vector(6, 26, 17, 13, 23)
Vector(3, 35, 34, 9, 37, 32)
Vector(33, 20, 31, 11, 16)
Vector(19, 30, 21, 39, 5, 15)
Vector(1, 38, 18, 10, 12)

scala> for (ff <- k_folds(7, indices)) println(ff.size)
6
6
5
6
5
6
5

scala> for (ff <- indices.grouped((indices.size+7-1)/7)) println(ff)
Vector(29, 8, 24, 14, 22, 2)
Vector(28, 36, 27, 7, 25, 4)
Vector(6, 26, 17, 13, 23, 3)
Vector(35, 34, 9, 37, 32, 33)
Vector(20, 31, 11, 16, 19, 30)
Vector(21, 39, 5, 15, 1, 38)
Vector(18, 10, 12)

scala> for (ff <- indices.grouped((indices.size+7-1)/7)) println(ff.size)
6
6
6
6
6
6
3

请注意如何grouped并不试图拉平所有子列表的大小。



Answer 4:

这是我对这个问题:

  def partition[T](items: Seq[T], partitionsCount: Int): List[Seq[T]] = {
    val minPartitionSize = items.size / partitionsCount
    val extraItemsCount = items.size % partitionsCount

    def loop(unpartitioned: Seq[T], acc: List[Seq[T]], extra: Int): List[Seq[T]] =
      if (unpartitioned.nonEmpty) {
        val (splitIndex, newExtra) = if (extra > 0) (minPartitionSize + 1, extra - 1) else (minPartitionSize, extra)
        val (newPartition, remaining) = unpartitioned.splitAt(splitIndex)
        loop(remaining, newPartition :: acc, newExtra)
      } else acc

    loop(items, List.empty, extraItemsCount).reverse
  }

这比一些其他解决方案更详细,但希望更清楚为好。 如果你想为了保存反向才是必需的。



Answer 5:

由于怪盗提到grouped正是你所期待的。 但是,如果你只是想知道如何实现这样的方法,有很多种方法;-)。 例如,你可以做这样的:

def grouped[A](xs: List[A], size: Int) = {
  def grouped[A](xs: List[A], size: Int, result: List[List[A]]): List[List[A]] = {
    if(xs.isEmpty) {
      result
    } else {
      val (slice, rest) = xs.splitAt(size)
      grouped(rest, size, result :+ slice)
    }
  }
  grouped(xs, size, Nil)
}


Answer 6:

我接近这样说:给定n元素和m分区(N> M),是n模m == 0在此情况下,每一分区将具有N / m个元素,或正模m = Y,在此情况下你必须与每个分区n/m的元素,你必须分发y对一些m

你必须y与插槽n/m+1的元素和(我的)插槽N / M。 你如何分配这些是你的选择。



文章来源: Partition a collection into “k” close-to-equal pieces (Scala, but language agnostic)