Converting String to Int by map() - swift

I have an input (String): "1 * 2 + 34 - 5" and I want to split it into array and then convert "convertible" elements into integers. My code looks like this:
let arr: [Any] = readLine()!.split(separator: " ").map {Int($0) != nil ? Int($0) : $0}
Splitting is not a problem but I don't know why mapping doesn't work as it should. I get error:
error: cannot invoke initializer for type 'Int' with an argument list of type '((AnySequence<String.Element>))'
note: overloads for 'Int' exist with these partially matching parameter lists: (Float), (Double), (Float80), (Int64), (Word), (NSNumber), (CGFloat)
I try to do the same in another way, with initializing new array:
let arr = readLine()!.split(separator: " ")
let newArray: [Any] = arr.map {Int($0) != nil ? Int($0) : $0}
but it also throws me an error:
error: 'map' produces '[T]', not the expected contextual result type '[Any]'
It is suprise for me that when I'm trying to do the same with for loop it works perfectly:
let arr = readLine()!.split(separator: " ")
var newArray = [Any]()
for x in arr
{
if Int(x) != nil {newArray.append(Int(x)!)}
else {newArray.append(x)}
}
print(newArray)
output: [1, "*", 2, "+", 34, "-", 5]
Can someone explain to me what is going on here? I mean if all 3 codes do the same thing so why only "for loop" works fine?

You'll need to specify that the return type of your map block is Any rather than the type which is being inferred by the compiler (Int), e.g.
let fullString = "1 * 2 + 34 - 5"
let elements = fullString
.components(separatedBy: " ")
.map { Int($0) ?? $0 as Any}

Related

Swift Return Sorted Dictionary based on Value Count

As the title suggests, I'm trying to sort an existing Dictionary of type [Character : Int] based on the value count, with most repeated characters appearing first. The following code returns the error error: cannot convert return expression of type '[(key: Character, value: Int)]' to return type '[Character : Int]. Now, I get that my function is returning an array of Tuples but I can't understand how...
Here's the code:
let str = "the brown fox jumps over the lazy dog"
func characterCount(str: String) -> [Character : Int] {
var results: [Character : Int] = [:]
for char in str {
results[char] = 0
let charCount = str.filter { char == $0 }
results[char] = charCount.count
}
let sortedResults = results.sorted { $0.value < $1.value }
return sortedResults
}
print(characterCount(str: str))
As Sweeper notes, dictionaries are inherently unordered, so you will need to transform the dictionary into something sortable. The simplest option here is an array of tuples
let sorted = str
.reduce(into: [Character: Int]()){ $0[$1] = $0[($1), default: 0] + 1}
.map{($0.key, $0.value)}
.sorted{$0.1 > $1.1}
This answer uses reduce(into:) to create the dictionary from the string, but you could use a for loop if you prefer. It then maps the dictionary into an array of tuples of (Character, Int) where the Character is the dictionary key (i.e. the string character) and the Int is it's value (i.e. the character's count), and sorts that based on the value of the count.

Using reduce() with an inferred return type

Given:
let a = [1, 2, 3]
Use reduce() to map to an array of strings:
let b: [String] = a.reduce([], { $0 + [String($1)] })
This returns ["1", "2", "3"] as you'd expect.
let c: [String] = a.reduce([], { $0 + [$1] })
This generates a compiler error (can't convert from Int to String), also as you'd expect.
let d = a.reduce([String](), { $0 + [$1] })
This returns [1, 2, 3] of type Array<Any>. Why doesn't the compiler consider the Result type to be Array<String>, and also generate an error in this case?
You want an error to occur (and I think a warning is in order), but the compiler assumes you want code to compile, instead. So it's going to fall back to Any for heterogenous collections, unless you type it otherwise. E.g.
protocol Protocol { }
extension Bool: Protocol { }
extension Int: Protocol { }
[[true]].reduce([0], +) as [any Protocol]
The correct return type can be inferred. Just not if you create a heterogeneous result.
a.map(String.init)
a.reduce(into: [String]()) { $0.append(.init($1)) }
a.reduce(into: []) { $0.append(String($1)) }
[Any] is a type of array that can contain 1, 2 and 3. When you try to append those values to [String] it just turns it into [Any].
It doesn't work on the other case because you're asking for the final array to be [String] by using it on the declaration.
let a = // array of [Int] ✅
let b: [String] = // array of [String] ✅
let c: [String] = // array of [Any] ❌
let d = // array of [Any] ✅

Converting String to Int while using reduce()

I have code:
let number: String = "111 15 111"
let result = number.components(separatedBy: " ").map {Int($0)!}.reduce(0, {$0 + $1})
First it takes given string and split into array of numbers. Next each number is converted to an integer and at the end all numbers are added to each other. It works fine but code is a little bit long. So I got the idea to get ride of map function and convert String to Int while using reduce, like this:
let result = number.components(separatedBy: " ").reduce(0, {Int($0)! + Int($1)!})
and the output is:
error: cannot invoke 'reduce' with an argument list of type '(Int, (String, String) -> Int)'
Thus my question is: Why I cannot convert String to Integer while using reduce()?
reduce second parameter is a closure with $0 is the result and $1 is the string. And instead of force unwrapping optional default value will be better.
let number: String = "111 15 111"
let result = number.components(separatedBy: " ").reduce(0, {$0 + (Int($1) ?? 0) })
Another alternative is with flatMap and reduce with + operator.
let result = number.components(separatedBy: " ").flatMap(Int.init).reduce(0, +)
Your mistake is first argument in the closure. If you look at reduce declaration, first closure argument is Result type, which is Int in your case:
public func reduce<Result>(_ initialResult: Result,
_ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
So the right code will be:
let result = number.components(separatedBy: " ").reduce(0, { $0 + Int($1)! })

Swift 3 - Can't subtract variables: Ambiguous reference to member [subtraction] [duplicate]

I am trying to concatenate multiple strings in swift 3:
var a:String? = "a"
var b:String? = "b"
var c:String? = "c"
var d:String? = a! + b! + c!
When compiling I get the following error:
error: cannot convert value of type 'String' to specified type 'String?'
var d:String? = a! + b! + c!
~~~~~~~~^~~~
This used to work in swift 2. I am not sure why it doesn't work anymore.
Bug report filed by OP:
SR-1122: Failure to typecheck chain of binary operators on force-unwrapped values
Which has been resolved (fix commited to master Jan 3 2017), and should hence no longer be an issue in upcoming Swift 3.1.
This seems to be a bug (not present in Swift 2.2, only 3.0) associated with the case of:
Using the forced unwrapping operator (!) for at least 3 terms in an expression (tested using at least 2 basic operators, e.g. + or -).
For some reason, given the above, Swift messes up type inference of the expression (specifically, for the x! terms themselves, in the expression).
For all the examples below, let:
let a: String? = "a"
let b: String? = "b"
let c: String? = "c"
Bug present:
// example 1
a! + b! + c!
/* error: ambiguous reference to member '+' */
// example 2
var d: String = a! + b! + c!
/* error: ambiguous reference to member '+' */
// example 3
var d: String? = a! + b! + c!
/* error: cannot convert value of type 'String'
to specified type 'String?' */
// example 4
var d: String?
d = a! + b! + c!
/* error: cannot assign value of type 'String'
to specified type 'String?' */
// example 5 (not just for type String and '+' operator)
let a: Int? = 1
let b: Int? = 2
let c: Int? = 3
var d: Int? = a! + b! + c!
/* error: cannot convert value of type 'Int'
to specified type 'Int?' */
var e: Int? = a! - b! - c! // same error
Bug not present:
/* example 1 */
var d: String? = a! + b!
/* example 2 */
let aa = a!
let bb = b!
let cc = c!
var d: String? = aa + bb + cc
var e: String = aa + bb + cc
/* example 3 */
var d: String? = String(a!) + String(b!) + String(c!)
However as this is Swift 3.0-dev, I'm uncertain if this is really a "bug", as well as what's the policy w.r.t. reporting "bugs" in a not-yet-production version of code, but possibly you should file radar for this, just in case.
As for answering your question as how to circumvent this issue:
use e.g. intermediate variables as in Bug not present: Example 2 above,
or explicitly tell Swift all terms in the 3-term expression are strings, as in Bug not present: Example 3 above,
or, better yet, use safe unwrapping of your optional, e.g. using optional binding:
var d: String? = nil
if let a = a, b = b, c = c {
d = a + b + c
} /* if any of a, b or c are 'nil', d will remain as 'nil';
otherwise, the concenation of their unwrapped values */
Swift 3
let q: String? = "Hello"
let w: String? = "World"
let r: String? = "!"
var array = [q, w, r]
print(array.flatMap { $0 }.reduce("", {$0 + $1}))
// HelloWorld!
let q: String? = "Hello"
let w: String? = nil
let r: String? = "!"
var array = [q, w, r]
print(array.flatMap { $0 }.reduce("", {$0 + $1}))
// Hello!
func getSingleValue(_ value: String?..., seperator: String = " ") -> String? {
return value.reduce("") {
($0) + seperator + ($1 ?? "")
}.trimmingCharacters(in: CharacterSet(charactersIn: seperator) )
}
let val: String? = "nil"
val.flatMap({(str: String) -> String? in
return str + "value"
})
var a:String? = "a"
var b:String? = "b"
var c:String? = "c"
var d:String? = ""
let arr = [a,b,c]
arr.compactMap { $0 }.joined(separator: " ")
compactMap be used to filter out nil values from flattened arrays

swift: can't reduce [sic] character array to string

I realize this is a unnecessary question, but... why can I NOT use reduce to convert a character array into a string?
e.g.,
let str = "this is a string"
let clist = Array(str)
let slist = clist.reduce("", +)
gives me: 'Character' is not a subtype of 'Uint8'
when
list dlist = [0, 1, 2, 3, 4, 5, 6]
let sum = dlist.reduce(0, +)
works
I know I can simply do slist = String(clist), but I just wanna know, ya know?
Swift 1.1 in playground of xcode 6.2
Thanks!
Inside the combine: closure of
let slist = clist.reduce("", +)
$0 is the so-far accumulated result – a String,
$1 is the current element from clist – a Character.
There is no + operator which takes (String, Character) as arguments.
This would work:
let slist = clist.reduce("") { $0 + String($1) }
In Swift 1.2:
let str = "this is a string"
let clist = Array(str)
let slist = clist.map { String($0) }.reduce("", combine: { $0 + $1 })
println(slist) // "this is a string"