磁带均衡ScalaCheck

时间:2019-04-26 07:42:51

标签: scala scalacheck

我一直在尝试编写一些scalacheck属性,以验证Codility TapeEquilibrium问题。对于那些不知道问题的人,请参见以下链接:https://app.codility.com/programmers/lessons/3-time_complexity/tape_equilibrium/

我编码了以下但不完整的代码。

test("Lesson 3 property"){
val left = Gen.choose(-1000, 1000).sample.get
val right = Gen.choose(-1000, 1000).sample.get
val expectedSum = Math.abs(left - right)
val leftArray =  Gen.listOfN(???, left) retryUntil (_.sum == left)
val rightArray =  Gen.listOfN(???, right) retryUntil (_.sum == right)

val property = forAll(leftArray, rightArray){ (r: List[Int], l: List[Int]) =>
  val array = (r ++ l).toArray
  Lesson3.solution3(array) == expectedSum
}

property.check()
}

想法如下。我选择两个随机数(左和右值)并计算其绝对差。然后,我的想法是生成两个数组。每个数组都是随机数,其总和为“左”或“右”。然后,通过串联这些数组,我应该能够验证该属性。

然后我的问题是生成leftArray和rightArray。这本身就是一个复杂的问题,我将不得不为此编写一个解决方案。因此,编写此属性似乎过于复杂。

有什么办法可以编码吗?对这个属性进行编码是否过大?

最佳。

2 个答案:

答案 0 :(得分:1)

  

然后我的问题是生成leftArray和rightArray

生成这些数组或(列表)的一种方法是提供一个nonEmptyList生成器,该生成器的元素和等于给定的数字,换句话说,是通过如下方法定义的:

import org.scalacheck.{Gen, Properties}
import org.scalacheck.Prop.forAll

def listOfSumGen(expectedSum: Int): Gen[List[Int]] = ???

验证属性:

forAll(Gen.choose(-1000, 1000)){ sum: Int =>
    forAll(listOfSumGen(sum)){ listOfSum: List[Int] =>
      (listOfSum.sum == sum) && listOfSum.nonEmpty
    }
  }

要构建这样的列表,只会对列表的一个元素构成约束,因此基本上,这是一种生成方法:

  • 生成列表
  • 额外受约束的元素将由the expectedSum - the sum of list
  • 给出
  • Insert将受约束的元素放入列表的随机索引中(因为显然,列表的任何排列都可以使用)

所以我们得到:

  def listOfSumGen(expectedSum: Int): Gen[List[Int]] =
    for {
      list <- Gen.listOf(Gen.choose(-1000,1000))
      constrainedElement = expectedSum - list.sum
      index <- Gen.oneOf(0 to list.length)
    } yield list.patch(index, List(constrainedElement), 0)

现在我们可以将上面的生成器leftArrayrightArray定义如下:

val leftArray =  listOfSumGen(left)
val rightArray =  listOfSumGen(right)

但是,我认为所描述的属性的整体方法不正确,因为它会构建一个数组,其中该数组的特定分区等于expectedSum,但这不能确保该数组的另一个分区会产生较小的和。 这是一个反例演练:

val left = Gen.choose(-1000, 1000).sample.get //  --> 4
val right = Gen.choose(-1000, 1000).sample.get // --> 9
val expectedSum = Math.abs(left - right) // --> |4 - 9| = 5
val leftArray =  listOfSumGen(left) // Let's assume one of its sample would provide List(3,1) (whose sum equals 4)
val rightArray =  listOfSumGen(right)// Let's assume one of its sample would provide List(2,4,3) (whose sum equals 9)

val property = forAll(leftArray, rightArray){ (l: List[Int], r: List[Int]) =>
  // l = List(3,1)
  // r = List(2,4,3)
  val array = (l ++ r).toArray // --> Array(3,1,2,4,3) which is the array from the given example in the exercise
  Lesson3.solution3(array) == expectedSum 
// According to the example Lesson3.solution3(array) equals 1 which is different from 5
}

下面是一个正确属性的示例,该属性本质上适用于该定义:

def tapeDifference(index: Int, array: Array[Int]): Int = {
  val (left, right) = array.splitAt(index)
  Math.abs(left.sum - right.sum)
}

forAll(Gen.nonEmptyListOf(Gen.choose(-1000,1000))) { list: List[Int] =>
    val array = list.toArray
    forAll(Gen.oneOf(array.indices)) { index =>
      Lesson3.solution3(array) <= tapeDifference(index, array)
    }
  }

此属性定义可能与实际解决方案的实现方式(这是scalacheck的潜在陷阱之一)相冲突,但是,这将是一个缓慢/低效的解决方案,因此,这将是一种更有效的方法对照慢速和正确的实施检查优化和快速的实施(请参见此presentation

答案 1 :(得分:0)

使用c#尝试一下:

using System;
using System.Collections.Generic;
using System.Linq;

private static int TapeEquilibrium(int[] A)
    {
        var sumA = A.Sum();
        var size = A.Length;
        var take = 0;
        var res = new List<int>();
        for (int i = 1; i < size; i++)
        {
            take = take + A[i-1];
            var resp = Math.Abs((sumA - take) - take);
            res.Add(resp);
            if (resp == 0) return resp;
        }
        return res.Min();

    }