【问题标题】:Scala Shapeless Code for Project Euler #2用于 Project Euler #2 的 Scala 无形代码
【发布时间】:2015-07-24 16:21:31
【问题描述】:

我已经开始对 Project Euler Problem #2 使用 Shapeless 解决方案。

我可以用这段代码将所有偶数小谎加在一起,直到 Nth 偶数:

import shapeless._, nat._, ops.nat.Sum

trait Fibonacci[N <: Nat] { type Out <: Nat }

object Fibonacci {
  type Aux[N <: Nat, Out0 <: Nat] = Fibonacci[N] { type Out = Out0 }

  def apply[N <: Nat](i: Nat)(implicit fib: Aux[i.N, N], n: Witness.Aux[N]):N = n.value

  implicit val fib0 = new Fibonacci[_0] { type Out = _2 }
  implicit val fib1 = new Fibonacci[_1] { type Out = _3 }

  implicit def fibN[I <: Nat, L <: Nat, M <: Nat](implicit l: Aux[I, L],
                                                  m: Aux[Succ[I], M],
                                                  sum: Sum[L, M]) =
    new Fibonacci[Succ[Succ[I]]] { type Out = sum.Out }
}

trait Fibs[N <: Nat] { type Out <: Nat }

object Fibs {
  type Aux[N <: Nat, Out0 <: Nat] = Fibs[N] { type Out = Out0 }

  def apply[N <: Nat](i: Nat)(implicit fibs: Aux[i.N, N], n: Witness.Aux[N]):N = n.value

  implicit def fibs0(implicit f: Fibonacci[_0]) = new Fibs[_0] { type Out = f.Out }

  implicit def fibsN[N <: Nat, R <: Nat, Z <: Nat](implicit fib: Fibonacci.Aux[Succ[Succ[Succ[N]]], R],
                                                   fibs: Aux[N, Z],
                                                   sum: Sum[R, Z]) =
    new Fibs[Succ[N]] {
      type Out = sum.Out
    }
}

现在我可以做:

val (evenFibs0, evenFibs1) = (Fibs(0), Fibs(1))
typed[_2](evenFibs0)
typed[_10](evenFibs1)

这就是我得到所有偶数的方法:我从序列 2、3、... 开始,然后每隔三个斐波那契数求和。

现在,我被困住了。我想拥有类似于takeWhile 的功能,所以我可以编写一个接受limit 的函数并返回我的偶数fib 的总和,其条款不超过该限制。有什么想法吗?

这是我迄今为止所做的努力:

trait EvenFibsWithLimit[N <: Nat, M <: Nat] { type Out <: Nat }

trait LowPriorityFibs3 {
  type Aux[N <: Nat, M <: Nat, Out0 <: Nat] = EvenFibsWithLimit[N, M] { type Out = Out0 }

  implicit def fibs0[M <: Nat] = new EvenFibsWithLimit[_0, M] { type Out = _0 }

  implicit def fibsGT[N <: Nat, M <: Nat, O <: Nat](implicit f: EvenFibsWithLimit[N, M],
                                                    fib: Fibs.Aux[N, O],
                                                    l: ops.nat.LT[M, O]) = f
}

object EvenFibsWithLimit extends LowPriorityFibs3 {
  def apply[N <: Nat, O <: Nat](limit: Nat)(implicit fibs: Aux[N, limit.N, O],
                                            o: Witness.Aux[O]): O = o.value

  implicit def fibsN[N <: Nat, M <: Nat, O <: Nat](implicit f: EvenFibsWithLimit[N, M],
                                                   f2: Fibs.Aux[Succ[N], O],
                                                   d: ops.nat.Diff[M, O]) =
    new EvenFibsWithLimit[Succ[N], d.Out] {
      type Out = O
    }
}

这个想法是通过输出递归地减去限制,直到输出小于限制。我肯定能闻到有什么不对劲。我认为我根本不需要Diff。我也尝试了其他一些变化,但我一直卡住。编译时出现错误diverging implicit expansion for fibsN.

编辑:

我在想也许我可以构造我的FibsHList,并使用带有谓词类型类的Selector 来模拟takeWhile。想法?

【问题讨论】:

    标签: scala shapeless type-level-computation


    【解决方案1】:

    我发现解决此类问题的最佳方法是逐步遍历自然数,同时考虑我需要跟踪的信息。

    在纸上我会使用这样的东西:

    N  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
    C  1  2  3  3  5  5  5  8  8  8  8  8 13 13 13
    P  1  1  2  2  3  3  3  5  5  5  5  5  8  8  8
    L  0  2  2  2  2  2  2 10 10 10 10 10 10 10 10
    

    这里C 跟踪斐波那契数列中的当前数字——即小于或等于N 的最大的。 P 是之前的斐波那契数,L 是我们目前看到的偶数的总和。

    我们可以把它翻译成一个类型类:

    import shapeless._, ops.nat.{ Mod, Sum }, nat.{ _0, _1, _2 }
    
    trait EvenFibs[N <: Nat] {
      type L <: Nat
      type C <: Nat
      type P <: Nat
    }
    

    现在我们需要处理四种情况。首先,我将给出需要具有最低优先级才能正确获得隐式解决方案的那个:

    trait LowPriorityEvenFibs {
      type Aux[N <: Nat, L0 <: Nat, C0 <: Nat, P0 <: Nat] = EvenFibs[N] {
        type L = L0
        type C = C0
        type P = P0
      }
    
      implicit def ef3[N <: Nat](implicit
        ef: EvenFibs[N]
      ): Aux[Succ[N], ef.L, ef.C, ef.P] = new EvenFibs[Succ[N]] {
        type L = ef.L
        type C = ef.C
        type P = ef.P
      }
    }
    

    这是Succ[N] 不是斐波那契数的情况。它不需要我们更新我们正在跟踪的任何值。下一个案例稍微复杂一点:

    trait MidPriorityEvenFibs extends LowPriorityEvenFibs {
      implicit def ef2[N <: Nat, L0 <: Nat, PP <: Nat, P0 <: Nat](implicit
        ef: Aux[N, L0, P0, PP],
        sum: Sum.Aux[PP, P0, Succ[N]]
      ): Aux[Succ[N], L0, Succ[N], P0] = new EvenFibs[Succ[N]] {
        type L = L0
        type C = Succ[N]
        type P = P0
      }
    }
    

    这是Succ[N] 是奇数斐波那契数的情况。在这种情况下,我们需要更新CP,而不是L

    我们最后一个Succ[N] 的情况是Succ[N] 是偶数斐波那契数。我将在这里给出基本情况:

    object EvenFibs extends MidPriorityEvenFibs {
      implicit def ef0: Aux[_0, _0, _1, _1] = new EvenFibs[_0] {
        type L = _0
        type C = _1
        type P = _1
      }
    
      implicit def ef1[N <: Nat, L <: Nat, PP <: Nat, P0 <: Nat](implicit
        ef: Aux[N, L, P0, PP],
        sum: Sum.Aux[PP, P0, Succ[N]],
        mod: Mod.Aux[Succ[N], _2, _0],
        current: Sum[Succ[N], L]
      ): Aux[Succ[N], current.Out, Succ[N], P0] = new EvenFibs[Succ[N]] {
        type L = current.Out
        type C = Succ[N]
        type P = P0
      }
    
      def apply[N <: Nat](implicit ef: EvenFibs[N]): Aux[N, ef.L, ef.C, ef.P] = ef
    }
    

    最后,我们可以定义一个帮助类,以便更轻松地检查我们的工作:

    class ComputeHelper[N <: Nat] {
      def value[L <: Nat, C <: Nat, P <: Nat](implicit
        ef: EvenFibs.Aux[N, L, C, P],
        wit: Witness.Aux[L]
      ): L = wit.value
    }
    
    def compute[N <: Nat]: ComputeHelper[N] = new ComputeHelper[N]
    

    然后:

    test.typed[_0](compute[_0].value)
    test.typed[_0](compute[_1].value)
    test.typed[_2](compute[_2].value)
    test.typed[_2](compute[nat._3].value)
    test.typed[_2](compute[nat._4].value)
    test.typed[_2](compute[nat._5].value)
    test.typed[_2](compute[nat._6].value)
    test.typed[_2](compute[nat._7].value)
    test.typed[nat._10](compute[nat._8].value)
    test.typed[nat._10](compute[nat._9].value)
    

    最后一行在我的机器上编译大约需要 20 秒,但它可以工作。

    【讨论】:

    • 技术不错。我现在开始掌握窍门了,谢谢。
    猜你喜欢
    • 2015-01-23
    • 1970-01-01
    • 1970-01-01
    • 2011-10-11
    • 2016-08-12
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    相关资源
    最近更新 更多