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]
Related
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
})
I want to return the first item in a given array of custom objects and return an array of indices and the first custom object element so I can wrap a guard statement around it.
ie:
let firstOrder = existingOrders.enumerated().flatMap{$0,$1}.first
or attempt #1
let array = existingOrders.enumerated().map { (a, b) in return [$0.index : $1.element] }.first
or attempt #2
let array = existingOrders.enumerated().map { ($0.offset, $0.element) }.first
print (array)
This isn't returning the actual object; and it seems to return a tuple.
where
existingOrders = [ExistingOrder, EngineYard.ExistingOrder, EngineYard.ExistingOrder]
it returns the following;
[(0, EngineYard.ExistingOrder), (1, EngineYard.ExistingOrder), (2, EngineYard.ExistingOrder)]
attempt #3;
let array = existingOrders.enumerated().map { ($0.offset, $0.element) }
print (array)
guard let firstOrder = array.first else {
break
}
print (firstOrder) // should be a tuple of index and custom object
How do I grab the optional first item in an array and return index and element?
Many thanks
Edit. the reason I'm doing this is so that I can transfer the correct object to another class.
// transfer all
while (factory.existingOrders.count > 0) {
let array = myOrderBook.existingOrders.enumerated().map { ($0.offset, $0.element) }
guard let firstOrder = array.first else {
break
}
let index = (firstOrder.0)
factory.orderBook.transfer(index: index, destination: .completedOrder)
}
Where the Orderbook is a class;
Factory {
var orderBook:OrderBook = OrderBook()
}
OrderBook {
var existingOrders: [ExistingOrder] = [ExistingOrder]()
var completedOrders: [CompletedOrder] = [CompletedOrder]()
}
And the idea is that I want to transfer an object from existing orders to completed orders and vice versa
The function requires an index, but I guess I could refactor it so I can transfer an object instead.
The answer I was looking for was;
let array = myOrderBook.existingOrders.enumerated().map { ($0.offset, $0.element) }
However, I found that my code needed to be refactored.
Thanks.
Issue closed.
I would like to know how can i fill my label from an Array
func metaDataUpdated(metaData : NSString){
var listItems: NSArray = [metaData.componentsSeparatedByString(";")]
if ([listItems.count] > 0){
println([listItems.objectAtIndex(0)])
titleSong.text = [listItems.objectAtIndex(0)]
}
}
I don't really know how to convert an array to string.
Direct conversion to Swift:
func metaDataUpdated(metaData : String) {
let listItems = metaData.componentsSeparatedByString(";")
if listItems.count > 0 {
print(listItems[0])
titleSong.text = listItems[0]
}
}
Nicer Swift:
func metaDataUpdated(metaData : String) {
let listItems = metaData.componentsSeparatedByString(";")
if let first = listItems.first {
print(first)
titleSong.text = first
}
}
Even nicer Swift, without using Foundation and without the function needing to get every component separated by ";", but only the first one (recommended):
func metaDataUpdated(metaData : String) {
if let index = metaData.characters.indexOf(";") {
let first = metaData[metaData.startIndex ..< index]
print(first)
titleSong.text = first
}
}
you cannot assign NSArray to NSString therefore you need to cast the value of this first index into a string
change this
titleSong.text = [listItems.objectAtIndex(0)]
to
titleSong.text = "\(listItems.objectAtIndex(0))"
or
titleSong.text = listItems[0] as! String
and also change this line to ([listItems.count > 0]) to (listItems.count > 0)
your code will look like this:
Note this not obj-c so remove all []
func metaDataUpdated(metaData : NSString){
var listItems: NSArray = metaData.componentsSeparatedByString(";")
if (listItems.count > 0)
{
println(listItems.objectAtIndex(0))
titleSong.text = listItems.objectAtIndex(0) as! String
}
}
Better use Swift types and objects now: Array instead of NSArray, Dictionary instead of NSDictionary, etc.
func metaDataUpdated(metaData : NSString) {
var listItems = metaData.componentsSeparatedByString(";")
if listItems.count > 0 {
print(listItems[0])
titleSong.text = listItems[0]
}
}
Here componentsSeparatedByString returns an array of strings: [String]. We then use simple index subscripting to retrieve its first value.
Note: I suppose you were trying to adapt code from Objective-C because your example was ridden with [] everywhere...
Put your to string item in "\\()".
For instance:
titleSong.text = "\\([listItems.objectAtIndex(0)])"
Not sure you need the [] brackets though
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"]
I have a multidimensional array with some repeated elements in the deepest level:
[
["taco","burrito"],
["chalupa","taco","bread"]
["pizza","lasagna"],
["pizza","taco","burrito"]
["salad","sandwich"],
["meat","turkey"]
["cups","chicken"],
["rabbit","taco", "chicken", "salad"]
]
I have flattened this array into...
[
"taco",
"burrito",
"chalupa",
"taco",
"bread",
"pizza",
"lasagna",
etc...
]
I have found the second occurrence of the word "taco" in the multidimensional array and know its index.
How can I translate that index to an index in the flattened array?
So in this example it would be...
multiDimensionalArray[0][1][1]
=
flatArray[3]
Swift doesn't have native support for flattening arrays. You could roll this out yourself, or you could use ExSwift which has a flatten method. If you flatten the array successfully, obviously you could then determine which index the value is at using indexOfObject. Since you have multiple identical objects, you may want to use indexesOfObjectsPassingTest, which will give you an index set for all indexes matching the test you provide.
First of all you need a function to flatten the array into an array of strings in order to gain the true indices of the elements.
public func flattenArray(array: Array<AnyObject>) -> Array<String> {
var flattened: Array<String> = []
for object: AnyObject in array {
if object is Array<AnyObject> {
for string in flattenArray(object as Array<AnyObject>) {
flattened.append(string)
}
} else if object is String {
flattened.append(object as String)
}
}
return flattened
}
Then you need to write a function that will obtain the index of an element in an array at a certain number of occurrences
public func indexOfString(string: String, inArray array: Array<String>, occurance: Int = 1) -> Int? {
var occurancesSoFar = 0
var index: Int? = nil
var currentIndex = 0
for object in array {
if object == string {
if (++occurancesSoFar == occurance) {
index = currentIndex
}
}
++currentIndex
}
return index
}
Calling the above functions in order gives the correct index value:
let array = [
["taco","burrito"],
["chalupa","taco","bread"],
["pizza","lasagna"],
["pizza","taco","burrito"],
["salad","sandwich"],
["meat","turkey"],
["cups","chicken"],
["rabbit","taco", "chicken", "salad"]
]
let flat = flattenArray(array)
let index = indexOfString("taco", inArray: flat, occurance: 2)
println(index) // Optional(3)
Using these methods you could encapsulate them into a single function perhaps called
func flattenAndFindElement(element: String,
inArray array: Array<AnyObject>,
atOccurrence occurrence: Int) -> Int?
or something similar.
Hope that helps answer your question.
Here is what I ended up doing:
func findFlattenedIndex(array: Array<Array<String>>, firstIndex: Int, secondIndex: Int) -> Int {
var flatIndex = 0
for (index1,secondTier) in enumerate(array) {
for element in secondTier {
if index1 != firstIndex{
println(element)
flatIndex += 1
}
}
if index1 == firstIndex {
flatIndex += secondIndex
return flatIndex
break
}
}
return flatIndex
}
Of course this could be modified to deal with any number of dimensions.
Thanks for everyone's help with this.