Swift - Joining two tuple arrays based on their values - arrays

I've a scenario where I have two arrays of tuples.
tuple1 = [(score1, index1), (score2, index2), (score3, index3)]
tuple2 = [(date1, index1), (date2, index2), (date3, index4)]
I want to get the scores and dates from these tuples and create a new array of tuples such that it contains the score and date having the same index like this:
tuple3 = [(score1, date1), (score2, date2)]
How can I implement this? What is the best practice to follow in this scenario?
Note: The arrays can be of different sizes
My implementation for the scenario is as follows:
var tuple3 = [(Double, Date)]()
for (i,psa) in tuple1.enumerated() {
let date = tuple2.filter({ $0.1 == i })
if date.count == 1 {
let newTuple = (tuple1.0, date[0].0)
tuple3.append(newTuple)
}
}
Is this a right way to do it or is there a better one?

You can try
let v1 = [("1","2"),("3","4")]
let v2 = [("1A","2A"),("3A","4A")]
let res = zip(v1,v2).map { ($0.0 , $1.0) } // [("1", "1A"), ("3", "3A")]
print(res)

let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]
let t2Dict = tuple2.reduce(into: [String:String]()) { (dict, args) in
let (date, index) = args
dict[index] = date
}
let tuple3 = tuple1.compactMap { args -> (String, String)? in
let (score, index) = args
guard let date = t2Dict[index] else { return nil }
return (score, date)
}
It's not as pretty as the others, but it's far more efficient to collapse one of the tuples into a dictionary first.

This should do the trick:
let tuple3 = tuple1.compactMap({ (scoreInTuple1, indexInTuple1) -> (String, String)? in
if let tupleIn2 = tuple2.first(where: { (scoreInTuple2, index2InTuple2) in index2InTuple2 == indexInTuple1 }){
return (scoreInTuple1, tupleIn2.0)
}
return nil
})
(String, String) should be change to the real type/class of score & date1.
Also, index2InTuple2 == indexInTuple1 might be changed also if it's custom type/class which might not be Equatable.
With sample code before:
let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]
Debug log after:
print("tuple3: \(tuple3)")
Output:
$> tuple3: [("score1", "date1"), ("score2", "date2")]

This might be what you're looking for:
let tuple1 = [("score1", "index1"), ("score2", "index2"), ("score3", "index3")]
let tuple2 = [("date1", "index1"), ("date2", "index2"), ("date3", "index4")]
let filtered = tuple1.filter {tuple2.map{$0.1}.contains($0.1)}
let result = filtered.map {tuple in
return (tuple.0, tuple2.first(where: {$0.1 == tuple.1})!.0)
}
print (result) // [("score1", "date1"), ("score2", "date2")]
I'm using Strings for simplicity, just make sure you are using Equatable objects in your tuples.

Related

Filter an array to sum of the another array

I have an array that many elements, but two of them are important to me.
Language
and number
I get the info from them someting like that
_ = cities[0].language
\\give me a string languages(it could be any languages and maybe it's nil, but all the language are written in same way (for example it's always English, not english)
and
_ = cities[0].number
\\give me an Int
for example it gives me something like that
"English" "124324"
"French" "5634"
"English" "753"
"German" "8643"
"German" "532"
nil "6532"
I want to create a new array(let's call languageInfo), that created by filtering the cities array.
I need it combine all the same lanugage together with sum of their numbers, for example the output should be something like that
for example it gives me something like that
"English" "125077"
"German" "9175"
"French" "5634"
"nil" "6532"
Could anyone tell me how to do that?
Use reduce to sum the numbers and convert to a dictionary
As pointed out in the comments reduce can be written in a much more condensed form
let dict = cities.reduce(into: [:]) { $0[$1.language ?? "", default: 0] += $1.number }
Original version
let dict = cities.reduce(into: [String: Int](), { result, city in
var value = city.number
let key = city.language ?? ""
if let sum = result[key] {
value += sum
}
result[key] = value
})
Note that I use an empty string as key if language is nil, another option could be to ignore those elements
let dict2 = cities.reduce(into: [String: Int](), { result, city in
var value = city.number
if let key = city.language {
if let sum = result[key] {
value += sum
}
result[key] = value
}
})
or as pointed out in the comments allow nil as key
let dict = cities.reduce(into: [String?: Int](), { result, city in
var value = city.number
let key = city.language
if let sum = result[key] {
value += sum
}
result[key] = value
})

Count Items in an Array of Arrays?

If I have an object that is declared as
let compoundArray = [Array<String>]
is there a property that would give me the number of strings in all the arrays contained in compoundArray?
I can do it by adding up all the items in each array within:
var totalCount = 0
for array in compoundArray {
totalCount += array.count }
//totalCount = total items in all arrays within compoundArray
But that seems clunky and it seems that swift would have a property/method of Array to do this, no?
Thanks!
You can add the nested array counts with
let count = compoundArray.reduce(0) { $0 + $1.count }
Performance comparison for large arrays (compiled and run on a MacBook Pro in Release configuration):
let N = 20_000
let compoundArray = Array(repeating: Array(repeating: "String", count: N), count: N)
do {
let start = Date()
let count = compoundArray.joined().count
let end = Date()
print(end.timeIntervalSince(start))
// 0.729196012020111
}
do {
let start = Date()
let count = compoundArray.flatMap({$0}).count
let end = Date()
print(end.timeIntervalSince(start))
// 29.841913998127
}
do {
let start = Date()
let count = compoundArray.reduce(0) { $0 + $1.count }
let end = Date()
print(end.timeIntervalSince(start))
// 0.000432014465332031
}
You can use joined or flatMap for that.
Using joined
let count = compoundArray.joined().count
Using flatMap
let count = compoundArray.flatMap({$0}).count
Since you are asking for a property, I thought I'd point out how to create one (for all collections, while we're at it):
extension Collection where Iterator.Element: Collection {
var flatCount: Int {
return self.reduce(0) { $0 + $1.count } // as per Martin R
}
}
Making this recursive seems to be an interesting exercise.

How to convert from a Swift String Set to an Array

I am trying to create an array of words from a string object retrieved from Parse. The object retrieved looks like this:
Then this line of code gives this.
let joinedWords = object["Words"] as! String
How do I convert joinedWords to an Array?
If you don't care about the order, you can use flatMap on the set:
var mySet = Set<String>()
for index in 1...5 {
mySet.insert("testwords\(index)")
}
let myArray = mySet.flatMap { $0 }
print(myArray) // "["testwords5", "testwords3", "testwords4", "testwords2", "testwords1"]"
If you want the list sorted alphabetically, you can make your array a var and use sortInPlace()
var myArray = mySet.flatMap { $0 }
myArray.sortInPlace()
print(myArray) // "["testwords1", "testwords2", "testwords3", "testwords4", "testwords5"]"
If object["Words"] is AnyObject, you will have to unwrap it.
if let joinedWordsSet = object["Words"] as? Set<String> {
var joinedWordsArray = joinedWordsSet.flatMap { $0 }
myArray.sortInPlace()
print(myArray)
}
Swift 3 note: sortInPlace() has been renamed sort().
Many thanks to #JAL for so much time on chat to solve this one. This is what we came up with. Its a bodge and no doubt there is a better way!
When uploading to Parse save the set as an array.
let wordsSet = (wordList?.words?.valueForKey("wordName"))! as! NSSet
let wordsArray = Array(wordsSet)
Then it saves to Parse - looking like a set, not an array or a dictionary.
let parseWordList = PFObject(className: "WordList")
parseWordList.setObject("\(wordsArray)", forKey: "Words")
parseWordList.saveInBackgroundWithBlock { (succeeded, error) -> Void in
if succeeded {
// Do something
} else {
print("Error: \(error) \(error?.userInfo)")
}
}
Then you can drop the [ ] off the string when its downloaded from Parse, and remove the , and add some "" and voila, there is an array that can be used e.g. to add to CoreData.
var joinedWords = object["Words"] as! String
joinedWords = String(joinedWords.characters.dropFirst())
joinedWords = String(joinedWords.characters.dropLast())
let joinedWordsArray = joinedWords.characters.split() {$0 == ","}.map{ String($0) } // Thanks #JAL!

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]

Iterate over two arrays simultaneously

I am new to Swift. I have been doing Java programming. I have a scenario to code for in Swift.
The following code is in Java. I need to code in Swift for the following scenario
// With String array - strArr1
String strArr1[] = {"Some1","Some2"}
String strArr2[] = {"Somethingelse1","Somethingelse2"}
for( int i=0;i< strArr1.length;i++){
System.out.println(strArr1[i] + " - "+ strArr2[i]);
}
I have a couple of arrays in swift
var strArr1: [String] = ["Some1","Some2"]
var strArr2: [String] = ["Somethingelse1","Somethingelse2"]
for data in strArr1{
println(data)
}
for data in strArr2{
println(data)
}
// I need to loop over in single for loop based on index.
Could you please provide your help on the syntaxes for looping over based on index
You can use zip(), which creates
a sequence of pairs from the two given sequences:
let strArr1 = ["Some1", "Some2"]
let strArr2 = ["Somethingelse1", "Somethingelse2"]
for (e1, e2) in zip(strArr1, strArr2) {
print("\(e1) - \(e2)")
}
The sequence enumerates only the "common elements" of the given sequences/arrays. If they have different length then the additional
elements of the longer array/sequence are simply ignored.
With Swift 5, you can use one of the 4 following Playground codes in order to solve your problem.
#1. Using zip(_:_:) function
In the simplest case, you can use zip(_:_:) to create a new sequence of pairs (tuple) of the elements of your initial arrays.
let strArr1 = ["Some1", "Some2", "Some3"]
let strArr2 = ["Somethingelse1", "Somethingelse2"]
let sequence = zip(strArr1, strArr2)
for (el1, el2) in sequence {
print("\(el1) - \(el2)")
}
/*
prints:
Some1 - Somethingelse1
Some2 - Somethingelse2
*/
#2. Using Array's makeIterator() method and a while loop
It is also easy to loop over two arrays simultaneously with a simple while loop and iterators:
let strArr1 = ["Some1", "Some2", "Some3"]
let strArr2 = ["Somethingelse1", "Somethingelse2"]
var iter1 = strArr1.makeIterator()
var iter2 = strArr2.makeIterator()
while let el1 = iter1.next(), let el2 = iter2.next() {
print("\(el1) - \(el2)")
}
/*
prints:
Some1 - Somethingelse1
Some2 - Somethingelse2
*/
#3. Using a custom type that conforms to IteratorProtocol
In some circumstances, you may want to create you own type that pairs the elements of your initials arrays. This is possible by making your type conform to IteratorProtocol. Note that by making your type also conform to Sequence protocol, you can use instances of it directly in a for loop:
struct TupleIterator: Sequence, IteratorProtocol {
private var firstIterator: IndexingIterator<[String]>
private var secondIterator: IndexingIterator<[String]>
init(firstArray: [String], secondArray: [String]) {
self.firstIterator = firstArray.makeIterator()
self.secondIterator = secondArray.makeIterator()
}
mutating func next() -> (String, String)? {
guard let el1 = firstIterator.next(), let el2 = secondIterator.next() else { return nil }
return (el1, el2)
}
}
let strArr1 = ["Some1", "Some2", "Some3"]
let strArr2 = ["Somethingelse1", "Somethingelse2"]
let tupleSequence = TupleIterator(firstArray: strArr1, secondArray: strArr2)
for (el1, el2) in tupleSequence {
print("\(el1) - \(el2)")
}
/*
prints:
Some1 - Somethingelse1
Some2 - Somethingelse2
*/
#4. Using AnyIterator
As an alternative to the previous example, you can use AnyIterator. The following code shows a possible implementation of it inside an Array extension method:
extension Array {
func pairWithElements(of array: Array) -> AnyIterator<(Element, Element)> {
var iter1 = self.makeIterator()
var iter2 = array.makeIterator()
return AnyIterator({
guard let el1 = iter1.next(), let el2 = iter2.next() else { return nil }
return (el1, el2)
})
}
}
let strArr1 = ["Some1", "Some2", "Some3"]
let strArr2 = ["Somethingelse1", "Somethingelse2"]
let iterator = strArr1.pairWithElements(of: strArr2)
for (el1, el2) in iterator {
print("\(el1) - \(el2)")
}
/*
prints:
Some1 - Somethingelse1
Some2 - Somethingelse2
*/
Try This:
zip([0,2,4,6], [1,3,5,7]).forEach {
print($0,$1)
}
zip([0,2,4,6], [1,3,5,7]).forEach {
print($0.0,$0.1)
}
You could also enumerate over one array and used the index to look inside the second array:
Swift 1.2:
for (index, element) in enumerate(strArr1) {
println(element)
println(strArr2[index])
}
Swift 2:
for (index, element) in strArr1.enumerate() {
print(element)
print(strArr2[index])
}
Swift 3:
for (index, element) in strArr1.enumerated() {
print(element)
print(strArr2[index])
}
You could use Range if you still want to use for in.
var strArr1: [String] = ["Some1","Some2"]
var strArr2: [String] = ["Somethingelse1","Somethingelse2"]
for i in Range(start: 0, end: strArr1.count) {
println(strArr1[i] + " - " + strArr2[i])
}
for(var i = 0; i < strArr1.count ; i++)
{
println(strArr1[i] + strArr2[i])
}
That should do it. Never used swift before so make sure to test.
Updated to recent Swift syntax
for i in 0..< strArr1.count {
print(strArr1[i] + strArr2[i])
}
> Incase of unequal count
let array1 = ["some1","some2"]
let array2 = ["some1","some2","some3"]
var iterated = array1.makeIterator()
let finalArray = array2.map({
let itemValue = iterated.next()
return "\($0)\(itemValue != nil ? "-"+itemValue! : EmptyString)" })
// result : ["some1-some1","some2-some2","some3"]

Resources