7

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.

Lanorkin
  • 7,310
  • 2
  • 42
  • 60

2 Answers2

1

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]]
Community
  • 1
  • 1
Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • 1
    Thanks for you answer 1) Arrays are "special" value types, so extracts you cite are basically wrong for arrays. For example, this "copy-on-write" feature as demonstrated here http://swiftlang.ng.bluemix.net/#/repl/f3dff0253eefbc2c86abc52e5f224210fe097c589bad1e8cbdd381c1eb61b9e9 shows arrays are not `always copied when they are passed around` 2) Copying `matrix[rowIdx] = r` is what I'm trying to avoid - I want always to work with array in-place 3) `UnsafeMutablePointer` is close, but as it is *unsafe* and *pointer* it raises that old good C pointer hell, but I will play more with them – Lanorkin Jan 19 '16 at 11:48
  • 1
    Fair points. 1) Arrays aren't *that* special. The copy on write behavior is completely "under the hood." There is no way to interact with them except as value types (pointers aside). 2) Why not `matrix[rowIdx] = [2, 2, 2]`? Maybe you're making it more complicated than it needs to be. 3) Yes, `UnsafeMutablePointer` is unsafe and a pointer. I think Apple discourages their use by giving them a scary name. But they are the only tool (AFAIK) for doing what you want. Big picture, letting value types behave as reference types would undermine the whole point of having value types. – Aaron Rasmussen Jan 19 '16 at 20:10
  • 1
    I think that pointer is closest thing to local array reference here, but the only *usable* solution I see is to go with separate `func(inout ...)` method for each local variable I need for array. I feel myself uncertain, disappointed and depressed, but accepting. – Lanorkin Jan 21 '16 at 09:43
  • 1
    Value types come with restrictions, but also benefits :) – Aaron Rasmussen Jan 21 '16 at 10:41
  • 2
    I do understand value types. I just do not accept arrays as value types.. I don't see a reason to pass array as a whole rather than a reference to it; Apple seems too, as they actually do that inside, but seems they have to play this game to satisfy their let / var containers.. – Lanorkin Jan 21 '16 at 10:44
1

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.

Alexander Danilov
  • 3,038
  • 1
  • 30
  • 35
  • Thanks for your answer; I still consider that as a workarounds, and speaking of original question - looks like (now) there's no way to obtain a reference to inner array of multidimensional array in a local variable, it will always create a copy. – Lanorkin Apr 17 '19 at 09:38
  • @Lanorkin yes, no way for swift default arrays. Just consider using good old NSMutableArray if you need it much. Swift arrays and dicts implementations seem to be a bad desicion. – Alexander Danilov Apr 17 '19 at 12:36