Howto reference an Array in swift - swift

I would like to have a reference to an array for better coding, but I don't know howto do. The following code should illustrate what I mean:
I have a class, with an Array of Array of Objects as follow:
class Group: NSObject {
var alGroup = [[NSObject]]();
}
I have the following 2 different codes from which I would like to prefer using the first one.
code 1, which doesn't work with a reference to the inner array. With not working I mean the object is lost (no syntax or runtime error) :
func addObjectto_new_Group(o:NSObject, inout group:Group){
var alGroup = group.alGroup;
var alNew = [NSObject]();
alNew.append(o);
//group.alGroup.append(alNew);
alGroup.append(alNew);
}
Code 2, which works, but not preferred:
func addObjectto_new_Group(o:NSObject, inout group:Group){
//var alGroup = group.alGroup;
var alNew = [NSObject]();
alNew.append(o);
group.alGroup.append(alNew);
//alGroup.append(alNew);
}
How can I have a reference to an array like in code 1 ?

When you create your 'alias' variable var alGroup = group.alGroup you do not copy by reference, but by value as explained in the comments to your question. So one way to solve this is to use the full name like in group.alGroup.append(alNew).
However there is a another option which might be to your liking:
func addObjectToNewAlGroup(o:NSObject, inout alGroup : [[NSObject]])
{
var alNew = [NSObject]()
alNew.append(o)
alGroup.append(alNew)
}
var aGroup = Group()
addObjectto_new_Group("a1", &aGroup)
addObjectto_new_Group("b2", &aGroup)
addObjectToNewAlGroup("c3", &aGroup.alGroup)
This uses your 'Code 2' version, and a new function doing the same, but passing the array by reference into the function. This is legal, and does work. It is only references within a function which doesn't work as you want.

Related

Mutating arrays in a nested Swift struct

I'm new to Swift, so I appreciate any feedback or suggestions on my approach. For reference, I'm using Xcode 12.1 and Swift 5.3. Essentially, I have a series of structs, one of which has an array of strings. What I'd like to do, is to append a string to that array. Consider the following code:
struct Collection {
var things: [Thing] = []
mutating func add(_ thing: Thing) {
things.append(thing)
}
}
struct Thing {
var messages: [String] = []
mutating func add(_ message: String) {
messages.append(message)
}
}
var collection = Collection()
collection.add(Thing())
var thing = collection.things.first
thing!.add("test")
print(collection.things.first!.messages.count)
I was expecting the final line to print 1, but instead it prints 0! The compile does not display any errors either. If I change the code so that struct Thing is class Thing and drop the mutating keyword from its add method, then the code works.
Having said that, I don't understand why my original code does not work as I would expect. I'm able to append a Thing instance to Collection, but not a string to that same Thing instance after the fact.
Have I misunderstood how the mutating keyword works?
You would get your expected 1 if you did:
print(thing!.messages.count)
because you have added the "test" to thing.messages, not collection.things.first!.messages.
"Now hold on a second!" I hear you say, "I just said var thing = collection.things.first on the previous line! How come adding to thing.messages doesn't imply adding to collection.things.first!.messages?".
This is because structs have value semantics. When you do var thing = collection.things.first, you are saying "copy the value of collection.things.first to a variable called thing". You are not saying "the variable thing now refers to the same thing as collection.things.first". To say that, Thing has to be a reference type (class).
So now you have two copies of the same value, one in thing and one in collection.things.first. You change the copy stored in thing. The other copy is unaffected.

Swift array reduction: cannot use mutating member on immutable value

I have the following code that attempts to consolidate redundant elements of an array:
var items : [String] = ["hello", "world", "!", "hello"]
var mutableSet = Set<String>()
items.reduce(mutableSet, combine: { (set: Set<String>, element: String) in
return set.insert(element)
})
set.insert(element) gives me the error Cannot use mutating member on immutable value: 'set' is a 'let' constant. What's wrong and how can I fix it?
The problem with the OP's code is that the accumulator in the reduce is immutable so it won't allow you to use the mutating function insert().
A tidy solution is to define an non-mutating equivalent to insert() called inserting() in an extension to Set as follows.
extension Set {
//returns a new set with the element inserted
func inserting(_ element: Element) -> Set<Element> {
var set = self
set.insert(element)
return set
}
}
Now we can write the reduce as follows
var items : [String] = ["hello", "world", "!", "hello"]
let set = items.reduce(Set<String>()){ accumulator, element in
accumulator.inserting(element)
}
In Swift, collections are value types. Value-typed variables declared with let (as implicitly are function parameters) cannot be modified. Additionally, your closure returns nothing, so reduce will probably not succeed.
I believe that reduce is not the best-suited tool for this task. Consider this for loop instead:
var set = Set<String>()
for element in items { set.insert(element) }
Another even simpler option would be to use the unionInPlace method:
var set = Set<String>()
set.unionInPlace(items)
Even better perhaps, create the set straight from the collection:
var set = Set<String>(items)
The 'set' value returned is a constant. This is important as it is the accumulator, which represents the values that have accumulated, thus far. It should not change in your closure.
Here is an example from a project I'm working on at the moment, where I want to find all of the unique performers, across many theatrical performances. Notice how I am using union, which does not modify the constant value 'performers', but instead consumes it to produce a new value.
let uniquePerformers = performances.reduce(Set<Performer>(), { (performers: Set<Performer>, performance) -> Set<Performer> in
return performers.union(Set(performance.performers))
})

How to deeply duplicate a multidimensional array in Swift

This question has been asked and answered for a couple other coding languages, but I think I may have a unique problem anyway. So, I want to duplicate a three dimensional array (filled with arbitrary objects). I believe I found that this:
var duplicateArray = originalArray
Does not work, since, for whatever reason, they thought it would a nice safety measure to have this create a duplicate array, but filled with pointers as sub-arrays instead of duplicating the sub-arrays as well. This seems like a strange design choice, since if duplicateArray and originalArray were one-dimensional, this would work as intended. Anyway, so I tried this (where object is some arbitrary object):
var duplicateArray = [[[object]]]()
for x in 0..<originalArray.count {
var tempArrYZ = [[object]]()
for y in 0..<originalArray[x].count {
var tempArrZ = [object]()
for z in 0..<originalArray[x][y].count {
let copiedObj = originalArray[x][y][z]
tempArrZ.append(copiedObj)
}
tempArrYZ.append(tempArrZ)
}
duplicateArray.append(tempArrYZ)
}
This still does not work; all the values in duplicateArray will act like a pointer for their values in originalArray. Perhaps someone has a simple way of deeply duplicating multidimensional arrays, or perhaps someone can find my error?
EDIT: How is this a duplicate of that other question? I'm asking specifically how to "deeply" duplicate. The question that's being referred to nebulously asked about duplicating arrays.
var duplicateArray = originalArray
Would work if the objects are not of reference type. However, for the reference type you need to actually create the copy of the object with copy. Your original code was pretty close.
var duplicateArray = [[[object]]]()
for x in 0..<originalArray.count {
var tempArrYZ = [[object]]()
for y in 0..<originalArray[x].count {
var tempArrZ = [object]()
for z in 0..<originalArray[x][y].count {
let copiedObj = originalArray[x][y][z].copy()
tempArrZ.append(copiedObj)
}
tempArrYZ.append(tempArrZ)
}
duplicateArray.append(tempArrYZ)
}
As already stated, your problem isn't really the copying of the array, it's the copying of Objects. Arrays, like all structs, are copied by value. Objects are copied by reference.
When you copy an array of objects, it's a brand new array with brand new references to the contained objects. Your code is simply creating additional references to the same objects then organizing them in a similar fashion.
Anyway, here's my simpler/functional implementation for copying arrays:
func copyArrayWithObjects <T: Copying>(items: [T]) -> [T]{
return items.map { $0.copy() }
}
func copy2DArrayWithObjects <T: Copying>(items: [[T]]) -> [[T]] {
return items.map(copyObjectsInArray)
}
func copy3DArrayWithObjects<T: Copying>(items: [[[T]]]) -> [[[T]]] {
return items.map(copy2DObjectInArray)
}
Then you can simply do this:
let copiedArray = copy3DArrayWithObjects(originalArray)
Theoretically I think it's possible to create a function to do this for an n-dimension array, but I haven't found a solution yet.
I think it would be best to write an extension on Array that adds conformance to NSCopying, which recursively copies the elements. This solution would be very elegant because it could scale to any number of dimmensions.
Swift arrays are value types so the snippet you provided is fine.
var duplicateArray = originalArray
See this example in a Playground as proof:
var array = [[["test"]]]
var newarray = array
// print different memory addresses
print(unsafeAddressOf(array[0][0][0])) // 0x00007ff7a302a760
print(unsafeAddressOf(newarray[0][0][0])) // 0x00007ff7a33000e0
If you use NSArray or reference types inside the Swift array, then they will no longer copy implicitly and will be treated with the same address - this can also be proved in the Playground. You would need to call copy() explicitly on reference types.

Access Class In A Dictionary - Swift

I am now writing a program involves class and dictionaries. I wonder how could I access a class's values inside a dictionary. For the code below how do I access the test1 value using the dictionary. I have tried using dict[1].test1but it doesn't work.
class test {
var tes1 = 1
}
var refer = test()
var dict = [1:refer]
There are a few problems with the line dict[1].test1:
Firstly, the subscript on a dictionary returns an optional type because there may not be a value for the key. Therefore you need to check a value exists for that key.
Secondly, in your class Test you've defined a variable tes1, but you're asking for test1 from your Dictionary. This was possibly just a type-o though.
To solve these problems you're code should look something like this:
if let referFromDictionary = dict[1] {
prinln(referFromDictionary.test1)
}
That's because the subscript returns an optional, so you have to unwrap it - and the most straightforward way is by using optional chaining:
dict[1]?.tes1
but you can also use optional binding:
if let test = dict[1] {
let value = test.tes1
}

Append a class object to an array

I am having trouble with the syntax here. Basically I created a simple class and hoping to add the object of that class to an Array.
class simpleClass {
var aNum = Int()
var aWord = String()
init(thisNum:Int,thisString:String)
{
aNum = thisNum
aWord = thisString
}
}
var aObj:simpleClass
var aArray:Array<simpleClass>
aObj = simpleClass(thisNum:12,thisString:"Test")
aArray.append(aObj)
As you can see I have created an object of simpleClass and trying to append it to an array of type simpleClass. However, I receive an error saying
passed by reference before being initialized
I guess I must be missing something in the syntax. Hoping someone out there could point out my mistake.
thanks,
sweekim
You need to assign an array to the array variable.
var aArray:Array<simpleClass> = []
Or if you prefer,
var aArray = Array<simpleClass>()
Or even (my preference)
var aArray: [simpleClass] = []
Or
var aArray = [simpleClass]()
Better yet you could even reorder things and do this:
var aArray = [simpleClass(thisNum:12,thisString:"Test")]
instead of the whole 4 last lines.
Incidentally, you might find it better to declare your class like this:
class simpleClass {
var aNum: Int
var aWord: String
init(thisNum:Int,thisString:String) {
aNum = thisNum
aWord = thisString
}
}
This types aNum and aWord, but does not assign them values, since you then do that in the init method. The reason being, if you ever forgot to assign a value in init the compiler will warn you, whereas if you default them, it won’t. It’s fine to default them instead, but then don’t include them in an init method – one or the other is best, both is a bit redundant and can lead to mistakes.
Change this line:
var aArray:Array<simpleClass>
To this:
var aArray:Array<simpleClass> = []
You were declaring the array type but you forgot to make any array. If you actually look at the error message, it tells you exactly that - you didn't initialize the variable.
Also I think you didn't quite declare your instance of simpleClass correctly. I did this to silence the errors:
var aArray:Array<simpleClass> = []
let aObj:simpleClass = simpleClass(thisNum:12,thisString:"Test")
aArray.append(aObj)
Note the way an instance of simpleClass is created.