Lazily populated dictionary


(Rudolf Adamkovič) #1

Hi there!

In my app, I have a very simple class that serves as a key-value cache. The whole thing is basically a lazily populated [String: AVAudioPCMBuffer] dictionary with a String -> AVAudioPCMBuffer function that generates values as needed:

final class PlayerAudioCache {
    
    // MARK: Retrieving audio buffers
    
    func audioBufferForAssetWithName(name: String) -> AVAudioPCMBuffer? {
        addAudioBufferForAssetWithNameIfNeeded(name)
        return cachedAudioBuffers[name]
    }
    
    // MARK: Adding audio buffers
    
    func addAudioBufferForAssetWithNameIfNeeded(name: String) {
        guard cachedAudioBuffers[name] == nil else { return }
        addAudioBufferForAssetWithName(name)
    }
    
    private func addAudioBufferForAssetWithName(name: String) {
        guard let dataAsset = NSDataAsset(name: name) else { fatalError() }
        cachedAudioBuffers[name] = dataAsset.map { URL -> AVAudioPCMBuffer in
            AVAudioPCMBuffer(contentsOfURL: URL)!
        }
    }
    
    private var cachedAudioBuffers: [String: AVAudioPCMBuffer] = [:]
    
}

I feel like there is a pre-made type in Swift’s standard library for what I’m doing here. Am I right?

Ideas? Pointers?

Thank you!

R+


(Erica Sadun) #2

Maybe something like this?

struct KeyedLazyCache<T: Hashable, U> {
    var backingDictionary: Dictionary<T, U>
    var builderDictionary: Dictionary<T, () -> U>
    mutating func clear() {backingDictionary.removeAll()}
    mutating func valueForKey(key: T) -> U? {
        if let value = backingDictionary[key] {return value}
        if let builder = builderDictionary[key] {
            let value = builder()
            backingDictionary[key] = value
            return value
        }
        return nil
    }
}

-- E

···

On Dec 28, 2015, at 11:36 AM, Rudolf Adamkovič via swift-users <swift-users@swift.org> wrote:

Hi there!

In my app, I have a very simple class that serves as a key-value cache. The whole thing is basically a lazily populated [String: AVAudioPCMBuffer] dictionary with a String -> AVAudioPCMBuffer function that generates values as needed:

final class PlayerAudioCache {
    
    // MARK: Retrieving audio buffers
    
    func audioBufferForAssetWithName(name: String) -> AVAudioPCMBuffer? {
        addAudioBufferForAssetWithNameIfNeeded(name)
        return cachedAudioBuffers[name]
    }
    
    // MARK: Adding audio buffers
    
    func addAudioBufferForAssetWithNameIfNeeded(name: String) {
        guard cachedAudioBuffers[name] == nil else { return }
        addAudioBufferForAssetWithName(name)
    }
    
    private func addAudioBufferForAssetWithName(name: String) {
        guard let dataAsset = NSDataAsset(name: name) else { fatalError() }
        cachedAudioBuffers[name] = dataAsset.map { URL -> AVAudioPCMBuffer in
            AVAudioPCMBuffer(contentsOfURL: URL)!
        }
    }
    
    private var cachedAudioBuffers: [String: AVAudioPCMBuffer] = [:]
    
}

I feel like there is a pre-made type in Swift’s standard library for what I’m doing here. Am I right?

Ideas? Pointers?

Thank you!

R+

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Donnacha Oisín Kidney) #3

This version kind of mirrors Python’s defaultdict:

final class DefaultDict<Key: Hashable, Value>: CollectionType, CustomStringConvertible {
  private var store: [Key:Value]
  private let factory: Key -> Value
  subscript(i: DictionaryIndex<Key,Value>) -> (Key,Value) {
    return store[i]
  }
  var startIndex: DictionaryIndex<Key,Value> { return store.startIndex }
  var endIndex: DictionaryIndex<Key,Value> { return store.endIndex }
  func generate() -> DictionaryGenerator<Key,Value> {
    return store.generate()
  }
  subscript(key: Key) -> Value {
    get {
      if let x = store[key] { return x }
      let x = factory(key)
      self.store[key] = x
      return x
    } set {
      store[key] = newValue
    }
  }
  init(_ def: Value) {
    self.store = [:]
    self.factory = { _ in def }
  }
  init(_ fac: Key -> Value) {
    self.store = [:]
    self.factory = fac
  }
  init(_ fac: () -> Value) {
    self.store = [:]
    self.factory = { _ in fac() }
  }
  var description: String { return store.description }
}

let dict = DefaultDict<Character,Int>(0)
for c in "abcdgeaaa".characters { dict[c] += 1 }
dict // ["b": 1, "e": 1, "a": 4, "g": 1, "d": 1, "c": 1]

···

On 28 Dec 2015, at 19:26, Erica Sadun via swift-users <swift-users@swift.org> wrote:

Maybe something like this?

struct KeyedLazyCache<T: Hashable, U> {
    var backingDictionary: Dictionary<T, U>
    var builderDictionary: Dictionary<T, () -> U>
    mutating func clear() {backingDictionary.removeAll()}
    mutating func valueForKey(key: T) -> U? {
        if let value = backingDictionary[key] {return value}
        if let builder = builderDictionary[key] {
            let value = builder()
            backingDictionary[key] = value
            return value
        }
        return nil
    }
}

-- E

On Dec 28, 2015, at 11:36 AM, Rudolf Adamkovič via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi there!

In my app, I have a very simple class that serves as a key-value cache. The whole thing is basically a lazily populated [String: AVAudioPCMBuffer] dictionary with a String -> AVAudioPCMBuffer function that generates values as needed:

final class PlayerAudioCache {
    
    // MARK: Retrieving audio buffers
    
    func audioBufferForAssetWithName(name: String) -> AVAudioPCMBuffer? {
        addAudioBufferForAssetWithNameIfNeeded(name)
        return cachedAudioBuffers[name]
    }
    
    // MARK: Adding audio buffers
    
    func addAudioBufferForAssetWithNameIfNeeded(name: String) {
        guard cachedAudioBuffers[name] == nil else { return }
        addAudioBufferForAssetWithName(name)
    }
    
    private func addAudioBufferForAssetWithName(name: String) {
        guard let dataAsset = NSDataAsset(name: name) else { fatalError() }
        cachedAudioBuffers[name] = dataAsset.map { URL -> AVAudioPCMBuffer in
            AVAudioPCMBuffer(contentsOfURL: URL)!
        }
    }
    
    private var cachedAudioBuffers: [String: AVAudioPCMBuffer] = [:]
    
}

I feel like there is a pre-made type in Swift’s standard library for what I’m doing here. Am I right?

Ideas? Pointers?

Thank you!

R+

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Rudolf Adamkovič) #4

That’s a nice one, Erica.

Does this mean there’s nothing ready-made for this in the standard library?

P.S. No need for the builder dictionary. A simple static function (T->U) would be more enough for my use-case.

R+

···

On 28 Dec 2015, at 20:26, Erica Sadun <erica@ericasadun.com> wrote:

Maybe something like this?

struct KeyedLazyCache<T: Hashable, U> {
    var backingDictionary: Dictionary<T, U>
    var builderDictionary: Dictionary<T, () -> U>
    mutating func clear() {backingDictionary.removeAll()}
    mutating func valueForKey(key: T) -> U? {
        if let value = backingDictionary[key] {return value}
        if let builder = builderDictionary[key] {
            let value = builder()
            backingDictionary[key] = value
            return value
        }
        return nil
    }
}

-- E

On Dec 28, 2015, at 11:36 AM, Rudolf Adamkovič via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi there!

In my app, I have a very simple class that serves as a key-value cache. The whole thing is basically a lazily populated [String: AVAudioPCMBuffer] dictionary with a String -> AVAudioPCMBuffer function that generates values as needed:

final class PlayerAudioCache {
    
    // MARK: Retrieving audio buffers
    
    func audioBufferForAssetWithName(name: String) -> AVAudioPCMBuffer? {
        addAudioBufferForAssetWithNameIfNeeded(name)
        return cachedAudioBuffers[name]
    }
    
    // MARK: Adding audio buffers
    
    func addAudioBufferForAssetWithNameIfNeeded(name: String) {
        guard cachedAudioBuffers[name] == nil else { return }
        addAudioBufferForAssetWithName(name)
    }
    
    private func addAudioBufferForAssetWithName(name: String) {
        guard let dataAsset = NSDataAsset(name: name) else { fatalError() }
        cachedAudioBuffers[name] = dataAsset.map { URL -> AVAudioPCMBuffer in
            AVAudioPCMBuffer(contentsOfURL: URL)!
        }
    }
    
    private var cachedAudioBuffers: [String: AVAudioPCMBuffer] = [:]
    
}

I feel like there is a pre-made type in Swift’s standard library for what I’m doing here. Am I right?

Ideas? Pointers?

Thank you!

R+

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Rudolf Adamkovič) #5

All right, here is the outcome:

https://github.com/salutis/KeyedCache/blob/master/KeyedCache.swift

Thanks Erica and Donnacha for examples and inspiration!

R+

···

On 28 Dec 2015, at 19:36, Rudolf Adamkovič via swift-users <swift-users@swift.org> wrote:

Hi there!

In my app, I have a very simple class that serves as a key-value cache. The whole thing is basically a lazily populated [String: AVAudioPCMBuffer] dictionary with a String -> AVAudioPCMBuffer function that generates values as needed:

final class PlayerAudioCache {
    
    // MARK: Retrieving audio buffers
    
    func audioBufferForAssetWithName(name: String) -> AVAudioPCMBuffer? {
        addAudioBufferForAssetWithNameIfNeeded(name)
        return cachedAudioBuffers[name]
    }
    
    // MARK: Adding audio buffers
    
    func addAudioBufferForAssetWithNameIfNeeded(name: String) {
        guard cachedAudioBuffers[name] == nil else { return }
        addAudioBufferForAssetWithName(name)
    }
    
    private func addAudioBufferForAssetWithName(name: String) {
        guard let dataAsset = NSDataAsset(name: name) else { fatalError() }
        cachedAudioBuffers[name] = dataAsset.map { URL -> AVAudioPCMBuffer in
            AVAudioPCMBuffer(contentsOfURL: URL)!
        }
    }
    
    private var cachedAudioBuffers: [String: AVAudioPCMBuffer] = [:]
    
}

I feel like there is a pre-made type in Swift’s standard library for what I’m doing here. Am I right?

Ideas? Pointers?

Thank you!

R+

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users