Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Local reference to inner array in swift

I came from C# world, and used to arrays being reference types. As I understand, in swift arrays are value types, but they try to play as reference ones.

I don't actually know how to ask what I need (I think this is the case when I need to know answer to be able to ask question), but in C# I would say I need to store a reference to inner array of a jagged array into local variable.

Consider the following piece of code:

// a function to change row values in-place
func processRow(inout row : [Int], _ value : Int)
{
    for col in 0..<row.count
    {
        row[col] = value;
    }
}

// a function to change matrix values in-place
func processMatrix(inout matrix : [[Int]])
{
    for rowIdx in 0..<matrix.count
    {
        // (1) Works with array in-place
        processRow(&(matrix[rowIdx]), -1)

        // (2) Creates local copy
        var r = matrix[rowIdx]; // <--- What to write here to not make a copy but still have this local variable?
        processRow(&r, -2);
    }
}

var matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

processMatrix(&matrix)

print(matrix) // outputs [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]

Swift sandbox here http://swiftlang.ng.bluemix.net/#/repl/8608824e18317e19b32113e1aa08deeb4ec5ab96ed8cdbe1dbfb4e753e87d528

Here I want to I process multidimensional array in-place, so that I don't create a copy of array or parts of array.

In option (1) I change everything to "-1", and it works, but is uses additional function for this.

In option (2) I try to use local variable to store matrix[rowIdx], but it actually creates a copy of inner array - not what I want; working with this variable changes copy of array, and not original one.

How can I achieve results like in option (1), but using local variable instead of function? That is, how can I obtain reference to inner array and put it to local variable?

I would understand answer "there's no way for this", but I want such answers to be accompanied by some Apple refs.

like image 621
Lanorkin Avatar asked Jan 18 '16 21:01

Lanorkin


2 Answers

I don't believe that there is a way to copy an array into a local variable without making a copy. That is just the way that value types work in Swift. Says Apple:

The most basic distinguishing feature of a value type is that copying — the effect of assignment, initialization, and argument passing — creates an independent instance with its own unique copy of its data[.]

And here:

A value type is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function...

[...]

All structures and enumerations are value types in Swift. This means that any structure and enumeration instances you create—and any value types they have as properties—are always copied when they are passed around in your code.

It's just part of the definition of value type - and every array is a value type.

The easiest way to get the result you are seeking is simply to reassign the value of r to the row that you want to change in matrix:

// (2) Creates local copy
var r = matrix[rowIdx]   
processRow(&r, -2)
matrix[rowIdx] = r

The closest thing to what you are suggesting is to work with pointers: UnsafeMutablePointer, UnsafePointer, etc. But that is really fighting against the way that Swift was designed to be used. If you wanted to, however, it would look something like this:

func processRow(ptr: UnsafeMutablePointer<Int>, _ value : Int, count: Int) {
    for col in 0..<count {
        ptr.advancedBy(col).memory = value
    }
}

func processMatrix(inout matrix : [[Int]]) {
    for rowIdx in 0..<matrix.count {
        
        let r = UnsafeMutablePointer<Int>(matrix[rowIdx])
        processRow(r, -2, count: matrix[rowIdx].count)
    }
}

var matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

processMatrix(&matrix)
print(matrix)   // [[2, 2, 2], [2, 2, 2], [2, 2, 2]]
like image 96
Aaron Rasmussen Avatar answered Sep 21 '22 15:09

Aaron Rasmussen


Swift arrays are pretty painful when it comes to work with mutable data. There are two ways to fix that:

1) Use NSMutableArray

import Foundation

func processRow(_ row : NSMutableArray, _ value : Int) {
    for col in 0..<row.count {
        row[col] = value;
    }
}

func processMatrix(_ matrix : inout [NSMutableArray]) {
    for rowIdx in 0..<matrix.count {
        let r = matrix[rowIdx]
        processRow(r, -2);
    }
}

var matrix = [
    NSMutableArray(array: [1, 2, 3]),
    NSMutableArray(array: [4, 5, 6]),
    NSMutableArray(array: [7, 8, 9])
]

processMatrix(&matrix)

print(matrix) // outputs , <__NSArrayM 0x6000027a1560>(-2,-2,-2)]

2) Use class wrapper

class Wrapper<T: CustomDebugStringConvertible>: CustomDebugStringConvertible {
    var value: T

    init(_ value: T) {
        self.value = value
    }

    var debugDescription: String {
        return value.debugDescription
    }
}

func processRow(_ row : Wrapper<[Int]>, _ value : Int) {
    for col in 0..<row.value.count {
        row.value[col] = value;
    }
}

func processMatrix(_ matrix : inout [Wrapper<[Int]>]) {
    for rowIdx in 0..<matrix.count {
        let r = matrix[rowIdx]
        processRow(r, -2);
    }
}

var matrix = [
    Wrapper([1, 2, 3]),
    Wrapper([4, 5, 6]),
    Wrapper([7, 8, 9])
]

processMatrix(&matrix)

print(matrix) // outputs [[-2, -2, -2], [-2, -2, -2], [-2, -2, -2]]

However it doesn't look very nice.

like image 38
Alexander Danilov Avatar answered Sep 18 '22 15:09

Alexander Danilov