Search code examples
swiftfunctional-programmingnsindexpath

Use a functional technique to discover an NSIndexPath


There has got to be a more expressive way to do this using map, filter, reduce and friends.

Background:
The categories property is an [Category]?, where each element has a videos property of type [Video].

I'm trying to find the index path for video.

    var indexPath: NSIndexPath?
    for var i = 0; i < categories?.count ; ++i {
        for var j = 0; j < categories?[i].videos.count ; ++j {
            if categories?[i].videos[j] == video {
                indexPath = NSIndexPath(forRow: j, inSection: i)
            }
        }
    }

    if let indexPath = indexPath {
        tableView.reloadRowsAtIndexPaths([ indexPath ], withRowAnimation: UITableViewRowAnimation.Automatic)
    }

Solution

  • A possible solution (written in Swift 2):

    let indexPaths = categories?.enumerate().flatMap() { (section, aCategory) in
        aCategory.videos.enumerate().filter() { (_, aVideo) in
            aVideo == video
        }.map { (row, _) in
            NSIndexPath(forRow: row, inSection: section)
        }
    } ?? []
    

    indexPaths is an array of all matching index paths (or an empty array). If you don't need/like that, use

    let indexPath = categories?.enumerate().flatMap() { (section, aCategory) in
        aCategory.videos.enumerate().filter() { (_, aVideo) in
            aVideo == video
        }.map { (row, _) in
            NSIndexPath(forRow: row, inSection: section)
        }
    }.first
    

    instead, which is an optional index path.

    categories?.enumerate() is a sequence of (section, aCategory) pairs. For each category, aCategory.videos.enumerate() is a sequence of (row, aVideo) pairs. This sequence is filtered according to the search term, and the filtered pairs are mapped to an index path.

    So the result of the transform passed to flatMap() is an array of index paths of the matching items, one array for each category. flatMap() joins these to a single array.

    In Swift 1.2 this would be

    let indexPaths = flatMap(enumerate(categories ?? [])) { (section, aCategory) in
        filter(enumerate(aCategory.videos)) { (_, aVideo) in
            aVideo == video
        }.map { (row, _) in
            NSIndexPath(forRow: row, inSection: section)
        }
    }