I have an array of type "Option".
The class Optioncontains an element optionDetail.
The class optionDetail contains elements of detailTraits, which is an [String] with each string being called the detailTraitName.
So my structure of getting the detailTraits looks like Option -> optionDetail -> detailTraitswhich would return me [String], or Option -> optionDetail -> detailTraits -> detailTraitName, which would return me just one String
I would like to match up the detailTraits array with another array, named selectedDetails, which is an [String] and find the elements in which all of the selectedDetails are contained inside of the detailTraits. I then want to return all of the Option in which this situation is true.
For example, if my selectedDetails array contains ["A", "B"], and I have one detailTraits array that has ["A","C"] and one that has ["A"] and one that has ["A", "B", "C"], I just want to return the option which had detailTraits of ["A", "B", "C"]
My current code looks like the following:
newOptions = option.filter({ $0.optionDetail?.detailTraits.filter({ selectedDetails.contains($0.detailTraitName ?? "") }).count == selectedDetails.count })
Is there a better way to do this? This algorithm seems pretty inefficient since It's probably in the order of magnitude of N^3, but I can't think of a better way to look through an array of arrays and match it to another array.
Thank you!
You can optimise this by first filtering by comparing count on selectedDetails and detailTraits, and then comparing actual values. This way options set would be reduced to only those detailTraits having exact same count. For example you would only need to compare the string values with array containing exact 3 items (if selectedDetails is ["A", "B", "C"]), completely avoiding one iteration in loop.
Hope this helps
Related
This question already has an answer here:
Check if an array contains elements of another array in swift
(1 answer)
Closed 2 years ago.
I have 2 500,000 count arrays of strings. is there a more efficient way to check if they have elements that match then:
let array1 = ["a", "b", "c", "d", "e"]
let array2 = ["d", "e", "f", "g", "h"]
var maching = [0]
for element1 in array1 {
for element2 in array2 {
if element1 == element1 {
maching.append(element1)
}
}
}
Thanks in advance
If elements are Hashable (strings are) and if we can ignore duplicates and ordering, then using Set is the easiest solution:
let matching = Set(array1).intersection(Set(array2))
Depending on the nature of the data, we could come with an even better solution, e.g. an interval tree. The more information we have, the better solution can be designed. However, an optimal solution specific to one use case will be much more complex.
I have an array:
["a", "b", "c", "d"]
How do I figure out the index of the first element of the above array to occur within a second array:
["next", "last", "d", "hello", "a"]
The index of the first element from the first array to occur within the above array would be 2; "d" belongs to the first array and occurs at position 2.
There's a couple of ways to do this, but the naive approach might work well enough to get going:
tests = ["a", "b", "c", "d"]
in_array = ["next", "last", "d", "hello", "a"]
in_array.each_with_index.find do |e, i|
tests.include?(e)
end
# => ["d", 2]
You can speed this up by making tests a Set which avoids a lot of O(N) lookups:
tests = Set.new([ ... ])
The same code will work with include? but that's now much faster on longer lists.
This approach, wrapped in a method, returns an array containing all indexes of common elements between two arrays.
def find_positions(original_array, look_up_array)
positions_array = []
original_array.each do |x|
if look_up_array.index(x) != nil
positions_array << look_up_array.index(x)
end
end
positions_array
# positions_array.first => for the first matched element
end
If you want only the first matched element you could return positions_array.first but this way you'll not avoid the extra lookups.
PS: you could also use #collect and avoid the extra array (positions_array)
You can iterate through the array you want to be compared and use .select or .find iterator method. .find will select the first element match in the arrays while .select will match all elements in the arrays. If you want to add the index in the selection you can add .each_with_index. '.index(a)' returns the element if present else it will return nil.
alphabet = %w(a b c d)
%w(next last d hello a).each_with_index.find {|a, _index| alphabet.index(a) }
=> ["d", 2]
%w(next last d hello a).each_with_index.select {|a, _index| alphabet.index(a) }[0]
=> ["d", 2]
# if you just need the index of the first match
%w(next last d hello a).index {|a| alphabet.index(a) }
=> 2
This question already has answers here:
Removing duplicate elements from an array in Swift
(49 answers)
Closed 6 years ago.
Say I have an array of strings:
let arrayOfStrings = ["a", "b", "a", "c", "a", "d"]
How would I get rid of the duplicates?
You can use the array function contains(_:) to check if an element is already part of the array, but that is fairly slow, and for large arrays it won’t perform well. (1.) Better to copy the entries into a Set and use Set operations to find and remove the duplicates. Sets are optimized to make testing for set membership fast, so if aSet.contains(item) is a lot faster than if anArray.contains(item).
If you don't care about preserving the order of your items, you can simply copy your array into a set and then back to an array. However, that does mean that the items in the resulting array will be in a different order.
A function to remove duplicates from an array of strings, while preserving the order, might look like this:
func uniqueElementsFrom(array: [String]) -> [String] {
//Create an empty Set to track unique items
var set = Set<String>()
let result = array.filter {
guard !set.contains($0) else {
//If the set already contains this object, return false
//so we skip it
return false
}
//Add this item to the set since it will now be in the array
set.insert($0)
//Return true so that filtered array will contain this item.
return true
}
return result
}
If you call it with code like this:
let arrayOfStrings = ["a", "b", "a", "c", "a", "d"]
let uniqueStrings = uniqueElementsFrom(array:arrayOfStrings)
print("Unique elements from \(arrayOfStrings) = \n” +
“\(uniqueStrings)")
The output would be
Unique elements from ["a", "b", "a", "c", "a", "d"] =
[“a”, "b", "c", "d"]
However, that function only works with arrays of strings. It would be good if we could write a function that could remove duplicates from any kind of array.
This is a job for Generics. There is a catch however. Sets can only contain objects that conform to the Hashable protocol, since Sets use hashes to make testing for set membership faster.
We can rewrite the uniqueElementsFrom(array:) function to take any array that conforms to the Hashable protocol using Generics. That code looks like this:
func uniqueElementsFrom<T: Hashable>(array: [T]) -> [T] {
var set = Set<T>()
let result = array.filter {
guard !set.contains($0) else {
return false
}
set.insert($0)
return true
}
return result
}
The <T: Hashable> bit after the function name says "The rest of this function will refer to a type T which is unspecified. The only thing you can be sure of is that the type T will conform to the Hashable protocol."
This form of the uniqueElementsFrom(array:) function will work on any array who’s elements are Hashable.
(1.) For arrays, contains(_:) has O(n) performance, and so looping through an array, testing the array to see if it contains each new element with contains(_:) has performance that's almost O(n^2), which is really, really bad for anything but a small array. I'm pretty sure that Set's contains(_:) function has constant time performance, so the whole process would have O(n) performance.
//this gives me an array for multiple rows selected in a tableView.
let rowsSelected = self.tableView.indexPathsForSelectedRows!.map{$0.row}
the print statement gives me a result like this for selection of row 2 and 5.....[2.5].
I now want to delete the two lines from the current list. I am struggling to find the right concept for creating the reduced array. Dictionary or working with NSIndexPath?
#vadian's answer is close, but it's dangerous; see my comment on his answer.
To fix it, you must first reverse the array of indices to be deleted. To see why, run the following lines in a playground:
let indicesToDelete = [4, 8]
var array = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
let goodReducedArray = indicesToDelete.reverse().map { array.removeAtIndex($0) }
goodReducedArray // prints ["i","e"]
let badReducedArray = indicesToDelete.map { array.removeAtIndex($0) }
badReducedArray // barfs EXC_BAD_INSTRUCTION
So the corrected version of #vadian's code will be:
if let selectedIndexPaths = self.tableView.indexPathsForSelectedRows {
selectedIndexPaths.reverse().map{tableData.removeAtIndex($0.row)}
tableView.deleteRowsAtIndexPaths(selectedIndexPaths, withRowAnimation:.Fade)
}
Assuming the name of your data source array is tableData, you can use these lines.
The items in the data source array and in the table view must be removed simultaneously, the sort and forEach functions are used to remove the items starting at the highest index.
if let selectedIndexPaths = self.tableView.indexPathsForSelectedRows {
let indexes = selectedIndexPaths.map{$0.row}.sort(>) // sort descending
indexes.forEach{tableData.removeAtIndex($0)}
tableView.deleteRowsAtIndexPaths(selectedIndexPaths, withRowAnimation:.Fade)
}
Scenario:
An array of strings, many are duplicated.
Goal:
Produce a UNIQUE array of strings.
Modus Operandi:
I was thinking of converting the array to a set of strings which become unique; from which to generate a new array of unique strings.
Question: How does one convert a Swift array into a Swift Set?
let nonUniqueArray = ["A", "B", "C", "C", "B", "A"]
let uniqueArray = Array(Set(nonUniqueArray))
print(uniqueArray)
produces
["C", "B", "A"]
Swift 2.2 produces exactly the same result as well.
Have you tried let myset = Set(myarray) ?