Related
Been racking my brains and can't seem to come up with an elegant solution. Wondering if anyone can help me.
I have a Swift dictionary with an array of values for each key like so...
[A:[1, 2, 3], B:[4, 5, 6], C:[7, 8, 9]]
And I need to convert it into an array of dictionaries with every combination of values like so...
[
[A:1, B:4, C:7],
[A:1, B:4, C:8],
[A:1, B:4, C:9],
[A:1, B:5, C:7],
[A:1, B:5, C:8],
[A:1, B:5, C:9],
[A:1, B:6, C:7],
[A:1, B:6, C:8],
[A:1, B:6, C:9],
[A:2, B:4, C:7],
[A:2, B:4, C:8],
[A:2, B:4, C:9],
[A:2, B:5, C:7],
[A:2, B:5, C:8],
[A:2, B:5, C:9],
[A:2, B:6, C:7],
...
[A:3, B:6, C:9],
]
Here's a recursive solution that works for any number of key/array pairs in the original dictionary, and for any number of values in the values arrays:
let dict = ["A":[1, 2, 3], "B":[4, 5, 6], "C":[7, 8, 9]]
func combine(source: [String : [Int]], partials: [[String : Int]] = [], final: inout [[String : Int]]) {
if source.isEmpty {
// base step of recursion
// there are no more (k:v) pairs in source, so add all of the partials (now final)
// to the final array
final.append(contentsOf: partials)
} else {
// source has a (k:v) pair, so take the first one
let (k, vals) = source.first!
var newsource = source
// remove key from newsource
newsource[k] = nil
// for each value in the key
for val in vals {
// add (k:v) to each partial dictionary
var newpartials = partials
// If new partials array is empty
if newpartials.isEmpty {
// create the array with the first [k:v]
newpartials = [[k : val]]
} else {
// otherwise, add [k:v] to each of the partial dictionaries
for pidx in newpartials.indices {
newpartials[pidx][k] = val
}
}
// recursive call to process the next value in source
combine(source: newsource, partials: newpartials, final: &final)
}
}
}
var result = [[String : Int]]()
combine(source: dict, final: &result)
print(result)
print(result.count)
[["A": 1, "B": 4, "C": 7], ["A": 2, "B": 4, "C": 7], ["A": 3, "B": 4, "C": 7], ["C": 7, "B": 5, "A": 1], ["C": 7, "B": 5, "A": 2], ["C": 7, "B": 5, "A": 3], ["C": 7, "B": 6, "A": 1], ["C": 7, "B": 6, "A": 2], ["C": 7, "B": 6, "A": 3], ["C": 8, "B": 4, "A": 1], ["C": 8, "B": 4, "A": 2], ["C": 8, "B": 4, "A": 3], ["C": 8, "B": 5, "A": 1], ["C": 8, "B": 5, "A": 2], ["C": 8, "B": 5, "A": 3], ["B": 6, "C": 8, "A": 1], ["B": 6, "C": 8, "A": 2], ["B": 6, "C": 8, "A": 3], ["A": 1, "C": 9, "B": 4], ["A": 2, "C": 9, "B": 4], ["A": 3, "C": 9, "B": 4], ["B": 5, "A": 1, "C": 9], ["B": 5, "A": 2, "C": 9], ["B": 5, "A": 3, "C": 9], ["B": 6, "A": 1, "C": 9], ["B": 6, "A": 2, "C": 9], ["B": 6, "A": 3, "C": 9]]
27
A generic solution
There's no reason to limit this to just String and Int. The keys should be any type that conforms to Hashable.
To make the function generic, change the signature to:
func combine<KEY, VALUE>(source: [KEY : [VALUE]], partials: [[KEY : VALUE]] = [],
final: inout [[KEY : VALUE]]) where KEY: Hashable {
Making a Dictionary extension
#LeoDabus extended this answer by turning it into an extension on Dictionary where the Value is a Collection (Thanks, Leo!):
extension Dictionary where Value: Collection {
func permutations() -> [[Key: Value.Element]] {
guard !isEmpty else { return [] }
var permutations: [[Key: Value.Element]] = []
permutate(&permutations)
return permutations
}
private func permutate(_ permutations: inout [[Key: Value.Element]], _ dictionaries: [[Key: Value.Element]] = []) {
if let (key, value) = first {
var dictionary = self
dictionary[key] = nil
for element in value {
var dictionaries = dictionaries
if dictionaries.isEmpty {
dictionaries += CollectionOfOne([key: element])
} else {
for index in dictionaries.indices {
dictionaries[index][key] = element
}
}
dictionary.permutate(&permutations, dictionaries)
}
} else {
permutations += dictionaries
}
}
}
let dict = ["A":[1, 2, 3], "B":[4, 5, 6], "C":[7, 8, 9]]
let result = dict.permutations()
print(result)
print(result.count)
[["B": 4, "A": 1, "C": 7], ["B": 4, "A": 2, "C": 7], ["B": 4, "A": 3, "C": 7], ["A": 1, "B": 4, "C": 8], ["A": 2, "B": 4, "C": 8], ["A": 3, "B": 4, "C": 8], ["A": 1, "B": 4, "C": 9], ["A": 2, "B": 4, "C": 9], ["A": 3, "B": 4, "C": 9], ["C": 7, "A": 1, "B": 5], ["C": 7, "A": 2, "B": 5], ["C": 7, "A": 3, "B": 5], ["C": 8, "A": 1, "B": 5], ["C": 8, "A": 2, "B": 5], ["C": 8, "A": 3, "B": 5], ["C": 9, "A": 1, "B": 5], ["C": 9, "A": 2, "B": 5], ["C": 9, "A": 3, "B": 5], ["B": 6, "C": 7, "A": 1], ["B": 6, "C": 7, "A": 2], ["B": 6, "C": 7, "A": 3], ["B": 6, "A": 1, "C": 8], ["B": 6, "A": 2, "C": 8], ["B": 6, "A": 3, "C": 8], ["B": 6, "A": 1, "C": 9], ["B": 6, "A": 2, "C": 9], ["B": 6, "A": 3, "C": 9]]
27
Are you sure you need an array of dictionaries in the end? If not, here's a jagged array of key-value pairs:
let keyValuePairArrays =
["A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]]
.sorted(\.key)
.map { key, value in
value.map { (key, $0) }
}
.combinations
If you really need the dictionaries, just map it! 🗺
keyValuePairArrays.map(Dictionary.init)
Supporting code:
public extension Sequence where Element: Sequence {
var combinations: [[Element.Element]] {
guard let initialResult = ( first?.map { [$0] } )
else { return [] }
return dropFirst().reduce(initialResult) { combinations, iteration in
combinations.flatMap { combination in
iteration.map { combination + [$0] }
}
}
}
}
public extension Sequence {
/// The first element of the sequence.
/// - Note: `nil` if the sequence is empty.
var first: Element? {
var iterator = makeIterator()
return iterator.next()
}
/// Sorted by a common `Comparable` value.
func sorted<Comparable: Swift.Comparable>(
_ comparable: (Element) throws -> Comparable
) rethrows -> [Element] {
try sorted(comparable, <)
}
/// Sorted by a common `Comparable` value, and sorting closure.
func sorted<Comparable: Swift.Comparable>(
_ comparable: (Element) throws -> Comparable,
_ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
) rethrows -> [Element] {
try sorted {
try areInIncreasingOrder(comparable($0), comparable($1))
}
}
}
I am looking for way to sort elements in an array based on the classes of the elements in another array. I have:
order = [String, Integer, NilClass ,TrueClass]
arry = [1, 2, 3, 4, 5, true, false, nil, 34, nil, 'Hello', 'World']
In order, the order of the elements is dynamic. The result should be:
result_arry = ['Hello', 'World', 1, 2, 3, 4, 5, 34, nil, nil, true, false]
How can I sort arry based on the class of the elements in order?
I tried:
hash = arry.group_by {|n| n.class }
# => {Fixnum=>[1, 2, 3, 4, 5, 34], TrueClass=>[true], FalseClass=>[false], NilClass=>[nil, nil], String=>["Hello", "World"]}
Using group_by is a good approach, because it keeps the element's order:
hash = arry.group_by(&:class)
#=> {
# Integer => [1, 2, 3, 4, 5, 34],
# TrueClass => [true],
# FalseClass => [false],
# NilClass => [nil, nil],
# String => ["Hello", "World"]
# }
We can use sort_by to sort the hash by its keys based on each key's index in the order array. If a class is missing from orders, we use the array's size as a fallback to have it sorted last:
sorted = hash.sort_by { |k, _| order.index(k) || order.size }
#=> [
# [String, ["Hello", "World"]],
# [Integer, [1, 2, 3, 4, 5, 34]],
# [NilClass, [nil, nil]],
# [TrueClass, [true]],
# [FalseClass, [false]]
# ]
Finally, flat_map extracts each element's last part:
sorted.flat_map(&:last)
#=> ["Hello", "World", 1, 2, 3, 4, 5, 34, nil, nil, true, false]
You can also use sort_by without prior grouping, but it may shuffle elements with the same class:
arry.sort_by { |e| order.index(e.class) || order.size }
#=> ["World", "Hello", 3, 4, 5, 1, 2, 34, nil, nil, true, false]
This is because sort_by is not stable.
In order to fix it, we have to take their indices into account:
arry.sort_by.with_index { |e, i| [order.index(e.class) || order.size, i] }
#=> ["Hello", "World", 1, 2, 3, 4, 5, 34, nil, nil, true, false]
If you also want to match subclasses (e.g. make Integer match Fixnum in older Ruby versions), you have to pass a block to index:
order.index { |cls| e.is_a?(cls) }
I think this works:
arry.sort_by{ |v|
order.map{ |c|
v.is_a?(Object.const_get(c)) ? -1 : 1
}
}
# ["Hello", "World", 1, 2, 3, 4, 5, 34, nil, nil, true, false]
Object.const_get(c) converts the string to class.
Sorting, per se, is not necessary.
missing_classes = arry.map(&:class) - order
#=> [Symbol, FalseClass]
arry.group_by(&:class).values_at(*(order + missing_classes)).flatten
#=> ["Hello", "World", 1, 2, 3, 4, 5, 34, nil, nil, true, :cat, false]
func combinations<T>(of array: [[T]]) -> [[T]] {
return array.reduce([[]]) { combihelper(a1: $0, a2: $1) }
}
func combihelper<T>(a1: [[T]], a2: [T]) -> [[T]] {
var x = [[T]]()
for elem1 in a1 {
for elem2 in a2 {
x.append(elem1 + [elem2])
}
}
return x
}
What's the best solution to write the code in one func and more swifty?
If all you want is to combine both methods into a single one just change a1 to $0 and a2 to $1:
func combinations<T>(of array: [[T]]) -> [[T]] {
return array.reduce([[]]) {
var x = [[T]]()
for elem1 in $0 {
for elem2 in $1 {
x.append(elem1 + [elem2])
}
}
return x
}
}
let multi = [[1,2,3,4,5],[1,2,3,4,5,6,7,8,9,0]]
combinations(of: multi) // [[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], [1, 9], [1, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 9], [2, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [3, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [4, 7], [4, 8], [4, 9], [4, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [5, 8], [5, 9], [5, 0]]
Extending Collection, constraining the element to RangeReplaceableCollection and using higher-order functions we can came up with:
extension Collection where Element: RangeReplaceableCollection {
func combinations() -> [Element] {
reduce([.init()]) { result, element in
result.flatMap { elements in
element.map { elements + CollectionOfOne($0) }
}
}
}
}
let strings = ["12345","1234567890"]
strings.combinations() // ["11", "12", "13", "14", "15", "16", "17", "18", "19", "10", "21", "22", "23", "24", "25", "26", "27", "28", "29", "20", "31", "32", "33", "34", "35", "36", "37", "38", "39", "30", "41", "42", "43", "44", "45", "46", "47", "48", "49", "40", "51", "52", "53", "54", "55", "56", "57", "58", "59", "50"]
You could also do it without any for loops:
func combinations<T>(of array: [[T]]) -> [[T]]
{
return array.reduce([[]]){ c,a in c.flatMap{ e in a.map{e + [$0] } } }
}
Is there an efficient way to remove subsets from an array of sets
E.g. array of arrays
[[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
to output an array
[[2, 3, 4, 7, 8, 9, 10], [1, 5, 6]]
The key is guaranteeing the source sets are sorted in descending order of size. That way all supersets precede their subsets.
Here’s a generic function to do it. You could adapt it to take any kind of sequence of sequence of hashable and convert them to an array of sets on the way in:
func removeSubsets<T: Hashable>(source: [Set<T>]) -> [Set<T>] {
let sets = source.sorted { $0.count > $1.count }
var supersets: [Set<T>] = []
for set in sets {
if !contains(supersets, { set.isSubsetOf($0) }) {
supersets.append(set)
}
}
return supersets
}
removeSubsets([[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]])
// returns [{10, 2, 9, 4, 7, 3, 8}, {5, 6, 1}]
It's still cubic unfortunately since contains is linear and so is isSubsetOf.
EDIT: here's the fully generic version:
func removeSubsets
<S0: SequenceType, S1: SequenceType
where S0.Generator.Element == S1,
S1.Generator.Element: Hashable>
(source: S0) -> [Set<S1.Generator.Element>]
{
let sets = map(source) { Set($0) }.sorted { $0.count > $1.count }
var supersets: [Set<S1.Generator.Element>] = []
for set in sets {
if !contains(supersets, { set.isSubsetOf($0) }) {
supersets.append(set)
}
}
return supersets
}
let a: [[Int]] = [
[2, 3, 4, 7, 8, 9, 10],
[1, 5, 6], [3, 7, 10],
[4, 8, 9], [5, 6],
[7, 10], [8, 9],
[6], [9]]
removeSubsets(a) // returns [{10, 2, 9, 4, 7, 3, 8}, {5, 6, 1}]
EDIT2: if you want the result to be an array of the original arrays (since converting them to sets loses their ordering), you could make the following change, which takes more space but is also slightly more efficient since it only converts the supersets to sets, not the subsets:
func removeSubsets<T: Hashable>(source: [[T]]) -> [[T]] {
// note, this is quite efficient since arrays are copy-on-write,
// so it is only really creating a new array of pointers
let sets = source.sorted { $0.count > $1.count }
var supersets: [Set<T>] = []
var result: [[T]] = []
for set in sets {
if !contains(supersets, { $0.isSupersetOf(set) }) {
supersets.append(Set(set))
result.append(set)
}
}
return result
}
removeSubsets([[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]])
// returns [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6]]
EDIT3: if you want to keep the original order of the sets (just with the subsets removed), you could tag them with a number on the way in before sorting, then re-sort them using it and strip it off the result at the end:
func removeSubsets<T: Hashable>(source: [[T]]) -> [[T]] {
let sets = sorted(enumerate(source)) { $0.1.count > $1.1.count }
var supersets: [Set<T>] = []
var result: [(Int,[T])] = []
for (n,set) in sets {
if !contains(supersets, { $0.isSupersetOf(set) }) {
supersets.append(Set(set))
result.append(n,set)
}
}
return result.sorted { $0.0 < $1.0 }.map { $1 }
}
// note, input not sorted in order of length
removeSubsets([[1, 5, 6], [2, 3, 4, 7, 8, 9, 10], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]])
// returns [[1, 5, 6], [2, 3, 4, 7, 8, 9, 10]]
Just like with any other (non-2D/set) array you could use an array extension like this ...
extension Array
{
func slice(indices:Int...) -> Array
{
var s = indices[0];
var e = self.count - 1;
if (indices.count > 1)
{
e = indices[1];
}
if (e < 0)
{
e += self.count;
}
if (s < 0)
{
s += self.count;
}
let count = (s < e ? e - s : s - e) + 1;
let inc = s < e ? 1 : -1;
var result = Array();
var idx = s;
for i in 0 ..< count
{
result.append(self[idx]);
idx += inc;
}
return result;
}
}
Usage:
let a = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]];
let b = a.slice(0, 1);
let c = a.slice(3);
If your array does not contain duplicated int values, you can convert to Set to use some feature from Swift:
(Take a look at Performing Set Operations)
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html
Here is my code to get another Array which does not contain subsets. This method is not optimized, it works however.
//let arrayOfArray = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
//use set instead
var setArray : [Set<Int>] = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
setArray.sort({$0.count > $1.count}) //sort to have ordered array (biggest set at first)
var result = [Set<Int>]() //you will get your result in this variable.
for _aSet in setArray {
var isSubSet = false
for _exitSet in result {
if _aSet.isSubsetOf(_exitSet) {
isSubSet = true
break;
}
}
if (!isSubSet) {
result.append(_aSet)
}
}
This is the most efficient way I could think of:
let nArrays = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
nArrays
.reduce([Set<Int>]()) {
accu, el in let setEl = Set(el)
return contains(accu) {setEl.isSubsetOf($0)} ? accu : accu + [setEl]
}
//[{10, 2, 9, 4, 7, 3, 8}, {5, 6, 1}]
Instead of checking if every array is a subset of every other array, you just check if they're a subset of the already checked arrays. Of course, that returns an array of Sets, not an array of Arrays, but you can map() over it to convert it back:
let nArrays = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
nArrays
.reduce([Set<Int>]()) {
accu, el in let setEl = Set(el)
return contains(accu) {setEl.isSubsetOf($0)} ? accu : accu + [setEl]
}
.map{Array($0)}
//[[10, 2, 9, 4, 7, 3, 8], [5, 6, 1]]
You could do this:
let arrayOfArray = [[2, 3, 4, 7, 8, 9, 10], [1, 5, 6], [3, 7, 10], [4, 8, 9], [5, 6], [7, 10], [8, 9], [6], [9]]
let output = arrayOfArray[0...1]
I have array:
a = [1, 3, 1, 3, 2, 1, 2]
And I want to group by values, but save it indexes, so result must be looks like this:
[[0, 2, 5], [1, 3], [4, 6]]
or hash
{1=>[0, 2, 5], 3=>[1, 3], 2=>[4, 6]}
Now I'm using pretty ugly and big code:
struc = Struct.new(:index, :value)
array = array.map.with_index{ |v, i| struc.new(i, v) }.group_by {|s| s[1]}.map { |h| h[1].map { |e| e[0]}}
`
If you use a hash with a default value to avoid iterating twice over the elements:
a = [1, 3, 1, 3, 2, 1, 2]
Hash.new { |h, k| h[k] = [] }.tap do |result|
a.each_with_index { |i, n| result[i] << n }
end
#=> { 1 => [0, 2, 5], 3 => [1, 3], 2 => [4, 6] }
a = [1, 3, 1, 3, 2, 1, 2]
a.each_with_index.group_by(&:first).values.map { |h| h.map &:last }
First we get an Enumerator in the form [val, idx], ... (each_with_index), then group_by the value (first value in pair), then take the index (last element) of each pair.
You can use:
Enumerable#each_with_index
Enumerable#group_by and
Array#transpose:
a = [1, 3, 1, 3, 2, 1, 2]
a.each_with_index.group_by(&:first).values.map { |b| b.transpose.last }
#=> [[0, 2, 5], [1, 3], [4, 6]]