220

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
}
5
  • 1
    isn't it like using add object of an array ? Commented Jun 28, 2014 at 9:28
  • I did not look into Swift itself yet but in Haskell and F# it's ` concat` - so maybe look something named like this? - I'm rather positive that this is there somewhere (most FP langs. know about monads and this is List's bind) Commented Jun 28, 2014 at 9:37
  • yes in haskell it is actually called concat. Commented Jun 28, 2014 at 10:38
  • You should accept andreschneider's answer. Commented Dec 12, 2015 at 2:16
  • related stackoverflow.com/questions/47544675/flatten-any-array-swift Commented Sep 4, 2018 at 13:50

15 Answers 15

617

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())

enter image description here

Sign up to request clarification or add additional context in comments.

13 Comments

Just to state this more generally, flatMap is available as of Swift 1.2.
what's the difference between joined(formally known as flatten) with flatMap? Is it that while flatMap joins, it can also map/transform things. but here in the example we really don't need ie we return $0
@Dschee flatMap will either flatten a 2D array into a 1D array or remove nil values, not both. It determines which to do based on if the 1st-level array's Element is an array or optional— so if you pass it a 2D array of optionals (e.g. [[Int?]]) it'll choose to flatten it to 1D (e.g. [Int?]). To both flatten to 1-D and remove 2nd-level nils, you'd have to do array.flatMap { $0 }.flatMap { $0 }. In other words, the dimension-flattening is equiv to Array(array.joined()) and the nil-removal “flattening” is equiv to array.filter{ $0 != nil }.map{ $0! }.
@Warpling flatMap is still appropriate for the use described in the question (flattening a 2D array to 1D). compactMap is explicitly for removing nil items from a sequence, as a variant of flatMap once did.
Flatmap is not depreciated for this usecase. Its just one signature that is depreciated . See: stackoverflow.com/questions/68938940/…
|
35

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.

7 Comments

@chrisco can you please elaborate on how my answer is incorrect and what is the criteria for "simplest correct answer"? Can you please also tell how deleting an answer could impact the question in any way?
Try running your snippet first - what do you think it does? What does it actually do? What was the original question? Is your answer correct? If not, it would be better to delete it to improve the clarity of this post. I have done the same with incorrect answers of my own.
@chrisco thank you very much for your suggestions, but I do run snippets before posting them anywhere. And my answer is correct as it returns exactly the same results as OP requested and using less code for that. I admit that my original answer was returning lazy collection instead of an array, although there was no restriction on that in the question. I still don't think that deletion of a correct answer improves the quality of the question in any way.
This was my point - that when testing / printing the output, you get an array of arrays: FlattenBidirectionalCollection<Array<Array<Int>>>(_base: [[1, 2, 3], [4], [5, 6, 7, 8, 9]])). Your point is valid though that you can access it like a flat array, so it would seem that that the CustomStringConvertable implementation is misleading. Your code snippet was and is still missing a test though.
As of swift 3.0, flatten() has been renamed to joined()
|
31

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: enter image description here

For generic type:

func flattenedArray<T>(array: [Any], ofType: T.Type) -> [T] {
    var myArray = [T]()
    for element in array {
        if let element = element as? T {
            myArray.append(element)
        }
        if let element = element as? [Any] {
            let result = flattenedArray(array: element, ofType: ofType)
            for i in result {
                myArray.append(i)
            }
        }
    }
    return myArray
}

4 Comments

This flattenedArray() function only works with arrays of integers. Can it be generic so it works on arrays of integers, floats, and doubles?
@wigging Yes definitely. Try func flattenedArray<T>(array: [Any], ofType: T.Type) -> [T] { var myArray = [T]() for element in array { if let element = element as? T { myArray.append(element) } if let element = element as? [Any] { let result = flattenedArray(array: element, ofType: ofType) for i in result { myArray.append(i) } } } return myArray } flattenedArray(array: ["This", ["is", "a"], "very", ["sweet", ["and", "lovely"]], "world"], ofType: String.self)
Can you put this code in your answer above? It's hard to read as a comment.
@wigging added.
21

Swift 4.x

This usage of flatMap isn't deprecated and it's made 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] 

Comments

15

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: +)

Comments

6

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([], +)
    }
}

2 Comments

Although other answers are perfectly right, I love to use the extension +1 for that
numbers.reduce([], +) is a bad idea. It'll create a new intermediate array for every subarray being joined. reduce(into: [], +=) can be used to avoid that pitfall, but it's better to just use .flatmap { $0 }
4

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()

Comments

2

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]

Comments

2

Apple Swift version 5.1.2 (swiftlang-1100.0.278 clang-1100.0.33.9)
Target: x86_64-apple-darwin19.2.0

Screenshot

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]

Comments

1

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]

Comments

1

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

Comments

0

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.

1 Comment

With your code I get: Use of unresolved identifier 'reduce'
-1
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.

Comments

-2

matrix is [[myDTO]]?

In swift 5 you can use this = Array(self.matrix!.joined())

Comments

-3
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)
}

enter image description here

1 Comment

While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn and apply that knowledge to their own code. You are also likely to have positive-feedback/upvotes from users, when the code is explained.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.