1. 程式人生 > 其它 >將不規則的Python多維陣列拉平到一維

將不規則的Python多維陣列拉平到一維

技術標籤:python

作者:小小明

簡介:Pandas資料處理專家,致力於幫助無數資料從業者解決資料處理難題。

原始需求:

image-20210109030420693

例如有一個列表:

l = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

希望把它轉換成下面這種形式:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

其實這個非常簡單,我將分享三個一行式程式碼來解決這個問題。

但如果是下面這種不規則的多維列表:

l = [[1, 2], [3, 4], [5, [6, 7, [8, 9]]], 10, [11, [12, 13, [14, 15, [16]]]]]

我們想將它拉平到一維列表:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

又該怎麼實現呢?

文末將演示通過遞迴或棧來實現深度優先遍歷策略來解決這個問題。

文章目錄

使用numpy拉平陣列

import numpy as np

np.array(l).flatten().tolist()

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

使用python拉平陣列

使用numpy陣列拉平陣列,其實很受限,一旦列表內部每個元素的長度不一致,numpy就不好使了:

l = [[1, 2, 3], [4, 5], [6, 7], [8, 9, 10, 11]]
np.array(l).flatten().tolist()
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray

​ 結果:

[[1, 2, 3], [4, 5], [6, 7], [8, 9, 10, 11]]

這時我們可以通過python的itertools來實現高效的操作:

import itertools

list(itertools.chain(*l))

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

當然還有一種更高階的操作方法是直接使用sum函式:

sum(l, [])

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

你可能一臉懵逼,為什麼sum函式可以實現列表的拉平?下面我翻譯一下,這段程式碼實際做了什麼:

result = []
for i in l:
    result += i
result

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

將不規則多維陣列拉平到1維

例如,對於下面這個複雜的列表:

l = [[1, 2], [3, 4], [5, [6, 7, [8, 9]]], 10, [11, [12, 13, [14, 15, [16]]]]]
l

結果:

[[1, 2], [3, 4], [5, [6, 7, [8, 9]]], 10, [11, [12, 13, [14, 15, [16]]]]]

這樣的列表,對於上面的方法來說已經都不好使了,這個時候怎麼辦呢?

當然對於這種長度不長的列表,我們可以玩點小技巧:

list_str = str(l).replace("[", "").replace("]", "")
eval(f"[{list_str}]")

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

當然,使用正則替換更佳:

import re

eval(re.sub("(?!^)\[|\](?!$)", "", str(l)))

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

原理就是先將這個列表轉成普通的字串,再將所有的[]字元都去掉,再轉成單維列表的字串形式之後,用eval函式進行解析。但這種方式在列表足夠長的時候顯然是不合適的,會出現效率低下的問題。

深度優先遍歷策略拉平多維陣列

下面我介紹一個正常的解決這個問題的辦法,那就是使用深度優先遍歷策略來解決這個問題,當然如果你對拉平的結果沒有順序的要求還可以使用廣度優先遍歷的策略。

深度優先遍歷策略,最簡單直接的思路是使用遞迴來實現:

def flatten(items, result=[]):
    for item in items:
        if isinstance(item, list):
            flatten(item, result)
        else:
            result.append(item)


result = []
flatten(l, result)
result

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

雖然遞迴可能出現呼叫棧過多導致效能下降或程式掛掉,但Python可以藉助生成器讓遞迴呼叫變成普通呼叫:

def flatten(items):
    for item in items:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item


result = [e for e in flatten(l)]
result

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

而如果我們想不使用遞迴或生成器類遞迴,可以直接藉助一個棧來實現。

為了保證結果是原有的順序,我們把左端作為棧頂,而陣列不適合刪除左端的資料,所以可以使用deque來作為棧。

首先,我們需要將原列表轉換為deque,下面是處理程式碼:

from collections import deque

stack = deque(l)
result = []
while len(stack) != 0:
    item = stack.popleft()
    if isinstance(item, list):
        for e in reversed(item):
            stack.appendleft(e)
    else:
        result.append(item)
result

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

如果我們將原列表作為一個右端為棧頂的棧,可以通過向結果左端插入資料來保持原有的順序:

from collections import deque

stack = l.copy()
result = deque()
while len(stack) != 0:
    item = stack.pop()
    if isinstance(item, list):
        for e in item:
            stack.append(e)
    else:
        result.appendleft(item)
result = list(result)
result

結果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

小結

想不到小小的列表拉平還有這麼多學問,希望今天的分享能夠對讓你學有所獲。

歡迎你在下方留言或評論,分享你的學習心得和體會。