String array reduce - swift

I am trying to join the elements of a String array via the reduce function. A tried for a bit now, but I can't get what the problem exactly is. This is what I believe should do the trick. I have tried other alternatives too, but given the huge amount I will wait for some input:
var genres = ["towel", "42"]
var jointGenres : String = genres.reduce(0, combine: { $0 + "," + $1 })
Error:
..:14:44: Cannot invoke '+' with an argument list of type
'(IntegerLiteralConvertible, combine: (($T6, ($T6, $T7) -> ($T6, $T7)
-> $T5) -> ($T6, ($T6, $T7) -> $T5) -> $T5, (($T6, $T7) -> ($T6, $T7) -> $T5, $T7) -> (($T6, $T7) -> $T5, $T7) -> $T5) -> (($T6, ($T6, $T7) -> $T5) -> $T5, (($T6, $T7) -> $T5, $T7) -> $T5) -> $T5)'
From my understanding, $0 should be inferred as a String and $1, by combination with $0, should result as a String too. I don't know what's the deal with the type system here. Any idea?

Your reduce closure should probably look like this:
var jointGenres : String = genres.reduce("", combine: { $0 == "" ? $1 : $0 + "," + $1 })
This has the "" instead of 0 like you had, and makes sure that there is no extra comma in the beginning of the return value.
The original code did not work because the return type that is represented as U in documentation was originally 0 in your answer, while you are trying to add a String to it. In your case, you really want both U and T to represent Strings instead of Ints.

reduce is not a straightforward solution here since you need special handling for the first element. String's join method is better for this purpose:
let strings = ["a", "b", "c"]
let joinedString = ",".join(strings)
If you know the array is not empty there is another possible solution with reduce that also avoids conditionals:
let joinedStrings = strings[1..<strings.count].reduce(strings[0]) { $0 + "," + $1 }

Cocoa already has a function to do this. It is marred by needing a typecast to NSArray.
var genres = ["towel", "42"]
var joinGenres = (genres as NSArray).componentsJoinedByString(",")
To my surprise, this function also can be applied to arrays of types other than String:
let ints = [1,5,9,15,29]
let listOfInts = (ints as NSArray).componentsJoinedByString(",")

If using Swift 4:
var jointGenres:String = genres.joined(separator: ",")

The problem is your first argument to reduce. This is an accumulator, it's an integer literal, and it's what's passed as $0 on the first run of the block. You're asking the reduce function to add a string to this.
Instead of 0 as the accumulator argument, you should be passing "", an empty string.
This works:
var genres = ["towel", "42"]
var jointGenres : String = genres.reduce("", combine: { $0 + "," + $1 })

Related

How can I define `unlines` function generically?

I would like to define a unlines function which works which any Sequence whose elements conform to the StringProtocol; this is my attempt:
func unlines<S: StringProtocol>(_ xs: Sequence<S>) -> S {
return xs.joined(separator: "\n")
}
Error: Use of protocol 'Sequence' as a type must be written 'any Sequence'
A version defined for a concrete type does work:
func unlines(_ xs: [String]) -> String {
return xs.joined(separator: "\n")
}
but can only be applied with list of String.
How can I develop a general definition ?
EDIT:
For example, I would like to apply it to the following value:
["Hello", "World"].lazy.map { $0.lowercased() }
joined returns a String so you need to change the return type and use any Sequence
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
return xs.joined(separator: "\n")
}
This then works both with String and Substring
String example:
let result = unlines(["A", "B", "C"])
Substring example:
let result = unlines("A-B-C".split(separator: "-"))
both returns
A
B
C
In Swift, you'd typically use a protocol extension to define instance functions that should operate on an instance, rather than using free functions that take a first argument.
Here's how that might work:
extension Sequence where Element: StringProtocol {
// FIXME: This is a pretty Haskelly, non-Swifty name
func unlines() -> String {
joined(separator: "\n")
}
}
let input = ["Hello", "World"]
.lazy
.map { $0.lowercased() }
.unlines()
print(input)
// Prints:
// hello
// world

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 closure syntax

I have following code that work:
let obsScan = source.scan(0, accumulator: +)
let obsReduce = source.reduce(0, accumulator: +)
let obs = Observable.zip(obsScan, obsReduce) { scan, reduce in
return "scan - \(scan), reduce - \(reduce)"
}
I want to rewrite it, using auto complete closure syntax, and i ended up with:
let obs = Observable.zip(obsScan, obsReduce, resultSelector: { (scan, reduce) -> _ in
return "scan - \(scan), reduce - \(reduce)"
})
However, that code throw me multiple errors:
Contextual type for closure argument list expects 2 arguments, which
cannot be implicitly ignored Consecutive statements on a line must be
separated by ';' Expected expression
I can't understand why i use tab to autocomplete function with closure, and when i fill arguments i got an errors?
Whole function declared like that:
public static func zip<O1, O2>(_ source1: O1, _ source2: O2, resultSelector: #escaping (O1.E, O2.E) throws -> RxSwift.Observable.E) -> RxSwift.Observable<RxSwift.Observable.E> where O1 : ObservableType, O2 : ObservableType
I'm not sure what are you waiting for. But this should work:
let obs = Observable.zip(obsScan, obsReduce, resultSelector: { scan, reduce in
return "scan - \(scan), reduce - \(reduce)"
})

Reduce higher order function in Swift 3.0 with Int enum

I am learning Swift higher order functions associated with Collections. I have following query with reduce
enum Coin : Int {
case Penny = 1
case Nickel = 5
case Dime = 10
case Quarter = 25
}
let coinArray: [Coin] = [.Dime, .Quarter, .Penny, .Penny, .Nickel, .Nickel]
coinArray.reduce(0,{ (x:Coin, y:Coin) -> Int in
return x.rawValue + y.rawValue
})
I am getting following error:
Declared closure result Int is incompatible with contextual type _
Let's see how reduce is declared:
public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
See the type of nextPartialResult? It is (Result, Element) -> Result. What is the type of Result, in your case? It is Int, because you want to reduce the whole thing to an integer.
Therefore, passing a (Coin, Coin) -> Int does not really work here, does it?
You should pass in a (Int, Coin) -> Int instead.
coinArray.reduce(0,{ (x:Int, y:Coin) -> Int in
return x + y.rawValue
})
Or simply:
coinArray.reduce(0) { $0 + $1.rawValue }
Once you apply reduce onto the coinArray you get the following signature:
Ask yourself what is the type of the generic Result? Is it of type coin or of type Int? What is the type of nextPartialResult? Is it of type coin or of type Int?
The answer is: Result is an Int and and nextPartialResult is a closure 'that takes one parameter of type result which here is Int and another parameter of type coin and eventually returns an Int'
So the correct way of writing it is:
coinArray.reduce(0,{ (x, y) -> Int in
return x + y.rawValue
})
Or in a more meaningful sense you could have wrote:
coinArray.reduce(0,{ (currentResult, coin) -> Int in
return currentResult + coin.rawValue
})
also coinArray isn't a good name. Just write coins. It being plural makes is more readable than coinArray / arrayOfCoins!

How to extension HalfOpenInterval with reduce method

I hope to extension HalfOpenInterval with reduce method
so can easy use some quick code snippet
for example:
var a = [3,4,9,7]
var mini = (0..<a.count).reduce(0, combine: { a[$0] > a[$1] ? $0 : $1 })
I notice that HalfOpenInterval fit IntervalType protocol, but not sure how to iterative each element in reduce function
extension HalfOpenInterval {
func reduce<T>(initialize: T, combine: (u: U, t:T) -> U) -> U {
...
}
}
tks
Maybe, what you should extend is Range:
extension Range {
func reduce<U>(initial:U, combine:(U, T) -> U) -> U {
return Swift.reduce(self, initial, combine)
}
}
let sum = (0 ..< 12).reduce(0, combine: { $0 + $1}) // -> 66
HalfOpenInterval or ClosedInterval is not for that, because it has only "start" and "end" values, but does not have "stride" of each values. Something like this:
Range also has "start" and "end", and these values itself know the next value of them:
Another similar structure, StrideTo and StrideThrough which constructed with stride(from:to:by:) or stride(from:through:by). It also has "start" and "end", and in this case, structure itself knows the "stride" between values.
You can extend the Range class in the following way.
extension Range {
func reduce<U>(initialize: U, combine: (u: U, t:T) -> U) -> U {
var result = initialize
for value in self {
result = combine(u: result,t: value)
}
return result
}
}