Removing the smaller sets of values from an array of arrays in Swift - arrays

Given an array that consists of arrays containing integers.
[[2], [3], [2, 2], [5], [7], [2, 2, 2], [3, 3]]
What would be the preferred way in Swift to remove the arrays that contains a smaller number of elements with a certain value and keep only the larger arrays containing that value.
The result from the input above would be
[[5], [7], [2, 2, 2], [3, 3]]

Use a [Int: [Int]] dictionary to keep track of the largest array for the value specified by the key.
let arrays = [[2], [3], [2, 2], [5], [7], [2, 2, 2], [3, 3]]
var largest = [Int: [Int]]()
for arr in arrays {
// Get the first value from the array
if let first = arr.first {
// current is the count for that key already in dictionary largest
// If the key isn't found, the nil coalescing operator ?? will
// return the default count of 0.
let current = largest[first]?.count ?? 0
// If our new array has a larger count, put it in the dictionary
if arr.count > current {
largest[first] = arr
}
}
}
// Convert the dictionary's values to an array for the final answer.
let result = Array(largest.values)
print(result) // [[5], [7], [2, 2, 2], [3, 3]]
This same logic can be used with reduce to provide the result in one line:
let result = arrays.reduce([Int: [Int]]()) { var d = $0; guard let f = $1.first else { return d }; d[f] = d[f]?.count > $1.count ? d[f] : $1; return d }.map { $1 }
Alternate Version
This version uses a [Int: Int] dictionary to just keep the counts of the largest array found for each key, and then reconstructs the arrays at the end using an array constructor.
let arrays = [[2], [3], [2, 2], [5], [7], [2, 2, 2], [3, 3]]
var counts = [Int: Int]()
for arr in arrays {
if let first = arr.first {
counts[first] = max(counts[first] ?? 0, arr.count)
}
}
let result = counts.map { [Int](count: $1, repeatedValue: $0) }
print(result) // [[5], [7], [2, 2, 2], [3, 3]]
This same logic can be used with reduce to provide the result in one line:
let result = arrays.reduce([Int: Int]()) { var d = $0; guard let f = $1.first else { return d }; d[f] = max(d[f] ?? 0, $1.count); return d }.map { [Int](count: $1, repeatedValue: $0) }

I was just about to write up my answer when I saw that vacawama had responded with something very similar. Decided to come back to it though just because it's an interesting problem to play around with. So my alternative is almost certainly much slower that vacawama's solutions and doesn't preserve the order, but I thought it was interesting as an example of the alternatives that you have for solving problems like this in Swift.
var items = [[2], [3], [2, 2], [5], [7], [2, 2, 2], [3, 3]]
let reduced = items.sort({
let lhs = $0.first, rhs = $1.first
return lhs == rhs ? $0.count > $1.count : lhs < rhs
}).reduce( [[Int]]()) { (res, items) in
return res.last?.last != items.last ? res + [items] : res
}
print(reduced) // [[2, 2, 2], [3, 3], [5], [7]]
Or if you'd rather cram all that on a single line:
var items = [[2], [3], [2, 2], [5], [7], [2, 2, 2], [3, 3]]
let reduced = items.sort({ let lhs = $0.first, rhs = $1.first; return lhs == rhs ? $0.count > $1.count : lhs < rhs }).reduce([[Int]]()) { $0.last?.last != $1.last ? $0 + [$1] : $0 }
print(reduced) // [[2, 2, 2], [3, 3], [5], [7]]

Just an alternative using forEach:
let arrays = [[2], [2, 2], [5], [7], [2, 2, 2], [3, 3], [3]]
var largest: [Int: [Int]] = [:]
arrays.forEach({
guard let first = $0.first else { return }
largest[first] = [Int](count: max($0.count,largest[first]?.count ?? 0), repeatedValue: first)
})
Array(largest.values) // [[5], [7], [2, 2, 2], [3, 3]]

Related

Get all possible combination of items in array without duplicate groups in Swift

I'm trying to create an extension on Array where I can get all possible combinations of an array without generating duplicate groups, including a no item combination.
For example, for this array:
[1, 2, 3, 4]
The following possible combinations should be generated:
[[], [1], [2], [3], [4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4], [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4], [1, 2, 3, 4]]
Please note that none of the groups repeat themselves, i.e: if there is a group [1, 2] there is no other group: [2, 1].
This is the closest I've been able to get to a result:
public extension Array {
func allPossibleCombinations() -> [[Element]] {
var output: [[Element]] = [[]]
for groupSize in 1...self.count {
for (index1, item1) in self.enumerated() {
var group = [item1]
for (index2, item2) in self.enumerated() {
if group.count < groupSize {
if index2 > index1 {
group.append(item2)
if group.count == groupSize {
output.append(group)
group = [item1]
continue
}
}
} else {
break
}
}
if group.count == groupSize {
output.append(group)
}
}
}
return output
}
}
But it is missing possible combination of items in the group size 3 (I only get back [1, 2, 3] and [2, 3, 4].
Much appreciated!
You can use flatMap also to combine them in one line.
extension Array {
var combinationsWithoutRepetition: [[Element]] {
guard !isEmpty else { return [[]] }
return Array(self[1...]).combinationsWithoutRepetition.flatMap { [$0, [self[0]] + $0] }
}
}
print([1,2,3,4].combinationsWithoutRepetition)
extension Array {
var combinations: [[Element]] {
if count == 0 {
return [self]
}
else {
let tail = Array(self[1..<endIndex])
let head = self[0]
let first = tail.combinations
let rest = first.map { $0 + [head] }
return first + rest
}
}
}
print([1, 2, 3, 4].combinations)
This is in Algorithms now.
import Algorithms
Array([1, 2, 3, 4].combinations(ofCount: 0...))

Best way to replace nils from array with elements of another array

I have two arrays:
a = [nil, 1, nil]
b = [4, 5, 6]
And i want to replace nil elements from first array with related elements from second array:
[4, 1, 6]
What is the best way to do it?
You can use zip and the || operator to do it:
result = a.zip(b).map{ |x,y| x || y }
If you want to replace exactly nil, but not false elements:
a.map.with_index { |e, i| e.nil? ? b[i] : e }
# => [4, 1, 6]
You can use
a.zip(b).map(&:compact).map(&:first) #=> [4, 1, 6]
Steps:
a.zip(b)
#=> [[nil, 4], [1, 5], [nil, 6]]
a.zip(b).map(&:compact)
#=> [[4], [1, 5], [6]]
a.zip(b).map(&:compact).map(&:first)
#=> [4, 1, 6]
By virtue of Array#compact this approach removes nil elements only from the zipped pairs i.e. false elements are not removed.
Another way is by using a block when creating this new array like so:
a = [nil, 1, nil]
b = [4, 5, 6]
Array.new(a.size) { |i| a[i].nil? ? b[i] : a[i] }
#=> [4, 1, 6]
Still another variant:
a.zip(b).map{|a, b| [*a, *b].first}
# => [4, 1, 6]
This distinguishes nil from false, as expected. But note that you cannot use this with elements that are broken by to_a, such as hashes.

Divide array into little arrays with the same elements

let's say I have an array this way : [1,4,7,4,2,2,4,7,1,2].
I need a function that divides this array into arrays with the same elements so it shows a result as this in swift :
result = [[1,1],[4,4,4],[7,7],[2,2,2]] .
How to do that in swift ? Thanks in advance
You can use a helper dictionary to categorize the values of your array into the appropriate bins. E.g.:
let arr = [1, 4, 7, 4, 2, 2, 4, 7, 1, 2]
var dict: [Int: [Int]] = [:]
arr.forEach { dict[$0] = (dict[$0] ?? []) + [$0] }
let inBins = dict.map{ $1 }.sorted{ $0.first ?? 0 < $1.first ?? 0 }
print(inBins) // [[1, 1], [2, 2, 2], [4, 4, 4], [7, 7]]
Or, make use of the general Sequence extension for the categorising part, as described in the accepted answer in thread linked to by #Hamish:
How to group by the elements of an array in Swift
E.g.:
/* from https://stackoverflow.com/a/39388832/4573247:
#mientus's Swift 3 translation of #oisdk's accepted answer */
public extension Sequence {
func categorise<U : Hashable>(_ key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
var dict: [U:[Iterator.Element]] = [:]
for el in self {
let key = key(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}
let arr = [1, 4, 7 ,4, 2, 2, 4, 7, 1, 2]
let inBins = arr.categorise{ $0 }.map{ $1 }.sorted{ $0.first ?? 0 < $1.first ?? 0 }
print(inBins) // [[1, 1], [2, 2, 2], [4, 4, 4], [7, 7]]
No need for the bins to be sorted (as above)? The two options above are then reduced to (simply dropping the last sortin part):
// ... first alternative above
let inBins = dict.map{ $1 }
// ... 2nd alternative above
let inBins = arr.categorise{ $0 }.map{ $1 }
Another option could be to create an NSCountedSet:
let array = [1,4,7,4,2,2,4,7,1,2]
let countedSet = NSCountedSet(array: array)
You could then easily get the count of each unique element:
let countForOne = countedSet.count(for: 1)
As far as I know there is no native Swift equivalent of NSCountedSet yet.

Join Multidimension Arrays

I have multiple arrays of different sizes, for instance:
let array1 = [[1, 2, 3], [1, 2, 3]]
let array2 = [[1, 2], [1, 2]]
let array3 = [[1, 2], [1, 2], [1, 2]]
And I wanna join them together so I get the final array:
let finalArray = [[1, 2, 3, 1, 2, 1, 2], [1, 2, 3, 1, 2, 1, 2], [1, 2]]
Any ideia on how can I achieve this goal in a efficient way?
Try like this:
var finalArray:[[Int]] = []
for index in 0..<max(array1.count,array2.count,array3.count) {
finalArray.append([])
if index < array1.count {
finalArray[index].appendContentsOf(array1[index])
}
if index < array2.count {
finalArray[index].appendContentsOf(array2[index])
}
if index < array3.count{
finalArray[index].appendContentsOf(array3[index])
}
}
finalArray // [[1, 2, 3, 1, 2, 1, 2], [1, 2, 3, 1, 2, 1, 2], [1, 2]]
How about:
var arrays = [array1, array2, array3];
var maxSize = Math.max.apply(null, arrays.map(function(a){ return a.length }))
var result = [];
for (var i = 0; i < arrays.length; i++) {
result[i] = [];
for (var j = 0; j < maxSize; j++) {
if (arrays[j][i]) {
result[i] = result[i].concat(arrays[j][i]);
}
}
}
This assumes that what you want to do is end up with a result array that has the same number of elements as the largest input array, putting together the elements in the same position in the input arrays into the output array in the same order as the original arrays.
zip is the obvious function needed to combine the arrays but it is not able to handle the uneven lengths and the map calls may not be as efficient as a C-like algorithm in this case. However as an exercise, here is a more functional approach IF the arrays can be preprocessed to include empty arrays for missing data
let z12 = zip(array1, array2)
let z123 = zip(z12, array3)
let answer = z123.map { [$0.0.0, $0.0.1, $0.1].flatMap { $0 } }
I have changed the arrays from let to var so that they can be appended and I have changed the data for indices 1 to make the answer more obviously correct.

Grails - sort collection by an element AND an array

Is there a "groovy" way to sort a collection by a first parameter, and if the first parameter repeats in two or more elements sort it by a second parameter (the second parameter being an array) ?
Example:
edited (Lenght of the sub array is variable):
[1, [1,2,3]]
[1, [4,6]]
[2, [1,2,3,4,5]]
[3, [1,2,3]]
[3, [1,2,4,5]]
Thanks in advance
You can use the .sort if you want to build up a custom sort:
def lis = []
lis << [3, [1,2,4]]
lis << [1, [1,2,3]]
lis << [3, [1,2,3]]
lis << [1, [4,5,6]]
lis << [2, [1,2,3]]
lis.sort{a,b->
if(a[0]==b[0])
{
def aArray = a[1]
def bArray = b[1]
for(int i=0;i<aArray.size();i++)
{
if(bArray[i])
{
if(aArray[i]!=bArray[i])
{
return aArray[i]<=>bArray[i]
}
}
else
{
return 1
}
}
return 0
}
else
return a[0]<=>b[0]
}
lis.each{x->
println x
}
I didn't really deal well with the case that the lists are different lengths you could improve on that for your apps needs.
Have a look at the following gist of tim yates.
It shows a way how to sort a list with multiple comparators.
For your example:
Collection.metaClass.sort = { boolean mutate, Closure... closures ->
delegate.sort( mutate ) { a, b ->
closures.findResult { c -> c( a ) <=> c( b ) ?: null }
}
}
def list = [[3, [1,2,3]],
[1, [4,5,6]],
[2, [1,2,3]],
[1, [1,2,3]],
[3, [1,2,4]]]
assert list.sort(false, {it[0]}, {it[1][0]}, {it[1][1]}, {it[1][2]}) == [[1, [1,2,3]],
[1, [4,5,6]],
[2, [1,2,3]],
[3, [1,2,3]],
[3, [1,2,4]]]
Hopes that helps ...

Resources