Initialize KeyValuePairs from array, from dictionary - swift

According to the doc, KeyValuePairs is lightweight collection of key-value pairs. Much like an ordered Dictionary. KeyValuePairs has similar initializer to init from literal. But unlike it's unordered counterpart it lacks inits from sequences of key-value tuples. Can we add more inits, to obtain KeyValuePairs from Sequences, Collections or Dictionary or at least from array of tuples?
For example, I need a sorted version of such a Dictionary
static var allRegionIsoCodesWithNames: Dictionary<String, String> {
get {
return Dictionary(uniqueKeysWithValues: Locale.isoRegionCodes.map({ ($0, String.emojiFlag(for: $0)! + LWUserDefaults.locale.localizedString(forRegionCode: $0)!) }).sorted(by: { $0.0 < $1.0 }))
}
}
/* A useless code since ordering is not applyed */
I need this one to work
static var allRegionIsoCodesWithNamesKeyVal: KeyValuePairs<String, String>? {
get {
return Array(Locale.isoRegionCodes.map({ ($0, String.emojiFlag(for: $0)! + LWUserDefaults.locale.localizedString(forRegionCode: $0)!) }).sorted(by: { $0.0 < $1.0 }))
}
}
/* Cannot convert return expression of type '[(String, String)]' to return type 'KeyValuePairs<String, String>?' */
In order to reproduce here is the String.emojiFlag (for reference):
extension String {
static func emojiFlag(for countryCode: String) -> String? {
func isLowercaseASCIIEnglishLetterScalar(_ scalar: Unicode.Scalar) -> Bool {
return scalar.value >= Unicode.Scalar("a").value && scalar.value <= Unicode.Scalar("z").value
}
func regionalIndicatorSymbol(for scalar: Unicode.Scalar) -> Unicode.Scalar {
precondition(isLowercaseASCIIEnglishLetterScalar(scalar))
return Unicode.Scalar(scalar.value + (0x1F1E6 - 0x61))!
}
let lowercasedCode = countryCode.lowercased()
guard lowercasedCode.unicodeScalars.allSatisfy(isLowercaseASCIIEnglishLetterScalar) else {return nil }
let indicatorSymbols = lowercasedCode.unicodeScalars.map({ regionalIndicatorSymbol(for: $0) })
return String(indicatorSymbols.map({ Character($0) }))
}
}

Related

Sum value of an array of structs in Swift

I'm looking for some help on how to sum a value within an array of structs.
If I have a struct defined like this:
struct Item {
let value : Float
let name : String
let planDate : String
}
And then an array of these structs like this:
let dataArray = [Item(value:100, name:"apple", planDate:"2020-10-01"),
Item(value:200, name:"lemon", planDate:"2020-10-04"),
Item(value:300, name:"apple", planDate:"2020-10-04"),
Item(value:400, name:"apple", planDate:"2020-10-01")
]
How can I sum the value while grouping by the name and planDate as well as sorting by name and planDate?
Here's what I'd like to return:
let resultArray = [Item(value:500, name:"apple", planDate:"2020-10-01"),
Item(value:300, name:"apple", planDate:"2020-10-04"),
Item(value:200, name:"lemon", planDate:"2020-10-04")
]
The easiest way (well, easy is in the eye of the beholder) is to make a dictionary that groups by a composite of your criteria, name and planDate. Now for each entry in the dictionary you've got an array of all the Items that go together! So just sum their values. Now make the dictionary back into an array and sort it.
let dataArray = [Item(value:100, name:"apple", planDate:"2020-10-01"),
Item(value:200, name:"lemon", planDate:"2020-10-04"),
Item(value:300, name:"apple", planDate:"2020-10-04"),
Item(value:400, name:"apple", planDate:"2020-10-01")
]
let dict = Dictionary(grouping: dataArray) { $0.name + $0.planDate }
let dict2 = dict.mapValues { (arr:[Item]) -> Item in
let sum = arr.reduce(0) {
$0 + $1.value
}
return Item(value:sum, name:arr[0].name, planDate:arr[0].planDate)
}
let dataArray2 = dict2.values.sorted { ($0.name, $0.planDate) < ($1.name, $1.planDate) }
print(dataArray2)
I would take a different approach as well. First make your Item conform to Equatable and Comparable. Then you can reduce your sorted items, check if each item is equal to the last item of the result. If true increase the value otherwise append a new item to the result:
extension Item: Equatable, Comparable {
static func ==(lhs: Self, rhs: Self) -> Bool {
(lhs.name, lhs.planDate) == (rhs.name, rhs.planDate)
}
static func < (lhs: Self, rhs: Self) -> Bool {
(lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
}
}
let result: [Item] = items.sorted().reduce(into: []) { partial, item in
if item == partial.last {
partial[partial.endIndex-1].value += item.value
} else {
partial.append(item)
}
}
I'll offer another variant using Dictionary(_:uniquingKeysWith:):
let dict = Dictionary(dataArray.map { ($0.name + $0.planDate, $0) },
uniquingKeysWith: {
Item(value: $0.value + $1.value, name: $0.name, planDate: $0.planDate)
})
let result = dict.values.sorted {($0.name, $0.planDate) < ($1.name, $1.planDate)}
For completeness, here's a solution that explicitly makes use of the fact that you want to use name&planDate as both group identifier, and sort key.
You can make use of the Identifiable protocol, and build a struct with name&planDate (structs are almost free in Swift):
extension Item: Identifiable {
struct ID: Hashable, Comparable {
let name: String
let planDate: String
static func < (lhs: Item.ID, rhs: Item.ID) -> Bool {
(lhs.name, lhs.planDate) < (rhs.name, rhs.planDate)
}
}
var id: ID { ID(name: name, planDate: planDate) }
// this will come in handly later
init(id: ID, value: Float) {
self.init(value: value, name: id.name, planDate: id.planDate)
}
}
Then you can destructure the Item struct by its identifier, accumulate the values, and restructure it back:
let valueGroups = dataArray
.reduce(into: [:]) { groups, item in
// here we destructure the Item into id and value, and accumulate the value
groups[item.id, default: 0] += item.value
}
.sorted { $0.key < $1.key } // the key of the dictionary is the id, we sort by that
// and were we restructure it back
let result = valueGroups.map(Item.init(id:value:))
We can take it even further and refine the operations we need by extensing Sequence:
extension Sequence {
/// returns an array made of the sorted elements by the given key path
func sorted<T>(by keyPath: KeyPath<Element, T>) -> [Element] where T: Comparable {
sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
/// Accumulates the values by the specified key
func accumulated<K, T>(values: KeyPath<Element, T>,
by key: KeyPath<Element, K>) -> [K:T]
where K: Hashable, T: AdditiveArithmetic {
reduce(into: [:]) { $0[$1[keyPath: key], default: T.zero] += $1[keyPath: values] }
}
}
The two new additions, sort by a key path, and accumulate key paths by using another key, are independent enough to deserve function for they own, as they are generic enough to be reusable in other contexts.
The actual business logic becomes simple as
let result = dataArray
.accumulated(values: \.value, by: \.id)
.map(Item.init(id:value:))
.sorted(by: \.id)
Even if this solution is more verbose than the other one, it has the following advantages:
clear separation of concerns
breaking the code into smaller units, which can be independently unit tested
code reusability
simple caller code, easy to understand and review

Custom comparator for Swift

This is my code (simplified code):
struct SomeStruct {
let id: Int
let age: Int
}
extension SomeStruct: Hashable {
var hashValue: Int {
return id.hashValue * age.hashValue
}
static func ==(lhs: SomeStruct, rhs: SomeStruct) -> Bool {
return lhs.id == rhs.id && lhs.age == rhs.age
}
}
struct Calculator {
let struct1: [SomeStruct]
let struct2: [SomeStruct]
func uniqueById() {
let struct3 = Set(struct2).union(Set(struct1))
// I want to union it by property 'id' only.
// If the property 'id' is equal for both objects,
// the object in struct2 should be used (since that can have a different age property)
}
}
SomeStruct is a generated struct which I do not want to edit. I want to create a Set for SomeStruct that is based on 1 property: id. For that, I think I need a custom Comparator, just as Java has. Is there any Swifty way? This is the only thing I can come up with, but I am wondering if there is a better way:
struct SomeStructComparatorById: Hashable {
let someStruct: SomeStruct
var hashValue: Int {
return someStruct.id.hashValue
}
static func ==(lhs: SomeStructComparatorById, rhs: SomeStructComparatorById) -> Bool {
return lhs.someStruct.id == rhs.someStruct.id
}
}
First, I don't think this would work in Java. addAll() doesn't take a Comparator (nor does contains, etc.) Comparators are for sorting, not equality. Conceptually this is breaking how Set works in any language. Two items are not "equal" unless they can be swapped in all cases.
That tells us that we don't want a Set here. What you want here is uniqueness based on some key. That's a Dictionary (as Daniel discusses).
You could either just have a "id -> age" dictionary or "id -> struct-of-other-properties" dictionary as your primary data type (rather than using Array). Or you can turn your Array into a temporary Dictionary like this:
extension Dictionary {
init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>)
where S : Sequence, S.Element == Value {
let keys = values.map { $0[keyPath: keyPath] }
self.init(uniqueKeysWithValues: zip(keys, values))
}
}
And merge them like this:
let dict1 = Dictionary(struct1, uniquelyKeyedBy: \.id)
let dict2 = Dictionary(struct2, uniquelyKeyedBy: \.id)
let merged = dict1.merging(dict2, uniquingKeysWith: { old, new in old }).values
This leaves merged as [SomeStruct].
Note that this Dictionary(uniquelyKeyedBy:) has the same preconditions as Dictionary(uniqueKeysWithValues:). If there are duplicate keys, it's a programming error and will raise precondition failure.
You could do something like this:
var setOfIds: Set<Int> = []
var struct3 = struct2.filter { setOfIds.insert($0.id).inserted }
struct3 += struct1.filter { setOfIds.insert($0.id).inserted }
The result would be an array of SomeStruct, with all elements with unique ids.
You could define this as a custom operator :
infix operator *>
func *> (lhs: [SomeStruct], rhs: [SomeStruct]) -> [SomeStruct] {
var setOfIds: Set<Int> = []
var union = lhs.filter { setOfIds.insert($0.id).inserted }
union += rhs.filter { setOfIds.insert($0.id).inserted }
return union
}
Your code would then look like this:
func uniqueById() {
let struct3 = struct2 *> struct1
//use struct3
}
The short answer is no. Swift sets do not have any way to accept a custom comparator and if you absolutely must have a Set, then your wrapper idea is the only way to do it. I question the requirement for a set though.
Instead of using Set in your calculator, I recommend using dictionary.
You can use a Dictionary to produce an array where each item has a unique ID...
let struct3 = Dictionary(grouping: struct1 + struct2, by: { $0.id })
.compactMap { $0.value.max(by: { $0.age < $1.age })}
Or you can keep the elements in a [Int: SomeStruct] dictionary:
let keysAndValues = (struct1 + struct2).map { ($0.id, $0) }
let dictionary = Dictionary(keysAndValues, uniquingKeysWith: { lhs, rhs in
lhs.age > rhs.age ? lhs : rhs
})

Transforming [String : String] to [String : URL] and flattening out nil values

Say I have a dictionary of type [String : String] which I want to transform to type [String : URL]. I can use map or flatMap to transform the dictionary, but due to the failable URL(string:) initializer, my values are optional:
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com"]
let result = source.flatMap { ($0, URL(string: $1)) }
This returns a value of type [(String, URL?)] and not [String : URL]. Is there a one-liner to transform this dictionary with a single method? My first thought was something like:
source.filter { $1 != nil }.flatMap { ($0, URL(string: $1)!) }
But I don't need to check if the value is nil (values will never return nil on a dictionary concrete values), I need to check if the return value of URL(string:) is nil.
I could use filter to remove the nil values, but this doesn't change the return type:
source.flatMap { ($0, URL(string: $1)) }.filter { $1 != nil }
You need to make sure you're returning tuples with only non-optional values, and since optional values themselves support flatMap you can use that to make the tuple optional as opposed to the individual value inside of it:
let source = [
"google": "http://google.com",
"twitter": "http://twitter.com",
"bad": "",
]
var dict = [String: URL]()
source.flatMap { k, v in URL(string: v).flatMap { (k, $0) } }.forEach { dict[$0.0] = $0.1 }
But since we've already expanded out the dictionary creation (I don't think there's a built-in way to create a dict from an array), you might as well do this:
var dict = [String: URL]()
source.forEach { if let u = URL(string: $1) { dict[$0] = u } }
Here are a few solutions:
//: Playground - noun: a place where people can play
import Foundation
let source = ["google": "http://google.com", "twitter": "http://twitter.com", "bad": ""]
//: The first solution takes advantage of the fact that flatMap, map and filter can all be implemented in terms of reduce.
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
let result = source.reduce([String: URL]()) { result, item in
guard let url = URL(string: item.value) else { return result }
return result.updatedValue(url, forKey: item.key)
}
print(result)
//: This soultion uses a custom Dictionary initializer that consums the Key/Value tuple.
extension Dictionary {
// construct a dictionary from an array of key/value pairs.
init(items: [(key: Key, value: Value)]) {
self.init()
for item in items {
self[item.key] = item.value
}
}
}
let items = source
.map { ($0, URL(string: $1)) } // convert the values into URL?s
.filter { $1 != nil } // filter out the ones that didn't convert
.map { ($0, $1!) } // force unwrap the ones that did.
let result2 = Dictionary(items: items)
print(result2)
//: This solution also uses the above initializer. Since unwrapping optional values is likely a common thing to do, this solution provides a method that takes care of the unwrapping.
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value: OptionalType {
// Flatten [Key: Optional<Type>] to [Key: Type]
func flattenValues() -> Dictionary<Key, Value.Wrapped> {
let items = self.filter { $1.asOptional != nil }.map { ($0, $1.asOptional!) }
return Dictionary<Key, Value.Wrapped>(items: items)
}
}
let result3 = Dictionary(items: source.map { ($0, URL(string: $1)) }).flattenValues()
print(result3)
Daniel T's last solution is quite nice if you want to write it in a more functional style. I'd do it a bit differently with the primary difference being a method to turn a tuple of optionals into an optional tuple. I find that to be a generally useful transform, especially combined with flatMap.
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com", "fail" : ""]
// Dictionary from array of (key, value) tuples. This really ought to be built it
extension Dictionary {
public init(_ array: [Element]) {
self.init()
array.forEach { self[$0.key] = $0.value }
}
}
//Turn a tuple of optionals into an optional tuple. Note will coerce non-optionals so works on (A, B?) or (A?, B) Usefull to have variants for 2,3,4 tuples.
func raiseOptionality<A,B>(_ tuple:(A?, B?)) -> (A, B)? {
guard let a = tuple.0, let b = tuple.1 else { return nil }
return (a,b)
}
let result = Dictionary(source.flatMap { raiseOptionality(($0, URL(string: $1))) } )
Easy as pie if you just want a good, known URL in place of the bad ones.
Use
let source = ["google" : "http://google.com", "twitter" : "http://twitter.com", "bad": ""]
let defaultURL = URL(string: "http://www.google.com")! // or whatever you want for your default URL
let result = source.flatMap { ($0, URL(string: $1) ?? defaultURL) }

Case insensitive Dictionary in Swift

Given a Dictionary whose Key is of type String, is there a way to access the value in a case-insensitive manner? For example:
let dict = [
"name": "John",
"location": "Chicago"
]
Is there a way to call dict["NAME"], dict["nAmE"], etc. and stil get "John"?
A cleaner approach, swift 4:
extension Dictionary where Key == String {
subscript(caseInsensitive key: Key) -> Value? {
get {
if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
return self[k]
}
return nil
}
set {
if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
self[k] = newValue
} else {
self[key] = newValue
}
}
}
}
// Usage:
var dict = ["name": "John"]
dict[caseInsensitive: "NAME"] = "David" // overwrites "name" value
print(dict[caseInsensitive: "name"]!) // outputs "David"
Swift support multiple subscripting so you can take advantage of that to define a case-insensitve accessor:
extension Dictionary where Key : StringLiteralConvertible {
subscript(ci key : Key) -> Value? {
get {
let searchKey = String(key).lowercaseString
for k in self.keys {
let lowerK = String(k).lowercaseString
if searchKey == lowerK {
return self[k]
}
}
return nil
}
}
}
// Usage:
let dict = [
"name": "John",
"location": "Chicago",
]
print(dict[ci: "NAME"]) // John
print(dict[ci: "lOcAtIoN"]) // Chicago
This extension is limited to Dictionary whose Key is of type String (as lowercase is meaningless with other data types). However, Swift will complain about constraining a generic type to a struct. The protocol that is closest to String is StringLiteralConvertible.
Note that if you have 2 keys whose lowercase forms are identical, there's no guarantee which one you will get back:
let dict = [
"name": "John",
"NAME": "David",
]
print(dict[ci: "name"]) // no guarantee that you will get David or John.
The existing answers are fine, but the time complexity of lookups/insertions with those strategies deteriorates from O(1) to O(N) (where N is the number of objects in the dictionary).
To retain O(1) you may want to consider the following approach:
/// Wrapper around String which uses case-insensitive implementations for Hashable
public struct CaseInsensitiveString: Hashable, LosslessStringConvertible, ExpressibleByStringLiteral {
public typealias StringLiteralType = String
private let value: String
private let caseInsensitiveValue: String
public init(stringLiteral: String) {
self.value = stringLiteral
self.caseInsensitiveValue = stringLiteral.lowercased()
}
public init?(_ description: String) {
self.init(stringLiteral: description)
}
public var hashValue: Int {
return self.caseInsensitiveValue.hashValue
}
public static func == (lhs: CaseInsensitiveString, rhs: CaseInsensitiveString) -> Bool {
return lhs.caseInsensitiveValue == rhs.caseInsensitiveValue
}
public var description: String {
return value
}
}
var dict = [CaseInsensitiveString: String]()
dict["name"] = "John"
dict["NAME"] = "David" // overwrites "name" value
print(dict["name"]!) // outputs "David"
can use Collection's first(where:) to find first lowercased match from all keys mapped lowercased, then return the value from this result.
extension Dictionary where Key == String {
func valueForKeyInsensitive<T>(key: Key) -> T? {
let foundKey = self.keys.first { $0.compare(key, options: .caseInsensitive) == .orderedSame } ?? key
return self[foundKey] as? T
}
}
first(where:) is a much efficient way to filter or iterate over the large collection
reference:
https://developer.apple.com/documentation/swift/anybidirectionalcollection/2906322-first#
https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where
This should do the job with O(1) while also not allowing to add the same string with different casing (e.g. if you first insert Def it is not replaced by DEF). It also works for Substring if necessary. Note, that this solution is more memory effective, but comes at the cost at recomputing the string transformation and hash on every lookup of a string. If you need to look-up the same value frequently it might be worth to have an implementation which caches the hashValue.
struct CaseInsensitiveString<T: StringProtocol>: Hashable, Equatable, CustomStringConvertible {
var string: T
init(_ string: T) {
self.string = string
}
var description: String { get {
return string.description
}}
var hashValue: Int { get {
string.lowercased().hashValue
} }
func hash(into hasher: inout Hasher) {
hasher.combine(hashValue)
}
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.string.compare(rhs.string, options: .caseInsensitive) == .orderedSame
}
}
typealias SubstringCI = CaseInsensitiveString<String>
var codeMap = [SubstringCI: Int]()
let test = "Abc Def Ghi"
let testsub = test[test.firstIndex(of: "D")!...test.lastIndex(of: "f")!]
codeMap[SubstringCI(String(testsub))] = 1
print(codeMap.keys, codeMap[SubstringCI("Def")]!, codeMap[SubstringCI("def")]!)
codeMap[SubstringCI("DEF")] = 1
print(codeMap.keys, codeMap[SubstringCI("Def")]!, codeMap[SubstringCI("def")]!)

Swift: Avoid imperative For Loop

What I'm trying to accomplish in imperative:
var mapNames = [String]()
var mapLocation = [String]()
for valueMap in valueMaps {
if let name = valueMap.name {
mapNames.append(name)
}
if let location = valueMap.location {
mapLocation.append(location)
}
}
What's the best way using a high order function or perhaps an array method (array.filter etc.) to compact the code above and also avoid using the for loop
Here is what I have tried, but the compiler gives an error:
let getArrayOfNames = valueMaps.filter() {
if let name = ($0 as valueMaps).name as [String]! {
return name;
}
}
let getArrayOfLocations = valueMaps.filter() {
if let type = ($0 as valueMaps).location as [String]! {
return type;
}
}
You need both filter() and map() :
let mapNames = valueMaps.filter( {$0.name != nil }).map( { $0.name! })
let mapLocations = valueMaps.filter( {$0.location != nil }).map( { $0.location! })
The filter takes a predicate as an argument (which specifies which
elements should be included in the result), and the map takes
a transformation as an argument. You were trying to merge both
aspects into the filter, which is not possible.
Update: As of Swift 2(?) has a flatMap() method for sequences, which
can be used to obtain the result in a single step:
let mapNames = valueMaps.flatMap { $0.name }
The closure is applied to all array elements, and the return value is an
array with all non-nil unwrapped results.
The filter() function needs its closure to return a bool - not the value you want to store in an array. You could chain filter and map together to get what you want, then:
let getArrayOfNames = valueMaps
.filter { $0.name != nil }
.map{ $0.name! }
Or, to do it in one function, with reduce:
let getArrayOfNames = valueMaps
.reduce([String]()) {
accu, element in
if let name = element.name {
return accu + [name]
} else {
return accu
}
}
Actually, the reduce can be a little better:
let getArrayOfNames = valueMaps.reduce([String]()) {
(names, value) in names + (value.name.map{[$0]} ?? [])
}
let getArrayOfLocations = valueMaps.reduce([String]()) {
(locs, value) in locs + (value.location.map{[$0]} ?? [])
}