trying to build a dictionary of dictionaries (in Swift)

I'm trying to do something in swift, and having trouble getting it to work.


I'm attempting to take a flat list of files, and create a date based directory tree.

2014

01

2014-01-06

fred.jpg

2014-01-15

harry.jpg

09

2014-09-07

george.jpg

2015

02

2015-02-03

ron.jpg


Step 1 is to create hierarchical dictionaries to build a tree of the nested folders to create.

I am able to get an enumeratable list of files, ie the following code appears to be working as expected...


typealias yearFolders = [String: monthFolders]  // use year as the key eg. "2014"
typealias monthFolders = [String: dayFiles]     // use month as the key eg. "01"
typealias dayFiles = [String: [String]]         // use date as the key eg. "2014-01-06"

...// create my list of files
var allFiles: yearFolders = [:]
while let fileDetails = enumerator?.nextObject() as? NSURL {

    if !(fileDetails.isDirectory())
    {
        if let createDate = fileDetails.creationDate() {
            addFileAt(fileDetails.absoluteString, forDate: createDate, inStructure: &allFiles)
        }
    }
}


but am having trouble getting a non-ugly version of addFileAt() to work

when I do this


func addFileAt(path: String, forDate date: NSDate, inout inStructure structure: yearFolders)
{
    let yearFolderName = yearFormatter.stringFromDate(date)

    if (structure[yearFolderName] == nil) {
        structure[yearFolderName] = [:]
    }
    var yearFolder = structure[yearFolderName]!
    let monthFolderName = monthFormatter.stringFromDate(date)

    if (yearFolder[monthFolderName] == nil) {
        yearFolder[monthFolderName] = [:]
    }


line 06. successfully adds an empty dictionary (of type monthFolders) to structure

line 08. creates a reference to this empty monthFolders dictionary

line 12. then adds an empty dayFiles dictionary to the yearFolder variable created in line 08.

However yearFolder is NOT the object that was added to structure. it seems to be a copy.

Is there a 'nice' way for me to create yearFolder as a reference to structure[yearFolderName] rather than copy?


when I change lines 11 & 12 as below, everything works, but it just feels a bit hard to read (and less robust)

    if (structure[yearFolderName]![monthFolderName] == nil) {
        structure[yearFolderName]![monthFolderName] = [:]
    }


any/all guidance appreciated,


Mike

Replies

It would help greatly if you could follow the Swift convention of capitalizing types. It's much harder to read your code if it's not easy to see what's a type and what's a variable.


The problem you're having is that you're using value types in a context where reference types would be a better choice. Since Dictionary is necessarily a value type, that means you should use something other than Dictionary.


Generically, what you would do is create a class that has the dictionary as a property. If it's non-private and mutable, then clients of the class can update the dictionary directory, or you can make it private (or at least immutable to clients) and have accessors methods on the class to update it in a controlled way.


But in a situation like yours, you're much better off putting as much as you can as properties of custom classes. Use arrays and dictionaries to provide your nesting, but make the non-structural parts be properties of custom classes, instead of being values of dictionary keys. Using dictionaries as you have may seem like a shortcut, but in any meaningful program it's not ultimately going to save you any work.

This is sort of experiment, but with writing some extension method for Dictionary giving an auto expanding feature:

protocol DefaultInitializable {
    init()
}
extension Dictionary: DefaultInitializable {}
extension Array: DefaultInitializable {}
extension Dictionary where Value: DefaultInitializable {
    subscript(auto key: Key) -> Value {
        mutating get {
            if self[key] == nil {
                self[key] = Value()
            }
            return self[key]!
        }
        set {
            self[key] = newValue
        }
    }
}

you can write something like this:

typealias YearFolders = [String: MonthFolders] 
typealias MonthFolders = [String: DayFiles]    
typealias DayFiles = [String: [String]]         

var allFiles: YearFolders = [:]
allFiles[auto: "2014"][auto: "01"][auto: "2014-01-06"].append("fred.jpg")
print(allFiles) //->["2014": ["01": ["2014-01-06": ["fred.jpg"]]]]


But I'm not sure this behaviour is consistent with Swift's value semantics and we can rely on it or not.


For me, your "everything works" code seems far more robust.

Thanks for the encouragment to capitalize typeAlias/ClassNames. (that was an oversight on my part)

I suspect I will go the custom class route you suggest. My only hesitation is that this program is not overly meaningful. Most likely a single use 'script'.

I'll weigh the balanace between a learning opportunity and expediency.

thanks,

Mike