213

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
}
Rakesha Shastri
  • 11,053
  • 3
  • 37
  • 50
Christian Dietrich
  • 11,778
  • 4
  • 24
  • 32

15 Answers15

596

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

Nike Kov
  • 12,630
  • 8
  • 75
  • 122
andreschneider
  • 6,384
  • 2
  • 18
  • 15
  • 3
    Just to state this more generally, `flatMap` is available as of Swift 1.2. – Mick MacCallum Apr 24 '15 at 04:56
  • 3
    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` – mfaani Dec 04 '16 at 13:55
  • @Honey: Yes, I'd say you're right. AFAIK `flatMap { $0 }` in this context is pretty much the same as `Array(map { $0 }.joined()).filter { $0 != nil }`. Please somebody correct me if I'm wrong though, that's what I understand under the documentation text line 'Returns an array containing the concatenated results of calling the given transformation with each element of this sequence.'. – Jeehut Dec 29 '16 at 12:47
  • 10
    @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! }`. – Slipp D. Thompson May 03 '17 at 00:22
  • the answer is wrong and should be fixed according to comments – Vyachaslav Gerchicov Mar 20 '18 at 14:55
  • `flatMap` is deprecated and `compactMap` is recommended instead. – Warpling Jul 31 '19 at 22:08
  • 1
    @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. – Jim Dovey Aug 08 '19 at 22:29
  • this answer doesn't flat array like [[1,2,3],[4,[5,6]],[7,8,9]] – Mohammad Reza Koohkan Oct 21 '19 at 15:23
  • 1
    @mohamadrezakoohkan that is correct. Since your array is of type `[[Any]]`, a `flatMap` simply transforms it to a type of `[Any]` ([1, 2, 3, 4, [5, 6], 7, 8, 9]). And if we would apply `flatMap` again, we would act on an `Any? type, where the compiler does not know anymore if it is a simple value or an array itself. – andreschneider Nov 04 '19 at 09:25
  • Love the simple explanation! Thank you so much for sharing :) – Adrian Jan 28 '20 at 00:44
  • 2
    @JimDovey, just to clarify: flatMap is *depreciated* meaning Apple doesn't want you to use it any more, for any reason. At some point it will disappear. So, it should probably be removed from the recommended answer – Thomas O'Dell Sep 09 '21 at 01:25
  • Flatmap is not depreciated for this usecase. Its just one signature that is depreciated . See: https://stackoverflow.com/questions/68938940/is-flatmap-deprecated-or-not-in-swift – SimeonRumy Dec 30 '22 at 06:33
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.

Max Desiatov
  • 5,087
  • 3
  • 48
  • 56
  • @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? – Max Desiatov Oct 09 '15 at 11:00
  • 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. – Chris Conover Oct 09 '15 at 17:38
  • 1
    @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. – Max Desiatov Oct 10 '15 at 11:16
  • This was my point - that when testing / printing the output, you get an array of arrays: `FlattenBidirectionalCollection>>(_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. – Chris Conover Oct 12 '15 at 00:30
  • @MaxDesiatov Could you briefly explain what lazy collection is about and why it is beneficial? Thanks – pixelfreak Oct 22 '15 at 05:41
  • @pixelfreak lazy collection's content is evaluated on demand, that means if only part of the collection is ever consumed, the rest of it won't be evaluated leading to reduced memory and CPU consumption. More information is available at http://airspeedvelocity.net/2014/07/26/lazy-by-name-lazy-by-nature/ and https://en.wikipedia.org/wiki/Lazy_evaluation – Max Desiatov Oct 22 '15 at 09:07
  • 1
    As of swift 3.0, `flatten()` has been renamed to `joined()` – Mr. Xcoder Mar 23 '17 at 13:53
30

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

Rajan Maheshwari
  • 14,465
  • 6
  • 64
  • 98
19

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] 
NikLoy
  • 428
  • 6
  • 15
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: +)
Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
5

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([], +)
    }
}
Francisco Durdin Garcia
  • 12,540
  • 9
  • 53
  • 95
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()
RahmiBozdag
  • 138
  • 6
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]
Swan
  • 305
  • 2
  • 6
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]
George
  • 3,384
  • 5
  • 40
  • 64
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

Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
hojin
  • 1,221
  • 14
  • 16
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]
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Melvin John
  • 71
  • 1
  • 3
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

matrix is [[myDTO]]?

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

dgalluccio
  • 85
  • 5
-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.

Vinoth Anandan
  • 1,157
  • 17
  • 15
-2
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

Rajesh Sharma
  • 99
  • 2
  • 10
  • 1
    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. – Amit Verma Feb 03 '21 at 11:22