使用Functional Swift

时间:2015-05-25 15:04:29

标签: swift functional-programming

我正在尝试学习Swift功能并开始从Project Euler做一些练习。

  

甚至斐波那契数字   问题2   Fibonacci序列中的每个新术语都是通过添加前两个术语生成的。从1和2开始,前10个术语将是:

     

1,2,3,5,8,13,21,34,55,89,......

     

通过考虑Fibonacci序列中的值不超过四百万的项,找到偶数项的总和。

根据WWDC高级Swift视频实现了一个memoized Fibonacci函数:

func memoize<T:Hashable, U>( body: ((T)->U,T) -> U) -> (T)->U {
  var memo = [T:U]()
  var result: ((T)->U)!
  result = { x in
    if let q = memo[x] { return q }
    let r = body(result,x)
    memo[x] = r
    return r
  }
  return result
}

let fibonacci = memoize { (fibonacci:Int->Double,n:Int) in n < 2 ? Double(n) : fibonacci(n-1) + fibonacci(n-2) }

并实现了符合Sequence协议

的类
class FibonacciSequence: SequenceType {
  func generate() -> GeneratorOf<Double> {
      var n = 0
      return GeneratorOf<Double> { fibonacci(n++) }
  }

  subscript(n: Int) -> Double {
      return fibonacci(n)
  }
}

问题的第一个(非功能性)解决方案:

var fib = FibonacciSequence().generate()
var n:Double = 0
var sum:Double = 0
while n < Double(4_000_000) {
  if n % 2 == 0 {
    sum += n
  }
n = fib.next()!
}

println(sum)

第二个更具功能性的解决方案,使用ExSwift进行takeWhile功能

let f = FibonacciSequence()
println((1...40).map { f[$0] }
                .filter { $0 % 2 == 0 }
                .takeWhile { $0 < 4_000_000 }
                .reduce(0, combine: +))

我想改进这个解决方案,因为乞讨的1...40范围无缘无故地计算了太多的术语。理想情况下,我希望能够拥有某种无限范围,但同时只计算满足takeWhile

条件的所需条件

有什么建议吗?

4 个答案:

答案 0 :(得分:3)

这里我生成一旦达到最大值就已经停止的序列。 然后你只需要在没有过滤的情况下进行缩减,只需在n为奇数时加0。

func fibonacciTo(max: Int) -> SequenceOf<Int> {
    return SequenceOf { _ -> GeneratorOf<Int> in
        var (a, b) = (1, 0)
        return GeneratorOf {
            (b, a) = (a, b + a)
            if b > max { return nil }
            return b
        }
    }
}


let sum = reduce(fibonacciTo(4_000_000), 0) {a, n in (n % 2 == 0) ? a + n : a }

作为替代方案,如果您希望fibonacci保持SequenceOf更通用的功能,可以takeWhile reduce1延长 extension SequenceOf { func takeWhile(p: (T) -> Bool) -> SequenceOf<T> { return SequenceOf { _ -> GeneratorOf<T> in var generator = self.generate() return GeneratorOf { if let next = generator.next() { return p(next) ? next : nil } return nil } } } // Reduce1 since name collision is not resolved func reduce1<U>(initial: U, combine: (U, T) -> U) -> U { return reduce(self, initial, combine) } } func fibonacci() -> SequenceOf<Int> { return SequenceOf { _ -> GeneratorOf<Int> in var (a, b) = (1, 0) return GeneratorOf { (b, a) = (a, b + a) return b } } } let sum2 = fibonacci() .takeWhile({ $0 < 4_000_000 }) .reduce1(0) { a, n in (n % 2 == 0) ? a + n : a} // Get data $query = "SELECT * FROM games WHERE player = '$thePlayer' ORDER BY whenPlayed"; $result = $mysqli->query($query); $data = array(); // Iterate over data, use 'whenPlayed` as a key to group your data while ($row = $result->fetch_array(MYSQLI_ASSOC)) { if (!array_key_exists($row['whenPlayed'], $data)) { $data[$row['whenPlayed']] = array(); } $data[$row['whenPlayed']][] = $row; } 获取类似的东西功能组成:

<ul>
    <?php foreach ($data as $date => $games): ?>
    <li>
        <?php echo $date ?>:
        <ul>
            <?php foreach ($games as $game): ?>
            <li><?php echo print_r($game, true) ?></li>
            <?php endforeach ?>
        </ul>
    </li>
    <?php endforeach ?>
</ul>

希望这有帮助

答案 1 :(得分:2)

有一个filter()函数,它将序列作为参数:

func filter<S : SequenceType>(source: S, includeElement: (S.Generator.Element) -> Bool) -> [S.Generator.Element]

但由于返回值是数组,如果您愿意,这不适用 使用“无限”序列。但是

lazy(FibonacciSequence()).filter ( { $0 % 2 == 0 })

你得到了一个偶数斐波纳契数的“无限”序列。你不能 在该序列上调用ExSwift的.takeWhile()方法,因为 .takeWhile()仅定义为struct SequenceOf,而非TakeWhileSequence( lazy(FibonacciSequence()).filter ( { $0 % 2 == 0 }), { $0 < 4_000_000 } ) 一般序列。但

let sum = reduce(TakeWhileSequence(
    lazy(FibonacciSequence()).filter ( { $0 % 2 == 0 }),
    { $0 < 4_000_000 }), 0, +)

工作并给出所有偶数Fibonacci数小于的序列 4000000。然后

struct FibonacciSequence : SequenceType {

    func generate() -> GeneratorOf<Int> {
        var current = 1
        var next = 1
        return GeneratorOf<Int>() {
            let result = current
            current = next
            next += result
            return result
        };
    }
}

给出预期结果并仅计算“必要” 斐波那契数字。

请注意,实际上不需要记住Fibonacci数字 这是因为它们是按顺序访问的。另外(作为@Matteo 已经注意到了),所有Fibonacci数都是整数。所以你可以 将序列更简单地定义为

/\d+\/\d+\.\d+\.\d+|\d+\.\d+\.\d+|\d+/g

并且上述计算仍然有效。

答案 2 :(得分:1)

使用Swift的懒惰序列,你可以非常接近你想要的东西。如果你把你的斐波纳契数字生成器(这里是我用的那个):

var (a, b) = (1, 0)

var fibs =  GeneratorOf<Int> {

  (b, a) = (a, b + a)

  return b

}

您可以将其包装在lazy()中:

var (a, b) = (1, 0)

var fibs = lazy(

  GeneratorOf<Int> {

    (b, a) = (a, b + a)

    return b

  }
)

将其公开为filter()作为一个惰性函数。这个filter()返回:

LazySequence<FilterSequenceView<GeneratorOf<Int>>>

现在,为了获得你的takeWhile()函数,你需要扩展LazySequence:

extension LazySequence {

  func takeWhile(condition: S.Generator.Element -> Bool)
    -> LazySequence<GeneratorOf<S.Generator.Element>> {

    var gen = self.generate()

    return lazy( GeneratorOf{ gen.next().flatMap{ condition($0) ? $0 : nil }})

  }
}

如果基础序列结束,或者条件不满足,则返回nil(停止生成器)。

所有这些,你给定数字下的斐波纳契序列看起来很像你想要的:

fibs
  .filter {$0 % 2 == 0}
  .takeWhile {$0 < 100}

//2, 8, 34

但是,因为reduce不是LazySequence的方法,你必须转换为数组:

fibs
  .filter {$0 % 2 == 0}
  .takeWhile {$0 < 100}.array
  .reduce(0, combine: +)

//44

你可以对LazySequence做一个快速而又脏的扩展来获得reduce():

extension LazySequence {

  func reduce<U>(initial: U, combine: (U, S.Generator.Element) -> U) -> U {

    var accu = initial

    for element in self { accu = combine(accu, element) }

    return accu

  }
}

你可以写下这样的最终内容:

fibs
  .filter {$0 % 2 == 0}
  .takeWhile {$0 < 100}
  .reduce(0, combine: +)

//44

所有这些序列都会在他们的懒惰中持续存在 - 纤维是无限的,所以他们不会在其他方面起作用。事实上,在减少之前不计算任何东西:直到那时它才是所有的thunk。

答案 3 :(得分:1)

在Swift 3.1中,这里是一个永远生成Fibonacci数的迭代器,以及从中得到的无限序列:

class FibIterator : IteratorProtocol {
    var (a, b) = (0, 1)

    func next() -> Int? {
        (a, b) = (b, a + b)
        return a
    }
}

let fibs = AnySequence{FibIterator()}

你可以得到四百万以下偶数项的总和:

fibs.prefix{$0 < 4000000}.filter{$0 % 2 == 0}.reduce(0){$0 + $1}

请注意,默认情况下filtermap是严格的,并且会在无限序列上永久运行。在上面的示例中,这并不重要,因为prefix仅返回有限数量的值。您可以致电.lazy以获取惰性序列,其中filtermap的行为非严格。例如,这里是前5个甚至斐波纳契数:

> print( Array(fibs.lazy.filter{$0 % 2 == 0}.prefix(5)) )
[2, 8, 34, 144, 610]