How to update an array with WritableKeyPath in Swift 4.0 - arrays

In Swift 4.0, I have an array of structs. Is there a way to use keyPaths to update all items in the array without using manually iterating like map or forEach? Something similar to objc [people makeObjectsPerformSelector: #selector(setName:) withObject: #"updated"];
struct Person {
var name: String? = "Empty"
}
var people = [Person(), Person()]
//This only updates one person:
people[keyPath: \[Person].[0].name] = "single update"
//I'm looking to accomplish something like this without a map
let updatedPeople = people.map { (person: Person) -> Person in
var copy = person
copy[keyPath: \Person.name] = "updated"
return copy
}
something like
people[keyPath: \[People].all.name] = "update all without manually iterating"

Mutating into a member of an array requires an l-value. Swift's mechanism for l-values is the subscript, so we can use that:
for i in people.indices {
people[i][keyPath: \Person.name] = updated
// or more simply, just:
// people[i].name = "updated"
// This even works too, but I can't see any reason why it would be desirable
// over the other 2 approaches:
// people[keyPath: \[Person].[i].name] = "update"
}
You could also use forEach, but I generally only recommend that over for in cases where you have an existing closure/function to pass in which has type (Index) -> Void:
// meh
people.indices.forEach {
people[$0][keyPath: \Person.name] = "updated"
}

EDIT Responding to your now edited question asking where is the Swift equivalent of [people makeObjectsPerformSelector: #selector(setName:) withObject: #"updated"], the simple answer is that map, which you for some reason reject in your question, is that equivalent. Of course, to do what Objective-C does, we have to use the Objective-C style of object type, namely a class:
class Person {
var name: String? = "Empty"
}
var people = [Person(), Person()]
people = people.map {$0.name = "updated"; return $0} // *
The starred line is how you make the objects in the array perform the "selector".
A struct is a value type, so as you rightly said in your question, we have to insert a temp variable with a var reference:
struct Person {
var name: String? = "Empty"
}
var people = [Person(), Person()]
people = people.map {var p = $0; p.name = "updated"; return p}
[Original answer:]
The use of key paths in your question seems to be a red herring. You're just asking how to set a property of all the structs in an array.
map is just a way of cycling through the array. You cannot magically do this without cycling through the array; if you don't do it explicitly, you have to do it implicitly.
Here's an explicit way of doing it:
struct Person {
var name: String? = "Empty"
}
var people = [Person(), Person()]
let kp = \Person.name
for i in 0..<people.count {
people[i][keyPath:kp] = "updated"
}
That's not actually any more efficient than using map, though, as far as I know; structs are not mutable in place, so we are still filling the array with entirely new Person objects, exactly as we would have done if using map.

Related

Find reference of item from array - swift

is there a shorter version of the code below?
Also, when accessed with "for var", it is not append because it is "pass by value". Any suggestions?
struct Food {
var fruits = [Fruit]()
var category = "summer fruits"
}
struct Fruit {
var name = ""
}
var foods = Food()
let fruit = Fruit()
fruit.name == "banana"
for var food in foods {
if food.category == "summer fruits" {
food.fruits.append(fruit)
}
}
Using var with arrays is tricky because they still act like values when being passed around (you get a copy, not a reference).
var foods = [Food()]
var food = foods[0]
food.fruits.append(Fruit(name: "banana"))
This does not work if you want to modify the struct inside the foods array. As soon as you do var food = foods[0] you get a mutable copy of foods[0]. The same principle is what causes your example to not work. You are getting a mutable copy of each element in your for loop.
Don't think about it as modifying the struct inside the array, instead think about modifying the array itself. Arrays in Swift are also structs, so changing any element in the array should be thought of as changing the array itself.
foods[0].fruits.append(Fruit(name: "banana"))
There is nothing creating a copy of a value here. You modify the element of the array directly. To do this in a loop, you must use an index:
for i in 0..<foods.count {
foods[i].fruits.append(...)
}
If you want to just modify the first element matching your criterion there is a method for finding a particular index
if let index = foods.firstIndex(where: { $0.category == "summer fruits" }) {
foods[index].fruits.append(fruit)
}
On another note, your code would really benefit from avoiding var and instead using let as much as possible.
let fruit = Fruit(name: "banana")
is much shorter and clearer than your code, and also would help you avoid bugs like using == instead of = for assignment.

Swift 3.0 inout ArraySlice: please explain getting, passing and overloading functions dealing with this type

Note:
I am aware that there are a lot of a similar questions out there - at least in regards to "What is an ArraySlice and what do I do with it?". I have yet to find an answer that I either understand of explains the proper way of handling this (to those of us who do not know ever corner of Swift).
Content
In languages like Python, making a new array from an array slice is a common function and doable, but in swift it is not so straightforward due to the ArraySlice.
Consider the below example:
struct MyStruct {
var myValue = 0
init(value:Int) {
self.myValue = value
}
}
var myArray = [MyStruct]()
for i in 0...10 {
myArray.append(MyStruct(value: i))
}
var myOtherArray: [MyStruct]
myOtherArray = myArray[0...4]
This throws the error:
Cannot subscript a value of type '[myStruct]' with an index of type 'CountableClosedRange<Int>'
Some similar questions have answers relating this to type casting, and suggest:
if let mySlice = myArray[0...4] as? [MyStruct] {
myOtherArray = mySlice
}
but this type casting will always fail.
But most answers give a more direct approach:
let mySlice = myArray[0...4]
myOtherArray = Array(mySlice)
which does not cause an error in this example.
However, if we up the complexity just a bit:
class MyClass {
var array = [MyStruct]()
init(length: Int) {
for i in 0...length {
self.array.append(MyStruct(value: i))
}
}
init(myArray: [MyStruct]){
self.array = myArray
}
}
var myInstance = MyClass(length: 10)
myInstance.array[0...4]
var myNewInstance = MyClass(myInstance.array[0...4]) still causes an error as expected, but now:
let mySlice = myInstance.array[0...4]
var myNewInstance = MyClass(mySlice)
does not work, with the error that there lacks the available overload.
Yes! I can overload this behavior by adding the following to MyClass
init(_ myArray: ArraySlice<MyStruct>){
self.array = Array(myArray)
}
Awesome.
Question
Can someone please explain why making a new array from a subarray requires so much effort? An what the special overload declaration _ : is?
Any time you would like to create a new Array from an ArraySlice, you just pass the slide into Array's initializer:
let sourceArray = [1, 2, 3]
let slice = sourceArray[0...1]
let array = Array(arraySlice)
You mention this yourself in your question. I'm not sure where the confusion is. In your particular example, you would just do this:
var myNewInstance = myClass(Array(myInstance.array[0...4]))
Now, to explain the "special overload declaration _ :", as you put it. _ is simply the token used to incite that a parameter shouldn't have a keyword.
If the declaration looked like this:
init(myArray: ArraySlice<myStruct>)
it would be called like so:
myClass(myArray: someSlice)
But the myArray keyword adds no meaningful information, and just clutters the call site. You can remove the keyword (which is set, by default, to the parameter's name) by explicitly setting it to _:
init(_ myArray: ArraySlice<myStruct>)
is called like so:
myClass(someSlice)

How do I make a exact duplicate copy of an array?

How would I make an exact duplicate of an array?
I am having hard time finding information about duplicating an array in Swift.
I tried using .copy()
var originalArray = [1, 2, 3, 4]
var duplicateArray = originalArray.copy()
Arrays have full value semantics in Swift, so there's no need for anything fancy.
var duplicateArray = originalArray is all you need.
If the contents of your array are a reference type, then yes, this will only copy the pointers to your objects. To perform a deep copy of the contents, you would instead use map and perform a copy of each instance. For Foundation classes that conform to the NSCopying protocol, you can use the copy() method:
let x = [NSMutableArray(), NSMutableArray(), NSMutableArray()]
let y = x
let z = x.map { $0.copy() }
x[0] === y[0] // true
x[0] === z[0] // false
Note that there are pitfalls here that Swift's value semantics are working to protect you from—for example, since NSArray represents an immutable array, its copy method just returns a reference to itself, so the test above would yield unexpected results.
There is a third option to Nate's answer:
let z = x.map { $0 } // different array with same objects
* EDITED * edit starts here
Above is essentially the same as below and actually using the equality operator below will perform better since the array won't be copied unless it is changed (this is by design).
let z = x
Read more here: https://developer.apple.com/swift/blog/?id=10
* EDITED * edit ends here
adding or removing to this array won't affect the original array. However, changing any of the objects' any properties that the array holds would be seen in the original array. Because the objects in the array are not copies (assuming the array hold objects, not primitive numbers).
Nate is correct. If you are working with primitive arrays all you need to do is assign duplicateArray to the originalArray.
For the sake of completeness, if you were working an NSArray object, you would do the following to do a full copy of an NSArray:
var originalArray = [1, 2, 3, 4] as NSArray
var duplicateArray = NSArray(array:originalArray, copyItems: true)
For normal objects what can be done is to implement a protocol that supports copying, and make the object class implements this protocol like this:
protocol Copying {
init(original: Self)
}
extension Copying {
func copy() -> Self {
return Self.init(original: self)
}
}
And then the Array extension for cloning:
extension Array where Element: Copying {
func clone() -> Array {
var copiedArray = Array<Element>()
for element in self {
copiedArray.append(element.copy())
}
return copiedArray
}
}
and that is pretty much it, to view code and a sample check this gist
If you want to copy the items of an array of some class object.
Then you can follow the below code without using NSCopying protocol but you need to have an init method which should take all the parameters that are required for your object.
Here is the code for an example to test on playground.
class ABC {
var a = 0
func myCopy() -> ABC {
return ABC(value: self.a)
}
init(value: Int) {
self.a = value
}
}
var arrayA: [ABC] = [ABC(value: 1)]
var arrayB: [ABC] = arrayA.map { $0.myCopy() }
arrayB.first?.a = 2
print(arrayA.first?.a)//Prints 1
print(arrayB.first?.a)//Prints 2

Swift - issue with initializing and appending to arrays in dictionary

Here's what I am trying to do:
I am starting with an array of ArticleItem objects. Those objects have a property on them named 'category' which is a string. I'm trying to loop through all of my ArticleItem objects and group items with like categories in a dictionary. I'm using the category name as my key. The issue I am having is that my dictionary keys are hold arrays that never contain more than 1 object. I definitely have more than 3 objects with the same category name. Here is the relevant code from my class. I'd love to understand the right way to do this..
private var _articlesDict:[String:[ArticleItem]]
init(articles:[ArticleItem]) {
_articlesDict = [String:[ArticleItem]]()
for item:ArticleItem in articles {
var optionalCatArray:[ArticleItem]? = _articlesDict[item.category]
if let catArray = optionalCatArray {
optionalCatArray!.append(item) //why can't I do catArray.append(item)?
} else {
var arr:[ArticleItem] = [ArticleItem]()
arr.append(item)
_articlesDict[item.category] = arr
}
}
}
The problem is that arrays are value types, so they are passed by value and not by reference. That means that every time you assign a variable holding an array to another variable (or array, or dictionary) you actually create a copy of it. But there's more.
1st problem
This line of code:
if let catArray = optionalCatArray {
creates an immutable copy of optionalCatArray, so it cannot be modified. Use this instead:
if optionalCatArray != nil {
2nd problem
This line of code:
var optionalCatArray:[ArticleItem]? = _articlesDict[item.category]
creates a copy of the array stored in the dictionary - here:
if optionalCatArray != nil {
optionalCatArray!.append(item)
assign a new item to the array, but remember: this is a copy, so you are not modifying the array contained in the dictionary. What's missing is setting it back into the dictionary:
if optionalCatArray != nil {
optionalCatArray!.append(item)
_articlesDict[item.category] = optionalCatArray!
}
Probably this code can be improved by avoiding the array copy like this:
if _articlesDict[item.category] != nil {
_articlesDict[item.category]!.append(item)
} else {
_articlesDict[item.category] = [item]
}
I haven't tested it, but conceptually it should work. Also note how I shortened the else branch, easier to read.
You cannot edit an array in a dictionary directly. You append to a local copy only.
See https://stackoverflow.com/a/24251066/1183577 for more info.
As an alternative to the native-array solutions in other answers, you could do this:
var _articlesDict = [String:NSMutableArray]()
init(articles:[ArticleItem]) {
for item:ArticleItem in articles {
if let array = _articlesDict[item.category] {
array.addObject(item)
} else {
_articlesDict[item.category] = NSMutableArray(object:item)
}
}
}
But keep in mind you'll have to cast to ArticleItem when you extract items from the arrays in the dictionary.

AS3 - Using an Object to lookup values in an array

So I'm having trouble figuring out how to compare two arrays for differences and be able to find which elements do not exist in each other. Most examples talk about object lookup for use with a "for each in" loop. That much makes sense, but I have no idea what is going on here:
var item:Sprite;
object_lookup[item] = true;
I'm quite confused because I've never seen anything other than an integer inside of [] such as with arrays.
Object is a dynamic, that means you can assign properties to it at runtime:
var o:Object = {};
o.sayHi = "Hi, it's me";
trace(o.sayHi); //traces: Hi, it's me
o.sayHiWithFunction = function () { return "Hi with function" };
trace(o.sayHiWithFunction()); //traces: Hi with function
If the property you want to assign is not a valid identifier, you have to use the [] and put it as a string, example:
var o:Object = {};
o.this = "Yes, it is this!"; // Error, this is a keyword! Won't compile
o["this"] = "And the correct this!"; //And this one works!
o["more words"] = "More words"; //Works too
Your code is confusing. If object_lookup is an Object instance, you created a property on it called null but you set this property to true. That means that it will have absolutely nothing to do with the Sprite item you declared above it. Which is null, of course, as you didn't assign a sprite object to it and that's why the property name gets evaluated to null. Do not confuse here: the property name "null" is just a string, it has nothing to do with the type null.
Now, the for in loop loops thru all the property names of an Object, and if you know the property names, you can actually look up the values, too. Looks something like this:
var o:Object = {a:"A", b:"B", c:"C"}; // This is equivalent to o.a = "A", o.b = "B", o.c = "C"... which is aquivalent to o["a"] = "A" ;)
for(var i in o) {
trace(i, o[i]) //traces: c C, a A, b B
}
The for each in is a bit different, if you trace i, you will see the values, not the property names as with for in loop.
Otherwise do what Vesper suggested you in the comment, read about Dictionary.

Resources