Search code examples
arraysswiftperformancecontains

Array Contains Too Slow Swift


I have been porting over an algorithm I've been using in Java (Android) to Swift (iOS), and have run into some issues with speed on the Swift version.

The basic idea is there are objects with depths (comment tree), and I can hide and show replies from the dataset by matching against a list of hidden objects. Below is a visualization

Top
- Reply 1
- - Reply 2
- - Reply 3
- Reply 4

and after hiding from the dataset

Top
- Reply 1
- Reply 4

The relevant methods I've converted from Java are as follows

 //Gets the "real" position of the index provided in the "position" variable. The comments array contains all the used data, and the hidden array is an array of strings that represent items in the dataset that should be skipped over.

    func getRealPosition(position: Int)-> Int{

        let hElements = getHiddenCountUpTo(location: position)
        var diff = 0
        var i = 0
        while i < hElements {
            diff += 1
            if(comments.count > position + diff && hidden.contains(comments[(position + diff)].getId())){
                i -= 1
            }
            i += 1
        }
        return position + diff
    }

    func getHiddenCountUpTo(location: Int) -> Int{
        var count = 0
        var i = 0
        repeat {
            if (comments.count > i && hidden.contains(comments[i].getId())) {
                count += 1
            }
            i += 1
        } while(i <= location && i < comments.count)
        return count
    }

This is used with a UITableViewController to display comments as a tree.

In Java, using array.contains was quick enough to not cause any lag, but the Swift version calls the getRealPosition function many times when calling heightForRowAt and when populating the cell, leading to increasing lag as more comment ids are added to the "hidden" array.

Is there any way I can improve on the speed of the array "contains" lookups (possibly with a different type of collection)? I did profiling on the application and "contains" was the method that took up the most time.

Thank you


Solution

  • i think you need the realPosition to link from a tap on a row in the tableview to the source array?

    1) make a second array with data only for the tableViewDataSource

    copy all visible elements to this new array. create a special ViewModel as class or better struct which only has the nessesary data to display in the tableview. save in this new ViewModel the realdataposition also as value. now you have a backlink to the source array

    2) then populate this TableView only from the new datasource

    3) look more into the functional programming in swift - there you can nicer go over arrays for example:

    var array1 = ["a", "b", "c", "d", "e"]
    let array2 = ["a", "c", "d"]
    array1 = array1.filter { !array2.contains($0) }
    

    or in your case:

    let newArray = comments.filter{ !hidden.contains($0.getId()) }
    

    or enumerated to create the viewmodel

    struct CommentViewModel {
      var id: Int
      var text: String
      var realPosition: Int
    }
    
    let visibleComments: [CommentViewModel] = comments
       .enumerated()
       .map { (index, element) in
          return CommentViewModel(id: element.getId(), text: element.getText(), realPosition: index)
       }
       .filter{ !hidden.contains($0.id) }