Swiftier Swift for 'Add to Array, or Create If Not There...'

Swiftier Swift for 'add to array, or create if not there...'

Swift 4 update:

As of Swift 4, dictionaries have a subscript(_:default:) method, so that

dict[key, default: []].append(newElement)

appends to the already present array, or to an empty array. Example:

var dict: [String: [Int]] = [:]
print(dict["foo"]) // nil

dict["foo", default: []].append(1)
print(dict["foo"]) // Optional([1])

dict["foo", default: []].append(2)
print(dict["foo"]) // Optional([1, 2])

As of Swift 4.1 (currently in beta) this is also fast,
compare Hamish's comment here.


Previous answer for Swift <= 3: There is – as far as I know – no way to "create or update" a dictionary
value with a single subscript call.

In addition to what you wrote, you can use the nil-coalescing operator

dict[key] = (dict[key] ?? []) + [elem]

or optional chaining (which returns nil if the append operation
could not be performed):

if dict[key]?.append(elem) == nil {
dict[key] = [elem]
}

As mentioned in SE-0154 Provide Custom Collections for Dictionary Keys and Values and also by @Hamish in the comments, both methods
make a copy of the array.

With the implementation of SE-0154 you will be able to mutate
a dictionary value without making a copy:

if let i = dict.index(forKey: key) {
dict.values[i].append(elem)
} else {
dict[key] = [key]
}

At present, the most efficient solution is given by Rob Napier
in Dictionary in Swift with Mutable Array as value is performing very slow? How to optimize or construct properly?:

var array = dict.removeValue(forKey: key) ?? []
array.append(elem)
dict[key] = array

A simple benchmark confirms that "Rob's method" is the fastest:

let numKeys = 1000
let numElements = 1000

do {
var dict: [Int: [Int]] = [:]

let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
if dict.index(forKey: key) == nil {
dict[key] = []
}
dict[key]!.append(elem)

}
}
let end = Date()
print("Your method:", end.timeIntervalSince(start))
}

do {
var dict: [Int: [Int]] = [:]

let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
dict[key] = (dict[key] ?? []) + [elem]
}
}
let end = Date()
print("Nil coalescing:", end.timeIntervalSince(start))
}


do {
var dict: [Int: [Int]] = [:]

let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
if dict[key]?.append(elem) == nil {
dict[key] = [elem]
}
}
}
let end = Date()
print("Optional chaining", end.timeIntervalSince(start))
}

do {
var dict: [Int: [Int]] = [:]

let start = Date()
for key in 1...numKeys {
for elem in 1...numElements {
var array = dict.removeValue(forKey: key) ?? []
array.append(elem)
dict[key] = array
}
}
let end = Date()
print("Remove and add:", end.timeIntervalSince(start))
}

Results (on a 1.2 GHz Intel Core m5 MacBook) for 1000 keys/1000 elements:


Your method: 0.470084965229034
Nil coalescing: 0.460215032100677
Optional chaining 0.397282958030701
Remove and add: 0.160293996334076

And for 1000 keys/10,000 elements:


Your method: 14.6810429692268
Nil coalescing: 15.1537700295448
Optional chaining 14.4717089533806
Remove and add: 1.54668599367142

Swift - Adding value to an array inside a dictionary

EDIT: Thanks to Martin's comment. The snippet below is the the most succinct answer I can think of. I was initially coming at it from a wrong direction. and I was getting an error. See comments

struct Student { 
let id: Int
let subject : String
}

var studentArray = [Student(id: 1, subject: "History"), Student(id: 2, subject: "History"), Student(id:1, subject: "Maths")]

typealias Subject = String
var dict : [Int: [Subject]] = [:]

for student in studentArray {

(dict[student.id, default: []]).append(student.subject)
}

print(dict)

Previous answers:

struct Student { 
let id: Int
let subject : String
}

var studentArray = [Student(id: 1, subject: "History"), Student(id: 2, subject: "History"), Student(id:1, subject: "Maths")]

typealias Subject = String
var dict : [Int: [Subject]] = [:]

for student in studentArray {
var subjects = dict[student.id] ?? [String]()
subjects.append(student.subject)
dict[student.id] = subjects
}

print(dict)

Or you can do it this way:

struct Student { 
let id: Int
let subject : String
}

var studentArray = [Student(id: 1, subject: "History"), Student(id: 2, subject: "History"), Student(id:1, subject: "Maths")]

typealias Subject = String
var dict : [Int: [Subject]] = [:]

for student in studentArray {
if let _ = dict[student.id]{
dict[student.id]!.append(student.subject)
}else{
dict[student.id] = [student.subject]
}
}

print(dict)

whichever you like

Swift - dictionary with array - get reference to array

You can simply use a default value for the subscript and your whole code will be simplified to this:

var dic = [String: [String]]()
dic[key, default: []].append(m)

Dictionary in Swift with Mutable Array as value is performing very slow? How to optimize or construct properly?

Copy on write is a tricky thing, and you need to think carefully about how many things are sharing a structure that you're trying to modify. The culprit is here.

countToColorMap[colorCount]?.append(CountedColor(color: color as! UIColor, colorCount: colorCount))

This is generating a temporary value that is modified and put back into the dictionary. Since two "things" are looking at the same underlying data structure (the dictionary, and append), it forces a copy-on-write.

The secret to fixing this is to make sure that there's only one copy when you modify it. How? Take it out of the dictionary. Replace this:

if countToColorMap[colorCount] != nil {
countToColorMap[colorCount]?.append(CountedColor(color: color as! UIColor, colorCount: colorCount))
} else {
countToColorMap[colorCount] = [CountedColor(color: color as! UIColor, colorCount: colorCount)]
}

which has a runtime of:

Elapsed Time: 74.2517465990022
53217

with this:

var countForColor = countToColorMap.removeValue(forKey: colorCount) ?? []
countForColor.append(CountedColor(color: color as! UIColor, colorCount: colorCount))
countToColorMap[colorCount] = countForColor

which has a runtime of:

Elapsed Time: 0.370953808000195
53217

add multiple objects to swift array in for loop

You need move the tempNoArray initialization outside of your for in loop, if not the your array will be initialized once for every item in your exportDataArray remaining only the las item as consequence

You need something like this

var tmpNoArray:Array = [String]()
for tempExportData in exportDataArray{

if let tmpRegNO = tempExportData[kRegisteredNo] as? String
{

print("tmpRegNO is",tmpRegNO)
tmpNoArray.append(tmpRegNO as String)

print("Count is",tmpNoArray.count)
print("ARRAY is",tmpNoArray)
}
}

Create Array of CGPoints in Swift

There are a few issues and bad practices

You are declaring the type [CGPoint].self, an empty array is

var points = [CGPoint]()

A much better way in Swift is a for loop

for i in 0..<closingprices.count {
points.append(CGPoint(x: Double(i+1),y: closingprices[i]))
}

or (preferred) Fast Enumeration

for (index, price) in closingprices.enumerated() {
points.append(CGPoint(x: Double(index+1),y: price))
}

Please read the Swift Language Guide, it's worth it.

Can Run code Once in Nested For loop in Swift?

To end a loop you use break

for for i in 0..<array.count {
if someCondition {
//do stuff
break
}
}

Reduce instances of an object in an array by rule

You can avoid brute-force O(n^2) nested looping (and enumeration) solution by first sorting the array, and thereafter filtering out duplicate colour objects using e.g. hash value lookup (Method 1 below) or clever exclusion with regard to the sorted array (Method 2 below).

Note also class type naming convention (CamelCase): so Foo rather than foo.

Disclaimer: Don't stare yourself blind on the asymptotic complexity notations below, as premature optimization is, depending on the context and intended usage area of your program, generally something of a sin. I've included them below simply to have some measure to compare the different methods by. Choose the one that you think makes most sense to you.


Method 1

Worst case...

  • time complexity: O(n log n)

  • space complexity: O(n)


Where space complexity refers to space used in excess of the array
to which the final result is assigned.

  • Let Foo conform to Hashable (let hashValue relate to .color property).
  • Sort the array of Foo instances w.r.t. decreasing size (.size property).
  • Filter the sorted array w.r.t. to first occurrence of each color, using the conformance to Hashable to swiftly use O(1) hash value lookup for existing color in a Foo:Bool dictionary. Adapted from the comments by Airspeed Velocity in the following answer.

Method 2 (as proposed by Nikolai Ruhe):

Worst case...

  • time complexity: O(n log n)

  • space complexity: O(1)

  • Sort the array on color (primary) and size (secondary).
  • Filter the sorted array for elements that has a different color than their predecessors.

For a third (probably the best one for this application) method, see Nikolai Ruhe:s answer below, presenting a method with O(n)/O(n) time/space worst case complexity, respectively.


Implementations

[This step is only needed for Method 1] Conform Foo to Hashable and Equatable:

/* Let Foo conform to Hashable */
class Foo : Hashable {

var color: String
var size: Int
var shape: String

init(color:String, size:Int, shape:String){
self.color = color
self.size = size
self.shape = shape
}

var hashValue: Int {
return color.hashValue
}

}

/* And Equatable */
func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.color == rhs.color
}

Set up and example for the filter method(s) that follows shortly:

/* Foo array example */
var array = [Foo]()

array.append(Foo(color: "Blue", size: 2, shape: "Round"))
array.append(Foo(color: "Red", size: 3, shape: "Square"))
array.append(Foo(color: "Blue", size: 5, shape: "Round"))
array.append(Foo(color: "Yellow", size: 1, shape: "Triangle"))
array.append(Foo(color: "Blue", size: 1, shape: "Hexagon"))

Filter as per your specifications:

/* Method 1 (assumes Foo conforms to Hashable (& Equatable))   */
var addedDict = [Foo:Bool]()
var arrFiltered = array.sort{ $0.0.size > $0.1.size }
.filter {addedDict.updateValue(true, forKey: $0) == nil }

/* Method 2 (as proposed by Nikolai Ruhe) */
var previousColor: String?
let arrFiltered = array.sort{ $0.color == $1.color ? $0.size > $1.size : $0.color < $1.color }
.filter{ if $0.color != previousColor { previousColor = $0.color; return true }; return false }
/* condensed .filter solution by @Nikolai Ruhe, thanks! */

Result:

for bar in arrFiltered {
print(bar.color, bar.size)
}

/* Blue 5
Red 3
Yellow 1 */

The sorting step is the dominant step in this solution (for both methods). From swift/stdlib/public/core/Sort.swift.gyb, it seems as if Swift uses introsort (specifically, a hybrid of introsort combined with insertion sort), running in, worst case, as O(n log n).

Make array type from type

Passing the type as foo.Type is a very objective-c-ish pattern.

In Swift I'd prefer a generic solution, something like

func serializeData<T : Mappable>() -> [T]? {
return try? response?.map(to: [T].self)
}

or still swiftier

func serializeData<T : Mappable>() throws -> [T] {
return try response?.map(to: [T].self) ?? []
}

Build a json array from multiple properties of a custom type in Swift

Thanks to the answer from @vadian, I was able to completely solve this by adding a new struct containing a key-value pair with an identifier (and an array of these 'trios') and then adding a second for-in loop. I'm sure my code can be optimised some more (this would be interesting to see) but I'm very pleased (and grateful) that this is now building the json array in the way I wanted. Here is my final solution:

func buildJsonArrayOfResults(_ rangeOfMotionResults: [ORKRangeOfMotionResult], withIdentifier variableIdentifiers: [String]) {

let record = getIDNumber().stringValue
let repeatInstance = getRepeatInstance().stringValue
let repeatInstrument = "range_of_motion_result"
let event = getEventName()
var fieldName: String?
var identifier: String?
var stringData: String?
var value: String?
var items: [Item] = []

struct Item: Codable {
let record: String
let fieldName: String
let repeatInstance: String
let repeatInstrument: String
let value: String
let event: String

enum CodingKeys: String, CodingKey {
case record
case fieldName = "field_name"
case repeatInstance = "redcap_repeat_instance"
case repeatInstrument = "redcap_repeat_instrument"
case value
case event = "redcap_event_name"
}
}

struct Trio {
let key: String
let value: String
let identifier: String
}
var trios = [Trio]()

assert(rangeOfMotionResults.count == variableIdentifiers.count, "The size of rangeOfMotionResults and variableIdentifiers must be equal")

for (index, result) in rangeOfMotionResults.enumerated() {
identifier = variableIdentifiers[index]

// add key-value pairs with their identifiers to array
let orientation = String(result.orientation)
trios.append(Trio(key: "orientation", value: orientation, identifier: identifier!))

let start = String(result.start)
trios.append(Trio(key: "start", value: start, identifier: identifier!))

let finish = String(result.finish)
trios.append(Trio(key: "finish", value: finish, identifier: identifier!))

let minimum = String(result.minimum)
trios.append(Trio(key: "minimum", value: minimum, identifier: identifier!))

let maximum = String(result.maximum)
trios.append(Trio(key: "maximum", value: maximum, identifier: identifier!))

let range = String(result.range)
trios.append(Trio(key: "range", value: range, identifier: identifier!))
}

for (_, result) in trios.enumerated() {

let ident = result.identifier
value = result.value

let fieldName: String
switch ident {
case let id where id.contains("forward"):
fieldName = result.key + "_forward"
case let id where id.contains("backward"):
fieldName = result.key + "_backward"
case let id where id.contains("left.bending"):
fieldName = result.key + "_left_bending"
case let id where id.contains("right.bending"):
fieldName = result.key + "_right_bending"
case let id where id.contains("left.rotation"):
fieldName = result.key + "_left_rotation"
case let id where id.contains("right.rotation"):
fieldName = result.key + "_left_rotation"
default:
fieldName = result.key + "_unknown"
}

let item = Item(record: record, fieldName: fieldName, repeatInstance: String(repeatInstance), repeatInstrument: repeatInstrument, value: value!, event: event)

items.append(item)
}

do {
let data = try JSONEncoder().encode(items)
stringData = String(data: data, encoding: .utf8)
print(stringData!)

// now do something with stringData

} catch {
print(error)
}
}


Related Topics



Leave a reply



Submit