Subscript array of structs - arrays

Given I have array of stucts like this:
let array = [Struct(key: "a", value: 1), Struct(key: "b", value:2)]
How can I subscript the array with the key?
array["b"] would be nice, but as expected, it doesn't work.
Edit: The reason I'm not using dictionary, is I need to preserve the order of items.

This is syntactic sugar around your solution, #AdamBardon.
You can extend Array to allow you to subscript it directly. Under the covers it is just using the same first(where:) call:
protocol HasKey {
var key: String { get }
}
struct Struct: HasKey {
var key: String
var value: Int
}
extension Array where Element: HasKey {
subscript(str: String) -> Element? {
return self.first(where: { $0.key == str })
}
}
Example:
let array = [Struct(key: "a", value: 1), Struct(key: "b", value:2)]
if let x = array["a"] {
print(x)
}
Output:
Struct(key: "a", value: 1)
Using the protocol allows you to easily extend this functionality to any class or struct that has a key: String property by having them adopt the HasKey property:
extension SomeOtherClass: HasKey { }
You can also accomplish it without the protocol by checking if Element == Struct:
extension Array where Element == Struct {
subscript(str: String) -> Element? {
return self.first(where: { $0.key == str })
}
}

You can make the dictionary of dictionaries like below code:
let myDictionaryOfDictionaries : [String : [String : String]] =
["Apples" : ["Colour" : "Red", "Type" : "Granny Smith"],
"Oranges" : ["Colour" : "Orange", "Type" : "Seville"]]
print(myDictionaryOfDictionaries["Apples"] ?? "")
Hope it will help you.

You should use a dictionary. Arrays are designed to access elements by their index and not by a property of the element. Indexes also have to be integers.
Dictionaries on the other hand are key-value pairs, so using a Dictionary seems to perfect for your use case.
let structs = ["a":Struct(key:"a",value:1),"b":Struct(key:"b",value:2)]
structs["b"] returns the struct with key "b".

This is not the exact way how I imagined the solution, but it's a good alternative which solves my problem.
I got what I needed using this:
array.first(where: { $0.key == "b" })

Related

How to change a value of struct that is in array?

I'm using swift for my project.
I have an array of structs named Instrument. Later on I made a function that returns specific Instrument from array. Then I wanted to change value on one of its property, but this change is not reflected inside the array.
I need to have this array to include all the changes on the elements inside. What do you think is the best practice here?
Change Instrument from struct to class.
Somehow rewrite the function that returns Instrument from array.
Right now I use this function:
func instrument(for identifier: String) -> Instrument? {
if let instrument = instruments.filter({ $0.identifier == identifier }).first {
return instrument
}
return nil
}
I start with the struct because swift is known to be language for structs and I want to learn when to use struct of class.
thanks
With an array of struct Instrument, you can obtain the index for the Instrument with a particular identifier, and use that to access and modify a property of the Instrument.
struct Instrument {
let identifier: String
var value: Int
}
var instruments = [
Instrument(identifier: "alpha", value: 3),
Instrument(identifier: "beta", value: 9),
]
if let index = instruments.index(where: { $0.identifier == "alpha" }) {
instruments[index].value *= 2
}
print(instruments) // [Instrument(identifier: "alpha", value: 6), Instrument(identifier: "beta", value: 9)]
If you stick to the value type approach (and given that the identifier is not unique: otherwise, consider using a dictionary for simple extract-and-replace logic), you could write a mutating function to the type which owns the [Instruments] array, which finds a (first) Instrument instance in the array and mutates it using a supplied closure. E.g. (thanks #Hamish for improvements!):
struct Instrument {
let identifier: String
var changeThis: Int
init(_ identifier: String, _ changeThis: Int) {
self.identifier = identifier
self.changeThis = changeThis
}
}
struct Foo {
var instruments: [Instrument]
#discardableResult // do not necessarily make use of the return result (no warning if not)
mutating func updateInstrument(forFirst identifier: String,
using mutate: (inout Instrument) -> ()) -> Bool {
if let idx = instruments.indices
.first(where: { instruments[$0].identifier == identifier }) {
// mutate this instrument (in-place) using supplied closure
mutate(&instruments[idx])
return true // replacement successful
}
return false // didn't find such an instrument
}
}
Example usage:
var foo = Foo(instruments:
[Instrument("a", 1), Instrument("b", 2),
Instrument("c", 3), Instrument("b", 4)])
// make use of result of call
if foo.updateInstrument(forFirst: "b", using: { $0.changeThis = 42 }) {
print("Successfully mutated an instrument")
} // Successfully mutated an instrument
// just attempt mutate and discard the result
foo.updateInstrument(forFirst: "c", using: { $0.changeThis = 99 })
print(foo.instruments)
/* [Instrument(identifier: "a", changeThis: 1),
Instrument(identifier: "b", changeThis: 42),
Instrument(identifier: "c", changeThis: 99),
Instrument(identifier: "b", changeThis: 4)] */
As shown in #Owen:s answer, an even neater approach to finding the first index for a certain predicate on the element is using the index(where:) method of array (rather than indices.first(where:) as used above). Using the index(where:) approach in the complete example above would simply correspond to replacing
if let idx = instruments.indices
.first(where: { instruments[$0].identifier == identifier }) { ...
with
if let idx = instruments
.index(where: { $0.identifier == identifier }) { ...
in the updateInstrument(forFirst:using) method of Foo.
We could further condense the updateInstrument(forFirst:using) method by applying the map function of Optional to perform the (possible) replacement and boolean return in a single line:
struct Foo {
var instruments: [Instrument]
#discardableResult
mutating func updateInstrument(forFirst identifier: String,
using mutate: (inout Instrument) -> ()) -> Bool {
return instruments
.index(where: { $0.identifier == identifier })
.map { mutate(&instruments[$0]) } != nil
}
}

Obtain a subset of tuple array in swift

I have a array of tuples like the following
var customProducts = [(productType: String, info:[String:AnyObject?])]
The parameter "productType" works like a "product category", like fruits, grains, beverage, etc.
The parameter "info" is a dictionary of nutritional information of the product.
I want to get a subset of the tuples array, based on the productType so I could obtain just the "info" dictionary for an specific productType. In C# I would try something like the following using Linq:
var fruits = customProducts.Where(q=>q.productType == "fruit").Select(q => q.info) as List<KeyValuePair<string, object>>;
How may I archive the same results using Swift (2.x)?
Thanks!
I think the Swift equivalent would be:
let fruits = customProducts.filter { $0.productType == "fruit" }.map { $0.info }
Here fruits is [[String : AnyObject?]], an array of dictionaries (an array of info, the same as your List<KeyValuePair<string, object>> if I'm not mistaken).
You can use the filter method
let beverageInfo = (customProducts.filter { $0.productType == "Beverage" }).first?.info
Now beverageInfo is [String : AnyObject?]?, an optional dictionary representing the info for "Beverage" tuple.
You should filter the array according to what type the product is
Eg
var customProducts = [(productType: String, info:[String:AnyObject?])]
let fruitProducts = customProducts.filter { product in
if product.productType == "fruit" {
return true
} else {
return false
}
}.map { $0.info }
Then you can use fruitProducts however you want.

Cannot append in Array extension

I have extended the Array class in order to create some methods. However I am not being able to append to the array:
private extension Array
{
mutating func addCharacterWithIndex(char: String, index: Int)
{
let dict = [
"char": char,
"index": index
]
self.append(dict) //Shows error: Cannot invoke 'append' with argument list of type '(NSDictionary)'
}
}
The only way I could make this go away was by using
self.append(dict as! Element)
However the element is not being added to the array at all.
What am I not understanding about this situation? Is this even possible?
Updated
I just realized DictionaryLiteralConvertible can be used here.
extension Array where Element: DictionaryLiteralConvertible, Element.Key == String, Element.Value == Int {
mutating func addCharacterWithIndex(char: String, index: Int)
{
self.append(Element.init(dictionaryLiteral: (char, index)))
}
}
Original answer...
Since Array in Swift is a generic struct, it's not possible (at least right now) to extend the Array with a specific type constraint.
For you question, one way to do is to make it optional.
extension Array
{
mutating func addCharacterWithIndex(char: String, index: Int)
{
if let dict = [
"char": char,
"index": index
] as? Element {
self.append(dict)
}
}
}
So you can use it like this:
var symbols: [[String: AnyObject]] = []
symbols.addCharacterWithIndex("*", index: 9)
However, this is not consistent. For other arrays such as [Int], [String] this method is accessible but does nothing.
Another way, given that you're only using append method, you can extend RangeReplaceableCollectionType instead.
extension RangeReplaceableCollectionType where Generator.Element == NSDictionary {
mutating func addCharacterWithIndex(char: String, index: Int)
{
let dict = [
"char": char,
"index": index
]
self.append(dict)
}
}
And use it like this:
var symbols: [NSDictionary] = []
symbols.addCharacterWithIndex("*", index: 9)

Transform from dictionary to array in swift without a for loop

I have some data returned from the server that look like this:
let returnedFromServer = ["title" : ["abc", "def", "ghi"],
"time" : ["1234", "5678", "0123"],
"content":["qwerty", "asdfg", "zxcvb"]]
I want to transform it into something like this:
let afterTransformation =
[["title" : "abc",
"time" : "1234",
"content": "qwerty"],
["title" : "def",
"time" : "5678",
"content": "asdfg"],
["title" : "ghi",
"time" : "0123",
"content": "zxcvb"]]
My current implementation is as follows:
var outputArray = [[String : AnyObject]]()
for i in 0..<(returnedFromServer["time"] as [String]).count {
var singleDict = [String: AnyObject]()
for attribute in returnedFromServer {
singleDict[attribute] = returnedFromServer[attribute]?[i]
}
outputArray.append(singleDict)
}
This works fine but I think it is not a very elegant solution. Given that Swift has some neat features such as reduce, filter and map, I wonder if I can do the same job without explicitly using a loop.
Thanks for any help!
Using the ideas and the dictionary extension
extension Dictionary {
init(_ pairs: [Element]) {
self.init()
for (k, v) in pairs {
self[k] = v
}
}
func map<OutKey: Hashable, OutValue>(transform: Element -> (OutKey, OutValue)) -> [OutKey: OutValue] {
return Dictionary<OutKey, OutValue>(Swift.map(self, transform))
}
}
from
What's the cleanest way of applying map() to a dictionary in Swift?,
you could achieve this with
let count = returnedFromServer["time"]!.count
let outputArray = (0 ..< count).map {
idx -> [String: AnyObject] in
return returnedFromServer.map {
(key, value) in
return (key, value[idx])
}
}
Martin R’s answer is a good one and you should use that and accept his answer :-), but as an alternative to think about:
In an ideal world the Swift standard library would have:
the ability to initialize a Dictionary from an array of 2-tuples
a Zip3 in addition to a Zip2 (i.e. take 3 sequences and join them into a sequence of 3-tuples
an implementation of zipWith (i.e. similar to Zip3 but instead of just combining them into pairs, run a function on the given tuples to combine them together).
If you had all that, you could write the following:
let pairs = map(returnedFromServer) { (key,value) in map(value) { (key, $0) } }
assert(pairs.count == 3)
let inverted = zipWith(pairs[0],pairs[1],pairs[2]) { [$0] + [$1] + [$2] }
let arrayOfDicts = inverted.map { Dictionary($0) }
This would have the benefit of being robust to ragged input – it would only generate those elements up to the shortest list in the input (unlike a solution that takes a count from one specific list of the input). The downside it its hard-coded to a size of 3 but that could be fixed with a more general version of zipWith that took a sequence of sequences (though if you really wanted your keys to be strings and values to be AnyObjects not strings you’d have to get fancier.
Those functions aren’t all that hard to write yourself – though clearly way too much effort to write for this one-off case they are useful in multiple situations. If you’re interested I’ve put a full implementation in this gist.
I'd create 2 helpers:
ZipArray (similar to Zip2, but works with arbitrary length):
struct ZipArray<S:SequenceType>:SequenceType {
let _sequences:[S]
init<SS:SequenceType where SS.Generator.Element == S>(_ base:SS) {
_sequences = Array(base)
}
func generate() -> ZipArrayGenerator<S.Generator> {
return ZipArrayGenerator(map(_sequences, { $0.generate()}))
}
}
struct ZipArrayGenerator<G:GeneratorType>:GeneratorType {
var generators:[G]
init(_ base:[G]) {
generators = base
}
mutating func next() -> [G.Element]? {
var row:[G.Element] = []
row.reserveCapacity(generators.count)
for i in 0 ..< generators.count {
if let e = generators[i].next() {
row.append(e)
}
else {
return nil
}
}
return row
}
}
Basically, ZipArray flip the axis of "Array of Array", like:
[
["abc", "def", "ghi"],
["1234", "5678", "0123"],
["qwerty", "asdfg", "zxcvb"]
]
to:
[
["abc", "1234", "qwerty"],
["def", "5678", "asdgf"],
["ghi", "0123", "zxcvb"]
]
Dictionary extension:
extension Dictionary {
init<S:SequenceType where S.Generator.Element == Element>(_ pairs:S) {
self.init()
var g = pairs.generate()
while let (k:Key, v:Value) = g.next() {
self[k] = v
}
}
}
Then you can:
let returnedFromServer = [
"title" : ["abc", "def", "ghi"],
"time" : ["1234", "5678", "0123"],
"content":["qwerty", "asdfg", "zxcvb"]
]
let outputArray = map(ZipArray(returnedFromServer.values)) {
Dictionary(Zip2(returnedFromServer.keys, $0))
}

How to check if an element is in an array

In Swift, how can I check if an element exists in an array? Xcode does not have any suggestions for contain, include, or has, and a quick search through the book turned up nothing. Any idea how to check for this? I know that there is a method find that returns the index number, but is there a method that returns a boolean like ruby's #include??
Example of what I need:
var elements = [1,2,3,4,5]
if elements.contains(5) {
//do something
}
Swift 2, 3, 4, 5:
let elements = [1, 2, 3, 4, 5]
if elements.contains(5) {
print("yes")
}
contains() is a protocol extension method of SequenceType (for sequences of Equatable elements) and not a global method as in
earlier releases.
Remarks:
This contains() method requires that the sequence elements
adopt the Equatable protocol, compare e.g. Andrews's answer.
If the sequence elements are instances of a NSObject subclass
then you have to override isEqual:, see NSObject subclass in Swift: hash vs hashValue, isEqual vs ==.
There is another – more general – contains() method which does not require the elements to be equatable and takes a predicate as an
argument, see e.g. Shorthand to test if an object exists in an array for Swift?.
Swift older versions:
let elements = [1,2,3,4,5]
if contains(elements, 5) {
println("yes")
}
For those who came here looking for a find and remove an object from an array:
Swift 1
if let index = find(itemList, item) {
itemList.removeAtIndex(index)
}
Swift 2
if let index = itemList.indexOf(item) {
itemList.removeAtIndex(index)
}
Swift 3, 4
if let index = itemList.index(of: item) {
itemList.remove(at: index)
}
Swift 5.2
if let index = itemList.firstIndex(of: item) {
itemList.remove(at: index)
}
Updated for Swift 2+
Note that as of Swift 3 (or even 2), the extension below is no longer necessary as the global contains function has been made into a pair of extension method on Array, which allow you to do either of:
let a = [ 1, 2, 3, 4 ]
a.contains(2) // => true, only usable if Element : Equatable
a.contains { $0 < 1 } // => false
Historical Answer for Swift 1:
Use this extension: (updated to Swift 5.2)
extension Array {
func contains<T>(obj: T) -> Bool where T: Equatable {
return !self.filter({$0 as? T == obj}).isEmpty
}
}
Use as:
array.contains(1)
If you are checking if an instance of a custom class or struct is contained in an array, you'll need to implement the Equatable protocol before you can use .contains(myObject).
For example:
struct Cup: Equatable {
let filled:Bool
}
static func ==(lhs:Cup, rhs:Cup) -> Bool { // Implement Equatable
return lhs.filled == rhs.filled
}
then you can do:
cupArray.contains(myCup)
Tip: The == override should be at the global level, not within your class/struct
I used filter.
let results = elements.filter { el in el == 5 }
if results.count > 0 {
// any matching items are in results
} else {
// not found
}
If you want, you can compress that to
if elements.filter({ el in el == 5 }).count > 0 {
}
Hope that helps.
Update for Swift 2
Hurray for default implementations!
if elements.contains(5) {
// any matching items are in results
} else {
// not found
}
(Swift 3)
Check if an element exists in an array (fulfilling some criteria), and if so, proceed working with the first such element
If the intent is:
To check whether an element exist in an array (/fulfils some boolean criteria, not necessarily equality testing),
And if so, proceed and work with the first such element,
Then an alternative to contains(_:) as blueprinted Sequence is to first(where:) of Sequence:
let elements = [1, 2, 3, 4, 5]
if let firstSuchElement = elements.first(where: { $0 == 4 }) {
print(firstSuchElement) // 4
// ...
}
In this contrived example, its usage might seem silly, but it's very useful if querying arrays of non-fundamental element types for existence of any elements fulfilling some condition. E.g.
struct Person {
let age: Int
let name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
let persons = [Person(17, "Fred"), Person(16, "Susan"),
Person(19, "Hannah"), Person(18, "Sarah"),
Person(23, "Sam"), Person(18, "Jane")]
if let eligableDriver = persons.first(where: { $0.age >= 18 }) {
print("\(eligableDriver.name) can possibly drive the rental car in Sweden.")
// ...
} // Hannah can possibly drive the rental car in Sweden.
let daniel = Person(18, "Daniel")
if let sameAgeAsDaniel = persons.first(where: { $0.age == daniel.age }) {
print("\(sameAgeAsDaniel.name) is the same age as \(daniel.name).")
// ...
} // Sarah is the same age as Daniel.
Any chained operations using .filter { ... some condition }.first can favourably be replaced with first(where:). The latter shows intent better, and have performance advantages over possible non-lazy appliances of .filter, as these will pass the full array prior to extracting the (possible) first element passing the filter.
Check if an element exists in an array (fulfilling some criteria), and if so, remove the first such element
A comment below queries:
How can I remove the firstSuchElement from the array?
A similar use case to the one above is to remove the first element that fulfils a given predicate. To do so, the index(where:) method of Collection (which is readily available to array collection) may be used to find the index of the first element fulfilling the predicate, whereafter the index can be used with the remove(at:) method of Array to (possible; given that it exists) remove that element.
var elements = ["a", "b", "c", "d", "e", "a", "b", "c"]
if let indexOfFirstSuchElement = elements.index(where: { $0 == "c" }) {
elements.remove(at: indexOfFirstSuchElement)
print(elements) // ["a", "b", "d", "e", "a", "b", "c"]
}
Or, if you'd like to remove the element from the array and work with, apply Optional:s map(_:) method to conditionally (for .some(...) return from index(where:)) use the result from index(where:) to remove and capture the removed element from the array (within an optional binding clause).
var elements = ["a", "b", "c", "d", "e", "a", "b", "c"]
if let firstSuchElement = elements.index(where: { $0 == "c" })
.map({ elements.remove(at: $0) }) {
// if we enter here, the first such element have now been
// remove from the array
print(elements) // ["a", "b", "d", "e", "a", "b", "c"]
// and we may work with it
print(firstSuchElement) // c
}
Note that in the contrived example above the array members are simple value types (String instances), so using a predicate to find a given member is somewhat over-kill, as we might simply test for equality using the simpler index(of:) method as shown in #DogCoffee's answer. If applying the find-and-remove approach above to the Person example, however, using index(where:) with a predicate is appropriate (since we no longer test for equality but for fulfilling a supplied predicate).
An array that contains a property that equals to
yourArray.contains(where: {$0.propertyToCheck == value })
Returns boolean.
The simplest way to accomplish this is to use filter on the array.
let result = elements.filter { $0==5 }
result will have the found element if it exists and will be empty if the element does not exist. So simply checking if result is empty will tell you whether the element exists in the array. I would use the following:
if result.isEmpty {
// element does not exist in array
} else {
// element exists
}
Swift 4/5
Another way to achieve this is with the filter function
var elements = [1,2,3,4,5]
if let object = elements.filter({ $0 == 5 }).first {
print("found")
} else {
print("not found")
}
As of Swift 2.1 NSArrays have containsObjectthat can be used like so:
if myArray.containsObject(objectImCheckingFor){
//myArray has the objectImCheckingFor
}
Array
let elements = [1, 2, 3, 4, 5, 5]
Check elements presence
elements.contains(5) // true
Get elements index
elements.firstIndex(of: 5) // 4
elements.firstIndex(of: 10) // nil
Get element count
let results = elements.filter { element in element == 5 }
results.count // 2
Just in case anybody is trying to find if an indexPath is among the selected ones (like in a UICollectionView or UITableView cellForItemAtIndexPath functions):
var isSelectedItem = false
if let selectedIndexPaths = collectionView.indexPathsForSelectedItems() as? [NSIndexPath]{
if contains(selectedIndexPaths, indexPath) {
isSelectedItem = true
}
}
if user find particular array elements then use below code same as integer value.
var arrelemnts = ["sachin", "test", "test1", "test3"]
if arrelemnts.contains("test"){
print("found") }else{
print("not found") }
Here is my little extension I just wrote to check if my delegate array contains a delegate object or not (Swift 2). :) It Also works with value types like a charm.
extension Array
{
func containsObject(object: Any) -> Bool
{
if let anObject: AnyObject = object as? AnyObject
{
for obj in self
{
if let anObj: AnyObject = obj as? AnyObject
{
if anObj === anObject { return true }
}
}
}
return false
}
}
If you have an idea how to optimize this code, than just let me know.
Swift
If you are not using object then you can user this code for contains.
let elements = [ 10, 20, 30, 40, 50]
if elements.contains(50) {
print("true")
}
If you are using NSObject Class in swift. This variables is according to my requirement. you can modify for your requirement.
var cliectScreenList = [ATModelLeadInfo]()
var cliectScreenSelectedObject: ATModelLeadInfo!
This is for a same data type.
{ $0.user_id == cliectScreenSelectedObject.user_id }
If you want to AnyObject type.
{ "\($0.user_id)" == "\(cliectScreenSelectedObject.user_id)" }
Full condition
if cliectScreenSelected.contains( { $0.user_id == cliectScreenSelectedObject.user_id } ) == false {
cliectScreenSelected.append(cliectScreenSelectedObject)
print("Object Added")
} else {
print("Object already exists")
}
what about using a hash table for the job, like this?
first, creating a "hash map" generic function, extending the Sequence protocol.
extension Sequence where Element: Hashable {
func hashMap() -> [Element: Int] {
var dict: [Element: Int] = [:]
for (i, value) in self.enumerated() {
dict[value] = i
}
return dict
}
}
This extension will work as long as the items in the array conform to Hashable, like integers or strings, here is the usage...
let numbers = Array(0...50)
let hashMappedNumbers = numbers.hashMap()
let numToDetect = 35
let indexOfnumToDetect = hashMappedNumbers[numToDetect] // returns the index of the item and if all the elements in the array are different, it will work to get the index of the object!
print(indexOfnumToDetect) // prints 35
But for now, let's just focus in check if the element is in the array.
let numExists = indexOfnumToDetect != nil // if the key does not exist
means the number is not contained in the collection.
print(numExists) // prints true
Swift 4.2 +
You can easily verify your instance is an array or not by the following function.
func verifyIsObjectOfAnArray<T>(_ object: T) -> Bool {
if let _ = object as? [T] {
return true
}
return false
}
Even you can access it as follows. You will receive nil if the object wouldn't be an array.
func verifyIsObjectOfAnArray<T>(_ object: T) -> [T]? {
if let array = object as? [T] {
return array
}
return nil
}
You can add an extension for Array as such:
extension Array {
func contains<T>(_ object: T) -> Bool where T: Equatable {
!self.filter {$0 as? T == object }.isEmpty
}
}
This can be used as:
if myArray.contains(myItem) {
// code here
}

Resources