I have two lists, lst1 and lst2. I want to perform lst1 - lst2, which returns all elements of lst1 not in lst2.
var lst1 = ["t1" , "t2" , "t3" , "t4"];
var lst2 = ["t2" , "t4" , "t5"];
//output: ["t1" , "t3"]
Convert to sets and take the difference, then convert back to a list (with the caveat that neither duplicates nor the ordering of elements will be preserved):
void main() {
var lst1 = ["t1" , "t2" , "t3" , "t4"];
var lst2 = ["t2" , "t4" , "t5"];
var set1 = Set.from(lst1);
var set2 = Set.from(lst2);
print(List.from(set1.difference(set2)));
}
Output
[t1, t3]
Try it online
The method using sets suggested by Ray Toal is probably the fastest, but if your first list contains duplicates that you want to keep, sets will completely destroy them.
Instead, you could use a simple list filtering method.
void main() {
var lst1 = ["t1" , "t2" , "t2", "t3" , "t3", "t4"]; // introduce duplicates
var lst2 = ["t2" , "t4" , "t5"];
var set1 = Set.from(lst1);
var set2 = Set.from(lst2);
print(List.from(set1.difference(set2)));
// Output : [t1, t3]
var filtered_lst = List.from(lst1.where(
(value) => !lst2.contains(value)));
print(filtered_lst);
// Output: [t1, t3, t3]
}
If there is duplicates in both lists and you actually want to subtract list item per item, you could use the remove method (warning: this will actually remove items from your first list, so you might need to create a copy first).
void main() {
var lst1 = ["t1" , "t2" , "t2", "t3" , "t3", "t4"]; // introduce duplicates
var lst2 = ["t2" , "t4" , "t5"];
for (var elem in lst2) {
lst1.remove(elem);
}
print(lst1);
// Output : [t1, t2, t3, t3]
// only one occurrence of "t2" was removed.
}
If you want to do this in a non-destructive way and preserve the order, you can create an Iterable extension for this.
void main() {
final lst1 = ["t1" , "t2" , "t3" , "t4"];
final lst2 = ["t2" , "t4" , "t5"];
final diff = lst1.whereNotIn(lst2).toList();
print(diff);
}
extension WhereNotInExt<T> on Iterable<T> {
Iterable<T> whereNotIn(Iterable<T> reject) {
final rejectSet = reject.toSet();
return where((el) => !rejectSet.contains(el));
}
}
Try it out here: https://dartpad.dev/88c5c949b6ac8c2d0812e15ce10e40ce?null_safety=true
Some benefits to this approach:
Doesn't mutate the original list
Preserves the original order
Works on Iterable too
It uses a set to keep track of the elements to reject, so it doesn't slow down a lot if both lists are large
var lst1 = ["t1" , "t2" , "t3" , "t4"];
var lst2 = ["t2" , "t4" , "t5"];
lst1 = lst1.removeWhere((item) => lst2!.contains(item));
solutions with sets are quite working.
however, it is worth remembering that if you have a set of objects, then you need overloading operator == and hashCode in these objects.
Related
I have an array myarray and I am using a for loop to get a few information which I add to myarray. But next time the for-loop runs, I don't want to create a separate index, but instead the 2nd time and so on, I want to append the information to myarray[0].
How do I do that?
var myarray = [String]()
for var j in 0 < 12 {
// do some stuff
for var i in 0 ..< 10 {
let parta = json?["users"][j]["name"].string
let partb = json?["users"][j]["Lname"].string
let partc = json?["users"][j]["dob"].string
myarray.append("\(parta)-\(partb)-\(partc)---")
// Here when the for loop comes back again (i = 1) , i dont want to make
// myarray[1] , but instead i want myarray[0] ,
// having value like [parta-partb-partc--parta-partb-partc]
}
}
Basically what I am trying to do is, append the new name/lname/dob values at myarray[0] without affecting the current value/string at myarray[0].
You can insert single element and also add array as below.
Swift 5
var myarray = [String]()
myarray.insert("NewElement", at: 0)
myarray.insert(contentsOf: ["First", "Second", "Third"], at: 0)
If I understand your question correctly, you want to create one long string and add the new data always at the beginning of the string. One way to do that would be:
// Store somewhere
var myString = String()
for var i in(0..<10) {
let parta = json?["name"].string
let partb = json?["Lname"].string
let partc = json?["dob"].string
let newString = "\(parta)-\(partb)-\(partc)---")
newString.append(myString)
myString = newString
// Here when the for loop comes back again (i = 1) , i dont want to make
//myarray[1] , but instead i want myarray[0] ,
//having value like [parta-partb-partc--parta-partb-partc]
}
Hi guys I want to ask how do you search the items for the nearest possible similar value regarding of the sequence. Example as below when I search for ["Restaurant","Bull"], it should return me str2 is the possible nearest values. Because this function only able to work for order sequence, it cannot for non-sequence. I really hope you guys can help me out....
func search(`for` searchItems: Set<String>, `in` searchArea: [Set<String>]) -> Set<String>? {
return searchArea.max(by: { (a, b) -> Bool in
return searchItems.intersection(a).count < searchItems.intersection(b).count || searchItems.intersection(a).count > searchItems.intersection(b).count
})
}
let str2: Set<String> = ["Bull","Restaurant","Corner"]
let str3: Set<String> = ["Corner","Restaurant","Mole"]
let area = [str3, str2] as [Any]
print("search result",self.search(for: ["Restaurant","Bull"], in: area as! [Set<String>]))
Probably because your str2 and str3 is not Set at all, it's array coz you are using array declaration, thus change it to this then it works if you use ["Bull", "Restaurant"]:
let str2 = Set(["Bull","Restaurant","Corner"])
let str3 = Set(["Corner","Restaurant","Mole"])
Also, Set is non-ordered sequence, Array is ordered sequence
Just make a set out of your query array and use its isSubset(of:) method to check wether it's a subset of your data.
let set1 = Set([1,2,3])
let set2 = Set([2,3,4])
let query = [1,2]
let querySet = Set(query)
querySet.isSubset(of: set1) // true
querySet.isSubset(of: set2) // false
let query2 = [2,1]
let querySet2 = Set(query)
querySet2.isSubset(of: set1) // true
querySet2.isSubset(of: set2) // false
For example I need to get all english "text" values of "ex" scope from JSON example
What I do:
let result = json["def"].arrayValue.map({ $0["tr"].arrayValue.map { $0["ex"] } })
but at as a result I got a double massive and if I intend to get all "text" then I will get a triple array. Guess should be another more elegant approach to this task. Is anyone can show a really good solution?
If your expression gives 3 arrays of Strings you could add .reduce([], +) at the end to join the 3 arrays into
one.
EDIT:
I was typing from memory, and said the wrong thing. You would use reduce, not joined.
let result = json["def"]
.arrayValue
.map({ $0["tr"].arrayValue.map { $0["ex"] } })
.reduce([], +)
That should give you what you want.
EDIT #2:
The reduce function operates on sequences (like arrays). It takes an "initial result" value that seeds the process, and then a closure that operates on 2 elements, and returns a result. (a "binary" closure)
The + operator is actually a binary closure in Swift. It takes 2 elements and returns a single result, so you can simply pass in + instead of a closure. For arrays, + returns the result of combining the arrays.
So when you use reduce() on an array of arrays, and [] as the initial result, it combines [] with the first array, then the result of each + operator with the next entry in the array.
Take this simplified code for example:
//Start with an array of arrays of strings
let arrays = [
["string1", "string2", "string3"],
["string4", "string5", "string6"],
["string7", "string8", "string9"]
]
//First loop through the outer arrays and log their contents
for (index, object) in arrays.enumerated() {
print("array[\(index)] = \(object)")
}
//Now combine the outer arrays into a single array
let combined = arrays.reduce([], + )
//Now print the entries in the combined array
print("\n---- combined arrays ----")
for (index, object) in combined.enumerated() {
print("array[\(index)] = \(object)")
}
That produces the following output:
array[0] = ["string1", "string2", "string3"]
array[1] = ["string4", "string5", "string6"]
array[2] = ["string7", "string8", "string9"]
---- combined arrays ----
array[0] = string1
array[1] = string2
array[2] = string3
array[3] = string4
array[4] = string5
array[5] = string6
array[6] = string7
array[7] = string8
array[8] = string9
let searchResultItem1 = SearchResult()
searchResultItem1.type = "contact"
searchResultItem1.typeTitle = "CONTACTS"
searchResultItem1.results = ["Joe" , "Smith" , "Alan" , "Nick" , "Jason"]
let searchResultItem2 = SearchResult()
searchResultItem2.type = "address"
searchResultItem2.typeTitle = "ADDRESS"
searchResultItem2.results = ["829 6th Street North Fullerton" , "669 Windsor Drive Randallstown" , "423 Front Street Lacey"]
searchResults.append(searchResultItem1)
searchResults.append(searchResultItem2)
When i search for "al" i am expecting to return one SearchResult with its results "[Alan]" and another SearchResult with its result "["669 Windsor Drive Randallstown"]"
I tried the below filter , but returns empty array.
let results = searchResults.filter({$0.results.contains("al")})
As both other answers point out, you're searching your array of results for an item with a value of "al". What you're wanting to is actually return an array of results, narrowed down to only those that match:
struct SearchResult {
let type:String
let typeTitle:String
let results:[String]
}
let searchResultItem1 = SearchResult(
type: "contact",
typeTitle: "CONTACTS",
results: ["Joe" , "Smith" , "Alan" , "Nick" , "Jason"]
)
let searchResultItem2 = SearchResult(
type:"address",
typeTitle: "ADDRESS",
results:["829 6th Street North Fullerton" , "669 Windsor Drive Randallstown" , "423 Front Street Lacey"]
)
var searchResults = [ searchResultItem1, searchResultItem2 ]
Now then, again, for convenience, define a case insensitive contains function for String:
extension String {
func containsIgnoreCase(substring:String) -> Bool {
return rangeOfString(
substring,
options: .CaseInsensitiveSearch,
range: startIndex..<endIndex,
locale: nil)?.startIndex != nil
}
}
Note that String already has a contains function, it's just case sensitive, but if that's sufficient, you don't even need to define your own.
Now, you can use map to get rid of results that don't contain your search string:
searchResults = searchResults.map({
return SearchResult(
type: $0.type,
typeTitle: $0.typeTitle,
results: $0.results.filter({
$0.containsIgnoreCase("al")
})
)
})
And, presumably, you also want to eliminate any SearchResult with no actual results, so use filter:
searchResults = searchResults.filter { $0.results.count > 0 }
Of course, the whole thing can be strung into one expression:
searchResults = searchResults.map({
return SearchResult(
type: $0.type,
typeTitle: $0.typeTitle,
results: $0.results.filter({
$0.contains("al")
})
)
}).filter { $0.results.count > 0 }
And, you can possibly further reduce some of the iteration by using flatMap, which is like map, but eliminates any nil values:
searchResults = searchResults.flatMap {
let results = $0.results.filter { $0.containsIgnoreCase("al") }
if results.count > 0 {
return SearchResult(type: $0.type, typeTitle: $0.typeTitle, results: results)
} else {
return nil
}
}
Within your filter, $0.results is, in each example:
["Joe" , "Smith" , "Alan" , "Nick" , "Jason"]
["829 6th Street North Fullerton" , "669 Windsor Drive Randallstown" , "423 Front Street Lacey"]
The Array.contains(_) function searches for an object exactly matching it's argument, not a substring of the argument
To get each entire array that contains a string element with the substring "al", use:
let results = searchResults.filter({$0.results.filter({$0.rangeOfString("al") != nil}).count > 0})
Alternatively, to get just the strings with the substring "al", use:
let results = searchResults.map{ $0.results }.reduce([], combine: {$0 + $1}).filter { $0.rangeOfString("al") != nil }
Note that this won't match "Alan" because rangeOfString is case-sensitive.
Your filter checks if the results array contains an entry of "al", not if any of the strings in results contains "al". You need to filter the results array and construct a new SearchResult with the filtered results array.
I would probably flat map a function that filtered the results array of the input SearchResult and return an optional depending on whether or not any matches were found.
Say I had two arrays:
var arrayA = ["Yes", "Yes2", "Not Answered", "No"]
var arrayB = ["Yes", "NA", "Yes2", "NA"]
And I wanted to remove the "NA"s from arrayB by doing:
var filtered = arrayB.filter({$0 != "NA"})
How could I remove the items at the same indexes removed in arrayA. I thought about using the find() function, but that only returns the first index a string appears. You can remove overlap from the arrays by:
let res = arrayA.filter { !contains(arrayB, $0) }
but how could I filter an array based on the filtering of another array?
the result would have:
arrayBFiltered = ["Yes", "Yes2"]
arrayAFiltered = ["Yes", "Not Answered"]
Any ideas?
You might prefer using zip:
var arrayA = ["Yes", "Yes2", "Not Answered", "No"]
var arrayB = ["Yes", "NA", "Yes2", "NA"]
let result = filter(zip(arrayA, arrayB)) { (a, b) in b != "NA" }
for (a, b) in result {
println("A: \(a) -> B: \(b)")
}
EDIT: SWIFT 2.0
In Swift 2.0 it will be even easier to obtain an array of a consolidated ad-hod struct (say ... Foo) for clearer subsequent code:
struct Foo {
let a: String
let b: String
}
// Foo.init will be the function automatically generated by the default initialiser
let result = zip(arrayA, arrayB)
.filter { (a, b) in b != "NA" }
.map(Foo.init)
// Order of a and b is important
// result is an Array<Foo> suitable for a clearer subsequent code
for item in result {
print("A: \(item.a) -> B: \(item.b)")
}
Hope this helps
The ideas from How can I sort multiple arrays based on the sorted order of another array could
be used here, that would work for two or more arrays:
let arrayA = ["Yes", "Yes2", "Not Answered", "No"]
let arrayB = ["Yes", "NA", "Yes2", "NA"]
// Determine array indices that should be kept:
let indices = map(filter(enumerate(arrayB), { $1 != "NA" } ), { $0.0 } )
// Filter arrays based on the indices:
let arrayAFiltered = Array(PermutationGenerator(elements: arrayA, indices: indices))
let arrayBFiltered = Array(PermutationGenerator(elements: arrayB, indices: indices))
println(arrayAFiltered) // [Yes, Not Answered]
println(arrayBFiltered) // [Yes, Yes2]
Another solution would be to make the remove code directly inside the filter closure:
// both are vars so you can mutate them directly
var arrayA = ["Yes", "Yes2", "Not Answered", "No"]
var arrayB = ["Yes", "NA", "Yes2", "NA"]
arrayA = filter(enumerate(arrayA)){
arrayB.removeAtIndex($0)
return $1 != "Na"
}
// use filtered arrayA and arrayB