How to Define an Extension to Collectiontype So That Its Methods Are Available to Dictionaries

How can I define an extension to CollectionType so that its methods are available to dictionaries?

This works for my current use case:

extension CollectionType where Self: DictionaryLiteralConvertible, Self.Key: StringLiteralConvertible, Self.Value: Encodable, Generator.Element == (Self.Key, Self.Value) { ... }

Extending an array of dictionaries using Swift 2.2

Xcode 8 beta • Swift 3

protocol Encodable {
var hashValue: Int { get }
}
extension Int: Encodable { }

extension Collection where Iterator.Element == [String: Encodable] {
var total: Int {
return reduce(0) {$0 + $1.values.reduce(0) {$0 + $1.hashValue}}
}
}

let dictionaries:[[String: Encodable]] = [["One":1],["Two":2],["Three":3, "Four":4]]

dictionaries.total // 10

Xcode 7.3.1 • Swift 2.2.1

protocol Encodable {
var hashValue: Int { get }
}

extension Int: Encodable { }

extension CollectionType where Generator.Element == [String: Encodable] {
var total: Int {
return reduce(0) {$0 + $1.values.reduce(0) {$0 + $1.hashValue}}
}
}

let dictionaries:[[String: Encodable]] = [["One":1],["Two":2],["Three":3, "Four":4]]

dictionaries.total // 10

CollectionType extension with return Type Self instead of Array

There is no promise that an arbitrary CollectionType can be instantiated or copied. So there's no way to implement your extension. A good way to see this is to try to implement this for FlattenCollection or LazyCollection. Or try creating a RandomValueCollection and then try to implement this method.

But you're free to do this on a RangeReplaceableCollectionType, which makes all the promises you need:

extension RangeReplaceableCollectionType {
func otherMap(block: Generator.Element -> Generator.Element) -> Self {
var copy = Self.dynamicType.init()
self.forEach {
copy.append(block($0))
}
return copy
}
}

Not all collections conform to RangeReplaceableCollectionType. There probably is a good reason Set doesn't, so you may have to create a simpler protocol of your own.

Remember, a CollectionType may represent some static thing that isn't meaningful or safe to copy. For example, I might make a CollectionType that represents "the files on this disk." Copying it could invalidate invariants (such as assumptions about caches and file pointers). There's no promise that a CollectionType is a value type. It is free to be a reference.

Extending CollectionType in Swift 2

'Key' and 'Value' are placeholders in Dictionary, so you can't access these in the method signature for a CollectionType protocol extension - so to get this to work you have to do a cast.

It seems slightly wrong to me, but I think you can just about get it to work with something like this:

extension Dictionary : CollectionAccess {
func getElement<T : Hashable>(key: T) -> Dictionary.Generator.Element? {
if key.self is Key.Type {
let tKey = key as! Key
if let value = self[tKey] {
return (tKey, value)
}
}
return nil
}
}

The thing is, CollectionType doesn't have a placeholder that corresponds to Dictionary's 'Key' placeholder. Not all CollectionType's consist of key value pairs, so therefore it doesn't make sense to make CollectionType have a Key / Value type method. For example another CollectionType is Array - what would getElement return for an array?

extension of Dictionary where String, AnyObject

>=3.1

From 3.1, we can do concrete extensions, ie:

extension Dictionary where Key == String {}

<3.1

We can not conform concrete types w/ concrete generics, ie:

extension Dictionary where Key == String

However, because Dictionary conforms to sequence and we can conform protocol types w/ concrete generics, we could do:

extension Sequence where Iterator.Element == (key: String, value: AnyObject) {
func doStuff() {...

Otherwise, we can constrain our key to a protocol that string conforms to like this:

extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
var jsonString: String {
return ""
}
}

As per your updated answer. Json serialization needs an object, Swift Dictionaries are structs. You need to convert to an NSDictionary You must specify Key to conform to NSObject to properly convert to an NSDictionary.

Small note: Dictionaries already type constrain Key to be Hashable, so your original constraint wasn't adding anything.

extension Dictionary where Key: NSObject, Value:AnyObject {

var jsonString:String {

do {
let stringData = try NSJSONSerialization.dataWithJSONObject(self as NSDictionary, options: NSJSONWritingOptions.PrettyPrinted)
if let string = String(data: stringData, encoding: NSUTF8StringEncoding){
return string
}
}catch _ {

}
return ""
}
}

Note, that the dictionaries must conform to this type to access the extension.

let dict = ["k" : "v"]

Will become type [String : String], so you must be explicit in declaring:

let dict: [NSObject : AnyObject] = ["k" : "v"]

Swift Array of dictionaries extension sortInPlace

First issue: you are comparing strings, not dates. Fortunately, your string's format make it directly comparable as both a string and the date value it represents. Hence, you don't need to convert it to NSDate at all.

The second issue is that typecasting Dictionary<Key,Value1> to Dictionary<Key,Value2> wholesale doesn't work, even when Value1 is covariant on Value2. It may work in trivial example like this...

let a : [String: String] = ["name": "David", "location": "Chicago"]
let b = a as [String: AnyObject]

...because the compiler can see the value type (String) in a and optimize it by compile time. For dynamic situations like yours, there's no information available ahead of time.


When you need dynamism, you can always go back to the old friend, Objective-C:

extension Array where Element: CollectionType {
mutating func sortDictionariesByDate(dateKey: String) {
self.sortInPlace {
if let a = $0 as? NSDictionary, b = $1 as? NSDictionary {
return (a[dateKey] as? String) < (b[dateKey] as? String)
} else {
return false
}
}
}
}

// Example:
var sampleArray1: [[String: AnyObject]] = [
["date": "2015-10-24T13:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T14:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T10:00:00.000Z", "foo": "bar"]
]
var sampleArray2: [[String: String]] = [
["date": "2015-10-24T13:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T14:00:00.000Z", "foo": "bar"],
["date": "2015-10-24T10:00:00.000Z", "foo": "bar"]
]

sampleArray1.sortDictionariesByDate("date")
sampleArray2.sortDictionariesByDate("date")

Note that since you are now comparing strings rather than date, no NSDateFormatter is needed.

Adding an element to a collection type without changing the collection type in Python

You could do this and it will work for all of the standard collection types:

def add_item(self, item):
# You could use append here, and it'd be faster
temporary_list = list(self.items) + [item]
self.items = type(self.items)(temporary_list)

This works because type(x) returns the "type" of that object. To extract out the actual type string ("list", "set", "tuple", etc.), we have to do some string manipulation. We can then construct a statement equivalent to:

# For lists
self.items = list(temporary_list)
# For sets
self.items = set(temporary_list)
# And so on...

EDIT: I have no idea why I was downvoted. Stackoverflow advises against downvoting without explaining why, just so you know.

Single extension method on IDictionaryK, IEnumerable/IList/ICollectionV

Because all collections implement IEnumerable<T>, we can just use it instead of the TCollection type paramter. Unfortunately the type inference does not know this. This is the code I wrote:

public static ILookup<TKey, TValue> ToLookup<TKey, TValue>
(this IDictionary<TKey, IEnumerable<TValue>> dict)
{
return dict.SelectMany(p => p.Value.Select
(v => new KeyValuePair<TKey, TValue>(p.Key, v)))
.ToLookup(p => p.Key, p => p.Value);
}

There seems to be no way of making the type inference work, but this method will work if you cast the Dictionary:

((IDictionary<int, IEnumerable<int>>)dictOfLists).ToLookup()

Also you can add Lists to a Dictionary of IEnumerables and cast them back when you need them.



Related Topics



Leave a reply



Submit