Group an array to match another array - swift

I have an array of dates that is grouped by months. I am trying to group another array of values so that it matches the first array. Is that possible?
For example:
array1 = [[1,2,3],[4,5,6]]
array2 = ["one","two","three","four","five","six"]
I would want the second array to be grouped the same as the first array so that they match:
array2 = [["one","two","three"],["four","five","six"]]

The evolution of an idea...
First, a solution for a 2D array:
If you know your array1 is a 2-dimensional array (array of arrays of elements), you can do this by making array2 into an iterator and using map and compactMap to replace the elements:
let array1 = [[1,2,3],[4,5,6]]
let array2 = ["one","two","three","four","five","six"]
var iter = array2.makeIterator()
let array3 = array1.map { arr in arr.compactMap { _ in iter.next() }}
print(array3)
Result:
[["one", "two", "three"], ["four", "five", "six"]]
A more general and generic solution:
Here is a more general solution that uses a sequence instead of array2, that doesn't depend on your knowing ahead of time the layout of array1 or the types of the values of either the array or the sequence:
func remap<S: Sequence>(_ array: [Any], using sequence: S) -> [Any] {
var iter = sequence.makeIterator()
func remap(_ array: [Any]) -> [Any] {
return array.compactMap { value in
if let subarray = value as? [Any] {
return remap(subarray)
} else {
return iter.next()
}
}
}
return remap(array)
}
How this works:
The second array or sequence is turned into an iterator called iter which allows us to get the values in order with repeated calls to iter.next().
Then a second recursive version of remap() is used to convert [Any] into [Any] in a depth-first traversal order. compactMap() is used to replace elements of the array. While replacing the elements of the array, if the element is another array, it recursively calls remap() on that array until it finally gets to values which aren’t arrays. If the element is a non-array element, it replaces it with the next value from the iterator which is serving up the elements of the sequence (or second array) in order. We use compactMap instead of map to handle the fact that iter.next() is returning optional values because it could run out of values in which case it returns nil. In that case, remap() will replace the remaining elements with nothing while still maintaining the structure of the first nested array.
Examples:
// replace Ints with Strings
let array1: [Any] = [1, [2, 3], [4, [5, 6]]]
let array2 = ["one","two","three","four","five","six"]
let array3 = remap(array1, using: array2)
print(array3)
["one", ["two", "three"], ["four", ["five", "six"]]]
// replace Strings with Ints
let array4: [Any] = ["a", ["b", "c"], [[["d"]], "e"]]
let array5 = [1, 2, 3, 4, 5]
let array6 = remap(array4, using: array5)
print(array6)
[1, [2, 3], [[[4]], 5]]
// map letters to numbers starting with 5 using a partial range
print(remap(["a", ["b"], ["c", ["d"]]], using: 5...))
[5, [6], [7, [8]]]
// using stride to create a sequence of even numbers
let evens = stride(from: 2, to: Int.max, by: 2)
print(remap([["a", "b"], ["c"], [["d"]]], using: evens))
[[2, 4], [6], [[8]]]
// an example of not enough values in replacement array
print(remap([["a", "b"], ["c"], [["d"]]], using: [1]))
[[1], [], [[]]]

You can use numpy.array instead of list, like this:
import numpy as np
arr2 = ["one","two","three","four","five","six"]
arr3 = np.array(arr2).reshape(2,3)
arr3
Result:
array([['one', 'two', 'three'],
['four', 'five', 'six']], dtype='<U5')

Related

Using Swift Get array of non repeating numbers from array of numbers

Example.
let numArray = [1,2,3,3,4,5,5]
From above array create array of non- repeating number using swift. But I don't want to use Set.
Expected output is [1,2,4]
You may try the following:
let numArray = [1, 2, 3, 3, 4, 5, 5]
// Group by value
let grouped = Dictionary(grouping: numArray, by: { $0 })
// Filter by its count, convert back to Array and sort
let unique = Array(grouped.filter { $1.count == 1 }.map(\.key)).sorted()
print(unique) // [1, 2, 4]
Here is an alternative way without using higher order functions:
let numArray = [1, 2, 3, 3, 4, 5, 5]
// Group by value
let grouped = Dictionary(grouping: numArray, by: { $0 })
var uniqueArray = [Int]()
for (key, value) in grouped {
if value.count == 1 {
uniqueArray.append(key)
}
}
print(uniqueArray.sorted()) // [1, 2, 4]

Convert pair of integers to tuples in Swift 4?

I have an array, each element of which is an array of two ints (like coordinate pairs):
`[ [1, 2], [2, 2], [11, 9], ... ]`
Elsewhere in my program there are places that need that info but as type Coord, which is defined as a tuple of (x: Int, y: Int).
What would be the best way to iterate over each element in the array and convert it to Coord tuples of (x: Int, y: Int)?
When you are sure that there exists only two objects in an internal array of main array, then you should try this
func getToupleFrom(arr: [Int]) -> (Int, Int) {
return (arr[0], arr[1])
}
Then define a global var to store the tuples, if you need them all together.
var arrayWithTuple: [(Int, Int)] = []
Your example array can have these values and can be iterated like this.
let arrayInt: [[Int]] = [[1,2], [3,4]]
for arr in arrayInt {
arrayWithTuple.append(getToupleFrom(arr: arr))
}
Try it and share results
You don't need to iterate, use map
let array = [ [1, 2], [2, 2], [11, 9]]
let tuples = array.map { ($0[0], $0[1]) }
However this will crash if the number of items in the inner arrays is less than 2.

Swift: Only appendContentsOf to unique elements

Given two array
var array1 = [['id':0], ['id':1], ['id':2], ['id':3]];
var array2 = [['id'=3], ['id':4], ['id':5], ['id':6]];
how can I do a
array1.appendContentsOf(array2)
so that only unique elements are added.
Current array1.appendContentsOf(array2)
results in
[['id':0], ['id":1], ['id':2], ['id':3], ['id':3], ['id':4], ['id':5], ['id':6]]
where id:3 is duplicated.
let array1 = [0,1,2,3]
let array2 = [3,4,5,6]
Array(Set(array1).union(array2)) // [2,4,5,6,0,1,3]
If you want the order to stay the same, you can test each new value to see if the first array already contains it:
var array3 = array1
for element in array2 where !array1.contains(element) {
array3.append(element)
}
// array3 now is [0,1,2,3,4,5,6]
Or shorter:
var array3 = array1
array3.appendContentsOf(array2.filter { !array1.contains($0) })
// array3 now is [0,1,2,3,4,5,6]
Edit
I don't think an array of dictionaries (each containing a single key-value pair) makes a lot of sense, but this works:
let array1 = [["id": 0], ["id": 1], ["id": 2], ["id": 3]]
let array2 = [["id": 3], ["id": 4], ["id": 5], ["id": 6]]
var array3 = array1
array3.appendContentsOf(array2.filter { !array1.map { $0["id"]! }.contains($0["id"]!) })

Optimal Way to Remove Unique Values from Two Arrays

I have two arrays of [PFObjects].
For example (simplified):
arr1: [PFObject] = [1, 2, 3, 4, 5, 6, 7, 8]
arr2: [PFObject] = [1, 2, 3, 4, 5]
What is the optimal way to compare arr1 with arr2 and only keep the duplicates (remove unique values).
So that arr1 looks like:
arr1 = [1, 2, 3, 4, 5]
let array = arr1.filter { arr2.contains($0) }
voilà !
First solution (Looping):
var arr1: [PFObject] = [1, 2, 3, 4, 5, 6, 7, 8]
var arr2: [PFObject] = [1, 2, 3, 4, 5]
var temp: [PFObject] = []
for element in arr1 {
if contains(arr2, element) {
temp.append(element)
}
}
arr1 = temp
You can loop over the first array, check if each element is contained in the array, if it is, you can add it to a temporary array. After looping over every element you can replace the value of the first array with your temporary array.
Second solution (Sets):
var arr1: [PFObject] = [1, 2, 3, 4, 5, 6, 7, 8]
var arr2: [PFObject] = [1, 2, 3, 4, 5]
let set1 = Set(arr1)
let set2 = Set(arr2)
var arr1= Array(set1.intersect(set2)) // [1, 2, 3, 4, 5]
What you do here is:
First you create sets from your arrays
Then you use the intersect method from sets to determine common elements
Finally you transform your set to an array before passing it back to arr1
Of course since you will be using sets, duplicate elements will be lost but I'm guessing that shouldn't be a problem in your case
Third solution (filter):
From the answer of Pham Hoan you can use filters to obtain a subset of arr1, the closure gives you the conditions, here it is that arr2 contains the value you are looking at.
let array = arr1.filter { arr2.contains($0) }
This is obviously the shorter solution in terms of code length.
I do not know which technique would be more efficient if you have very large arrays however.

Remove multiple indices from array

I have an array and I want to remove a bunch of indices
var arr = [0,1,2,3,4,5,6]
var rmIndices = [1,4,5]
What is the best way to remove indices 1,4,5 from arr?
Note that PermutationGenerator is going away in Swift 3 and also doesn't keep the ordering the same, though perhaps it did at one time. Using the accepted answer results in [2, 6, 0, 3] which may be unexpected. A couple of alternative approaches that give the expected result of [0, 2, 3, 6] are:
let flatArr = arr.enumerate().flatMap { rmIndices.contains($0.0) ? nil : $0.1 }
or
let filterArr = arr.enumerate().filter({ !rmIndices.contains($0.0) }).map { $0.1 }
Rather than a list of indices to remove, it may be easier to have a list of indices to keep, which you can do using the Set type:
let rmIndices = [1,4,5]
let keepIndices = Set(arr.indices).subtract([1,4,5])
Then you can use PermutationGenerator to create a fresh array of just those indices:
arr = Array(PermutationGenerator(elements: arr, indices: keepIndices))
rmIndices.sort({ $1 < $0 })
for index in rmIndices
{
arr.removeAtIndex(index)
}
Note that I've sorted the indices in descending order. This is because everytime you remove an element E, the indices of the elements beyond E reduce by one.
For Swift 3
var arr = [0,1,2,3,4,5,6]
let rmIndices = [1,4,5]
arr = arr.filter{ !rmIndices.contains($0) }
print(arr)
if you want to produce output very fastly then you can use
var arr = [0,1,2,3,4,5,6]
let rmIndices = [1,4,5]
arr = Array(Set(arr).subtracting(rmIndices))
print(array)
But it will change order of your array
Remove elements using indexes array:
Array of Strings and indexes
let animals = ["cats", "dogs", "chimps", "moose", "squarrel", "cow"]
let indexAnimals = [0, 3, 4]
let arrayRemainingAnimals = animals
.enumerated()
.filter { !indexAnimals.contains($0.offset) }
.map { $0.element }
print(arrayRemainingAnimals)
//result - ["dogs", "chimps", "cow"]
Array of Integers and indexes
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
let indexesToRemove = [3, 5, 8, 12]
numbers = numbers
.enumerated()
.filter { !indexesToRemove.contains($0.offset) }
.map { $0.element }
print(numbers)
//result - [0, 1, 2, 4, 6, 7, 9, 10, 11]
Remove elements using element value of another array
Arrays of integers
let arrayResult = numbers.filter { element in
return !indexesToRemove.contains(element)
}
print(arrayResult)
//result - [0, 1, 2, 4, 6, 7, 9, 10, 11]
Arrays of strings
let arrayLetters = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
let arrayRemoveLetters = ["a", "e", "g", "h"]
let arrayRemainingLetters = arrayLetters.filter {
!arrayRemoveLetters.contains($0)
}
print(arrayRemainingLetters)
//result - ["b", "c", "d", "f", "i"]
In Swift 4:
let newArr = arr.enumerated().compactMap {
rmIndices.contains($0.0) ? nil : $0.1
}
enumerated() generates (index, value) pairs
compactMap concatenates non-nil values
In the closure, $0.0 is the index (first element of enumerated pair) as $0.1$ is the value
compactMap gathers values whose indices are not found in rmIndices
The problem with flatmap is that it gives incorrect results if your array contains optionals.
The following is much faster than the functional style solutions provided and works with optionals. You just have to make sure rmIndices is sorted and unique. It's also fairly language agnostic.
var numRemoved: Int = 0
for index in rmIndices {
let indexToRemove = index - numRemoved
arr.remove(at: indexToRemove)
numRemoved += 1
}
If you need to make sure rmIndices is sorted and unique:
rmIndices = Set(rmIndices).sorted()
Using XCTest to remove 500 elements (including the operation to ensure uniqueness and sorted):
0.006 sec
vs.
arr.enumerated().filter({ !rmIndices.contains($0.0) }).map { $0.1 }:
0.206 sec
I use this as an extension on Array
extension Array {
mutating func remove(at indices: [Int]) {
let rmIndices = Set(indices).sorted()
var numRemoved: Int = 0
for index in rmIndices {
let indexToRemove = index - numRemoved
self.remove(at: indexToRemove)
numRemoved += 1
}
}
}
Using lodash https://lodash.com/
var arr = [0,1,2,3,4,5,6]
var rmIndices = [1,4,5]
_.pullAt(arr, rmIndices);