将集合划分为“k”接近相等的部分(Scala,但语言不可知)

时间:2012-07-12 16:12:25

标签: algorithm scala slice

在此代码块之前定义:

  • dataset可以是VectorList
  • numberOfSlices是一个Int,表示切片数据集的“次数”

我想将数据集拆分为numberOfSlices个切片,尽可能均匀分布。通过“拆分”我想我的意思是“分区”(所有的交集应该是空的,所有的联合应该是原始的)来使用集合理论术语,尽管这不一定是集合,只是一个任意集合。

e.g。

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
}

6 个答案:

答案 0 :(得分:14)

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

答案 1 :(得分:7)

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

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

答案 2 :(得分:3)

这是一个为我工作的单线程,使用熟悉的递归函数的Scala技巧返回Slider。请注意使用label2.SetBinding(Label.ContentProperty, new Binding("Value") { Source = sl }); 来舍入块大小,在最终列表中插入较小和较大的块,所有块的大小最多只有一个差异元素。如果您使用Slider sl; Label label1; Label label2; private void AllFour_Unchecked(object sender, RoutedEventArgs e) { label1 = new Label(); label1.HorizontalAlignment = HorizontalAlignment.Center; sl = new Slider(); sl.Minimum = 1; sl.Maximum = 4; sl.TickFrequency = 1; sl.IsSnapToTickEnabled = true; sl.SetResourceReference(Control.StyleProperty, "SliderStyle"); sl.SetResourceReference(Slider.ForegroundProperty, "SliderSelectionRangeBackgroundBrush"); label2 = new Label(); label2.HorizontalAlignment = HorizontalAlignment.Center; label2.BorderBrush = new SolidColorBrush(Colors.Gray); label2.SetBinding(Label.ContentProperty, new Binding("Value") { Source = sl }); Dispatcher.BeginInvoke(new Action(() => { Upgrades.Children.Add(label1); Upgrades.Children.Add(sl); Upgrades.Children.Add(label2); }), System.Windows.Threading.DispatcherPriority.Background); } private void AllFour_Checked(object sender, RoutedEventArgs e) { Upgrades.Children.Remove(label1); Upgrades.Children.Remove(sl); Upgrades.Children.Remove(label2); } 进行向上舍入,则将较小的块移动到末尾,Stream将它们移动到开头。

(x+k/2)/k

演示:

(x+k-1)/k

请注意x/k如何不尝试均衡所有子列表的大小。

答案 3 :(得分:0)

Kaito提到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)
}

答案 4 :(得分:0)

我这样做:给定n个元素和m个分区(n> m),n mod m == 0在这种情况下,每个分区都有n / m个元素或者n mod m = y,在这种情况下,每个分区都有n/m元素,你必须在某些y上分发m

您将拥有y个带有n/m+1元素的广告位和带有n / m的(m-y)广告位。你如何分发它们是你的选择。

答案 5 :(得分:0)

这是我要解决的问题:

  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
  }

它比其他一些解决方案更为冗长,但也希望更加清晰。 撤消仅在您希望保留订单时才需要。