В Swift, последовательность пинг-понга наружу? - PullRequest
3 голосов
/ 25 марта 2020

Скажем, у вас есть

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)?

Как сформулировать такую ​​последовательность ?


Вот наивный пример того, как вы будете выполнять внешний пинг-понг на уровне вызова.

Вызовите exampleLoad для каждого из элементов в RA, внешний пингпоинг:

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)
    }
}

Ответы [ 4 ]

4 голосов
/ 27 марта 2020
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) ),
  []
)
3 голосов
/ 11 апреля 2020

Так что я пошел по пути, серьезно относясь к аналогии с пинг-понгом. Я оставил некоторые комментарии для ясности.

Он имитирует фактическое отскок мяча для пинг-понга (начиная с net, как ни странно), взад и вперед на столе для пинг-понга, который имеет net, который может не быть по центру. Если он собирается go от края с одной стороны, то он просто переходит на другую сторону, и мне нравится представлять, что он делает все меньшие и меньшие отскоки, пока не скатится со стола.

Вот код с комментарии и тест:

// 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
1 голос
/ 11 апреля 2020

без состояний

Только для тех, кто работает над синтаксисом.

Я потратил час своей жизни, чтобы выяснить преобразование без состояния.

(я не смог этого сделать простой или элегантный - может быть, кто-то другой может!)

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)) ")
}

внутренняя - это сколько от начала включительно до ближнего конца включительно.

pp - это полный бесконечный пинг-понг.

путь - это правильное направление +/-, которое нужно добавить после прохождения внутренней области.

1 голос
/ 11 апреля 2020

Подумайте, как бы вы сделали это с колодой карт или набором спичек. Просто разбейте ряд на две в той точке, с которой вы хотите начать, измените порядок одного из результирующих рядов и поочередно извлеките значения из двух рядов.

Вот утилита, которая чередует два ряда, пока оба исчерпаны:

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]

Обтекание этой в вашем желаемом синтаксисе тривиально и оставлено в качестве упражнения для читателя.

...