2

What's the syntax for extending an Array with an Element type of Double ?

I've seen this sort of answer around:

extension Sequence where Iterator.Element == Double {
    public func multiply(by factor: Double) -> [Double] {
        return self.map { $0 * factor }
    }
}

But it's extending a generic Sequence, so it allows neither random access by index, nor to a count property. So e.g., I can't implement the following:

public func windowingFunc(index: Int, N: Int) -> Double {
    // ...
}

extension Sequence where Iterator.Element == Double {
    public func applyWindowing() -> [Double] {
        return (0..<self.count).map{self[$0] * windowingFunc(index: $0, N: self.count)}
    }
}
Danra
  • 9,546
  • 5
  • 59
  • 117
  • 3
    In Swift 3.1 (available with Xcode 8.3 beta) you can say `extension Array where Element == Double` :) – Hamish Jan 29 '17 at 16:10
  • 1
    Related: [Can't extend generic struct for specific type](http://stackoverflow.com/questions/41657221/cant-extend-generic-struct-for-specific-type) – Hamish Jan 29 '17 at 16:12
  • Sounds simple enough! Doesn't work on my latest release Xcode :( On that note, where does Xcode provide info on the Swift version it supports? – Danra Jan 29 '17 at 16:12
  • 2
    You can currently download Xcode 8.3 beta from Apple's developer page – you can also run `xcrun swift -version` to find out your Swift version. – Hamish Jan 29 '17 at 16:13

2 Answers2

3

If you want to map your array elements and need also its index position you should use the method enumerated(). I would also extend BidirectionalCollection instead of RandomAccessCollection and as already mentioned in comments by @Hamish using enumerated you can omit the Index == Int constraint.

protocol BidirectionalCollection : BidirectionalIndexable, Collection

Description: A collection that supports backward as well as forward traversal. Bidirectional collections offer traversal backward from any valid index, not including a collection’s startIndex. Bidirectional collections can therefore offer additional operations, such as a last property that provides efficient access to the last element and a reversed() method that presents the elements in reverse order. In addition, bidirectional collections have more efficient implementations of some sequence and collection methods, such as suffix(_:).

extension BidirectionalCollection where Iterator.Element == Double, IndexDistance == Int {
    public func applyWindowing() -> [Iterator.Element] {
        return enumerated().map{ $0.element * windowingFunc(index: $0.offset, N: count)}
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
2

I got this to work by adding lots and lots of constraints:

extension RandomAccessCollection where Iterator.Element == Double, IndexDistance == Int, Index == Int {
    public func applyWindowing() -> [Double] {
        return (0..<self.count).map{self[$0] * windowingFunc(index: $0, N: self.count)}
    }
}

IndexDistance == Int makes the type of count Int. Index == Int makes you access the array with Int.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Marking this as the accepted answer, hopefully it will soon(!) be outdated :) – Danra Jan 29 '17 at 16:14
  • @Danra Why would this be outdated? – Alexander Jan 29 '17 at 16:32
  • 1
    @Alexander it wont be outdated in the sense that it will be deprecated, but as Swift 3.1 will allow concrete constrained extensions, a simplified version (given the OPs specific demands) can make use of this new feature, simply extending `Array` constrained to a concrete `Element` type (namely, `Double`). – dfrib Jan 29 '17 at 18:56
  • @dfri I'm aware of this new feature, but keeping this more generic by extending `RandomAccessCollection` rather than `Array` would still be preferable. In particular, because ArraySlices could also be used. – Alexander Jan 29 '17 at 18:58
  • 1
    @Alexander repeating; _"given the OPs specific demands"_, namely the explicit question of the OP, _"What's the syntax for extending an Array with an Element type of Double ?"_ (or even the question title) then the new feature allows exactly this approach. Naturally, advices about good practices (and _why_, e.g. for slicing) is always good, but given the discussion in the comments to the question, I found OPs comment to this answer quite non-confusing myself, and offered a comment myself (on part of the OP) to explain this to you. Or was the question not a querying one to being with...? :) – dfrib Jan 29 '17 at 19:10
  • 1
    ... on another point, I generally believe that non-questions posed as questions (not saying that this is the case here) rather than sharing instructive insights is quite non-constructive in itself, as it will lead to time-wasting confusing comment discussions where a useful insightful good-practices comment could have been left instead, in the first place. – dfrib Jan 29 '17 at 19:12
  • @dfri Fair point, I guess it was a roundabout way of me asking OP "what leads you to prefer an extension on Array rather than a more generic extension like this one?" – Alexander Jan 29 '17 at 19:13
  • 1
    @Alexander no ambiguity in that one! As I humbly ask my colleagues when performing code reviews: be verbose, if possible! :) finally, the point about array slices is very good (Y) – dfrib Jan 29 '17 at 19:21
  • @Alexander I mostly write research code in Swift, so normally I don't need the generic functionality. Just extending an `Array` is great for my purposes, and the code for it is obviously simpler. – Danra Jan 29 '17 at 20:44
  • @Alexander Regardless, even if I *did* need more generic code, I hope Swift improves so I'm able to more simply extend array-like collections with less verbose constraints. (or, maybe a less verbose way already exists and I just don't know about it) – Danra Jan 29 '17 at 20:45
  • 3
    @Danra `return enumerated().map{ $0.element * windowingFunc(index: $0.offset, N: count)}` – Leo Dabus Jan 29 '17 at 20:53
  • 2
    @LeoDabus Great! Please, do post as an answer for the benefit of other readers. Thanks! – Danra Jan 29 '17 at 21:20
  • 1
    @LeoDabus Note that using that will also let you drop the `Index == Int` constraint :) – Hamish Jan 29 '17 at 22:08
  • @Hamish Yes I've already notice that. Besides that I would make it available to subsequences extending BidirectionalCollection and constraining SubSequence Iterator element to Double also `extension BidirectionalCollection where Iterator.Element == Double, SubSequence.Iterator.Element == Double, IndexDistance == Int {` – Leo Dabus Jan 29 '17 at 22:16
  • 1
    @LeoDabus It doesn't even require `BidirectionalCollection`, just plain old `Collection` would do. Although I don't see the point in the `SubSequence.Iterator.Element` constraint as you aren't working with a `SubSequence`. Also note that if you are to extend `Collection`, you should factor the `count` out of the map due to the fact that non random access collections will have an O(n) `count` implementation. You should totally post all this as an answer btw :) – Hamish Jan 29 '17 at 22:19
  • 1
    @LeoDabus No, it has a default implementation which returns 0 (see [this Q&A](http://stackoverflow.com/q/40152826/2976878)). It's only really meant to be used for optimisation purposes like pre-allocating memory to store the elements of a given sequence in a buffer. – Hamish Jan 29 '17 at 22:51
  • @Hamish Last question: Wouldn't it be easier remove the IndexDistance constraint and change the type of the windowingFunc parameters? `func windowingFunc(index: Index, count: IndexDistance) -> Iterator.Element {`Is there any risk forcing casting the index offset to Index `return enumerated().map{ $0.element * windowingFunc(index: $0.offset as! Index, count: count)}` or another way of doing it safelly or probably I need to declare it optional `windowingFunc(index: Index?, ` ? – Leo Dabus Jan 30 '17 at 00:35
  • @Hamish https://gist.github.com/leodabus/f0e9bc9886fca6d7f3e28ff9cc1abe96 – Leo Dabus Jan 30 '17 at 00:41
  • @Hamish Is there any disadvantage adding the `Index == Int` constraint? – Leo Dabus Jan 30 '17 at 00:47
  • @LeoDabus The problem is that `windowingFunc` most probably requires `Int` parameters in order to perform its logic, rather than `IndexDistance` and `Index` which, without constraints, have unknown concrete types. `$0.offset as! Index` would crash for any `Index` type that isn't `Int` (as `enumerated()` creates a lazy sequence of elements with `Int`s counting from 0). The constraint `IndexDistance == Int` isn't very restrictive to be honest – most collections that I can think of use `Int` as their `IndexDistance` (it has to be some `SignedNumber` at the end of the day). – Hamish Jan 30 '17 at 00:56
  • 1
    `Index == Int` on the other hand is a pretty restrictive constraint – as collections can have an `Index` of any concrete type that conforms to `Comparable`. Non random access collections in particular rarely have `Int` indices. – Hamish Jan 30 '17 at 00:56