-
Notifications
You must be signed in to change notification settings - Fork 656
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add last / prepend to CircularBuffer #347
Conversation
FYI @helje5 |
Very nice! 👯 Should that also do the power-of-2 buffer size increases instead of doubling? For Redi/S to be fast, I had to do manual capacity increases. |
Oh, I just noticed you already did that in #313 . |
Sources/NIO/CircularBuffer.swift
Outdated
} else { | ||
return self.buffer[self.headIdx] | ||
} | ||
// As self.buffer is filled with `nil` this will correctly return `nil` if the CircularBuffer is empty. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change worth it? The path of checking isEmpty
is going to be faster, because it saves us the additional level of indirection into the buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa not sure I think it is a nice change but I am not strong on it...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weissi for the tie-break!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feels too clever to me, I'd leave the old code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks really good but I think the naming of the functions should be improved. Also paged the stdlib guys so they can help us out here.
Sources/NIO/CircularBuffer.swift
Outdated
/// Add an element to the end of the ring buffer. | ||
/// | ||
/// Amortized *O(1)* | ||
public mutating func addLast(_ value: E) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we have this method? We have append
already which matches Array
's append
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weissi I added it for consistent naming ;)
Sources/NIO/CircularBuffer.swift
Outdated
/// Add an element to the front of the ring buffer. | ||
/// | ||
/// Amortized *O(1)* | ||
public mutating func addFirst(_ value: E) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swift doesn't really seem to use add...
. I would suggest prepend
maybe as it's the opposite of append
? CC @lorentey, @moiseev, @airspeedswift
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also: what protocols should this auto expanding ring buffer implement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appending to the front gets us to BidirectionalCollection
. Now that we're at that, we can actually hop the next level up to RandomAccessCollection
. Given the way this behaves, we could also give it RangeReplaceableCollection
too, which is a pretty easy cousin of RandomAccessCollection
.
So I'd recommend adding conformances to those two, though we may want to do them in a different PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa isn't RangeReplaceableCollection
a thing where you can replace any range by any other range? So for example put 5 elements in a space where there used to be 3 ?
Like thing[1...3] = ["foo", "bar", "buz", "qux", "quux"]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, and our buffer can do that. It's no less efficient at it than a regular array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that (startIndex=0) wouldn't matter much. (though Redi/S lists also have that getrange-family of operations, but that could be mapped in the frontend).
The thing I'm wondering is whether there should also be a prepend(contentsOf:)
which reserves the proper capacity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can see, CircularBuffer
does not have even an append(contentsOf:)
right now.
I had this idea for a moment, but then I quickly hit a wall trying to name prepend(contentsOf:)
correctly and specify the behavior when tailIdx
reaches the headIdx
, so I decided not to =)
Going back to indices. FWIW, in the collection types provided by the standard library there are no guarantees about the values of indices, so one should always use startIndex
/endIndex
combo and index
family of methods, which might be annoying, but is the only guaranteed way of writing generic code over collections, even when Index: FixedWidthInteger
or Index == Int
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moiseev Many thanks! I'm not personally attached to the 0 based indexing and I think we can change it as SemVer patch I think. What do @normanmaurer & @Lukasa think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and obvs I'm fine with prepend
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weissi sounds good to me... That said I think we can pull in the PR as it is now... WDYT ?
Sources/NIO/CircularBuffer.swift
Outdated
let value = self.buffer[self.headIdx] | ||
self.buffer[headIdx] = nil | ||
|
||
precondition(value != nil, "CircularBuffer is empty") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be the first instruction I think, just check isEmpty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weissi sure I can do this but I would argue that this way we it may be a bit faster...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want a speedup, the better thing to do is to do this:
guard let value = self.buffer[headIdx] else {
preconditionFailure("Circular buffer is empty")
}
self.buffer[headIdx] = nil
self.headIdx = (self.headIdx + 1) & self.mask
return value
This checks the optional only once, not twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine with me as well...(would use fatalError tho) @weissi ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use fatalError
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa actually you are right preconditionFailure
may be more correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
happy with either
Sources/NIO/CircularBuffer.swift
Outdated
let idx = (self.tailIdx - 1) & self.mask | ||
let value = self.buffer[idx] | ||
|
||
precondition(value != nil, "CircularBuffer is empty") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #347 (comment)
Sources/NIO/CircularBuffer.swift
Outdated
public mutating func removeLast() -> E { | ||
let idx = (self.tailIdx - 1) & self.mask | ||
guard let value = self.buffer[idx] else { | ||
fatalError("CircularBuffer is empty") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think preconditionFailure
here is fine, right @weissi?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep I think you are right...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, happy with either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙌 thanks, looks great
Motivation: Its often useful to be able to add / retrieve elements on both sides of a CircularBuffer. Modifications: - Add last / prepend to be able to act on both ends. - Add tests Result: Fixes apple#279
Will merge one the CI completes |
Created #350 as a followup. |
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` apple#347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation: During the `PR` #347 there was a discussion around which protocols this autoexpanding buffer should conform; the following 3 being the winners: 1. BidirectionalCollection 2. RandomAccessCollection 3. RangeReplaceableCollection Modifications: Added conformance to the 3 protocolos and provided implementaiton for 4 methods: 1. replaceSubrange(:with:) 2. index(before:) 3. removeSubrange(:) 4. remove(at:) 5. removeLast(:) Result: With the conformance of this 3 protocols and the implementation of this 5 functions we get dropLast(), dropLast(:), removeFirst(), removeLast() & popLast() for free.
Motivation:
Its often useful to be able to add / retrieve elements on both sides of a CircularBuffer.
Modifications:
Result:
Fixes #279