在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 是正确的方向 +/- 一旦你通过内部区域就添加。