How to group items from array that match, into another array? - arrays

For example say I have an array like so:
var someArray = ["1", "1", "2"]
I need to put this into two arrays that look like:
["1","1"]
["2"]
How can I go about this?
Any help would be great!

Use Dictionary initializer init(grouping:by:)
Then just get arrays by accessing values property.
Example:
let dic = Dictionary(grouping: someArray) { $0 }
let values = Array(dic.values)
print(values)
Result:
[["2"], ["1", "1"]]

Here are some facts (the upvote and answer should go to #kirander)
With #kirander method's is using the Dictionary to map the objects in a O(N) runtime and O(N) memory.
The other solutions are mostly running in O(N*N) runtime and O(N) memory. Because of this, grouping a random array of 1000 items will take: 0.07s with #kirander solution and 34s. with other solutions.
func benchmark(_ title:String, code: ()->()) {
let startTime = CFAbsoluteTimeGetCurrent()
code()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(title): \(timeElapsed) s.")
}
var array:[String] = []
for _ in 0...1000 {
array.append("\(Int(arc4random_uniform(10)))")
}
// #kirander solution 0.07s
benchmark("Dictionary", code: {
let dic = Dictionary(grouping: array, by: { $0 })
let values = Array(dic.values)
})
// #Bruno solution ~34s
benchmark("Array", code: {
var resultingArrays = [[String]]()
for value in array {
let ar = array.filter({ $0 == value })
if !resultingArrays.contains(where: {$0 == ar}) {
resultingArrays.append(ar)
}
}
})

You could try something like this:
var someArray = ["1", "1", "2"]
var resultingArrays = [[String]]()
for value in someArray {
let array = someArray.filter({ $0 == value })
if !resultingArrays.contains(where: {$0 == array}) {
resultingArrays.append(array)
}
}

You can try this one :
let arrM = ["1","3","4","6","1","1","3"]
let arrSrtd = Array(Set(arrM))
for ele in arrSrtd{
let a = arrM.filter( {$0 == ele})
print(a)
}

Related

Does Swift offer any built-in function to return the result of appending to an immutable array?

Writing the question and answer from here, I'm curious to know if there is any simpler way to write the following:
var nums = [1,2,3]
let sum1 = nums.reduce([Int]()){
let temp = $0
temp.append($1)
return temp
}
I know I can do:
var nums = [1,2,3]
let sum1 = nums.reduce([Int]()){
return $0 + [$1]
}
But that comes off as a hack.
To explain this better, I want to get closer to the example (from docs) below, just that it should be for an array:
let numbers = [1, 2, 3, 4]
let numberSum = numbers.reduce(0, { x, y in
x + y
})
EDIT:
Since folks asked what was I trying to achieve:
I was doing the leetcode's group Anagram's challenge.
My solution was:
struct WordTraits: Equatable{
let count: Int
let charactersSet: Set<Character>
}
struct Word: Equatable{
let string: String
let wordTraits: WordTraits
}
class Solution{
func groupAnagrams(_ strs: [String]) -> [[String]]{
var words : [Word] = []
var answers : [(traits: WordTraits, words: [Word])] = []
var count = 0
strs.forEach{ str in
count += 1
let count = str.count
let string = str
let characterSet = Set(str)
let wordTraits = WordTraits(count: count, charactersSet: characterSet)
let word = Word(string: string, wordTraits: wordTraits)
words.append(word)
}
while words.count != 0{
let word = words[0]
let traits = word.wordTraits
var isWordAdded = false
for (i, answer) in answers.enumerated(){
if answer.traits == traits{
answers[i].words.append(word)
isWordAdded = true
break
}
}
if !isWordAdded{
answers.append((traits: traits, words:[word]))
}
words.removeFirst()
}
let emptyArray : [[String]] = []
let finalAnswer = answers.reduce(emptyArray, { total, answer in
let strings : [String] = answer.words.reduce([String](), {
return $0 + [$1.string]
})
return total + [strings]
})
return finalAnswer
}
}
let s = Solution()
print(s.groupAnagrams(["ate", "eta", "beta", "abet"])) // [["ate", "eta"], ["beta", "abet"]]
reduce(..) has to know which type it is working with. To infer this it can use the return type or the type of the first argument. So you can also write:
var nums = [1,2,3]
let sum1: [Int] = nums.reduce([]){
return $0 + [$1]
}
[$1] can't be replaced with $1 because +-operator between value and collection is undefined.
Nope. But you can add it:
extension Array {
func appending(_ newElement: Element) -> Array<Element> {
return self + [newElement]
}
func appending(contentsOf sequence: Sequence) -> Array<Element> {
return self + sequence
}
}
Um, how about the + operator?
let nums = [1, 3, 5]
let more = nums + [7]
Your code is trying to convert a complex structure to an array of arrays. You can use map for this.
This should work:
let finalAnswer = answers.map { answer in
answer.words.map {
$0.string
}
}
Edit:
I was able to solve it using minimal code:
class Solution {
func groupAnagrams(_ words: [String]) -> [[String]] {
let processedWords = words.map {
(key: String($0.sorted()), value: $0)
}
return Dictionary(grouping: processedWords, by: { $0.key }).map { groupedValue in
groupedValue.value.map {
$0.value
}
}
}
}
You've greatly overcomplicated your computation of "final answers". It could just be:
return answers.map { $0.words.map { $0.string } }

Filtering Dictionary with an array of random Ints to make a new dict

So I have this method to get an array of random ints between 1-9, a random number of times between 1 and 7.
let n = arc4random_uniform(7) + 1
var arr: [UInt32] = []
for _ in 0 ... n {
var temp = arc4random_uniform(9) + 1
while arr.contains(temp) {
temp = arc4random_uniform(9) + 1
}
print(temp)
arr.append(temp)
}
print(arr)
So that gets me an array like [1,4,2] or [5,7,3,4,6]. And I have a method to turn another array of strings into a enumerated dictionary.
var someArray: [String] = ["War", "Peanuts", "Cats", "Dogs", "Nova", "Bears", "Pigs", "Packers", "Mango", "Turkey"]
extension Collection {
var indexedDictionary: [Int: Element] {
return enumerated().reduce(into: [:]) { $0[$1.offset] = $1.element }
}
}
let dict1 = someArray.indexedDictionary
print(dict1)
giving me the indexed dictionary
[1:"War", 2:"Peanuts",..etc]
MY question is using the Ints of the random array how do I create a new dictionary that only includes those keys and their values?
So for example if arr = [3,1,5]
how do I get a new dictionary of
[3:"dogs", 1:"Peanuts",5:"Bears"].
This should do it:
let finalDict = dict1.filter { arr.contains($0.key) }
Update:
You can even go a step further and skip the whole strings to array mapping. So remove
extension Collection {
var indexedDictionary: [Int: Element] {
return enumerated().reduce(into: [:]) { $0[$1.offset] = $1.element }
}
}
let dict1 = someArray.indexedDictionary
print(dict1)
and just use this:
Swift 4:
let finalArray = someArray.enumerated().flatMap { arr.contains($0.offset) ? $0.element : nil }
Swift 4.1:
let finalArray = someArray.enumerated().compactMap { arr.contains($0.offset) ? $0.element : nil }
Update 2:
If you need a dictionary and not an array in the end use this:
Swift 4:
let finalDict = someArray.enumerated().flatMap { randomInts.contains($0.offset) ? ($0.offset, $0.element) : nil }.reduce(into: [:]) { $0[$1.0] = $1.1 }
Swift 4.1:
let finalDict = someArray.enumerated().compactMap { randomInts.contains($0.offset) ? ($0.offset, $0.element) : nil }.reduce(into: [:]) { $0[$1.0] = $1.1 }

Find unique values in a Swift Array

I am building a project that tells me the unique words in a piece of text.
I have my orginal string scriptTextView which I have added each word into the array scriptEachWordInArray
I would now like to create an array called scriptUniqueWords which only includes words that appear once (in other words are unique) in scriptEachWordInArray
So I'd like my scriptUniqueWords array to equal = ["Silent","Holy"] as a result.
I don't want to create an array without duplicates but an array that has only values that appeared once in the first place.
var scriptTextView = "Silent Night Holy Night"
var scriptEachWordInArray = ["Silent", "night", "Holy", "night"]
var scriptUniqueWords = [String]()
for i in 0..<scriptEachWordInArray.count {
if scriptTextView.components(separatedBy: "\(scriptEachWordInArray[i]) ").count == 1 {
scriptUniqueWords.append(scriptEachWordInArray[i])
print("Unique word \(scriptEachWordInArray[i])")}
}
You can use NSCountedSet
let text = "Silent Night Holy Night"
let words = text.lowercased().components(separatedBy: " ")
let countedSet = NSCountedSet(array: words)
let singleOccurrencies = countedSet.filter { countedSet.count(for: $0) == 1 }.flatMap { $0 as? String }
Now singleOccurrencies contains ["holy", "silent"]
Swift
lets try It.
let array = ["1", "1", "2", "2", "3", "3"]
let unique = Array(Set(array))
// ["1", "2", "3"]
Filtering out unique words without preserving order
As another alternative to NSCountedSet, you could use a dictionary to count the the number of occurrences of each word, and filter out those that only occur once:
let scriptEachWordInArray = ["Silent", "night", "Holy", "night"]
var freqs: [String: Int] = [:]
scriptEachWordInArray.forEach { freqs[$0] = (freqs[$0] ?? 0) + 1 }
let scriptUniqueWords = freqs.flatMap { $0.1 == 1 ? $0.0 : nil }
print(scriptUniqueWords) // ["Holy", "Silent"]
This solution, however (as well as the one using NSCountedSet), will not preserve the order of the original array, since a dictionary as well as NSCountedSet is an unordered collection.
Filtering out unique words while preserving order
If you'd like to preserve the order from the original array (removing element which appear more than once), you could count the frequencies of each word, but store it in a (String, Int) tuple array rather than a dictionary.
Making use of the Collection extension from this Q&A
extension Collection where Iterator.Element: Hashable {
var frequencies: [(Iterator.Element, Int)] {
var seen: [Iterator.Element: Int] = [:]
var frequencies: [(Iterator.Element, Int)] = []
forEach {
if let idx = seen[$0] {
frequencies[idx].1 += 1
}
else {
seen[$0] = frequencies.count
frequencies.append(($0, 1))
}
}
return frequencies
}
}
// or, briefer but worse at showing intent
extension Collection where Iterator.Element: Hashable {
var frequencies: [(Iterator.Element, Int)] {
var seen: [Iterator.Element: Int] = [:]
var frequencies: [(Iterator.Element, Int)] = []
for elem in self {
seen[elem].map { frequencies[$0].1 += 1 } ?? {
seen[elem] = frequencies.count
return frequencies.append((elem, 1))
}()
}
return frequencies
}
}
... you may filter out the unique words of your array (while preserving order) as
let scriptUniqueWords = scriptEachWordInArray.frequencies
.flatMap { $0.1 == 1 ? $0.0 : nil }
print(scriptUniqueWords) // ["Silent", "Holy"]
you can filter the values that are already contained in the array:
let newArray = array.filter { !array.contains($0) }

Compare the values in two different arrays

I am trying to compare the values of two arrays in Swift. If a value of array2 is not found in array1 all the array2 found values need to be list and deleted.
I was trying to use the code below but its not working anymore in Swift 2:
let array1 = [["aaa","12"],["bbb","349"],["ccc","91"],["ddd","143"]]
let array2 = ["aaa","SSS","bbb","ccc","QQQ","ZZZ","ddd"]
let notNeededValues = filter(enumerate(zip(array1,array2))) { $1.0 == $1.1 }.map{ $0.0 }
print(notNeededValues)
Not sure if I understand your problem correctly, but the problem seems to be, that your code needs a simple conversion to Swift 2 syntax:
let array1 = [["aaa","12"],["bbb","349"],["ccc","91"],["ddd","143"]]
let array2 = ["aaa","SSS","bbb","ccc","QQQ","ZZZ","ddd"]
let notNeededValues = zip(array1, array2).enumerate().filter { $1.0 == $1.1 }.map { $0.0 }
print(notNeededValues)
Swift is moving away from globally defined functions, like filter and enumerate once were, and is using dot-syntax instead. This change was made possible by protocol extensions, and makes code more readable.
Update:
I assume this is what you mean(?):
let notNeededValues = array2.filter { !array1.map { $0[0] }.contains($0) }
// or like this:
let array1FirstElements = array1.map { $0[0] }
let notNeededValues = array2.filter { !array1FirstElements.contains($0) }
How about this:
let array1 = [["aaa","12"],["bbb","349"],["ccc","91"],["ddd","143"]]
let array2 = ["aaa","SSS","bbb","ccc","QQQ","ZZZ","ddd"]
extension Array where Element: Equatable {
func removeObject(object: Element) -> [Element] {
return filter {$0 != object}
}
}
var filteredArray2 = array2.reduce(array2) {
if array1.flatMap({$0}).contains($1) {
return $0.removeObject($1)
}
return $0
}
print(filteredArray2)

Reduce a string to a dictionary in Swift

What woudl be a simple way to reduce a string like AAA:111;BBB:222;333;444;CCC:555 to a dictionary in Swift. I have the following code:
var str = "AAA:111;BBB:222;333;444;CCC:555"
var astr = str.componentsSeparatedByString(";").map { (element) -> [String:String] in
var elements = element.componentsSeparatedByString(":")
if elements.count < 2 {
elements.insert("N/A", atIndex: 0)
}
return [elements[0]:elements[1]]
}
The code above produces an Array of Dictionaries:
[["A": "111"], ["BBB": "222"], ["UKW": "333"], ["UKW": "444"], ["CCC": "555"]]
I want it to produce
["A": "111", "BBB": "222", "UKW": "333", "UKW": "444", "CCC": "555"]
but no mater what I try, since i call the map function on an Array it seems impossible to convert the nature of the map function's result.
NOTE: The dictionary in string format is described as either having KEY:VALUE; format or VALUE; format, in which case the mapping function will add the "N/A" as being the key of the unnamed value.
Any help on this matter is greatly appreciated.
Your map produces an array of dictionaries. When you want to combine them into 1, that's a perfect job for reduce:
func + <K,V>(lhs: Dictionary<K,V>, rhs: Dictionary<K,V>) -> Dictionary<K,V> {
var result = Dictionary<K,V>()
for (key, value) in lhs {
result[key] = value
}
for (key, value) in rhs {
result[key] = value
}
return result
}
var str = "AAA:111;BBB:222;333;444;CCC:555"
var astr = str
.componentsSeparatedByString(";")
.reduce([String: String]()) {
aggregate, element in
var elements = element.componentsSeparatedByString(":")
if elements.count < 2 {
elements.insert("N/A", atIndex: 0)
}
return aggregate + [elements[0]:elements[1]]
}
print(astr)
Swift has no default operator to "combine" two Dictionaries so you have to define one. Note that the + here is not commutative: dictA + dictB != dictB + dictA. If a key exist in both dictionaries, the value from the second dictionary will be used.
This is a work for reduce:
let str = "AAA:111;BBB:222;333;444;CCC:555"
let keyValueStrings = str.componentsSeparatedByString(";")
let dictionary = keyValueStrings.reduce([String: String]()) {
aggregate, element in
var newAggregate = aggregate
let elements = element.componentsSeparatedByString(":")
let key = elements[0]
// replace nil with the value you want to use if there is no value
let value = (elements.count > 1) ? elements[1] : nil
newAggregate[key] = value
return newAggregate
}
print(dictionary)
You can also make aggregate mutable directly:
let dictionary = keyValueStrings.reduce([String: String]()) {
(var aggregate: [String: String], element: String) -> [String: String] in
let elements = element.componentsSeparatedByString(":")
let key = elements[0]
// replace nil with the value you want to use if there is no value
let value = (elements.count > 1) ? elements[1] : nil
aggregate[key] = value
return aggregate
}
This is a functional approach, but you can achieve the same using a for iteration.
The reason this is happening is because map can only return arrays. If you are using this method to parse your string, then you need to convert it to a dictionary after.
var newDict = [String:String]()
for x in astr {
for (i, j) in x {
newDict[i] = j
}
}
The current issue with your code is that map function iterates over array containing [["key:value"],["key:value"]..] and you separate it again. But it returns ["key":"value"] which you then add to your array.
Instead you can add elements[0]:elements[1] directly to a locally kept variable which will fix your problem. Something like
finalVariable[elements[0]] = elements[1]

Resources