Algorithm 将集合划分为;k";接近相等的片段(Scala,但语言不可知)

Algorithm 将集合划分为;k";接近相等的片段(Scala,但语言不可知),algorithm,scala,slice,Algorithm,Scala,Slice,在此代码块之前定义: 数据集可以是向量或列表 numberOfSlices是一个Int表示对数据集进行切片的次数 我想将数据集分割成numberOfSlicesslices,尽可能均匀地分布。我想我所说的“分割”是指使用集合论术语的“分割”(所有的交集应该是空的,所有的并集应该是原始的),尽管这不一定是一个集合,只是一个任意的集合 e、 g 有没有比下面的更好的方法?(我甚至不确定这是否是最佳的…) 或者这可能不是一个算法上可行的尝试,在这种情况下,任何已知的好的启发式 val slices

在此代码块之前定义:

  • 数据集
    可以是
    向量
    列表
  • numberOfSlices
    是一个
    Int
    表示对数据集进行切片的次数
我想将数据集分割成
numberOfSlices
slices,尽可能均匀地分布。我想我所说的“分割”是指使用集合论术语的“分割”(所有的交集应该是空的,所有的并集应该是原始的),尽管这不一定是一个集合,只是一个任意的集合

e、 g

有没有比下面的更好的方法?(我甚至不确定这是否是最佳的…) 或者这可能不是一个算法上可行的尝试,在这种情况下,任何已知的好的启发式

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
}

正如Kaito所提到的,分组的正是您想要的。但是,如果您只想知道如何实现这种方法,有很多方法;-)。例如,您可以这样做:

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)
}

如果
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)
}

我会这样处理:给定
n
元素和
m
分区(n>m),要么n mod m==0,在这种情况下,每个分区都有n/m个元素,要么n mod m=y,在这种情况下,每个分区都有
n/m
元素,并且必须将
y
分布在一些
m

您将拥有
y
插槽和
n/m+1
元素,以及(m-y)插槽和n/m元素。如何分配它们是您的选择。

典型的“最佳”分区在切割后计算精确的分数长度,然后四舍五入以找到实际要采用的数量:

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),5)
res6: Vector[Seq[Int]] = 
  Vector(List(1), List(), List(2), List(), List(3))

这里有一个为我做这项工作的单行程序,它使用了我们熟悉的Scala技巧,即返回
流的递归函数。请注意使用
(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 index=scala.util.Random.shuffle(1到39)

scala>for(ff for)(ff for)(ff for)(ff以下是我对这个问题的看法:

  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
  }

它比其他一些解决方案更详细,但希望也更清晰。只有在您希望保留顺序时才需要反向。

我不确定如何解释“尽可能均匀地分布”。按照您的代码,
Seq:grouped(Int)
已经做了你想做的事情,只是它从来没有超过切片大小。似乎
分组
会将它分成“x”组,而我想把一个集合分成“x”组。我在回复中尝试了它,
列表(1,2,3,4,5)。分组(2)。toList
给出了
列表(列表(1,2),列表(3,4),列表(5))
而我想要的是类似于
列表(列表(1,2),列表(3,4,5))
的东西。这很好,但不幸地延伸了“尽可能均匀分布”的规定要求,因为所有的“大”段都排在最后-例如,
剪切(1到15,10)。toList.map(\uu.size)
产生5个单元素分段,后面是5个双元素分段。
分组后的子列表可能会比其他子列表小得多。@Vasily802我不知道为什么
#::
可能不起作用,但我已经替换了它,并且对代码进行了一些改进,并修复了演示。谢谢。。.
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
  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
  }