简体   繁体   中英

How to filter a struct containing a string and an array?

Basically this is a struct object holding a country and its ports

struct countryAndPorts {
   var country: String?
   var ports: [String]?
}

These are the arrays holding each country mapped to its ports. The filtered array should hold filtered results either by country or ports.

var countryAndPortsArray = [countryAndPorts]()
var filteredArray = [countryAndPorts]()

Currently the function below successfully produces a filtered result using countries as search key

func filteredContent(searchKey: String) {
    filteredArray = countryAndPortsArray.flatMap{ $0 }.filter{$0.country?.lowercased().range(of: searchKey) != nil }
}

Now my problem is I wish to get a filtered result either by using a port or country as search key. I have tried my possible best but can't seem to get desirable results using ports as the search key. Improvements on my current method would be much appreciated. Answers in Objective-C are welcomed as well.

//Just incase it's of any use, these are my UITableView delegates 
extension SearchDealsViewController: UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if let count = filteredArray[section].ports?.count {
        return count
    }
    return 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

    if let port = filteredArray[indexPath.section].ports?[indexPath.row] {
        cell.textLabel?.text = port
    }
    cell.textLabel?.font = UIFont.systemFont(ofSize: 10)
    return cell
}

func numberOfSections(in tableView: UITableView) -> Int {
    return filteredArray.count
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    portsFromTextField.text = filteredArray[indexPath.section].ports?[indexPath.row]
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let header = tableView.dequeueReusableCell(withIdentifier: "header")
    let tapSectionHeaderGesture = UITapGestureRecognizer(target: self, action: #selector(getCountryText(sender:)))
    tapSectionHeaderGesture.numberOfTouchesRequired = 1
    tapSectionHeaderGesture.numberOfTapsRequired = 1
    header?.addGestureRecognizer(tapSectionHeaderGesture)

    if header != nil {
        header?.textLabel?.textColor = THEME_COLOUR
        header?.textLabel?.font = UIFont.boldSystemFont(ofSize: 12)
        header?.textLabel?.text = filteredArray[section].country
    }

    return header
}

func getCountryText(sender: UITapGestureRecognizer) {
    if let country = sender.view as? UITableViewCell {
        portsFromTextField.text = country.textLabel?.text
    }
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 30
}

-------------------------------- UDPATE -------------------------------

At the moment typing "vlone" as a search string returns both "vlone" and "vlore". Vlone and Vlore are both ports in Albania

Result yielded using vlone as search string

However the desired output should only include "vlone" and leave out every other port in Albania because the user was specific with their query.

2nd scenario would be using "barcelona" as search key. It returns a result containing every port in Spain however the desired output should only contain Barcelona.

Results using barcelona as search key

If you want to filter the array to find any entry that has a matching country or port, then you can do:

func filteredContent(searchKey: String) {
    filteredArray = countryAndPortsArray.filter {
        $0.country?.lowercased().range(of: searchKey) != nil ||
        !($0.ports?.filter {
            $0.lowercased().range(of: searchKey) != nil
        }.isEmpty ?? true)
    }
}

Let's break this down. At the top level is the call to filter on the countryAndPortsArray array. The filter consists of two parts. 1) checking to see if the country contains the search text, and 2) checking to see if any of the ports contain the search text.

The first check is done with:

$0.country?.lowercased().range(of: searchKey) != nil

which is the part you already had.

The second check is done with:

!($0.ports?.filter {
    $0.lowercased().range(of: searchKey) != nil
}.isEmpty ?? true)

Since ports is an array, a filter is being used to see if any of the ports contains the search text. The isEmpty is checking if the resulting filter list of matching ports is empty. Since ports is optional, the use of ?? true ?? true means to act as if the list of matching ports is empty. The ! negates the results. So if the matching list is empty (or ports is nil ), the true is negated to false indicating that there are no matching ports.

The two checks are combined using the standard logical "or" operator || meaning only one of the two checks needs to be true for the top level filter to match that record.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM