Computing properties once only

Is this the best way to ensure the computations for the properties are only done once?


struct GenericDataFrequency<T: Hashable & Comparable> {

  let data: [T]
  let frequencies: [T : Int]
  let sortedFrequencies: [(T, Int)]

  init(data: [T]) {
    self.data = data
    frequencies = Dictionary((data.map {($0, 1)}), uniquingKeysWith: {$0 + $1})
    sortedFrequencies = Array(frequencies).sorted {$0.key < $1.key}
  }

  func frequency(of elements: Set<T>) -> [T : Int] {
    return Dictionary(uniqueKeysWithValues: elements.map {($0, frequencies[$0] ?? 0)})
  }
}

Replies

Is this the best way to ensure the computations for the properties are only done once?

Honestly, I’m not sure what you’re aiming for here. The code you posted seems to take the input data in two different ways:

  • The initialiser takes an array of

    T
    , calculates the frequency map, and store that in properties.
  • The

    frequency(of:)
    takes a set of
    T
    , calculates the frequency map, and returns that as the function result.

It’s not clear why you’d have two chunks of code that do such similar things. If you want to calculate the frequency map once for any given set up, the first approach seems like the only one you need.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

With apologies to Quinn, here's some details about what I want the code to do.


I was testing it with an array of 10,000 random integers between 1 and 10 (which is why I'm concerned that the main computation should run only once.)


In this test case, the struct is initialized with the array of integers as data. It then creates the dictionary frequencies, with the integers 1 to 10 as its keys. The values are counts of how often each key appears in data. That's the part that should only run once, so I didn't set it up as a computed property.


The function frequency(of:) takes a set of arbitrary integers elements as input, and returns a dictionary with the same structure as frequencies, but with elements as the keys. So some of the values in that dictionary may be zero. I used a set as input so that there'd be no duplicate keys from the outset, since I believe the uniqueKeysWithValues: Dictionary initializer will crash if there are.


The main difference between the frequency(of:) function and the struct initializer is that the initializer finds its own keys within the data array.


The app I'm working on is intended to calculate frequencies and distributions of letters, words, etc. in multi-megabyte text files, so it's handy to set things up generically. It's also more fun.

What do you mean by "once"? Once per instance of GenericDataFrequency that is initialized by "init(data:)"? Once per execution of the program containing this code? Once per generic specialization?. Once per unique value of "[T]"?


Keep in mind that if your meaning of "once" is something less than once per instance, you have a theoretical risk of thread unsafety in whatever code eliminates or prevents repeated calculations.

I mean once per instance.


I originally had frequencies defined as a computed property of a struct. In a playground, I called frequencies.count twice on the struct after it was initialized, and the calculation was done both times. Would that also happen when an app was running? Or would the compiler be able to optimize things so it was calculated only once?

In the code above, "frequencies" is a "let" property, so it can never be changed after it's set in the initializer. That's precisely "once per instance" semantics, so I think your code already does what you want.