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"
Related
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] ✅
In swift, we have higher order functions like map, filter, reduce and so on for array
But what if i have an array such as [Any] = [1, 2, 3, "1"].
and i wish to convert this array to an array of Int. But since there is "1" in the array, my logic is to accept this entire array invalid, which i will map to an empty array, let say.
How do i do it with higher functions in this case?
Filtering is easy
let array: [Any] = [1, 2, 3, "1"]
let filtered = array.compactMap{ $0 as? Int}
/// prints [1, 2, 3]
but i want the end result to be [], not [1, 2, 3].
How do i achieve that using higher order functions?
CompactMap
Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
So you can't return what you want by using the compactMap
let array: [Any] = [1, 2, 3, "1"]
for case let element in array {
if let _ = element as? Int {
continue
} else {
print("\(element) isn't an Int, so array is invalid")
break
}
}
Assuming the array as? [Int] ?? [] is inapplicable (e.g. you need to apply a more complex transformation), you can throw an error instead.
enum ConversionError: Error {
case notAllValuesWereInts
}
func convertOrThrow(_ input: Any) throws -> Int {
switch input {
case let int as Int: return int
default: throw ConversionError.notAllValuesWereInts
}
}
let array: [Any] = [1, 2, 3, "1"]
let ints: [Int]
do {
ints = try array.map(convertOrThrow)
} catch {
ints = []
}
print(ints)
In a case like this where the error is used for control flow, doesn't carry any useful information, you can use try? to simplify things:
let array: [Any] = [1, 2, 3, "1"]
let ints = (try? array.map(convertOrThrow)) ?? []
Though this should do the trick as you just want to check whether the array is an array of Integers or not
array as? [Int] ?? []
But in case you don't want to use above, you might be interested in the following code
let filtered = array.filter{($0.self as? Int) == nil}.count == 0 ? array as! [Int] : []
The above code yields [] if there is any element which is not Int
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}
Say I have the following scenario:
var str: String
var num: Int?
if let num = num {
str = "\(num) foo"
}
else {
str == "? foo"
}
can the flow control statements be simplified to one line? I.e. something along the lines of:
var str: String
var num: Int?
str = "\(String(num) ?? "?") foo"
You can use call the description property with optional chaining and then use the nil coalescing operator ?? to either unwrap that or replace it with "?" if num is nil (causing the optional chain to return nil):
str = "\(num?.description ?? "?") foo"
Example:
for num in [nil, 5] {
let str = "\(num?.description ?? "?") foo"
print(str)
}
? foo
5 foo
Here's one solution:
let str = "\(num.map { String($0) } ?? "?") foo"
This returns "? foo" if num is nil or it returns "42 foo" if num is set to 42.
Not exactly in a clean one liner.
But if you write a simple extension on Optionals, like so:
extension Optional where Wrapped: CustomStringConvertible {
var nilDescription: String {
switch self {
case .none: return "?"
case let .some(wrapped): return wrapped.description
}
}
}
you could write
let str = "\(num.nilDescription) foo"
I think this approach would be more readable.
You can also write it like below.
var number:Int? = 0
var str = String(format: "%# foo", (number != nil) ? NSNumber(integer: number!) : "?");
Hope it will help you.
This question already has answers here:
Type 'String.Index' does not conform protocol 'IntegerLiteralConvertible'
(3 answers)
Closed 8 years ago.
I get compiler error at this line:
UIDevice.currentDevice().identifierForVendor.UUIDString.substringToIndex(8)
Type 'String.Index' does not conform to protocol 'IntegerLiteralConvertible'
My intention is to get the substring, but how?
In Swift, String indexing respects grapheme clusters, and an IndexType is not an Int. You have two choices - cast the string (your UUID) to an NSString, and use it as "before", or create an index to the nth character.
Both are illustrated below :
However, the method has changed radically between versions of Swift. Read down for later versions...
Swift 1
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substringToIndex(5) // "Stack"
//let ss2: String = s.substringToIndex(5)// 5 is not a String.Index
let index: String.Index = advance(s.startIndex, 5)
let ss2:String = s.substringToIndex(index) // "Stack"
CMD-Click on substringToIndex confusingly takes you to the NSString definition, however CMD-Click on String and you will find the following:
extension String : Collection {
struct Index : BidirectionalIndex, Reflectable {
func successor() -> String.Index
func predecessor() -> String.Index
func getMirror() -> Mirror
}
var startIndex: String.Index { get }
var endIndex: String.Index { get }
subscript (i: String.Index) -> Character { get }
func generate() -> IndexingGenerator<String>
}
Swift 2
As commentator #DanielGalasko points out advance has now changed...
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substringToIndex(5) // "Stack"
//let ss2: String = s.substringToIndex(5)// 5 is not a String.Index
let index: String.Index = s.startIndex.advancedBy(5) // Swift 2
let ss2:String = s.substringToIndex(index) // "Stack"
Swift 3
In Swift 3, it's changed again:
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substring(to: 5) // "Stack"
let index: String.Index = s.index(s.startIndex, offsetBy: 5)
var ss2: String = s.substring(to: index) // "Stack"
Swift 4
In Swift 4, yet another change:
let s: String = "Stack Overflow"
let ss1: String = (s as NSString).substring(to: 5) // "Stack"
let index: String.Index = s.index(s.startIndex, offsetBy: 5)
var ss3: Substring = s[..<index] // "Stack"
var ss4: String = String(s[..<index]) // "Stack"