在Swift,向外的乒乓序列?
In Swift, outwards pingpong sequence?
假设你有
for i in 0 ... 10 {
print(i)
}
当然会打印0,1,2,3,4,5,6,7,8,9,10
for i in 0 ..< 5 {
那是 0,1,2,3,4。
我想从某个整数开始,然后在数字计数上向外乒乓球
所以,
for i in function or something (10, 3)
那是 3 4 2 5 1 6 0 7 8 9
for i in function or something (10, 8) {
将是 8 9 7 6 5 4 3 2 1 0
for i in function or something (10, 2) {
将是 2 3 1 4 0 5 6 7 8 9
所以这只是一个向外的乒乓球。
我应该在我写的地方输入什么function or something (10, 2)
?
可能会有一些非常酷的语法,如 0 # 7 # 10
.
(0..<10).outPong(3)
之类的东西怎么样?
如何制定这样的序列?
这是一个简单的示例,说明您如何在通话级别进行向外乒乓球。
为 RA 中的每个项目调用 exampleLoad
,向外 pingpoing:
func loadItemsPongwise(startWith: Int) {
// RA = ... this is your array of some type
exampleLoad(startWith)
let k = RA.count
var howManyDone: Int = 0
var distance: Int = 1
while howManyDone < ( k - 1 ) {
let tryRight = alreadyLoaded + distance
if tryRight < k {
howManyDone = howManyDone + 1
exampleLoad(RA[tryRight])
}
let tryLeft = alreadyLoaded - distance
if tryLeft >= 0 {
howManyDone = howManyDone + 1
exampleLoad(RA[tryLeft])
}
distance = distance + 1
}
}
当然,这样的东西会更好:
func loadItemsPongwise(startWith: Int) {
for i in ???? {
exampleLoad(i)
}
}
public extension ClosedRange where Bound: AdditiveArithmetic {
func (
by contiguousAdvancement: Bound,
startingAt start: Bound
) -> AnySequence<Bound> {
guard contains(start)
else { return .init( EmptyCollection() ) }
var advancement = contiguousAdvancement
typealias Operate = (Bound, Bound) -> Bound
var pingPong: Operate = (+)
var contiguouslyAdvance: Operate = (-)
return .init(
sequence(first: start) { previous in
pingPongIterate: do {
defer { advancement += contiguousAdvancement }
let pingPonged = pingPong(previous, advancement)
guard self.contains(pingPonged)
else { break pingPongIterate }
(pingPong, contiguouslyAdvance) = (contiguouslyAdvance, pingPong)
return pingPonged
}
let contiguouslyAdvanced = contiguouslyAdvance(previous, contiguousAdvancement)
return self.contains(contiguouslyAdvanced)
? contiguouslyAdvanced
: nil
}
)
}
}
public extension ClosedRange where Bound: AdditiveArithmetic & ExpressibleByIntegerLiteral {
func (startingAt start: Bound) -> AnySequence<Bound> {
(by: 1, startingAt: start)
}
}
public extension ClosedRange where Bound: BinaryInteger {
func (by contiguousAdvancement: Bound = 1) -> AnySequence<Bound> {
(by: contiguousAdvancement, startingAt: (upperBound + lowerBound) / 2)
}
}
public extension ClosedRange where Bound: FloatingPoint {
func (by contiguousAdvancement: Bound = 1) -> AnySequence<Bound> {
(by: contiguousAdvancement, startingAt: (upperBound + lowerBound) / 2)
}
}
XCTAssertEqual(
Array( (2...10).() ),
[6, 7, 5, 8, 4, 9, 3, 10, 2]
)
XCTAssertEqual(
Array( (2...10).(startingAt: 7) ),
[7, 8, 6, 9, 5, 10, 4, 3, 2]
)
XCTAssertEqual(
Array( (-1.5...7.5).(by: 1.5) ),
[3, 4.5, 1.5, 6, 0, 7.5, -1.5]
)
XCTAssertEqual(
Array( (0...6).(by: -1) ),
[3, 2, 4, 1, 5, 0, 6]
)
XCTAssertEqual(
Array( (0...3).(startingAt: 4) ),
[]
)
所以我走上了认真对待乒乓球类比的道路。为了清楚起见,我留下了一些评论。
它模拟了一个真实的乒乓球弹跳(奇怪的是从网开始),在乒乓 table 上来回弹跳,网可能不在中心。如果它即将离开一侧的边缘,那么它就会到达另一侧,我喜欢想象它会越来越小地反弹,直到它滚出 table.
这是带有注释和测试的代码:
// It's supposed to be a ping pong table ♂️
struct : IteratorProtocol, Sequence {
typealias Element = Int
// The table *is* the iterator
typealias Iterator =
let leftEdgePosition: Int
/// The starting point for the ball
let netPosition: Int
let rightEdgePosition: Int
/// For convenience in checking whether different ball positions are on the table.
private let tableBounds: ClosedRange<Int>
init(leftEdgePosition: Int, netPosition: Int, rightEdgePosition: Int) {
self.leftEdgePosition = leftEdgePosition
self.netPosition = netPosition
self.rightEdgePosition = rightEdgePosition
self.tableBounds = leftEdgePosition...rightEdgePosition
}
private var distanceFromNet = 0
/// The side of the table the ping pong ball is headed toward
private var ballDirection: PingPongBallDirection = .towardLeftEdge
func makeIterator() -> {
return self
}
/// This gets called for each iteration in the for loop. Once the ball goes beyond the table, we should return nil to stop the for loop.
mutating public func next() -> Int? {
// the ball position we will return if this position is on the table
let ballPosition = ballDirection.locationCalculator(netPosition, distanceFromNet)
// the ball position we will return if the first ball position is not on the table
let redirectedPosition = (!ballDirection).locationCalculator(netPosition, distanceFromNet)
// determine which ball position to return and set up our state for the next call to next()
var ballPositionToReturn: Int?
if tableBounds.contains(ballPosition) {
ballPositionToReturn = ballPosition
let ballMirrorPosition = (!ballDirection).locationCalculator(netPosition, distanceFromNet)
let ballIsTrailingOff = !tableBounds.contains(ballMirrorPosition)
if !ballIsTrailingOff {
// switch the direction because the ball hit the table
ballDirection = !ballDirection
}
// If we're heading to the right, i.e 3 -> 4 in the case of 0 << 3 >> 10, then increase
// the distance from the net.
// If we're trailing off and not ping-ponging any more, then we need to add distance.
if ballDirection == .towardRightEdge || ballIsTrailingOff {
distanceFromNet += 1
}
} else if tableBounds.contains(redirectedPosition) {
ballPositionToReturn = redirectedPosition
// reflect the redirection
ballDirection = !ballDirection
// add distance when we redirect
distanceFromNet += 1
}
return ballPositionToReturn
}
}
enum PingPongBallDirection {
case towardLeftEdge
case towardRightEdge
/// Returns the oppposite direction
static prefix func !(direction: PingPongBallDirection) -> PingPongBallDirection {
switch direction {
case towardLeftEdge: return towardRightEdge
case towardRightEdge: return towardLeftEdge
}
}
// In our world, right is greater and left is lesser.
var locationCalculator: (Int, Int) -> Int {
switch self {
case .towardLeftEdge: return (-)
case .towardRightEdge: return (+)
}
}
}
// Make the syntax work
precedencegroup PingPongPrecedenceGroup {
associativity: left
// this makes sure the ping pong operator gets evaluated before the assignment operator
higherThan: AssignmentPrecedence
}
infix operator ...: PingPongPrecedenceGroup
func ... (lhs: ClosedRange<Int>, rhs: Int) -> {
return (leftEdgePosition: lhs.lowerBound, netPosition: lhs.upperBound, rightEdgePosition: rhs)
}
for i in 0...10 {
for j in 0...i...10 {
print(j, terminator: " ")
}
print()
}
// OUTPUT:
// 0 1 2 3 4 5 6 7 8 9 10
// 1 2 0 3 4 5 6 7 8 9 10
// 2 3 1 4 0 5 6 7 8 9 10
// 3 4 2 5 1 6 0 7 8 9 10
// 4 5 3 6 2 7 1 8 0 9 10
// 5 6 4 7 3 8 2 9 1 10 0
// 6 7 5 8 4 9 3 10 2 1 0
// 7 8 6 9 5 10 4 3 2 1 0
// 8 9 7 10 6 5 4 3 2 1 0
// 9 10 8 7 6 5 4 3 2 1 0
// 10 9 8 7 6 5 4 3 2 1 0
想一想如何用一副纸牌或一组火柴来做到这一点。只需在您想要开始的地方将系列分成两部分,反转结果系列之一的顺序,并交替从两个系列中提取值。
这是一个在两个系列之间交替直到都用完的实用程序:
func alternateUntilBothAreExhausted<T> (arr1:Array<T>, arr2:Array<T>)
-> Array<T> {
var result = Array<T>()
var arr1 = arr1; var arr2 = arr2
while true {
if let last1 = arr1.popLast() {
result.append(last1)
}
if let last2 = arr2.popLast() {
result.append(last2)
}
if arr1.isEmpty && arr2.isEmpty {
return result
}
}
}
所以我们从一个系列开始,拆分它,反转一个,然后交替:
func pingPong<T>(array:Array<T>, startingAtIndex ix:Int) -> Array<T> {
let arr1 = array[..<ix]
let arr2 = array[ix...]
return alternateUntilBothAreExhausted(
arr1: Array(arr1), arr2: Array(arr2.reversed()))
}
示例:
let ping = pingPong(array: Array(0..<10), startingAtIndex:4)
// [3, 4, 2, 5, 1, 6, 0, 7, 8, 9]
用你想要的语法包装它是微不足道的,留作 reader 的练习。
无国籍
仅供研究语法的任何人使用。
我浪费了一个小时的时间来搞清楚无状态转换。
(我无法让它变得简单或优雅 - 也许其他人可以!)
var plaground = "directly convert a single ping pong index to a plain index"
let L: Int = 10
let S: Int = 7
func ppiToIndex(_ ppi: Int) -> Int {
let inner = S+1 < (L-S) ? (S+1) : (L-S)
let pp = (ppi+1) / ( (ppi % 2 == 1) ? 2 : -2 )
let way = (S < L/2) ? -(inner-ppi-1) : (inner-ppi-1)
return (ppi < inner*2-1) ? S+pp : S+way
}
for i in 0..<L {
print(" \(i) \(ppiToIndex(i)) ")
}
inner 是从开始到结束包括多少。
pp 是一个完整的无尽乒乓球。
way 是正确的方向 +/- 一旦你通过内部区域就添加。
假设你有
for i in 0 ... 10 {
print(i)
}
当然会打印0,1,2,3,4,5,6,7,8,9,10
for i in 0 ..< 5 {
那是 0,1,2,3,4。
我想从某个整数开始,然后在数字计数上向外乒乓球
所以,
for i in function or something (10, 3)
那是 3 4 2 5 1 6 0 7 8 9
for i in function or something (10, 8) {
将是 8 9 7 6 5 4 3 2 1 0
for i in function or something (10, 2) {
将是 2 3 1 4 0 5 6 7 8 9
所以这只是一个向外的乒乓球。
我应该在我写的地方输入什么function or something (10, 2)
?
可能会有一些非常酷的语法,如 0 # 7 # 10
.
(0..<10).outPong(3)
之类的东西怎么样?
如何制定这样的序列?
这是一个简单的示例,说明您如何在通话级别进行向外乒乓球。
为 RA 中的每个项目调用 exampleLoad
,向外 pingpoing:
func loadItemsPongwise(startWith: Int) {
// RA = ... this is your array of some type
exampleLoad(startWith)
let k = RA.count
var howManyDone: Int = 0
var distance: Int = 1
while howManyDone < ( k - 1 ) {
let tryRight = alreadyLoaded + distance
if tryRight < k {
howManyDone = howManyDone + 1
exampleLoad(RA[tryRight])
}
let tryLeft = alreadyLoaded - distance
if tryLeft >= 0 {
howManyDone = howManyDone + 1
exampleLoad(RA[tryLeft])
}
distance = distance + 1
}
}
当然,这样的东西会更好:
func loadItemsPongwise(startWith: Int) {
for i in ???? {
exampleLoad(i)
}
}
public extension ClosedRange where Bound: AdditiveArithmetic {
func (
by contiguousAdvancement: Bound,
startingAt start: Bound
) -> AnySequence<Bound> {
guard contains(start)
else { return .init( EmptyCollection() ) }
var advancement = contiguousAdvancement
typealias Operate = (Bound, Bound) -> Bound
var pingPong: Operate = (+)
var contiguouslyAdvance: Operate = (-)
return .init(
sequence(first: start) { previous in
pingPongIterate: do {
defer { advancement += contiguousAdvancement }
let pingPonged = pingPong(previous, advancement)
guard self.contains(pingPonged)
else { break pingPongIterate }
(pingPong, contiguouslyAdvance) = (contiguouslyAdvance, pingPong)
return pingPonged
}
let contiguouslyAdvanced = contiguouslyAdvance(previous, contiguousAdvancement)
return self.contains(contiguouslyAdvanced)
? contiguouslyAdvanced
: nil
}
)
}
}
public extension ClosedRange where Bound: AdditiveArithmetic & ExpressibleByIntegerLiteral {
func (startingAt start: Bound) -> AnySequence<Bound> {
(by: 1, startingAt: start)
}
}
public extension ClosedRange where Bound: BinaryInteger {
func (by contiguousAdvancement: Bound = 1) -> AnySequence<Bound> {
(by: contiguousAdvancement, startingAt: (upperBound + lowerBound) / 2)
}
}
public extension ClosedRange where Bound: FloatingPoint {
func (by contiguousAdvancement: Bound = 1) -> AnySequence<Bound> {
(by: contiguousAdvancement, startingAt: (upperBound + lowerBound) / 2)
}
}
XCTAssertEqual(
Array( (2...10).() ),
[6, 7, 5, 8, 4, 9, 3, 10, 2]
)
XCTAssertEqual(
Array( (2...10).(startingAt: 7) ),
[7, 8, 6, 9, 5, 10, 4, 3, 2]
)
XCTAssertEqual(
Array( (-1.5...7.5).(by: 1.5) ),
[3, 4.5, 1.5, 6, 0, 7.5, -1.5]
)
XCTAssertEqual(
Array( (0...6).(by: -1) ),
[3, 2, 4, 1, 5, 0, 6]
)
XCTAssertEqual(
Array( (0...3).(startingAt: 4) ),
[]
)
所以我走上了认真对待乒乓球类比的道路。为了清楚起见,我留下了一些评论。
它模拟了一个真实的乒乓球弹跳(奇怪的是从网开始),在乒乓 table 上来回弹跳,网可能不在中心。如果它即将离开一侧的边缘,那么它就会到达另一侧,我喜欢想象它会越来越小地反弹,直到它滚出 table.
这是带有注释和测试的代码:
// It's supposed to be a ping pong table ♂️
struct : IteratorProtocol, Sequence {
typealias Element = Int
// The table *is* the iterator
typealias Iterator =
let leftEdgePosition: Int
/// The starting point for the ball
let netPosition: Int
let rightEdgePosition: Int
/// For convenience in checking whether different ball positions are on the table.
private let tableBounds: ClosedRange<Int>
init(leftEdgePosition: Int, netPosition: Int, rightEdgePosition: Int) {
self.leftEdgePosition = leftEdgePosition
self.netPosition = netPosition
self.rightEdgePosition = rightEdgePosition
self.tableBounds = leftEdgePosition...rightEdgePosition
}
private var distanceFromNet = 0
/// The side of the table the ping pong ball is headed toward
private var ballDirection: PingPongBallDirection = .towardLeftEdge
func makeIterator() -> {
return self
}
/// This gets called for each iteration in the for loop. Once the ball goes beyond the table, we should return nil to stop the for loop.
mutating public func next() -> Int? {
// the ball position we will return if this position is on the table
let ballPosition = ballDirection.locationCalculator(netPosition, distanceFromNet)
// the ball position we will return if the first ball position is not on the table
let redirectedPosition = (!ballDirection).locationCalculator(netPosition, distanceFromNet)
// determine which ball position to return and set up our state for the next call to next()
var ballPositionToReturn: Int?
if tableBounds.contains(ballPosition) {
ballPositionToReturn = ballPosition
let ballMirrorPosition = (!ballDirection).locationCalculator(netPosition, distanceFromNet)
let ballIsTrailingOff = !tableBounds.contains(ballMirrorPosition)
if !ballIsTrailingOff {
// switch the direction because the ball hit the table
ballDirection = !ballDirection
}
// If we're heading to the right, i.e 3 -> 4 in the case of 0 << 3 >> 10, then increase
// the distance from the net.
// If we're trailing off and not ping-ponging any more, then we need to add distance.
if ballDirection == .towardRightEdge || ballIsTrailingOff {
distanceFromNet += 1
}
} else if tableBounds.contains(redirectedPosition) {
ballPositionToReturn = redirectedPosition
// reflect the redirection
ballDirection = !ballDirection
// add distance when we redirect
distanceFromNet += 1
}
return ballPositionToReturn
}
}
enum PingPongBallDirection {
case towardLeftEdge
case towardRightEdge
/// Returns the oppposite direction
static prefix func !(direction: PingPongBallDirection) -> PingPongBallDirection {
switch direction {
case towardLeftEdge: return towardRightEdge
case towardRightEdge: return towardLeftEdge
}
}
// In our world, right is greater and left is lesser.
var locationCalculator: (Int, Int) -> Int {
switch self {
case .towardLeftEdge: return (-)
case .towardRightEdge: return (+)
}
}
}
// Make the syntax work
precedencegroup PingPongPrecedenceGroup {
associativity: left
// this makes sure the ping pong operator gets evaluated before the assignment operator
higherThan: AssignmentPrecedence
}
infix operator ...: PingPongPrecedenceGroup
func ... (lhs: ClosedRange<Int>, rhs: Int) -> {
return (leftEdgePosition: lhs.lowerBound, netPosition: lhs.upperBound, rightEdgePosition: rhs)
}
for i in 0...10 {
for j in 0...i...10 {
print(j, terminator: " ")
}
print()
}
// OUTPUT:
// 0 1 2 3 4 5 6 7 8 9 10
// 1 2 0 3 4 5 6 7 8 9 10
// 2 3 1 4 0 5 6 7 8 9 10
// 3 4 2 5 1 6 0 7 8 9 10
// 4 5 3 6 2 7 1 8 0 9 10
// 5 6 4 7 3 8 2 9 1 10 0
// 6 7 5 8 4 9 3 10 2 1 0
// 7 8 6 9 5 10 4 3 2 1 0
// 8 9 7 10 6 5 4 3 2 1 0
// 9 10 8 7 6 5 4 3 2 1 0
// 10 9 8 7 6 5 4 3 2 1 0
想一想如何用一副纸牌或一组火柴来做到这一点。只需在您想要开始的地方将系列分成两部分,反转结果系列之一的顺序,并交替从两个系列中提取值。
这是一个在两个系列之间交替直到都用完的实用程序:
func alternateUntilBothAreExhausted<T> (arr1:Array<T>, arr2:Array<T>)
-> Array<T> {
var result = Array<T>()
var arr1 = arr1; var arr2 = arr2
while true {
if let last1 = arr1.popLast() {
result.append(last1)
}
if let last2 = arr2.popLast() {
result.append(last2)
}
if arr1.isEmpty && arr2.isEmpty {
return result
}
}
}
所以我们从一个系列开始,拆分它,反转一个,然后交替:
func pingPong<T>(array:Array<T>, startingAtIndex ix:Int) -> Array<T> {
let arr1 = array[..<ix]
let arr2 = array[ix...]
return alternateUntilBothAreExhausted(
arr1: Array(arr1), arr2: Array(arr2.reversed()))
}
示例:
let ping = pingPong(array: Array(0..<10), startingAtIndex:4)
// [3, 4, 2, 5, 1, 6, 0, 7, 8, 9]
用你想要的语法包装它是微不足道的,留作 reader 的练习。
无国籍
仅供研究语法的任何人使用。
我浪费了一个小时的时间来搞清楚无状态转换。
(我无法让它变得简单或优雅 - 也许其他人可以!)
var plaground = "directly convert a single ping pong index to a plain index"
let L: Int = 10
let S: Int = 7
func ppiToIndex(_ ppi: Int) -> Int {
let inner = S+1 < (L-S) ? (S+1) : (L-S)
let pp = (ppi+1) / ( (ppi % 2 == 1) ? 2 : -2 )
let way = (S < L/2) ? -(inner-ppi-1) : (inner-ppi-1)
return (ppi < inner*2-1) ? S+pp : S+way
}
for i in 0..<L {
print(" \(i) \(ppiToIndex(i)) ")
}
inner 是从开始到结束包括多少。
pp 是一个完整的无尽乒乓球。
way 是正确的方向 +/- 一旦你通过内部区域就添加。