Better way to find sums in a grid in Swift - swift

I have an app with a 6x7 grid that lets the user input values. After each value is obtained the app checks to find if any of the consecutive values create a sum of ten and executes further code (which I have working well for the 4 test cases I've written). So far I've been writing if statements similar to the below:
func findTens() {
if (rowOneColumnOnePlaceHolderValue + rowOneColumnTwoPlaceHolderValue) == 10 {
//code to execute
} else if (rowOneColumnOnePlaceHolderValue + rowOneColumnTwoPlaceHolderValue + rowOneColumnThreePlaceHolderValue) == 10 {
//code to execute
} else if (rowOneColumnOnePlaceHolderValue + rowOneColumnTwoPlaceHolderValue + rowOneColumnThreePlaceHolderValue + rowOneColumnFourPlaceHolderValue) == 10 {
//code to execute
} else if (rowOneColumnOnePlaceHolderValue + rowOneColumnTwoPlaceHolderValue + rowOneColumnThreePlaceHolderValue + rowOneColumnFourPlaceHolderValue + rowOneColumnFivePlaceHolderValue) == 10 {
//code to execute
}
That's not quite halfway through row one, and it will end up being a very large set of if statements (231 if I'm calculating correctly, since a single 7 column row would be 1,2-1,2,3-...-2,3-2,3,4-...-67 so 21 possibilities per row). I think there must be a more concise way of doing it but I've struggled to find something better.
I've thought about using an array of each of the rowXColumnYPlaceHolderValue variables similar to the below:
let rowOnePlaceHolderArray = [rowOneColumnOnePlaceHolderValue, rowOneColumnTwoPlaceHolderValue, rowOneColumnThreePlaceHolderValue, rowOneColumnFourPlaceHolderValue, rowOneColumnFivePlaceHolderValue, rowOneColumnSixPlaceHolderValue, rowOneColumnSevenPlaceHolderValue]
for row in rowOnePlaceHolderArray {
//compare each element of the array here, 126 comparisons
}
But I'm struggling to find a next step to that approach, in addition to the fact that those array elements then apparently because copies and not references to the original array anymore...
I've been lucky enough to find some fairly clever solutions to some of the other issues I've come across for the app, but this one has given me trouble for about a week now so I wanted to ask for help to see what ideas I might be missing. It's possible that there will not be another approach that is significantly better than the 231 if statement approach, which will be ok. Thank you in advance!

Here's an idea (off the top of my head; I have not bothered to optimize). I'll assume that your goal is:
Given an array of Int, find the first consecutive elements that sum to a given Int total.
Your use of "10" as a target total is just a special case of that.
So I'll look for consecutive elements that sum to a given total, and if I find them, I'll return their range within the original array. If I don't find any, I'll return nil.
Here we go:
extension Array where Element == Int {
func rangeOfSum(_ sum: Int) -> Range<Int>? {
newstart:
for start in 0..<count-1 {
let slice = dropFirst(start)
for n in 2...slice.count {
let total = slice.prefix(n).reduce(0,+)
if total == sum {
return start..<(start+n)
}
if total > sum {
continue newstart
}
if n == slice.count && total < sum {
return nil
}
}
}
return nil
}
}
Examples:
[1, 8, 6, 2, 8, 4].rangeOfSum(10) // 3..<5, i.e. 2,8
[1, 8, 1, 2, 8, 4].rangeOfSum(10) // 0..<3, i.e. 1,8,1
[1, 8, 3, 2, 9, 4].rangeOfSum(10) // nil
Okay, so now that we've got that, extracting each possible row or column from the grid (or whatever the purpose of the game is) is left as an exercise for the reader. 🙂

Related

Array Performing XOR On Itself With Offset

Trying to implement an autocorrelation algorithm, meaning, for example:
let exampleData: [Float] = [1, 2, 3, 4, 5]
Trying to find the fastest way to evaluate 1 ^ 2 + 2 ^ 3 + 3 ^ 4 + 4 ^ 5.
Essentially, iterate through the array, and for every element, calculate the result of XOR between it and another element a set distance away.
Trouble is, this also has to be done for many different values of the offset.
Right now I just have a nested for loop, and I don't know how to make it faster...
var data: [Bool]
var result: [Int]
...
for offset in start..<end {
for index in 0..<(end - offset) {
if (data[index] ^ data[index + frequency]) {
result[offset] += 1
}
}
}
Sounds like you might want windows(ofCount:) from swift-algorithms:
https://github.com/apple/swift-algorithms/blob/main/Guides/Windows.md
That will give you a sliding window through any collection, and if your offset is relatively small (or you actually want the whole window, e.g. to do a moving average), that will be great.
The swift-algorithms stuff is nice since it's more optimized than whatever you'll do ad hoc, plus offers lazy eval.
You might also consider aligning and zipping up your sequence and then mapping over that, e.g.:
zip(data, data.dropFirst(offset))
.map { $0 ^ $1 }
...and so on

Swift: enumerated starting at 1?

In Swift, is it possible to enumerate a sequence starting at 1?
In my case, I'm using the SQLite C interface to bind values to prepared statements. The second argument of the sqlite3_bind_*() routines is the index of the SQL parameter to be set. The indices start at 1. (Ie, they're one-based.)
I could use Sequence.enumerated() and just add 1 to n inside each iteration, like so:
for (n, value) in values.enumerated() {
sqlite3_bind_int(stmt, Int32(n)+1, value)
}
But is there a way to start n from 1?
No, all collections indices in Swift are zero based but if you really want you can create your own custom enumeration zipping a range of Int32 values and the source collection:
extension Collection {
var enumerated: Zip2Sequence<PartialRangeFrom<Int32>, Self> { zip(1..., self) }
}
usage:
let values: [Int32] = [10, 20, 30]
for (n, value) in values.enumerated {
print("value:", value, "at:", n)
}
This will print
value: 10 at: 1
value: 20 at: 2
value: 30 at: 3
As others have said, array indexes start at 0 in Swift, so if you want to have 1-based indexes out of the box, you'll need to write some extra code.
If you're only using the index once then any workarounds might not worth the effort, and the incrementing at the call site is the most straightforward solution.
If however you will need to use the incremented index multiple times within the loop, another approach you could take would be to shadow the index:
for (n, element) in [1, 2,3].enumerated() {
let n = n + 1
sqlite3_bind_int(stmt, Int32(n), value)
}
Another approach could be using map():
for (n, element) in [1, 2,3].enumerated().map({($0+1,$1)}) {
sqlite3_bind_int(stmt, Int32(n), value)
}
, however not sure if you gain much with this solution, as the code is a little bit obscure.

I seem to have an infinite while loop in my Swift code and I can't figure out why

var array: [Int] = []
//Here I make an array to try to dictate when to perform an IBaction.
func random() -> Int {
let rand = arc4random_uniform(52)*10+10
return Int(rand)
}
//this function makes a random integer for me
func finalRand() -> Int {
var num = random()
while (array.contains(num) == true){
if (num == 520){
num = 10
}else {
num += 10
}
}
array.append(num)
return num
}
The logic in the while statement is somewhat confusing, but you could try this:
var array:Array<Int> = []
func finalRand() -> Int {
var num = Int(arc4random_uniform(52)*10+10)
while array.contains(num) {
num = Int(arc4random_uniform(52)*10+10)
}
array.append(num)
return num
}
This way there will never be a repeat, and you have less boiler code.
There is probably a better method involving Sets, but I'm sorry I do not know much about that.
A few things:
Once your array has all 52 values, an attempt to add the 53rd number will end up in an infinite loop because all 52 values are already in your array.
In contemporary Swift versions, you can simplify your random routine to
func random() -> Int {
return Int.random(in: 1...52) * 10
}
It seems like you might want a shuffled array of your 52 different values, which you can reduce to:
let array = Array(1...52).map { $0 * 10 }
.shuffled()
Just iterate through that shuffled array of values.
If you really need to continue generating numbers when you’re done going through all of the values, you could, for example, reshuffle the array and start from the beginning of the newly shuffled array.
As an aside, your routine will not generate truly random sequence. For example, let’s imagine that your code just happened to populate the values 10 through 500, with only 510 and 520 being the final possible remaining values: Your routine is 51 times as likely to generate 510 over 520 for the next value. You want to do a Fisher-Yates shuffle, like the built-in shuffled routine does, to generate a truly evenly distributed series of values. Just generate array of possible values and shuffle it.

safely remove item while iterating backward in Swift 3

When I want to pass through and remove an item or items from an array (when certain conditions are met), I typically iterate backward in the C-style for-loop and remove the item by index, avoiding the problem of index numbers being changed of the next item to be processed, or the changing size of the list affecting how many times the loop is passed through. But the C for-loop has been removed in Swift 3.
Here is my Swift 2.3 code for the initialization of the loop:
for (var i = allowedItems.count - 1; i > -1; i -= 1)
Here is the monstrosity created by the Swift 3 converter:
for (i in ((-1 + 1)...allowedItems.count - 1).reversed())
This version does not compile however. ("Expected ',' separator" at the "in" operator).
I simplify the "-1 + 1" bit to zero:
for (i in (0...allowedItems.count - 1).reversed())
Now the error is "Expected Sequence expression for for-each loop".
What is the safe and hopefully reasonably elegant way of iterating backward in Swift 3, in which an index or counter variable is made available for use in specifying which item should be removed? This type of logic appears a number of places in my code so I want to make sure to find the best solution.
Thanks.
What is the safe and hopefully reasonably elegant way of iterating backward in Swift 3
The built-in way is:
for i in (0 ..< allowedItems.count).reversed()
The elegant way is:
for i in allowedItems.count >>> 0
(where >>> is the custom operator that I define here).
Use stride:
for i in stride(from: allowedItems.count - 1, through: 0, by: -1) {
}
What is the safe and hopefully reasonably elegant way of iterating
backward in Swift 3, in which an index or counter variable is made
available for use in specifying which item should be removed?
This doesn't answer the technical question, but possibly the underlying XY problem: have you considered simply filtering your array based on the criteria "when certain conditions are met"?
func certainConditionsForKeepingAreMet(_ element: YourElementType) -> Bool { /* ... */ }
allowedItems = allowedItems.filter(certainConditionsForKeepingAreMet)
E.g.
var allowedItems = [1, 3 ,6, 2]
func certainConditionsForKeepingAreMet(_ element: Int) -> Bool { return element < 3 }
allowedItems = allowedItems.filter(certainConditionsForKeepingAreMet)
print(allowedItems) // [1, 2]
If you'd like to remove and use the removed elements (on-the-fly), you could simply pipe the elements that are to be removed to some "use this element" function, in the course of checking the conditions for the elements.
func doSomethingWith(_ element: Int) { print("Removed", element) }
func certainConditionsForKeepingAreMet(_ element: Int) -> Bool {
if element >= 3 {
doSomethingWith(element)
return false
}
return true
}
var allowedItems = [1, 3 ,6, 2]
allowedItems = allowedItems.filter(certainConditionsForKeepingAreMet)
/* Removed 3
Removed 6 */
print(allowedItems) // [1, 2]

How do I generate a random number not including one without using a while loop?

Let's say I want to generate a random number between 1 and 100, but I don't want to include 42. How would I do this without repeating the random method until it is not 42.
Updated for Swift 5.1
Excluding 1 value
var nums = [Int](1...100)
nums.remove(at: 42)
let random = Int(arc4random_uniform(UInt32(nums.count)))
print(nums[random])
Excluding multiple values
This extension of Range does provide a solution when you want to exclude more than 1 value.
extension ClosedRange where Element: Hashable {
func random(without excluded:[Element]) -> Element {
let valid = Set(self).subtracting(Set(excluded))
let random = Int(arc4random_uniform(UInt32(valid.count)))
return Array(valid)[random]
}
}
Example
(1...100).random(without: [40,50,60])
I believe the computation complexity of this second solution is O(n) where n is the number of elements included in the range.
The assumption here is the no more than n excluded values are provided by the caller.
appzYourLife has some great general purpose solutions, but I want to tackle the specific problem in a lightweight way.
Both of these approaches work roughly the same way: Narrow the range to the random number generator to remove the impossible answer (99 answers instead of 100), then map the result so it isn't the illegal value.
Neither approach increases the probability of an outcome relative to another outcome. That is, assuming your random number function is perfectly random the result will still be random (and no 2x chance of 43 relative to 5, for instance).
Approach 1: Addition.
Get a random number from 1 to 99. If it's greater than or equal to the number you want to avoid, add one to it.
func approach1()->Int {
var number = Int(arc4random_uniform(99)+1)
if number >= 42 {
number = number + 1
}
return number
}
As an example, trying to generate a random number from 1-5 that's not 3, take a random number from 1 to 4 and add one if it's greater than or equal to 3.
rand(1..4) produces 1, +0, = 1
rand(1..4) produces 2, +0, = 2
rand(1..4) produces 3, +1, = 4
rand(1..4) produces 4, +1, = 5
Approach 2: Avoidance.
Another simple way would be to get a number from 1 to 99. If it's exactly equal to the number you're trying to avoid, make it 100 instead.
func approach2()->Int {
var number = Int(arc4random_uniform(99)+1)
if number == 42 {
number = 100
}
return number
}
Using this algorithm and narrowing the range to 1-5 (while avoiding 3) again, we get these possible outcomes:
rand(1..4) produces 1; allowed, so Result = 1
rand(1..4) produces 2, allowed, so Result = 2
rand(1..4) produces 3; not allowed, so Result = 5
rand(1..4) produces 4, allowed, so Result = 4