Is there a counterpart in Swift to flatten in Scala, Xtend, Groovy, Ruby and co?
var aofa = [[1,2,3],[4],[5,6,7,8,9]]
aofa.flatten() // shall deliver [1,2,3,4,5,6,7,8,9]
of course i could use reduce for that but that kinda sucks
var flattened = aofa.reduce(Int[]()){
a,i in var b : Int[] = a
b.extend(i)
return b
}
Swift >= 3.0
reduce:
let numbers = [[1,2,3],[4],[5,6,7,8,9]]
let reduced = numbers.reduce([], +)
flatMap:
let numbers = [[1,2,3],[4],[5,6,7,8,9]]
let flattened = numbers.flatMap { $0 }
joined:
let numbers = [[1,2,3],[4],[5,6,7,8,9]]
let joined = Array(numbers.joined())
In Swift standard library there is joined function implemented for all types conforming to Sequence protocol (or flatten on SequenceType before Swift 3), which includes Array:
let numbers = [[1,2,3],[4],[5,6,7,8,9]]
let flattened = Array(numbers.joined())
In certain cases use of joined() can be beneficial as it returns a lazy collection instead of a new array, but can always be converted to an array when passed to Array() initialiser like in the example above.
Swift 4.x/5.x
Just to add a bit more complexity in the array, if there is an array that contains array of arrays, then flatMap will actually fail.
Suppose the array is
var array:[Any] = [1,2,[[3,4],[5,6,[7]]],8]
What flatMap or compactMap returns is:
array.compactMap({$0})
//Output
[1, 2, [[3, 4], [5, 6, [7]]], 8]
In order to solve this problem, we can use our simple for loop logic + recursion
func flattenedArray(array:[Any]) -> [Int] {
var myArray = [Int]()
for element in array {
if let element = element as? Int {
myArray.append(element)
}
if let element = element as? [Any] {
let result = flattenedArray(array: element)
for i in result {
myArray.append(i)
}
}
}
return myArray
}
So call this function with the given array
flattenedArray(array: array)
The Result is:
[1, 2, 3, 4, 5, 6, 7, 8]
This function will help to flatten any kind of array, considering the case of Int here
Playground Output:
Swift 4.x
This usage of flatMap isn't deprecated and it's make for this.
https://developer.apple.com/documentation/swift/sequence/2905332-flatmap
var aofa = [[1,2,3],[4],[5,6,7,8,9]]
aofa.flatMap { $0 } //[1,2,3,4,5,6,7,8,9]
Edit: Use joined() instead:
https://developer.apple.com/documentation/swift/sequence/2431985-joined
Original reply:
let numbers = [[1, 2, 3], [4, 5, 6]]
let flattenNumbers = numbers.reduce([], combine: +)
Swift 5.1
public extension Array where Element: Collection {
func flatten() -> [Element.Element] {
return reduce([], +)
}
}
In case you also want it for Dictionary values:
public extension Dictionary.Values where Value : Collection {
func flatten() -> [Value.Element]{
return self.reduce([], +)
}
}
Swift 4.2
I wrote a simple array extension below. You can use to flatten an array that contains another array or element. unlike joined() method.
public extension Array {
public func flatten() -> [Element] {
return Array.flatten(0, self)
}
public static func flatten<Element>(_ index: Int, _ toFlat: [Element]) -> [Element] {
guard index < toFlat.count else { return [] }
var flatten: [Element] = []
if let itemArr = toFlat[index] as? [Element] {
flatten = flatten + itemArr.flatten()
} else {
flatten.append(toFlat[index])
}
return flatten + Array.flatten(index + 1, toFlat)
}
}
usage:
let numbers: [Any] = [1, [2, "3"], 4, ["5", 6, 7], "8", [9, 10]]
numbers.flatten()
Apple Swift version 5.1.2 (swiftlang-1100.0.278 clang-1100.0.33.9)
Target: x86_64-apple-darwin19.2.0
let optionalNumbers = [[1, 2, 3, nil], nil, [4], [5, 6, 7, 8, 9]]
print(optionalNumbers.compactMap { $0 }) // [[Optional(1), Optional(2), Optional(3), nil], [Optional(4)], [Optional(5), Optional(6), Optional(7), Optional(8), Optional(9)]]
print(optionalNumbers.compactMap { $0 }.reduce([], +).map { $0 as? Int ?? nil }.compactMap{ $0 }) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(optionalNumbers.compactMap { $0 }.flatMap { $0 }.map { $0 as? Int ?? nil }.compactMap{ $0 }) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(Array(optionalNumbers.compactMap { $0 }.joined()).map { $0 as? Int ?? nil }.compactMap{ $0 }) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
let nonOptionalNumbers = [[1, 2, 3], [4], [5, 6, 7, 8, 9]]
print(nonOptionalNumbers.compactMap { $0 }) // [[1, 2, 3], [4], [5, 6, 7, 8, 9]]
print(nonOptionalNumbers.reduce([], +)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nonOptionalNumbers.flatMap { $0 }) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(Array(nonOptionalNumbers.joined())) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
You can flatten nested array using the following method:
var arrays = [1, 2, 3, 4, 5, [12, 22, 32], [[1, 2, 3], 1, 3, 4, [[[777, 888, 8999]]]]] as [Any]
func flatten(_ array: [Any]) -> [Any] {
return array.reduce([Any]()) { result, current in
switch current {
case(let arrayOfAny as [Any]):
return result + flatten(arrayOfAny)
default:
return result + [current]
}
}
}
let result = flatten(arrays)
print(result)
/// [1, 2, 3, 4, 5, 12, 22, 32, 1, 2, 3, 1, 3, 4, 777, 888, 8999]
Modified #RahmiBozdag's answer,
1. Methods in public extensions are public.
2. Removed extra method, as start index will be always zero.
3. I did not find a way to put compactMap inside for nil and optionals because inside method T is always [Any?], any suggestions are welcomed.
let array = [[[1, 2, 3], 4], 5, [6, [9], 10], 11, nil] as [Any?]
public extension Array {
func flatten<T>(_ index: Int = 0) -> [T] {
guard index < self.count else {
return []
}
var flatten: [T] = []
if let itemArr = self[index] as? [T] {
flatten += itemArr.flatten()
} else if let element = self[index] as? T {
flatten.append(element)
}
return flatten + self.flatten(index + 1)
}
}
let result: [Any] = array.flatten().compactMap { $0 }
print(result)
//[1, 2, 3, 4, 5, 6, 9, 10, 11]
flatten() was renamed to joined() in Swift 3 per SE-0133:
https://github.com/apple/swift-evolution/blob/master/proposals/0133-rename-flatten-to-joined.md
Another more generic implementation of reduce,
let numbers = [[1,2,3],[4],[5,6,7,8,9]]
let reduced = reduce(numbers,[],+)
This accomplishes the same thing but may give more insight into what is going on in reduce.
From Apple's docs,
func reduce<S : SequenceType, U>(sequence: S, initial: U, combine: (U, S.Generator.Element) -> U) -> U
Description
Return the result of repeatedly calling combine with an accumulated value initialized to initial and each element of sequence, in turn.
struct Group {
var members: [String]?
}
let groups = [Group]()
let outputMembers: [String] = Array(groups.compactMap({ $0.members }).joined())
Description
If you want to make single array of the array of object model. Ex: we get outputMembers single array from all groups.
matrix is [[myDTO]]?
In swift 5 you can use this = Array(self.matrix!.joined())
func convert(){
let arr = [[1,2,3],[4],[5,6,7,8,9]]
print("Old Arr = ",arr)
var newArr = [Int]()
for i in arr{
for j in i{
newArr.append(j)
}
}
print("New Arr = ",newArr)
}
Related
I am trying to find the easiest way to sort an array without using sort() function. I tried searching but i could not find any questions that were on SWIFT. I found several questions about php and javascript and so far nothing on swift.
var arr = [7,6456,2135,164,1345,4,8,5,87456,123,2,87,724,6523,1]
var arrSorted = arr
var index = arr.count
repeat {
var previousSwapIndex = 0
for i in 1..<index {
if (arrSorted[i - 1] as! Int) > (arrSorted[i] as! Int) {
let prevVal = arrSorted[i - 1]
let currentVal = arrSorted[i]
arrSorted[i] = prevVal
arrSorted[i - 1] = currentVal
previousSwapIndex = i
}
}
index = previousSwapIndex
} while (index != 0)
print(arrSorted as Array)
This method works but i am looking for something that is better than this and easier than this.
(Edit[Clarification] :- better = faster / quicker ,as this iterates 120 times before the array is sorted)
Could someone help me out?
Here's a generic implementation of insertion sort in Swift. It takes an inout array, but you should be able to modify it to return an array if that's what you want.
func sort<T: Comparable>(_ array: inout [T]) {
var i = 1
while i < array.count {
var x = array[i]
var j = i - 1
while j >= 0 && array[j] > x {
array[j+1] = array[j]
j -= 1
}
array[j+1] = x
i += 1
}
}
To use it:
var intArr = [1, 7, 3, 6, 4]
sort(&intArr)
print(intArr) // [1, 3, 4, 6, 7]
var stringArr = ["hello", "goodbye", "a", "string", "z", "another string"]
sort(&stringArr)
print(stringArr) // ["a", "another string", "goodbye", "hello", "string", "z"]
It will work on any type that conforms to Comparable.
You can find about all the different methods of sorting from this git.
https://github.com/raywenderlich/swift-algorithm-club
I checked a few and none of them are using any .sort() functions. Pick whichever feels easier for you.
var unsortedStringArray = ["B", "C", "Z", "A", "H"]
var unsortedIntArray = [7,8,3,4,5,9,1,2,6]
func sortFunction<T:Comparable>(array: [T]) -> [T]{
var unsortedArray = array
for i in 0..<unsortedArray.count {
for j in 0..<unsortedArray.count{
var temp: T
if unsortedArray[i] < unsortedArray[j] {
temp = unsortedArray[i]
unsortedArray[i] = unsortedArray[j]
unsortedArray[j] = temp
}
}
}
return unsortedArray
}
let resultStringArray = sortFunction(array: unsortedStringArray)
let resultIntArray = sortFunction(array: unsortedIntArray)
print(resultStringArray) //["A", "B", "C", "H", "Z"]
print(resultIntArray) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
This is the array of elements:
let numbers = [7, 7, 6, 6, 4, 4, 4, 5, 5, 7]
and the output will be in the given form
[2, 2, 3, 2, 1]
I tried this method:
for item in numbers {
let trueCount = numbers.filter { $0 == item }.count
print("\(item) Total Count is \(trueCount)")
}
But it's not working.
What you are looking for is something like that:
let input = [7,7,6,6,4,4,4,5,5,7]
let reduced = input.reduce(into: [(value: Int, count: Int)]()) { acc, value in
if (acc.last?.value == value) {
acc[acc.count - 1].count += 1
} else {
acc.append((value: value, count: 1))
}
}
let counts = reduced.map { $0.count }
print(counts)
Output:
[2, 2, 3, 2, 1]
My question is very simple, how do I make this code lazy:
/*
input: [
[1, 2],
[3, 4],
[5, 6]
]
output: [
[1, 3, 5],
[1, 3, 6],
[1, 4, 5],
[1, 4, 6],
[2, 3, 5],
[2, 3, 6],
[2, 4, 5],
[2, 4, 6],
]
*/
func combinations<T>(options: [[T]]) -> [[T]] {
guard let head = options.first else {
return [].map({ [$0] })
}
if options.count == 1 {
return head.map({ [$0] })
}
let tailCombinations = combinations(options: Array(options.dropFirst()))
return head.flatMap({ option in
return tailCombinations.map({ combination -> [T] in
return [option] + combination
})
})
}
The code above works to calculate the combinations, but it does so creating the entire array of arrays in memory.
What I need is to have it return something like LazySequence<Array<T>>, except the Swift type system doesn't let me do something that generic.
Any ideas how to achieve this and keep the functional style?
Ps.: I did think of another way to solve this problem with generators and keeping track of indexes, but I don't wanna keep track of any state, I want a pure functional (as in FP) solution.
Haskell does it by default, btw, and I'm looking for the same thing.
EDIT: I've managed to solve part of the problem, the type system, with AnyCollection
func combinations<T>(options: [[T]]) -> LazyCollection<AnyCollection<[T]>> {
guard let head = options.first else {
return AnyCollection([].lazy.map({ [$0] })).lazy
}
if options.count == 1 {
return AnyCollection(head.lazy.map({ [$0] })).lazy
}
let tailCombinations = combinations(options: Array(options.dropFirst()))
return AnyCollection(head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
})).lazy
}
But when I use the function, it loads the entire collection in memory, i.e., not lazy.
EDIT 2: Doing some more investigation, turns out the problem is with AnyCollection
// stays lazy
let x1 = head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
})
// forces to load in memory
let x2 = AnyCollection(head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
}))
Not sure how to solve this yet.
Here is what I came up with:
func combinations<T>(options: [[T]]) -> AnySequence<[T]> {
guard let lastOption = options.last else {
return AnySequence(CollectionOfOne([]))
}
let headCombinations = combinations(options: Array(options.dropLast()))
return AnySequence(headCombinations.lazy.flatMap { head in
lastOption.lazy.map { head + [$0] }
})
}
The main difference to this solution is that the recursive
call creates a sequence
of the first N-1 options, and then combines each element of
that sequence with each element of the last option. This is more
efficient because the sequence returned from the recursive call
is enumerated only once, and not once for each element that it is
combined with.
Other differences are:
There is no need to call .lazy on the AnySequence if that
sequence is already lazy. The return type is therefore "simplified"
to AnySequence<[T]>.
I have used CollectionOfOne to create a single-element sequence
for the empty array.
Treating the case options.count == 1 separately is not necessary
for the algorithm to work (but might be a possible performance
improvement).
A completely different approach is to define a custom collection type
which computes each combination as a function of the index, using
simple modulo arithmetic:
struct Combinations<T> : RandomAccessCollection {
let options: [[T]]
let startIndex = 0
let endIndex: Int
init(options: [[T]]) {
self.options = options.reversed()
self.endIndex = options.reduce(1) { $0 * $1.count }
}
subscript(index: Int) -> [T] {
var i = index
var combination: [T] = []
combination.reserveCapacity(options.count)
options.forEach { option in
combination.append(option[i % option.count])
i /= option.count
}
return combination.reversed()
}
}
No extra storage is needed and no recursion. Example usage:
let all = Combinations(options: [[1, 2], [3, 4], [5, 6]])
print(all.count)
for c in all { print(c) }
Output:
8
[1, 3, 5]
[1, 3, 6]
[1, 4, 5]
[1, 4, 6]
[2, 3, 5]
[2, 3, 6]
[2, 4, 5]
[2, 4, 6]
Testing with
let options = Array(repeating: [1, 2, 3, 4, 5], count: 5)
this collection-based method turned out to be faster then the
my above sequence-based method by a factor of 2.
I found one possible solution, but I'll leave this answer not accepted for a while to see if someone knows a better one.
func combinations<T>(options: [[T]]) -> LazySequence<AnySequence<[T]>> {
guard let head = options.first else {
return AnySequence([].lazy.map({ [$0] })).lazy
}
if options.count == 1 {
return AnySequence(head.lazy.map({ [$0] })).lazy
}
let tailCombinations = combinations(options: Array(options.dropFirst()))
return AnySequence(head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
})).lazy
}
The solution was to use AnySequence instead of AnyCollection.
I'm not sure why though, I'd still like to have the AnyCollection interface rather than AnySequence, since it provides me with a few more methods, like count.
I have looked on google and wasn't able to find a library that would allow me to create an image of all the sets. I was wondering if anyone new if such a library existed.
My second question is that at the moment I am doing
// create sets
var set1: Set = [2, 3, 5, 7]
var set2: Set = [2, 3, 4, 6]
var set3: Set = [1, 3, 8, 7]
var new: Set = [2,3]
var finally = [String]()
if(new.isSubset(of:set1)
{
finally.append("set1")
}
if(new.isSubset(of:set2)
{
finally.append("set2")
}
if(new.isSubset(of:set3)
{
finally.append("set3")
}
print(finally)
this is a horrible way of doing it and have been working on a function that automates this process. I tried to create a list of sets:
var foo = [set1,set2,set3]
the issue with that is that I lose the name of the sets since the output is
[{5,7,2,3},{6,2,4,3},{7,3,1,8}]
therefore I decided to create a dictionary:
var baz = [ 'a': set1,
'b': set2,
'c': set3]
//for loop through dictionary
var finally2 = [String]()
for(key,value) in baz {
if(newset.isSubset(of: value))
{
finally2.append(key)
}
}
print(finally2)
I don't like my final dictionary solution and was wondering if there was a better solution just using sets.
I suggest using array and exploiting the fact that array elements has an order, if you insist of using sets you can use the same solution with NSOrderedSet because sets does not have an order.
var set1: Set = [2, 3, 5, 7]
var set2: Set = [2, 3, 4, 6]
var set3: Set = [1, 3, 8, 7]
var new: Set = [2,3]
//let or:NSOrderedSet = [set1, set2, set3] // NSOrderedSet
let ArrayOfSets: Array = [set1, set2, set3]
let finalArry: [String] = ArrayOfSets.flatMap {
if new.isSubset(of: $0) {
let i = ArrayOfSets.index(of: $0)
return "Set\(String(describing: i!+1))"
}
return ""
}.filter{ $0 != ""}
print(finalArry) //["Set1", "Set2"]
You can do the same with for loop of course
Looking at the following code (playground ready).
I'm unable to append data to an unwrapped optional array from within an if let statement.
What's the best method to allow appending for an optional array?
class ValueTypeTest {
var data: [Int]?
func tryAppend() {
if var unwrappedData = self.data {
unwrappedData += [2, 3] // problem !
self.data! += [4, 5] // works
} else {
// Initialize paging and initial results
self.data = [0, 1]
}
}
}
var v = ValueTypeTest()
v.data // nil
v.tryAppend()
v.data // [0, 1]
v.tryAppend()
v.data // expected: [0, 1, 2, 3, 4, 5] actual: [0, 1, 4, 5]
Because Array is a value type and will be copied when you do the if var operation. So you're not changing self.data but unwrappedData.
Try this:
self.data = (self.data ?? [0, 1]) + [2, 3, 4, 5]
This is what I mean.
func tryAppend() {
if let unwrappedData = self.data {
self.data = unwrappedData + [2, 3, 4, 5]
} else {
// Initialize paging and initial results
self.data = [0, 1]
}
}
var unwrappedData declared inside the function tryAppend() is a local variable for that function.
The result pane at that line shows you the output containing: [0, 1, 2, 3] for unwrappedData.