Hehcate
Hehcate

Reputation: 41

Error when using higher order function reduce

I am working on a function that takes the total cost of all the "MP" in a value and adds it up. Here is my code for context.

typealias Spell = (name: String, cat: Category, cost: Int)

let startingSpellList: [Spell] = [
    ("Poison", .attack, 3),
    ("Bio", .attack, 26),
    ("Fire", .attack, 4),
    ("Fire 2", .attack, 20),
    ("Fire 3", .attack, 51),
    ("Ice", .attack, 5),
    ("Ice 2", .attack, 21),
    ("Ice 3", .attack, 52),
    ("Bolt", .attack, 6),
    ("Bolt 2", .attack, 22),
    ("Bolt 3", .attack, 53),
    ("Pearl", .attack, 40),
    ("Quake", .attack, 50),
    ("Break", .attack, 25),
    ("Doom", .attack, 35),
    ("Flare", .attack, 45),
    ("Meteor", .attack, 62),
    ("Ultima", .attack, 80),

Here is the function :

func totalCost(_ spells: [Spell]) -> Int {
let cost = spells.cost
let sum = cost.reduce(0, +)
return sum

}

With this code, I get the error that `"Value of type '[Spell]' (aka 'Array<(name: String, cat: Category, cost: Int)>') has no member 'cost'." How should I fix this error?

Upvotes: 3

Views: 112

Answers (2)

user652038
user652038

Reputation:

rob mayoff's answer is great, but everybody needs this stuff all the time, so you should put your own extensions in a reusable package until they get included in the standard library.

startingSpellList.map(\.cost).sum
startingSpellList.reduce(\.cost, +)
public extension Sequence {
  /// - Returns: `nil` If the sequence has no elements, instead of an "initial result".
  func reduce(
    _ nextPartialResult: (Element, Element) throws -> Element
  ) rethrows -> Element? {
    var iterator = makeIterator()
    return try iterator.next().map { first in
      try IteratorSequence(iterator).reduce(first, nextPartialResult)
    }
  }
  
  /// Accumulates transformed elements.
  /// - Returns: `nil`  if the sequence has no elements.
  func reduce<Result>(
    _ transform: (Element) throws -> Result,
    _ getNextPartialResult: (Result, Result) throws -> Result
  ) rethrows -> Result? {
    try lazy.map(transform).reduce(getNextPartialResult)
  }
}
public extension Sequence where Element: AdditiveArithmetic {
  var sum: Element? { reduce(+) }
}

Upvotes: 0

rob mayoff
rob mayoff

Reputation: 385760

spells is a [Spell], which is shorthand for Array<Spell>, and Array<Spell> doesn't have a cost property. Each individual Spell in the array has its own cost property. You could say this to get an array of the spell costs and sum the costs array:

func totalCost(_ spells: [Spell]) -> Int {
    let costs = spells.map { $0.cost }
    let sum = costs.reduce(0, +)
    return sum
}

Or you could use a key-path literal, which can act as a function:

func totalCost(_ spells: [Spell]) -> Int {
    let costs = spells.map(\.cost)
    let sum = costs.reduce(0, +)
    return sum
}

However, using map that way creates a temporary array to hold the costs, and that's wasteful. You can avoid the temporary array by using the .lazy operator first:

func totalCost(_ spells: [Spell]) -> Int {
    let costs = spells.lazy.map(\.cost)
    let sum = costs.reduce(0, +)
    return sum
}

Or you can fuse the extraction of cost and the summation:

func totalCost(_ spells: [Spell]) -> Int {
    let sum = spells.reduce(0) { $0 + $1.cost }
    return sum
}

Upvotes: 3

Related Questions