How do I remove duplicates from an array [duplicate] - arrays

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.

Related

Is there a way to efficiently check two huge arrays for matching elements [duplicate]

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.

How can I make an array true or false?

On Code.org, I'm trying to make a quiz that picks an answer based on what the user has placed in the array. However, regardless of what is in the array it always comes out false.
I've tried changing the statement to have more than one equal sign, I've tried doing it backward and making the if statement if list !== [list] and I've tried removing the quotations. Nothing has worked. I've also tried defining the correctlist variable inside the if statement, but that still produces false.
var mylist = ["a". "b", "c"];
var correctlist;
if (mylist == ["a", "b", "c"]) {
correctlist = true;
} else {
correctlist = false;
}
console.log(correctlist);
It always comes out as false.
I expected the console log to state true but it always says false.
You will notice that:["a", "b", "c"] == ["a", "b", "c"]
always returns false.
That is because these are two different arrays. Just because the elements string match, the arrays are not the same.
You will either need to iterate through the elements to compare each, convert them to something that can be compared through a plain equality, or use a library with a "deep" equals.
JSON.stringify(["a", "b", "c"]) == JSON.stringify(["a", "b", "c"])

Faster Way to Filter?

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

I want to create an array from multiple tableView selection

//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)
}

Cannot use "find()" method with an array of type UIView

When I try to use this method the compiler shows the next error: "Type 'UIView' does not conform to protocol 'IntegerLiteralConvertible'"
if find(_views, 1) {
}
That method signature is:
find(domain: C, value: C.Generator.Element) -> C.Index?
Where C is a typed array, C.Generator.Element is the type of the elements in that array, and C.Index? is an optional that will contain the index the element is found at, if found at all.
So the error you are getting is because it looking at the instances in your array UIView and trying to compare them to 1 which is an IntegerLiteral. And UIView is not IntegerLiteralConvertible because it would make no sense to convert a view to an integer.
So find will return the index where some instances can be found in an array of those instances.
var strings: [String] = ["A", "B", "C"]
find(strings, "C")! // 2
But you don't seem to want the index. if find(views, 1) seems to indicate to me that you want to check if index 1 exists in the array. If this is really what you want, you can do this very simply by checking the count.
if _views.count > 1 {
println("index 1 exists in this array")
}

Resources