Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active September 20, 2020 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steipete/e2ed747f41e0148de9cfb21e0e99908b to your computer and use it in GitHub Desktop.
Save steipete/e2ed747f41e0148de9cfb21e0e99908b to your computer and use it in GitHub Desktop.
public var byteRange: [Range<UInt64>]? {
// Guaranteed to be in pairs. Example:
// - 0 : 0
// - 1 : 71826
// - 2 : 83948
// - 3 : 34223
guard let boxedRange = __byteRange else { return nil }
let range = boxedRange.map { $0.uint64Value }
var ranges: [Range<UInt64>] = []
for rangeIndex in stride(from: 0, to: range.count, by: 2) {
let lower = range[rangeIndex]
let count = range[rangeIndex + 1]
ranges.append(Range(uncheckedBounds: (lower: lower, upper: lower + count)))
}
return ranges
}
@fpillet
Copy link

fpillet commented Aug 27, 2020

From a performance standpoint, you could eliminate the first map which creates an unnecessary temporary array (although maybe the optimiser can wipe it out ? I have not checked that):

guard let boxedRange = __byteRange else { return nil }

return stride(from: 0, to: boxedRange.count, by: 2).map { rangeIndex in
    let lower = boxedRange[rangeIndex].uint64Value
    let count = boxedRange[rangeIndex + 1].uint64Value
    return Range(uncheckedBounds: (lower: lower, upper: lower + count))
}

@asiliuk
Copy link

asiliuk commented Aug 27, 2020

As a crazy idea you can try creating custom sequence that consists of pairs and then just map it. It just hides complexity of managing pair values in more cleaner API

public var byteRange: [Range<UInt64>]? {
     // Guaranteed to be in pairs. Example:
     // - 0 : 0
     // - 1 : 71826
     // - 2 : 83948
     // - 3 : 34223
    return __byteRange?
        .map { $0.uint64Value }
        .pairs()
        .map { Range(uncheckedBounds: (lower: $0, upper: $0 + $1)) }
 }

extension Collection where Element == UInt64 {
    func pairs() -> AnySequence<(UInt64, UInt64)> {
        return .init { () -> AnyIterator<(UInt64, UInt64)> in
            var iterator = self.makeIterator()
            return .init {
                guard let first = iterator.next(), let second = iterator.next() else { return nil }
                return (first, second)
            }
        }
    }
}

@steipete
Copy link
Author

@fpillet: Beautiful, thank you!
@asiliuk: I'm afraid this looks unmaintainable. Smart but too smart for shipping code.

@fjtrujy
Copy link

fjtrujy commented Aug 27, 2020

Here you have my more functional alternative.

public var byteRange: [Range<UInt64>]? {
    // Guaranteed to be in pairs. Example:
    // - 0 : 0
    // - 1 : 71826
    // - 2 : 83948
    // - 3 : 34223
    guard let boxedRange = __byteRange else { return nil }
    
    return stride(from: .zero, to: boxedRange.count, by: 2).reduce(into: [Range<UInt64>]()) {
        let lower = boxedRange[$1].uint64Value
        let count = boxedRange[$1 + 1].uint64Value
        $0.append(Range(uncheckedBounds: (lower: lower, upper: lower + count)))
    }
}

@fpillet
Copy link

fpillet commented Aug 27, 2020

@fjtrujy using reduce here doesn't bring any improvement in clarity, maintainability or speed IMHO. The advantage of map is that it's straightforward to read the code and understand what it does.

@fjtrujy
Copy link

fjtrujy commented Aug 27, 2020

@fjtrujy using reduce here doesn't bring any improvement in clarity, maintainability or speed IMHO. The advantage of map is that it's straightforward to read the code and understand what it does.

Yes, you're totally right, I was thinking to use the "reduce" at the very beginning because I was thinking to discard the "even" values while iterating, but at the end, the discard of the values is better implemented with the stride.

@allenhumphreys
Copy link

Not sure if this meets the bar of readability and maintainability but personally I think it improves readability to define fluent extensions:

extension Swift.ClosedRange where Bound: Strideable {
    func by(_ strideAmount: Bound.Stride) -> StrideThrough<Bound> {
        return stride(from: lowerBound, through: upperBound, by: strideAmount)
    }
}

extension Swift.Range where Bound: Strideable {
    func by(_ strideAmount: Bound.Stride) -> StrideTo<Bound> {
        return stride(from: lowerBound, to: upperBound, by: strideAmount)
    }
}

for rangeIndex in (0...7).by(2) {
     // reads as 0 through 7 by 2
}

for rangeIndex in (0..<7).by(2) {
    // reads as 0 to 7 by 2
}

I think it's mildly easier to parse visually than the stride initializer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment