Is there any way to have an n dimensional array in swift? I would like to be able to make a function that creates an array with n dimensions but I cannot figure out how.
Basically something like this:
func ndarray <T> (dimensions: Int...) -> [[T]] { // What do I tell it I return?
var out
for d in dimensions {
out = Array<T>(repeating: out, count: d)
}
return out
}
The above code does not work for obvios reasons but, I think it points out the main problems I am having:
How do I define a return type
How do I actually create the array
Once created how do I traverse and populate the array
Here is the implementation of an N-Dimensional Array. It uses a normal array internally for storage and converts the multi-dimensional indices into a single index for the internal array.
struct NDimArray<T> {
let dimensions: [Int]
var data: [T]
init(dimensions: Int..., initialValue: T) {
self.dimensions = dimensions
data = Array(repeating: initialValue, count: dimensions.reduce(1, *))
}
init(dimensions: Int..., initUsing initializer: () -> T) {
self.dimensions = dimensions
data = (0 ..< dimensions.reduce(1, *)).map { _ in initializer() }
}
// Compute index into data from indices
private func computeIndex(_ indices: [Int]) -> Int {
guard indices.count == dimensions.count else { fatalError("Wrong number of indices: got \(indices.count), expected \(dimensions.count)") }
zip(dimensions, indices).forEach { dim, idx in
guard (0 ..< dim) ~= idx else { fatalError("Index out of range") }
}
var idx = indices
var dims = dimensions
var product = 1
var total = idx.removeLast()
while !idx.isEmpty {
product *= dims.removeLast()
total += (idx.removeLast() * product)
}
return total
}
subscript(_ indices: Int...) -> T {
get {
return data[computeIndex(indices)]
}
set {
data[computeIndex(indices)] = newValue
}
}
}
Example:
// Create a 3 x 4 x 5 array of String with initial value ""
var arr = NDimArray<String>(dimensions: 3, 4, 5, initialValue: "")
for x in 0 ..< 3 {
for y in 0 ..< 4 {
for z in 0 ..< 5 {
// Encode indices in the string
arr[x, y, z] = "(\(x),\(y),\(z))"
}
}
}
// Show internal storage of data
print(arr.data)
["(0,0,0)", "(0,0,1)", "(0,0,2)", "(0,0,3)", "(0,0,4)", "(0,1,0)", "(0,1,1)", "(0,1,2)", "(0,1,3)", "(0,1,4)", "(0,2,0)", "(0,2,1)", "(0,2,2)", "(0,2,3)", "(0,2,4)", "(0,3,0)", "(0,3,1)", "(0,3,2)", "(0,3,3)", "(0,3,4)", "(1,0,0)", "(1,0,1)", "(1,0,2)", "(1,0,3)", "(1,0,4)", "(1,1,0)", "(1,1,1)", "(1,1,2)", "(1,1,3)", "(1,1,4)", "(1,2,0)", "(1,2,1)", "(1,2,2)", "(1,2,3)", "(1,2,4)", "(1,3,0)", "(1,3,1)", "(1,3,2)", "(1,3,3)", "(1,3,4)", "(2,0,0)", "(2,0,1)", "(2,0,2)", "(2,0,3)", "(2,0,4)", "(2,1,0)", "(2,1,1)", "(2,1,2)", "(2,1,3)", "(2,1,4)", "(2,2,0)", "(2,2,1)", "(2,2,2)", "(2,2,3)", "(2,2,4)", "(2,3,0)", "(2,3,1)", "(2,3,2)", "(2,3,3)", "(2,3,4)"]
print(arr[2, 2, 2]) // "(2,2,2)"
print(arr[3, 0, 0]) // Fatal error: Index out of range
print(arr[0, 4, 0]) // Fatal error: Index out of range
print(arr[2]) // Fatal error: Wrong number of indices: got 1, expected 3
Initializing an Array with a Reference Type
As #DuncanC noted in the comments, you have to be careful when initializing an array with a value which is a reference type, because the array will be filled with references to the object and modifying the object at any index will modify all of them.
To solve this, I added a second initializer:
init(dimensions: Int..., initUsing initializer: () -> T)
which takes a closure () -> T which can be used to create a new object for each element of the array.
For example:
class Person {
var name = ""
}
// Pass a closure which creates a `Person` instance to fill the array
// with 25 person objects
let arr = NDimArray(dimensions: 5, 5, initUsing: { Person() })
arr[3, 3].name = "Fred"
arr[2, 2].name = "Wilma"
print(arr[3, 3].name, arr[2, 2].name)
Fred Wilma
Nope, it's not possible. Array dimensions is something that needs to be determined at compile time, while the argument you want to pass to the initializer will not be known until runtime. If you really want to achieve something like this, then you'll need to move the array indexing from compile time to runtime, e.g. by accessing the array via an array of indexes. Still you don't have compile validation, since the array length can at runtime to not match the dimensions of the array.
This problem is similar to the one that attempts to convert a tuple to an array.
So say I have an array:
var stringArray = ["a","b","c","d","e","f","g","h","i","j"]
Now, how do I delete "a", "c", "e", "g", and "i" (all the even number indexes from the array)?
Thanks!
Instead of using C-style for-loops (which are set to be deprecated in an upcoming version of Swift), you could accomplish this using strides:
var result = [String]()
for i in stride(from: 1, through: stringArray.count - 1, by: 2) {
result.append(stringArray[i])
}
Or for an even more functional solution,
let result = stride(from: 1, to: stringArray.count - 1, by: 2).map { stringArray[$0] }
Traditional
var filteredArray = []
for var i = 1; i < stringArray.count; i = i + 2 {
filteredArray.append(stringArray[i])
}
Functional alternative
var result = stringArray.enumerate().filter({ index, _ in
index % 2 != 0
}).map { $0.1 }
enumerate takes a array of elements and returns an array of tuples where each tuple is an index-array pair (e.g. (.0 3, .1 "d")). We then remove the elements that are odd using the modulus operator. Finally, we convert the tuple array back to a normal array using map. HTH
There are a bunch of different ways to accomplish this, but here are a couple that I found interesting:
Using flatMap() on indices:
let result: [String] = stringArray.indices.flatMap {
if $0 % 2 != 0 { return stringArray[$0] }
else { return nil }
}
Note: result needs to be defined as a [String] otherwise the compiler doesn't know which version of flatMap() to use.
Or, if you want to modify the original array in place:
stringArray.indices.reverse().forEach {
if $0 % 2 == 0 { stringArray.removeAtIndex($0) }
}
In this case you have to call reverse() on indices first so that they're enumerated in reverse order. Otherwise by the time you get to the end of the array you'll be attempting to remove an index that doesn't exist anymore.
Swift 4.2
A function accepting generics and producing reduced result
func stripElements<T>(in array:[T]) -> [T] {
return array.enumerated().filter { (arg0) -> Bool in
let (offset, _) = arg0
return offset % 2 != 0
}.map { $0.element }
}
I am trying to compare two arrays (array1, array2) and if a specific key value is contained in array2, the key value in array1 that contains the array2 value needs to be printed out with its 'indexPath'.
The code I have almost works however, the app crashes because while going trough the keys, array2 goes out of range because it contains less indexes that the array1
How can I make the code look for matches if other array is smaller?
let array1 = [["aaa","bbb","ccc","ddd","eee"], ["fff","ggg","hhh","matched","iii"], ["lll","mmm","nnn","ooo","ppp"], ["666","777","888","999","000"] ] //4 elements
let countArray1 = array1.enumerate()
let array2 = [["111","222"], ["333","444"], ["matched","555"]] // 3 elements
for (index, element) in countArray1{
let containedValue = array1[index].contains(array2[index][0])
if (containedValue) == true{
print("The index of the contained value is: ????") //error
}
}
I would turn the smaller 2D array into a 1D array and then loop through the both dimensions of the bigger array and use NSArray.contains:
DISCLAIMER: this code is untested. #Community: if you stumble upon this and notice a mistake or something that can be better written, comment or edit. I use Obj-C more than Swift.
//convert 2D array to 1D
func from2Dto1D(array: NSArray) -> NSArray {
var newArr = NSMutableArray()
for (d1) in array {
for (d2) in d1 {
newArr.append(d2)
}
}
return newArr as NSArray
}
func hasMatch(array1: NSArray, array2: NSArray) -> boolean {
var bigger: NSArray
var smaller: NSArray
if (array1.count > array2.count){
bigger = array1
smaller = from2Dto1D(array2)
}else{
bigger = array2
smaller = from2Dto1D(array1)
}
for (d1) in bigger{
for (item) in d1 {
if (smaller.contains(item)){
print("Match found");
return true
}
}
}
return false
}
You could flatten out both the arrays and then enumerate them to check which index the objects match at.
let array1 = [["aaa","bbb","ccc","ddd","eee"], ["fff","ggg","hhh","matched","iii"], ["lll","mmm","nnn","ooo","ppp"], ["666","777","888","999","000"] ] //4 elements
let array2 = [["111","222"], ["333","444"], ["matched","555"]] // 3 elements
let flat1 = Array(array1.flatten())
let flat2 = Array(array2.flatten())
for (index1,object1) in flat1.enumerate() {
for (index2,object2) in flat2.enumerate() {
if object2 == object1 {
print("index1 is: \(index1). Index 2 is \(index2)")
}
}
}
I have an array of letters, and want to match the characters against the letters and then do something to that letter (in this case turn it yellow) and then remove that matched character from the characters array.
If I have a word1 like "recruitment" and specialLetters like "ment" the removeAtIndex works fine, but in the below example which includes 2 s's in [ness] I get this crash:
fatal error: Array index out of range
Reading other posts on here suggest it is dangerous to remove items from an array when in use, but how come it works ok with some words and not others? I thought enumerating the array would only give each letter one index? And any suggestion on how to fix it so it works for all types of characters?
var letters = Array(word1) // [r,a,n,d,o,m,n,e,s,s]
var characters = Array(specialLetters) // [n,e,s,s]
// delete the special letters
for (index, element) in enumerate(characters) {
if letter == element {
tile.letterLabel.textColor = UIColor.yellowColor()
// remove that character from the array so can't be matched twice
characters.removeAtIndex(index)
}
}
So you have an array of Character(s):
let letters = Array("randomness")
An array of special Character(s)
let specials = Array("ness")
And you want to remove from letters, the specials right?
Here it is:
let set = Set(specials)
let filtered = letters.filter { !set.contains($0) }
filtered // ["r", "a", "d", "o", "m"]
Update
This version keep also consider the occurrences of a char
let letters = Array("randomness")
let specials = Array("ness")
var occurrencies = specials.reduce([Character:Int]()) { (var dict, char) in
dict[char] = (dict[char] ?? 0) + 1
return dict
}
let filtered = letters.filter {
if let num = occurrencies[$0] where num > 0 {
occurrencies[$0] = num - 1
return false
} else {
return true
}
}
filtered // ["r", "a", "d", "o", "m", "n"]
Yes it is very dangerous to modify the number of items in an array while being enumerated.
Imagine you have an array of three items, the range is 0...2. In the first iteration you delete the first item, then array[2] is now array[1] and there is no item at index 2 any more.
Either you enumerate the array backwards, so the index of the removed item is always equal to or higher than the current index or you use a variable to collect the indexes to be deleted and delete the characters by range.
Of course Swift has more reliable functions to accomplish this task as mentioned by the others therefore it's not needed to use enumerate In this case.
May i suggest a different approach where instead of removing from the initial array you make a new one, from which you can exclude the characters you don't want to appear in your array in the first place.
PS: keep in mind the following code works with Swift 2.0 Xcode 7 beta 6
var letters : [Character] = ["a","b", "c"]
var lettersToBeRemoved : [Character] = ["a"]
var newLetters : [Character] = []
for i in letters
{
for j in lettersToBeRemoved
{
if i == j
{
continue
}
newLetters.append(i)
}
}
print(newLetters)
just fixed it after reading the SDs post here: Swift buttons hidden fatal error: Array index out of range
I updated this question in case it helps anyone else.
I added an if statement to check the index was still in range. Not really sure why this works but it does :-)
var letters = Array(word1) // [r,a,n,d,o,m,n,e,s,s]
var characters = Array(specialLetters) // [n,e,s,s]
// delete the special letters
for (index, element) in enumerate(characters) {
if letter == element {
tile.letterLabel.textColor = UIColor.yellowColor()
// remove that character from the array so can't be matched twice.
// first check that the index is still in range.
if index < characters.count { // added this if statement
characters.removeAtIndex(index)
}
}
}
For this purpose (removing elements of an array), you may want to use an indexing generator. Let me give an example:
var array = [1, 2, 3, 4]
var ig = array.generate()
var index = 0
var element = ig.next()
while element != nil {
if element == 2 {
array.removeAtIndex(index)
} else {
index = index + 1
}
element = ig.next()
}