Mapping swift enum with associated values - arrays

Let say we have an enum with associated value types. In the example below the two value types are simple object that hold an image and a url to share.
enum Content {
case Image(ShareableImage)
case Video(ShareableVideo)
}
Now let's have an array of video and image cases.
let media: [Content] = [*a lot of enum cases inside here*]
All the code above so far cannot be changed in any way in the codebase, I need to work with it.
Here starts my problem:
Let's filter the array with media to only image cases
let imageOnlyCases: [Content] = media.filter { item -> Bool in
switch item {
case .Image: return true
default: return false
}
}
Next step, I want to get from array of enum to an array of their associated values
[Content] -> [ShareableImage] by using map.
so I do this
let shareablemages = imageOnlyCases.map { imageCase -> ShareableImage in
switch imageCase {
case .Image(let image): return image
default: return WHAT TO DO HERE?
}
}
You see, I have a problem with return type..I know that the enum cases are all .Image..and I want a simple map. But the swift syntax is not helping me.
Any ideas?

You could return image for case .Image, and nil otherwise, within a .flatMap operation (to "filter" out nil entries):
/* Example */
enum Foo {
case Bar(Int)
case Baz(Int)
}
let foo: [Foo] = [.Bar(1), .Bar(9),. Baz(3), .Bar(39), .Baz(5)]
/* 1. using 'switch' */
let barOnlyValues: [Int] = foo.flatMap {
switch $0 {
case .Bar(let val): return val
case _: return nil
}}
/* 2. alternatively, as pointed out in MartinR:s answer;
as you're only looking for a single case, the alternative
'if case let' clause could be preferred over 'switch': */
let barOnlyValuesAlt: [Int] = foo.flatMap {
if case let .Bar(val) = $0 { return val }
else { return nil }}
print(barOnlyValues) // [1, 9, 39]
Applied to your use case: note that you needn't perform the filtering to create the imageOnlyCases array, as you can apply the above directly on the media array:
/* 1. using switch */
let shareableImages : [ShareableImage] = media.flatMap {
switch $0 {
case .Image(let image): return image
case _: return nil
}}
/* 2. 'if case let' alternative, as per MartinR:s suggestion */
let shareableImagesAlt : [ShareableImage] = media.flatMap {
if case let .Image(image) = $0 { return image }
else { return nil }}
Disclaimer: I cannot verify your specific use case in practice as I don't have access to the ShareableImage class/struct.
(Thanks #MartinR for advice that .map{ ... }.flatMap{ ... } can be simplified to just .flatMap{ ... }).

If it is guaranteed that only the .Image case can occur then
you can call fatalError() in all other cases:
let shareableImages = imageOnlyCases.map { imageCase -> ShareableImage in
if case let .Image(image) = imageCase {
return image
} else {
fatalError("Unexpected content")
}
}
fatalError() causes the program to terminate immediately. It is
only meant for situations that "cannot occur", i.e. to find programming
errors.
It satisfies the compiler because the function is marked as #noreturn.
If you cannot make that guarantee then use flatMap() as suggested
in the other answer.
Note also that you can use if case here with a pattern instead
of switch/case.

Related

Swift: Sort array of objects alphabetically, but Put Objects Matching a particular Field First

I have this struct of locations:
struct Location: Identifiable, Codable {
var id: String
var name: String
var country: String
}
I can easily sort this by name:
self.userData = self.userData.sorted(by: {$0.name < $1.name })
But I also want the ability to put locations with a particular country first in the list.
I tried this:
self.userData.sorted(by: { ($0.country == "United States"), ($0.name < $1.name) })
but I get an error "Consecutive statements on a line must be separated by ;".
How can I sort alphabetically by a particular country first? Then sort the remaining locations alphabetically by the name property.
You probably still want to sort by name if both locations have a country of "United States".
let topCountries: Set<String> = ["United States"]
userData.sorted { a, b in
switch (topCountries.contains(a.country), topCountries.contains(b.country)) {
case (true, false): return true
case (false, true): return false
default: return a.name < b.name
}
}
Look at the full syntax of passing a closure. You are using a shortcut for the special case that the closure consists of one return statement only. Expand it to the full closure syntax where you can use arbitrary code, then write the code you need.
The solution #rob mayoff gave worked. Here is my final piece of code:
let topCountries: Set<String> = ["United States"] // or the user's country
filteredLocations = allLocations.filter { $0.name.contains(searchText) }.sorted { a, b in
switch (topCountries.contains(a.country), topCountries.contains(b.country)) {
case (true, false): return true
case (false, true): return false
default: return a.name < b.name
}
}

Swift Function to Return Array Reference

I'm fairly new to Swift, so be gentle...
I have a class, called ProgramOptions, as so:
class ProgramOptions {
var startDate: Date = Date()
var trackedActivities = [TrackedItem]()
var trackedFoods = [TrackedItem]()
var trackedDrugs = [TrackedItem]()
...
}
TrackedItem is another class of mine.
In a TableViewController's code I want to select one of the arrays from an instance of ProgramOptions, based on what section of the table is in question. I want to then do many possible things, like remove or add items, edit them, etc.
Being a Swift beginner, I naively wrote this function:
func trackedArrayForSection(_ section: Int) -> [TrackedItem]? {
switch(section) {
case 1: return programOptions.trackedActivities
case 2: return programOptions.trackedFoods
case 3: return programOptions.trackedDrugs
default: return nil
}
}
(Section 0 and Sections > 3 don't have associated arrays so I return nil)
But then harsh reality bit me. I guess the array is a copy. (Or my weak understanding of similar question on StackOverflow indicates that it is sometimes copied.)
So here's the question for you... How could I write my trackedArrayForSection so that I get a reference to actual array sitting in ProgramOptions that I can then add and remove items from?
I could, of course, have a switch statement every place I use this, but there are close to a zillion of them and I'd like to avoid that. I'm assuming there is an easy answer to this and I'm just too ignorant at this point to know it.
Thanks for your help!
I would extract your switch logic to an instance method of your class:
class ProgramOptions {
var startDate: Date = Date()
private var trackedActivities = [TrackedItem]()
private var trackedFoods = [TrackedItem]()
private var trackedDrugs = [TrackedItem]()
func trackedItems(forSection section: Int) -> [TrackedItem]? {
switch section {
case 1: return self.trackedActivities
case 2: return self.trackedFoods
case 3: return self.trackedDrugs
default: return nil
}
}
func add(trackedActivity activity: TrackItem){
self.trackedActivities.append(activity)
}
}
and then in you view you only have to reload the table.
One possibility is to create a wrapper class to store the array of TrackedItems. Say, for example:
class TrackedItemCollection {
var items = Array<TrackedItem>()
}
and then in your implementation:
class ProgramOptions {
var startDate: Date = Date()
var trackedActivities = TrackedItemCollection()
var trackedFoods = TrackedItemCollection()
var trackedDrugs = TrackedItemCollection()
...
}
func trackedCollectionForSection(_ section: Int) -> TrackedItemCollection? {
switch(section) {
case 1: return programOptions.trackedActivities
case 2: return programOptions.trackedFoods
case 3: return programOptions.trackedDrugs
default: return nil
}
}
When you want the array, you use the items property in TrackedItemCollection. As TrackedItemCollection is a class and thus a reference type, you won't have the arrays copyed.

Swift - Get first item in array, and return element and indices

I want to return the first item in a given array of custom objects and return an array of indices and the first custom object element so I can wrap a guard statement around it.
ie:
let firstOrder = existingOrders.enumerated().flatMap{$0,$1}.first
or attempt #1
let array = existingOrders.enumerated().map { (a, b) in return [$0.index : $1.element] }.first
or attempt #2
let array = existingOrders.enumerated().map { ($0.offset, $0.element) }.first
print (array)
This isn't returning the actual object; and it seems to return a tuple.
where
existingOrders = [ExistingOrder, EngineYard.ExistingOrder, EngineYard.ExistingOrder]
it returns the following;
[(0, EngineYard.ExistingOrder), (1, EngineYard.ExistingOrder), (2, EngineYard.ExistingOrder)]
attempt #3;
let array = existingOrders.enumerated().map { ($0.offset, $0.element) }
print (array)
guard let firstOrder = array.first else {
break
}
print (firstOrder) // should be a tuple of index and custom object
How do I grab the optional first item in an array and return index and element?
Many thanks
Edit. the reason I'm doing this is so that I can transfer the correct object to another class.
// transfer all
while (factory.existingOrders.count > 0) {
let array = myOrderBook.existingOrders.enumerated().map { ($0.offset, $0.element) }
guard let firstOrder = array.first else {
break
}
let index = (firstOrder.0)
factory.orderBook.transfer(index: index, destination: .completedOrder)
}
Where the Orderbook is a class;
Factory {
var orderBook:OrderBook = OrderBook()
}
OrderBook {
var existingOrders: [ExistingOrder] = [ExistingOrder]()
var completedOrders: [CompletedOrder] = [CompletedOrder]()
}
And the idea is that I want to transfer an object from existing orders to completed orders and vice versa
The function requires an index, but I guess I could refactor it so I can transfer an object instead.
The answer I was looking for was;
let array = myOrderBook.existingOrders.enumerated().map { ($0.offset, $0.element) }
However, I found that my code needed to be refactored.
Thanks.
Issue closed.

How to create a predicate to filter array of enums with associated values in Swift?

enum EnumType {
case WithString(String)
}
var enums = [EnumType]()
enums.append(EnumType.WithString("A"))
enums.append(EnumType.WithString("B"))
enums.append(EnumType.WithString("C"))
enums.append(EnumType.WithString("D"))
enums.append(EnumType.WithString("E"))
enums.append(EnumType.WithString("F"))
How to filter my enums array to find the one with associated value equal C. What predicate do I need to use?
The filter function can either be invoked as a global function over an array, or as an instance method (I prefer the later as it is more OO).
It accepts a closure with one parameter (the element being evaluated) that return a boolean (indicating whether the element matches the required condition).
Since it's a simple closure in a clear situation it's ok to use the abbreviated form.
I guess that other "With" cases would be added to your enum, so you could go with something like:
let filteredArray = enums.filter {
switch $0 {
case let .WithString(value):
return value == "C"
default:
return false
}
}
That should do the trick in your example.
As someone already mentioned, for Swift > 2.0 there's if case statement available:
enums.filter {
if case .WithString("C") = $0 {
return true
}
return false
}
But, it doesn't look nice, especially if you are going to repeat same logic again. What we can do here is to make EnumType conform to Equatable:
extension EnumType: Equatable {
}
func ==(lhs: EnumType, rhs: EnumType) -> Bool {
switch (lhs, rhs) {
case (.WithString(let lStr), .WithString(let rStr)):
return lStr == rStr
}
}
And now you can just:
enums.filter { $0 == .WithString("C") }
You could try adding a simple extension with a computed property to your enum and filter to that property:
extension EnumType {
var isC: Bool {
switch self {
case .WithString(let message): return message == "C"
default: return false
}
}
}
After this, you could simpy use the filtering as usual:
enums.filter { $0.isC }
var filteredArray = enums.filter { element in
switch element {
case EnumType.WithString(let string):
return string == "A"
default:
return false
}
}
This can probably be simplified with Swift 2.0 binding of assosciated values in if statements.
Inspired by #Jessy and SwiftLee, here is my solution:
// -----------------------
// CaseReflectable
// -----------------------
// designed for enums only
// (use it on other types not recommended)
protocol CaseReflectable {}
// default behaviors.
extension CaseReflectable {
/// case name
var caseName: String {
let mirror = Mirror(reflecting: self)
// enum cases:
// - normal case: no children
// - case with associated values: one child (with label)
guard let label = mirror.children.first?.label else {
return "\(self)" // normal case
}
// case with associated values
return label
}
/// associated values
var associatedValues: Any? {
// if no children, a normal case, no associated values.
guard let firstChild = Mirror(reflecting: self).children.first else {
return nil
}
return firstChild.value
}
}
// --------------------------
// custom operator ~=
// --------------------------
/// match enum cases with associated values, while disregarding the values themselves.
/// usage: `Enum.enumCase ~= instance`
func ~= <Enum: CaseReflectable, AssociatedValue>(
// an enum case (with associated values)
enumCase: (AssociatedValue) -> Enum, // enum case as function
// an instance of Enum
instance: Enum
) -> Bool
{
// if no associated values, `instance` can't be of `enumCase`
guard let values = instance.associatedValues else { return false }
// if associated values not of the same type, return false
guard values is AssociatedValue else { return false }
// create an instance from `enumCase` (as function)
let case2 = enumCase(values as! AssociatedValue)
// if same case name, return true
return case2.caseName == instance.caseName
}
// ------------
// Enum
// ------------
// enum with associated values
// (conforms to `CaseReflectable`)
enum Enum: CaseReflectable {
case int(Int)
case int2(Int)
case person(name: String, age: Int)
case str(String)
}
// ------------
// main
// ------------
let a: Enum = .int(3)
Enum.int ~= a // true
Enum.int2 ~= a // false
let joe = Enum.person(name: "joe", age: 8)
Enum.person ~= joe // true
Enum.int ~= joe // false
// array of enum cases
let items: [Enum] = [
.int(1), .str("hi"), .int(2)
]
// filter enum cases
let filtered = items.filter { Enum.int ~= $0 }
print(filtered) // [Enum.int(1), Enum.int(2)]
You can achieve something more reusable by implementing Equatable protocol:
enum EnumType {
case WithString(String)
}
extension EnumType: Equatable {
static func ==(lhs: EnumType, rhs: String) -> Bool {
switch lhs {
case .WithString(let value):
return value == rhs
}
}
}
EnumType.WithString("F") == "A" // false
EnumType.WithString("F") == "F" // true

Enum with array of string

I'm a newbie in Swift and I found on the internet a utiliy class to handle errors when I use Objective C classes from swift. Here the utility class, which is a enum:
enum Result<A> {
case Success(Box<A>), Error(NSError)
static func success(v: A) -> Result<A> {
return .Success(Box(v))
}
static func error(e: NSError) -> Result<A> {
return .Error(e)
}
}
final class Box<A> {
let value: A
init(_ value: A) {
self.value = value
}
}
I have a function that returns an Array of strings of type result
func getFiles(account:DBAccount, curFolder:String) ->Result<[String]>{ ...}
Now how can I access the results in an easy way. A println on the console gives me (Enum Value).
I tried to get the results using the following:
let dicList = getFiles(account, currFolder)
switch dicList {
case let .Success(aBox): results = aBox.value
case let .Error(err): results = []
}
now the array results contains the data, but is there no easier way to access the results.
thanks
arnold

Resources