Transform a Swift Sequence in to Adjacent Pairs

Transform a Swift sequence in to adjacent pairs

We can use zip() and dropFirst() if we define an extension
on the Collection type:

extension Collection {
func pairs() -> AnySequence<(Element, Element)> {
return AnySequence(zip(self, self.dropFirst()))
}
}

Example:

let array = [1, 2, 3, 4]
for p in array.pairs() {
print(p)
}

Output:


(1, 2)
(2, 3)
(3, 4)

More examples:

print(Array("abc".pairs()))
// [("a", "b"), ("b", "c")]

print([1, 2, 3, 4, 5].pairs().map(+))
// [3, 5, 7, 9]

print([3, 1, 4, 1, 5, 9, 2].pairs().filter(<))
// [(1, 4), (1, 5), (5, 9)]

(Unlike I wrote in the first version of this answer ...) this
approach is not safe when applied to a Sequence, because it is
not guaranteed that a sequence can be traversed multiple times
non-destructively.

Here is a direct implementation with a custom iterator type
which works on sequences as well:

struct PairSequence<S: Sequence>: IteratorProtocol, Sequence {
var it: S.Iterator
var last: S.Element?

init(seq: S) {
it = seq.makeIterator()
last = it.next()
}

mutating func next() -> (S.Element, S.Element)? {
guard let a = last, let b = it.next() else { return nil }
last = b
return (a, b)
}
}

extension Sequence {
func pairs() -> PairSequence<Self> {
return PairSequence(seq: self)
}
}

Example:

print(Array([1, 2, 3, 4].pairs().pairs()))
// [((1, 2), (2, 3)), ((2, 3), (3, 4))]

Convert an array to matrix to find adjacent elements iOS swift

In the desired output, I guess that it's missing (9,15), (10,11), (10,16) and that (10,15) isn't valid.

If we think about your desired output, we notice something.
Let's name width = 6, it's the "width" of your matrix.

We see the pattern:
(value, value + 1), (value, value + width)
With some excluded tests: does (value + width) exists ? And we are not at the end of the width.

Let's, with a little reduce method:

let ptsArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]

let width = 6
let tuples = ptsArray.indices.reduce(into: [(Int, Int)]()) { partialResult, anIndex in
if ptsArray.count > anIndex.advanced(by: 1) && anIndex % width != width - 1 {
let newValue = (ptsArray[anIndex], ptsArray[anIndex.advanced(by: 1)])
print(newValue)
partialResult.append(newValue)
}
if ptsArray.count > anIndex.advanced(by: width) {
let newValue = (ptsArray[anIndex], ptsArray[anIndex.advanced(by: width)])
print(newValue)
partialResult.append(newValue)
}
return
}
print(tuples)

I used "index", because in fact, points are in order here, but it could be any value, no? So let's use the index instead.

So, with something a little more generic:

extension Array {
func coupling(with width: Int) -> [(Element, Element)] {
let couples = indices.reduce(into: [(Element, Element)]()) { partialResult, anIndex in
if count > anIndex.advanced(by: 1) && anIndex % width != width - 1 {
let newValue = (self[anIndex], self[anIndex.advanced(by: 1)])
partialResult.append(newValue)
}
if count > anIndex.advanced(by: width) {
let newValue = (self[anIndex], self[anIndex.advanced(by: width)])
partialResult.append(newValue)
}
return
}
return couples
}
}

Use:

let ptsArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]
let tuples2 = ptsArray. coupling(with: width)
print(tuples2)

let lettersArray = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", "Q", "R"]
let tuples3 = lettersArray. coupling(with: width)
print(tuples3)

Swift: What's the best way to pair up elements of an Array

This is now available as

Sequence.chunks(ofCount: 2) of the swift-algorithms package

for chunk in input.chunks(ofCount: 2) {
print(chunk)
}

How to create an array of Tuples from an array of Double?

let doubles: [Double] = [0, 1, 2, 3, 4]
let tuples = zip(doubles.dropLast(), doubles.dropFirst()).map { ($0, $1) }

Result:

[(0, 1), (1, 2), (2, 3), (3, 4)]

@Edit

As pointed out in comments you can use even shorter form:

Array(zip(doubles, doubles.dropFirst()))

I'd consider NOT skipping .dropLast(), just to make code more obvious.

Iterate through an array to find pairs of values

An alternative but similar approach as @Sulthan's answer is to use a dictionary to count occurrences rather than NSCountedSet:

let colors = [10, 20, 20, 10, 10, 30, 50, 10, 20]
let numberOfPairs = colors
.reduce(into: [:]) { counts, num in counts[num, default: 0] += 1 }
.reduce(0) { cumsum, kv in cumsum + kv.value / 2 } // 3

Or, using shorthand argument names in the two closures:

let numberOfPairs = colors
.reduce(into: [:]) { $0[$1, default: 0] += 1 }
.reduce(0) { $0 + $1.value / 2 }

Where above, for the number occurrence count, we make use of @vacawama's answer in the Q&A that I initially used as target for dupe-marking this Q&A.

Swift Comparison in consecutive numbers inside array and its count

From this answer by Martin R, you can check how to create pairs as below,

let input = [1,2,4,6,7,10,12,13]
let output = stride(from: 0, to: input.count - 1, by: 2).map{(input[$0], input[$0 + 1])}

Now you can create differences array and find the one's count as below,

let differences = output.map({ $0.1 - $0.0 })
let onesCount = differences.filter({ $0 == 1}).count

print(differences)
print(onesCount)

Output

[1, 2, 3, 1]
2

Custom operation to split sorted array into subarrays in Swift

Your code does not work correctly because:

  • You do not compare adjacent elements.
  • You start by comparing the first element with itself, this can lead
    to an never-terminating loop.
  • An empty array is not handled correctly.

Here is a working variation of your approach:

extension Array {
public func splitSorted(by condition: (Element, Element)->(Bool)) -> [[Element]] {
var result = [[Element]]()
var start = startIndex
while start != endIndex {
var end = start
repeat {
end += 1
} while end != endIndex && condition(self[end - 1], self[end])
result.append(Array(self[start..<end]))
start = end
}
return result
}
}

Example:

let arr = [1, 2, 3, 2, 3, 4, 3, 4, 5]
let split = arr.splitSorted(by: <)
print(split) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

A generalization to Sequence would be:

extension Sequence {
public func splitSorted(by condition: (Element, Element)->(Bool)) -> [[Element]] {
var it = makeIterator()
guard var currentElem = it.next() else {
return [] // Input sequence is empty
}
var result = [[Element]]()
var currentSegment = [currentElem]
while let nextElem = it.next() {
if condition(currentElem, nextElem) {
// Append to current segment:
currentSegment.append(nextElem)
} else {
// Start new segment:
result.append(currentSegment)
currentSegment = [nextElem]
}
currentElem = nextElem
}
result.append(currentSegment)
return result
}
}

Example (group Fibonacci numbers by same parity):

// From https://stackoverflow.com/a/40203183/1187415
let fibs = sequence(state: (0, 1),
next: { (pair: inout (Int, Int)) -> Int? in
defer { pair = (pair.1, pair.0 + pair.1) }
return pair.1
})

print(fibs.prefix(12).splitSorted(by: { ($0 - $1) % 2 == 0 }))
// [[1, 1], [2], [3, 5], [8], [13, 21], [34], [55, 89], [144]]

Is there a way to shuffle an array so that no two consecutive values are the same?

Despite appearances, this is non-trivial. As the commentator @antonio081014 points out, it's actually an algorithmic question, and (as @MartinR points out) is addressed here. Here's a very simple heuristic that (unlike the solution from @appzYourLife) is not an algorithm, but will work in most cases, and is much faster (O(n^2) rather than O(n!)). For randomness, simply shuffle the input array first:

func unSort(_ a: [String]) -> [String] {
// construct a measure of "blockiness"
func blockiness(_ a: [String]) -> Int {
var bl = 0
for i in 0 ..< a.count {
// Wrap around, as OP wants this on a circle
if a[i] == a[(i + 1) % a.count] { bl += 1 }
}
return bl
}
var aCopy = a // Make it a mutable var
var giveUpAfter = aCopy.count // Frankly, arbitrary...
while (blockiness(aCopy) > 0) && (giveUpAfter > 0) {
// i.e. we give up if either blockiness has been removed ( == 0)
// OR if we have made too many changes without solving

// Look for adjacent pairs
for i in 0 ..< aCopy.count {
// Wrap around, as OP wants this on a circle
let prev = (i - 1 >= 0) ? i - 1 : i - 1 + aCopy.count
if aCopy[i] == aCopy[prev] { // two adjacent elements match
let next = (i + 1) % aCopy.count // again, circular
// move the known match away, swapping it with the "unknown" next element
(aCopy[i], aCopy[next]) = (aCopy[next], aCopy[i])
}
}
giveUpAfter -= 1
}
return aCopy
}

var colors = ["blue", "red", "green", "red", "blue", "blue", "blue", "green"]
unSort(colors) // ["blue", "green", "blue", "red", "blue", "green", "blue", "red"]

// Add an extra blue to make it impossible...
colors = ["blue", "blue", "green", "red", "blue", "blue", "blue", "green"]
unSort(colors) //["blue", "green", "blue", "red", "blue", "blue", "green", "blue"]


Related Topics



Leave a reply



Submit