I have a very long tableView that I am able to search and filter results.
However, if I were to type the letter "i" as input,
all words with the letter "i" show up.
Is it possible to filter in a way so that the letter I type corresponds to the first letter of the word I want to filter.
For example my array ["should not use","Tristan","biscuit","is","should not use"]
and if I search the word "is"
, can that word automatically show up before the word "biscuit"?
Expected Result :
["is","biscuit","Tristan"]
You can use filter and sorted function to get the expected result.
import UIKit
var theArray: [String] = ["biscuit", "Tristan", "is", "iser", "instrument", "look"]
var keyword: String = "is"
let result = theArray
.filter { $0.contains(keyword) }
.sorted() { ($0.hasPrefix(keyword) ? 0 : 1) < ($1.hasPrefix(keyword) ? 0 : 1) }
print(result)
OUTPUT
["is", "iser", "biscuit", "Tristan"]
Related
I have string array in Swift 2:
var myList : [String] = []
And I have dynamic strings inside and I explode them with * character myList examples:
print(myList[0]) output = 2018-04-05*type2*namea
print(myList[1]) output = 2018-04-05*type2*nameb
print(myList[2]) output = 2018-04-05*type3*nameb
print(myList[3]) output = 2018-04-06*type3*named
I want to delete objects have type3 ones in myList:
IF IN same date AND same name AND have type2 ones
Must be my strings like that:
print(myList[0]) output = 2018-04-05*type2*namea
print(myList[1]) output = 2018-04-05*type2*nameb
print(myList[2]) output = 2018-04-06*type3*named
This item below must be deleted:
print(myList[2]) output = 2018-04-05*type3*nameb
I want to delete type3 ones in myList if before have type2 with same date and same name basically.
Explain:
2018-04-05*type2*nameb and 2018-04-05*type3*nameb, have same date and same name but 2018-04-05*type3*nameb before have type2(2018-04-05*type2*nameb) ? so 2018-04-05*type3*nameb line must be delete
How can I do it?
This playground code will do what you want:
//: Playground - noun: a place where people can play
import UIKit
let myList = ["2018-04-05*type2*namea",
"2018-04-05*type2*nameb",
"2018-04-05*type3*nameb",
"2018-04-06*type3*named"]
//Define a class that lets us map from a string to a date, type, and name string
class ListEntry {
let fullString: String
//define lazy vars for all the substrings
lazy var subStrings: [Substring] = fullString.split(separator: "*")
lazy var dateString = subStrings[0]
lazy var typeString = subStrings[1]
lazy var nameString = subStrings[2]
//Create a failable initializer that takes a full string as input
//and tries to break it into exactly 3 substrings
//using the "*" sparator
init?(fullString: String) {
self.fullString = fullString
if subStrings.count != 3 { return nil }
}
}
print("---Input:---")
myList.forEach { print($0) }
print("------------")
//Map our array of strings to an array of ListEntry objects
let items = myList.compactMap { ListEntry(fullString: $0) }
//Create an output array
var output: [String] = []
//Loop through each item in the array of ListEntry objects, getting an index for each
for (index,item) in items.enumerated() {
//If this is the first item, or it dosn't have type == "type3", add it to the output
guard index > 0,
item.typeString == "type3" else {
print("Adding item", item.fullString)
output.append(item.fullString)
continue
}
let previous = items[index-1]
/*
Add this item if
-the previous type isn't "type2"
-the previous item's date doesn't match this one
-the previous item's name doesn't match this one
*/
guard previous.typeString == "type2",
item.dateString == previous.dateString,
item.nameString == previous.nameString else {
print("Adding item", item.fullString)
output.append(item.fullString)
continue
}
print("Skipping item ", item.fullString)
}
print("\n---Output:---")
output.forEach { print($0) }
The output of the code above is:
---Input:---
2018-04-05*type2*namea
2018-04-05*type2*nameb
2018-04-05*type3*nameb
2018-04-06*type3*named
------------
Adding item 2018-04-05*type2*namea
Adding item 2018-04-05*type2*nameb
Skipping item 2018-04-05*type3*nameb
Adding item 2018-04-06*type3*named
---Output:---
2018-04-05*type2*namea
2018-04-05*type2*nameb
2018-04-06*type3*named
I'll start you off with a simple (albeit hack-ish) approach:
let myList = ["2018-04-05*type2*namea", "2018-04-05*type2*nameb", "2018-04-05*type3*nameb", "2018-04-06*type3*named"]
Define the function:
func swapLastTwoComps(_ s: String) -> String {
let parts = s.split(separator: "*")
return [parts[0], parts[2], parts[1]].joined(separator: "*")
}
Now if you do
let myListS = myList.map {swapLastTwoComps($0)}.sorted()
you get
["2018-04-05*namea*type2", "2018-04-05*nameb*type2", "2018-04-05*nameb*type3", "2018-04-06*named*type3"]
i.e. the sort has left strings to be removed adjacent and to the right of their equivalent, so now you can easily loop through the array and remove the strings you want (because you only need to compare each String's prefix with the String immediately to its left to determine whether it should be removed).
Once you've done that, map swapLastTwoComps over the final array again to restore the strings to their previous format.
I am trying to filter items based on text (mySearchText) from a search bar. So far I have got:
items = try context.fetch(Item.fetchRequest())
filtered data = items.filter { ($0.myArray?.contains(mySearchText))!}
This works if I enter the full text e.g. if I enter "Hello" into the search bar it will filter items with Hello in myArray.
However if I just enter "Hel" it won't filter the same item. How can I filter the items even if only a partial search term is used?
I want filteredData to be the items that have an array containing the string so
var filteredData: [Item] = []
e.g. if
item 1.myArray = ["cat", "dog", "monkey"] and
item 2.myArray = ["horse", "zebra", "cow"]
mySearchText = "o"
I would like filteredData to be item1 and item2 (cow in item 1 and dog in item 2 both contain "o")
You are calling contains() on an array which means you're not doing string matching but instead looking for objects equal to your mySearchText object, so your looking for objects equals to "Hello" or "Hel" in that array.
To do string matching instead you need to filter one level down so to speak. Here is my version where I assume myArray is an array of strings
items = try context.fetch(Item.fetchRequest())
var filteredData = [Item]()
for item in items {
if item.myArray?.filter({$0.contains(mySearchText)}).count > 0 {
filteredData.append(item)
}
}
Its work fine when i use the code below, maybe you should use string to filter based on it.
var mySearchText = "a"
var items = ["Adam", "Ahmad", "abdalla", "abcd", "efj", "hijk"]
let filtereddata = items.filter { ($0.lowercased().contains(mySearchText))}
print(filtereddata.count) // 4
Let's say I have a variable of type String holding a single String. In this string, there are 1 or more words. I also have an array of Strings. I want to check the String against the array, looking for, and replacing words found in the array.
For instance, the word "able" is in the array. The word "able" is also in the String variable. I want the word able replaced with the "+" character.
I've tried like this:
//stopWords = array of Strings / keywords = String variable
for words in stopWords {
keywordsFormatted = keywords.replacingOccurrences(of: words, with: "+", options: .regularExpression, range: nil)
}
and that doesn't change anything. I've also tried in a while loop, and a few other ways that I don't even care to recall right now lol. Any help is appreciated
Given
let stopWords = ["red", "green", "blue"]
let keywords = "The sky is blue not green"
let's create a set
let stopWordsSet = Set(stopWords)
and finally let's solve the problem
let result = keywords
.components(separatedBy: " ")
.map { stopWordsSet.contains($0) ? "+" : $0 }
.joined(separator: " ")
Oh... and lets test it
print(result) // The sky is + not +
Please note this solution is case sensitive. Furthermore it will not work is words in keywords are not delimited by spaces. E.g. "The sky is blue, not green" will not work properly because of the ,.
Addendum from Adrian:
If you want to update the textField, just use didSet, like so:
var keywords = "The sky is blue not green" {
didSet {
// if keywords is not empty, set the myTextField.text to keywords
if !keywords.characters.isEmpty {
myTextField.text = keywords
}
}
}
i have a struct array that i want "break up" into smaller arrays that can be called as needed or at least figure out how i can map the items needed off one text value.
the struct:
struct CollectionStruct {
var name : String
var description : String
var title : String
var image : PFFile
var id: String
}
and the array made from the struct
var collectionArray = [CollectionStruct]()
var i = 0
for item in collectionArray {
print(collectionArray[i].name)
i += 1
}
printing partArray[i].name gives the following result:
pk00_pt01
pk00_pt02
pk00_pt03
pk01_pt01
pk01_pt02
pk01_pt03
pk01_pt04
pk01_pt05
pk01_pt06
pk01_pt07
pk01_pt08
this is just some test values but there could be thousands of entries here so i wanted to filter the entire array just by the first 4 characters of [i].name i can achieve this by looping through as above but is this achievable using something like .map?
I wanted to filter the entire array just by the first 4 characters of
[i].name
You can achieve this by filtering the array based on the substring value of the name, as follows:
let filteredArray = collectionArray.filter {
$0.name.substring(to: $0.name.index($0.name.startIndex, offsetBy: 4)).lowercased() == "pk00"
// or instead of "pk00", add the first 4 characters you want to compare
}
filteredArray will be filled based on what is the compared string.
Hope this helped.
If you want to group all data automatically by their name prefix. You could use a reducer to generate a dictionary of grouped items. Something like this:
let groupedData = array.reduce([String: [String]]()) { (dictionary, myStruct) in
let grouper = myStruct.name.substring(to: myStruct.name.index(myStruct.name.startIndex, offsetBy: 4))
var newDictionart = dictionary
if let collectionStructs = newDictionart[grouper] {
newDictionart[grouper] = collectionStructs + [myStruct.name]
} else {
newDictionart[grouper] = [myStruct.name]
}
return newDictionart
}
This will produce a dictionary like this:
[
"pk00": ["pk00_pt01", "pk00_pt02", "pk00_pt03"],
"pk01": ["pk01_pt01", "pk01_pt02", "pk01_pt03", "pk01_pt04", "pk01_pt05", "pk01_pt06", "pk01_pt07"],
"pk02": ["pk02_pt08"]
]
Not sure if i am understanding you correctly but it sounds like you are looking for this...
To create a new array named partArray from an already existing array named collectionArray (that is of type CollectionStruct) you would do...
var partArray = collectionArray.map{$0.name}
In Parse, I have a class called Queries. In this class I have a column that is of array type called favorites I would like to display that array in a UITableView The trouble is that the query downloads the favorites array column as one array, instead of multiple.
For example:
Row 1 has ["Bananas"] in favorites column
Row 2 has ["Apples", "Oranges"] in favorites column.
Row 3 has ["Tomatoes"] in favorites column
I would like the tableView to show:
Bananas
Apples, Oranges
Tomatoes
But now its showing:
Bananas
Apples
Oranges
var favorites : [String] = []
let query = PFQuery(className: "Queries")
query.findObjectsInBackground { (object, error) in
if object != nil && error == nil {
if let returnedObjects = object {
for objects in returnedObjects {
let getFavorites = objects["favorites"] as! [String]
self.favorites.append(contentsOf: getFavorites)
self.tableView.reloadData()
}
}
}
}
The tableView is populated like this: cell.favoritesLabel.text = favorites[indexPath.row]
Your favourites variable is a single dimension array, so you should either join values from a single row, or make it a two dimensional array and handle it appropriately.
To join values replace your append line with this one:
self.favorites.append(getFavorites.joined(separator: ", "))
First of all change your favorites array to this
var favorites: [[String]] = []
And change this self.favorites.append(contentsOf: getFavorites)
To this self.favorites.append(getFavorites)
The mistake is that you are appending string to an array of strings ignoring that they came in an array in the first place,
now after that you can reduce your items to one string to set it in the cell
like this
cell.favoritesLabel.text = (favorites[indexPath.row])[1..<array.count].reduce(array[0]) { $0 + ", " + $1 }
Although not strictly relevant to your question, I thought I would mention that the Parse iOS SDK does provide a set of prebuilt UI elements one of which is PFQueryTableViewController.
If you haven't already, it may be worth taking a look at the PFQueryTableViewController section in the docs to see if that would simplify your implementation in this case.