Map all properties to a new value but maintain Collection type?

I have a struct that has an array of structs that each have a price property.

struct Ticket{
  var price = 0.0
}

struct Purchase{
  var tickets = [Ticket]()
    ...

How do I change the price of every ticket and update the tickets collection? Here's what I'm trying:

mutating func accumulate() -> Double{
    let mapper = tickets.map{$0.price + 2.0}
    tickets = mapper
    return tickets.reduce(0){$0 + $1}
}

//error: cannot assign value of type '[Double]' to type '[Ticket]'

Is there a way to do this with structs or do I have to use classes? Thanks.

$0.price + 2.0 returns a Double, so the type of mapper is [Double] instead of [Ticket].

I'm guessing what you're trying to do with your map closure is to increase the price value of each Tickets in the ticket array by 2.0, right? And you're probably running into headaches because you're being told $0 is immutable in your map closures. Try making Ticket a class instead of a struct. Doing so makes it a reference type, so you should be able to do:

class Ticket {
    var price = 0.0
}

struct Purchase {
    var tickets = [Ticket]()

    mutating func accumulate() -> Double {
        tickets.map{$0.price += 2.0}
        return tickets.reduce(0){$0 + $1.price}
    }
}

In short, what you're trying to do can be done with structs if you really need it to be, yes, but using classes will result in less awkward code. EDIT: But if you want to keep using structs, see @nuclearace 's smart response below.

A way to do what you seem to be trying to do is:

mutating func accumulate() -> Double {
    tickets = tickets.map {
      var copy = $0
      copy.price += 2.0
      return copy
    }
    return tickets.reduce(0) {$0 + $1}
}
1 Like

If you're down to get a bit functional here's some twisty code:

struct Ticket {
    var price = 0.0
}

struct Purchase {
    var tickets = [Ticket]()

    mutating func accumalate() -> Double {
        tickets = tickets.map({ Ticket(price: $0.price + 2.0 ) })
        
        return tickets.reduce(0.0, { $0 + $1.price })
    }
}

Also, just an FYI. If "price" is actual money in this case, you shouldn't use Double. You'll start running into weird floating-point stuff.

Edit: Just realized I replied to the wrong post, oops.

1 Like

Since you're opting for mutability, you're probably looking for forEach rather than map here.
tickets.forEach { $0.price += 2 }

2 Likes

Note that Ticket is a class here, so you don't actually have to mutate the value stored in the collection. A simple for ... in ... loop suffices.

Since you’re opting for mutability, you’re probably looking for forEach rather than map here.
tickets.forEach { $0.price += 2 }

I agree and was going to recommend the same. However, if fails when tried in a playground because $0 is not mutable.

This seems to work.

struct Ticket {
    var price = 0.0
}

struct Purchase{
    var tickets = [Ticket]()

    mutating func accumulate() -> Double{
        tickets.mapInPlace{$0.price += 2.0}
        return tickets.reduce(0){$0 + $1.price}
    }
}

extension Array {
    mutating func mapInPlace(_ x: (inout Element) -> ()) {
        for i in indices {
            x(&self[i])
        }
    }
}

var p = Purchase()
p.tickets = [Ticket(price: 1.0), Ticket(price: 2.0), Ticket(price: 1.50) ]
print(p.accumulate()) // 10.5

mapInPlace code stolen from Chris Eidhof

1 Like

Oops, nice catch—I misread Ticket to be a class rather than struct.

Oops, nice catch—I misread Ticket to be a class rather than struct.

It was a struct in the initial question. One of the answers changed it to a class. That lead to some confusion, IMHO.

Marc

The problem that you’re having, as you probably discovered, is that we don’t have a mutable forEach (see previous discussion: In-place map for MutableCollection)

So the only way to do this is to call forEach on the Collection’s indices and use the mutating subscript.

We should resurrect the previous discussion. You’re asking for something simple; it shouldn’t be this hard.