Как адаптировать значения словаря для чтения в swift? - PullRequest
1 голос
/ 12 апреля 2019

Отредактированный вопрос: Как адаптировать значения словаря ДЛЯ ЧТЕНИЯ в swift?

Я хочу адаптировать значения словаря для чтения без изменения происхождения. Для этого я реализовал следующий класс.

class DictionaryView<Key, Value> where Key: Hashable {
   let origin: Dictionary<Key, Value>
   let map: (Value) -> Value

   init(_ origin: Dictionary<Key, Value>,
        map: @escaping (Value) -> Value) {
       self.origin = origin
       self.map = map
    }

   subscript(_ key: Key) -> Value?{
      let value = origin[key]
      return value == nil ? value : map(value!)
   }

}

Но не работает, как я ожидал. Следующий код

var origin = ["A":2, "B":3]
var adapted = DictionaryView(origin, map: { $0 * 2 })
origin["C"] = 6

print(origin)
print(adapted["A"])
print(adapted["B"])
print(adapted["C"]) 

дает мне вывод:

["B": 3, "C": 6, "A": 2]
Дополнительно (4)
Дополнительно (6)
ноль

Мне нужна адаптация, которая будет печатать Optional (12) вместо нуля. Как это сделать?


Оригинальный вопрос: Как адаптировать значения словаря в swift?

у меня есть словарь origin

var origin = ["A":2, "B":3]

И хотите создать новый словарь, который будет основан на origin, но содержит двойные значения. Моим наивным решением было нанести на карту mapValues

var adapted = origin.mapValues { $0 * 2 }

Но это решение создает новый преобразованный словарь, основанный на моменте состояния origin. Например следующий код

origin["C"] = 6

print(origin)
print(adapted)
* 1 035 * выходы

["A": 1, "B": 2, "C": 6]
["A": 2, "B": 4]

Но я хочу иметь настоящую адаптацию. Мне нужно решение, которое будет выводить результат, как показано ниже для кода, как указано выше.

["A": 1, "B": 2, "C": 6]
["A": 2, "B": 4, "C": 12]

Я новичок в Свифте и не обладаю глубокими знаниями стандартной библиотеки. Перед тем, как начать проектирование велосипедов, я спрашиваю: каково будет общее решение этой проблемы в Swift?


Примечание 1

Я нашел проблему в реальной ситуации, но создал упрощенный пример, чтобы извлечь суть проблемы (избегать шума деталей) для текущего вопроса. Но, как я понимаю, реальная ситуация необходима, чтобы прояснить вопрос.

Мне нужно было реализовать наблюдаемый шаблон и разработать его 4 части в файле Observable.swift.

Подписка:

struct Subscription {
    private let handler: () -> ()

    init(cancel handler: @escaping () -> ()) {
        self.handler = handler
    }

    func cancel() {
        handler()
    }
}

Наблюдатель:

struct Observer<Event> {
    private let handler: (Event) -> ()

    init(send handler: @escaping (Event) -> ()) {
        self.handler = handler
    }

    func send(_ event: Event) {
        handler(event)
    }
}

Наблюдаемое:

class Observable<Event> {

    fileprivate var observers: [UUID : Observer<Event>] = [:]
    func subscribe(_ observer: Observer<Event>) -> Subscription {
        let id = UUID()
        observers[id] = observer
        return Subscription(cancel: { self.observers[id] = nil })
    }
}

Тема:

class Subject<Event> : Observable<Event> {
    private(set) lazy var observer = Observer<Event>(
        send: { event in
            for o in self.observers.values {
                o.send(event)
            }})
}

Как правило, я предоставляю один экземпляр Subject as Observable вне клиентского класса и использую его как Observer внутри для хорошей инкапсуляции. Пример использования:

class Acc {
   private let numberAdded: Observer<Int> 
   let numberDidAdd: Observable<Int>

   init() {
     let subject = Subject<Int>()
     numberAdded = subject.observer
     numberDidAdd = subject
   }

   func add(_ number: Int) {
       numberAdded.send(number)
   }
}

let acc = Acc()
acc.numberDidAdd.subscribe(Observer<Int>(send: { print($0) }))
acc.add(4)
acc.add(5)

Это решение полезно для меня в ситуации, когда мне нужно простое событие. Проблемы начались, когда мне нужно было реализовать более сложный интерфейс подписки на события. Мне нужно было предоставить словарь событий (Observables снаружи, Observers внутри). Следующий код является частью реального контекста. Нелегко описать этот контекст, но я думаю, что можно поймать его из кода.

class RealClass {

    let buttonEvents = ButtonEvents()

    override func viewDidLoad() {
        super.viewDidLoad()

        for (event, observer) in buttonEvents.observers {
            someInternalSubscriptionFunc(event, observer)
        }

        buttonEvents.newEventTypeDidAdd.subscribe(
            Observer(send: { event in
                self.someInternalSubscriptionFunc(
                   event, self.buttonEvents.observers[event]!)
            })
    }

    public class ButtonEvents {

        private let newEventTypeAdding = Subject<UIControl.Event>()
        private(set) lazy var newEventTypeDidAdd
            : Observable<UIControl.Event> 
            = newEventsTypeAdding

        private var subjects: [UIControl.Event : Subject<Point>] = [:]
        fileprivate private(set) lazy var observers
            : [UIControl.Event : Observer<Point>] 
            = subjects.mapValues({ subject in subject.observer })

        public subscript(event: UIControl.Event) -> Observable<Point> {
            if subjects[event] == nil {
               subjects[event] = Subject<Point>()
               newEventTypeAdding.send(event)
            }

            return subjects[event]!
        }
    }
}

Как мы видим, ButtonEvents.observers - это семя проблемы.

Я могу переопределить observers как вычисляемое свойство, но я потеряю производительность O (1) при доступе к элементам словаря.

Ответы [ 4 ]

3 голосов
/ 12 апреля 2019

Это происходит потому, что

Словарь - это типы значений

Что вы должны сделать здесь, это использовать Computed Property

var adapted: [String: Int] {
    return origin.mapValues { $0 * 2 }
}

Теперь вызовитев любом месте результат будет, как и ожидалось

2 голосов
/ 12 апреля 2019

Согласно вашему последнему редактированию:

struct AdaptedDict<Key: Hashable, Value> {

    private var origin: UnsafeMutablePointer<[Key: Value]>
    private let transform: (Value) -> Value

    init(_ origin: inout [Key: Value], transform: @escaping (Value) -> Value) {
        self.origin = UnsafeMutablePointer(&origin)
        self.transform = transform
    }

    subscript(_ key: Key) -> Value? {
        if let value = origin.pointee[key] {
            return transform(value)
        }
        return nil
    }

}

var origin = ["A": 10, "B": 20]
var adaptedDict = AdaptedDict(&origin) { $0 * 2 }
print(origin["A"], adaptedDict["A"])
origin["A"] = 20
print(origin["A"], adaptedDict["A"])

Таким образом, вы храните словарь, используя указатель.

1 голос
/ 12 апреля 2019

Swift.Dictionary имеет семантику значений.Если вы хотите ссылочную семантику, тогда Foundation имеет NSMutableDictionary.Вы действительно не должны бояться Swift.Dictionary, если только у вас нет реальной проблемы с производительностью (т.е. вы изменяете миллионы записей и уверены, что это фактически вызывает копирование при записи).

import PlaygroundSupport
import Foundation

let d: NSMutableDictionary = [ "a": 1, "b": 2]
for (key, value) in d {
    guard let value = value as? Int else { continue}
    d[key] = value * 2
}
print(d)
0 голосов
/ 12 апреля 2019

Как сказал Martin R , если вы измените массив origin после генерации массива adapted, это не повлияет на него.Итак, вы сначала создаете origin

var origin = ["A":2, "B":3]

, затем изменяете его, добавляя origin["C"] = 6, а затем вы можете создать массив adapted.

var adapted = origin.mapValues { $0 * 2 }
print(origin)
print(adapted)

это должно дать ваможидаемый результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...