Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift map(_:) extension for Set() ?

  let numberSet = Set(1...11)
  let divideSet = numberSet.map({  $0 / 10 }) 
  //Error: Set does not have a member named map   :(

Swift 1.2 supports Set() for unordered collections, but map(_:) doesn't seem to work on Sets, so i decide to get smart on my playground and tried:

 let stringSet = Set(map(numberSet, { String($0)}))
 println(stringSet)
 stringSet =  ["2", "11", "1", "8", "6", "4", "3", "9", "7", "10", "5]

This seemed to work. So I tried extending Set:

    extension Set {
        func map<U>(transform: (T) -> U) -> Set<U> {
        return Set(Swift.map(self, transform))  }
    }
   Error: "couldn't find initialiser for Set(T) that accepts argument of type U"

And i think there is a good reason why it doesn’t work, like this example here:

   let smarDividSet = Set(map(numberSet, {$0 / 2})) 
   println(smarDividSet)
   smartDividSet = "[5, 0, 2, 4, 1, 3]” 
  //Somehow elements is the Set are going missing.

Any ideas on how to extend Set to use map(_:) reliably ?. Thank guys.

like image 941
Blessing Lopes Avatar asked Mar 17 '15 19:03

Blessing Lopes


People also ask

How define set in Swift?

Swift makes it as easy to create a new set as to create a new array. Simply assign an array literal to a variable or constant with the Set type specified. let ingredients: Set = ["cocoa beans", "sugar", "cocoa butter", "salt"] if ingredients.

What is a set map?

A "map of sets" just means a map that doesn't necessarily bother to preserve any structure. It's best explained by contrast: a "map of groups" is a map that preserves the group operation. A "map of rings" is a map that preserves both ring operations.

What is .map in Swift?

Swift version: 5.6. The map() method allows us to transform arrays (and indeed any kind of collection) using a transformation closure we specify. The return value will be an array of the same size, containing your transformed elements.


2 Answers

Update: Quite a lot changed with Swift 2 and 3. The generic placeholder of Set is now Element instead of T, and all collections have a map() method which returns an array.

There were also good arguments given about the problems of a Set -> Set mapping (such as different elements mapping to the same result). On the other hand, there may be a use-case for such a mapping, so here is an update for Swift 3 (now using a different name).

extension Set {
    func setmap<U>(transform: (Element) -> U) -> Set<U> {
        return Set<U>(self.lazy.map(transform))
    }
}

Example:

let numberSet = Set(1...11)
let divideSet = numberSet.setmap { $0 / 2 }
print(divideSet) // [5, 0, 2, 4, 1, 3]

(Old answer:) You were almost there. For some reason, the generic type of the returned set must be specified explicitly:

extension Set {
    func map<U>(transform: (T) -> U) -> Set<U> {
        return Set<U>(Swift.map(self, transform))
    }
}

Example:

let numberSet = Set(1...11)

let divideSet = numberSet.map { $0 / 2 }
println(divideSet) // [5, 0, 2, 4, 1, 3]

The resulting set has less elements because the integer division $0 / 2 truncates the quotient, e.g. both 4/2 and 5/2 map to the same element 2. This does not happen with floating point division:

let floatdivideSet = numberSet.map { Double($0) / 2.0 }
println(floatdivideSet) // [4.0, 5.0, 4.5, 5.5, 2.0, 3.0, 3.5, 2.5, 1.5, 1.0, 0.5]

Another possible implementation is

extension Set {
    func map<U>(transform: (T) -> U) -> Set<U> {
        return Set<U>(lazy(self).map(transform))
    }
}

Here lazy(self) returns a LazyForwardCollection which has a map() method and that returns a LazyForwardCollection again. The advantage might be that no intermediate array is created.

like image 51
Martin R Avatar answered Sep 30 '22 07:09

Martin R


The issue with Set.map(_:) is the same with Dictionary.map(_:), and they didn't implement it in the Swift module (standard library) because there's actually no correct way to implement it. The reason is: mapping isn't just enumerating (which you can do with any SequenceType in a for-in), but it's transforming (with the argument closure) each value into another. So you would expect that the result would have the all the transform(element) in it, but guess what, if the values are the same they collapse (for Dictionarys only the keys behave this way).

e.g. (with the proposed implementation) Set([1, 2, 3, 4, 5]).map { 1 }.count == 1

That's also why Swift.map returns an Array and not the same type of SequenceType/CollectionType passed as the source argument.

To digress slightly, Dictionary (as said before) has the same issue on the key values (which is basically a Set), so any map that works either on the Set<K> or the Set<(K, V)> doesn't ensure a consistent mapping, but one that changes the values only would be ok. It wouldn't be a true map though since Dictionarys are collections of tuples, not on value only. So there could be something similar to mapValues which would be correct, but still not a true map on the collection.

TL;DR

map is really useful but be careful on what you're actually doing, because you can't really do a map from Set to Set.
like image 43
DeFrenZ Avatar answered Sep 30 '22 06:09

DeFrenZ