تحويل قائمة متداخلة إلى قائمة واحدة في بايثون | بايثون بالمثال 24

تحويل قائمة متداخلة إلى قائمة واحدة في بايثون

إذا كانت لديك قائمة متداخلة مثل [[1, 2], [3, 4], [5, 6]] وتريد تحويلها إلى قائمة واحدة مثل [1, 2, 3, 4, 5, 6]، فأنت تحتاج إلى ما يسمى غالبًا Flatten Nested List أو تسطيح القائمة المتداخلة.

في هذا الدرس من سلسلة بايثون بالمثال سنبدأ بأوضح حل باستخدام for وextend()، ثم نكتب نفس الفكرة باستخدام List Comprehension، وبعدها نستخدم itertools.chain.from_iterable(). وفي النهاية سنوضح لماذا تحتاج إلى Recursion إذا كانت القائمة متداخلة بأكثر من مستوى.

{alertInfo} الخلاصة السريعة: إذا كانت لديك List of Lists بمستوى واحد، فاستخدم List Comprehension أو extend(). أما إذا كان العمق غير معروف مثل [1, [2, [3, 4]]]، فأنت تحتاج إلى حل Recursive.

{getToc} $title={محتوى المقال}

ما معنى تحويل Nested List إلى قائمة واحدة؟

القائمة المتداخلة هي قائمة تحتوي على قوائم أخرى بداخلها. مثلًا:

nested = [
    [1, 2],
    [3, 4],
    [5, 6]
]

الهدف هو الحصول على:

[1, 2, 3, 4, 5, 6]

هذه العملية لا تعني حذف العناصر أو ترتيبها؛ نحن فقط نأخذ العناصر الموجودة داخل القوائم الداخلية ونضعها في قائمة واحدة مع الحفاظ على ترتيبها.

شرح فكرة تحويل Nested List إلى Flat List في بايثون

الطريقة الأولى: استخدام for مع extend()

هذه من أوضح الطرق للمبتدئ. ننشئ قائمة فارغة، ثم نمر على كل قائمة داخلية ونضيف عناصرها باستخدام extend():

nested = [
    [1, 2],
    [3, 4],
    [5, 6]
]

flat = []

for row in nested:
    flat.extend(row)

print(flat)

الناتج:

[1, 2, 3, 4, 5, 6]

الفرق المهم هنا أن extend() تضيف عناصر القائمة الداخلية واحدًا واحدًا إلى القائمة الجديدة، بدل أن تضيف القائمة الداخلية كعنصر مستقل.

إذا كانت فكرة extend() غير واضحة لك، راجع مقال الفرق بين append وextend في بايثون.

الطريقة الثانية: استخدام حلقتي for وappend()

يمكنك كتابة العملية يدويًا بحلقتين. الحلقة الأولى تمر على القوائم الداخلية، والثانية تمر على عناصر كل قائمة:

nested = [
    [1, 2],
    [3, 4],
    [5, 6]
]

flat = []

for row in nested:
    for number in row:
        flat.append(number)

print(flat)

الناتج هو نفسه:

[1, 2, 3, 4, 5, 6]

هذه الطريقة أطول، لكنها مفيدة جدًا لفهم ما يحدث فعليًا قبل الانتقال إلى List Comprehension.

الطريقة الثالثة: استخدام List Comprehension

هذه هي الطريقة المختصرة التي تظهر كثيرًا في نتائج البحث، وهي مناسبة عندما تكون القائمة متداخلة بمستوى واحد:

nested = [
    [1, 2],
    [3, 4],
    [5, 6]
]

flat = [
    number
    for row in nested
    for number in row
]

print(flat)

الناتج:

[1, 2, 3, 4, 5, 6]

لفهم السطر، اقرأه كحلقتين عاديتين:

for row in nested:
    for number in row:

ثم ضع العنصر الذي تريد إضافته في بداية List Comprehension:

number

فتصبح الصيغة:

[number for row in nested for number in row]
{alertSuccess} متى أختار هذه الطريقة؟ عندما تكون البنية بسيطة وواضحة، مثل List تحتوي Lists أخرى مباشرة، وتريد حلًا قصيرًا ومقروءًا.

لدينا أيضًا شرح مستقل إذا أردت فهم List Comprehension في بايثون من الأساس.

تسطيح قائمة باستخدام List Comprehension في بايثون

الطريقة الرابعة: itertools.chain.from_iterable()

توفر مكتبة itertools الدالة chain.from_iterable() لربط عدة Iterables متتابعة في Iterator واحد. وهي مناسبة عندما تكون لديك قائمة من القوائم وتريد المرور على العناصر كتيار واحد.

from itertools import chain

nested = [
    [1, 2],
    [3, 4],
    [5, 6]
]

flat = list(
    chain.from_iterable(nested)
)

print(flat)

الناتج:

[1, 2, 3, 4, 5, 6]

الدالة chain.from_iterable() نفسها تعيد Iterator، لذلك استخدمنا list() فقط لأننا نريد النتيجة النهائية كقائمة.

إذا كنت ستستهلك العناصر واحدًا واحدًا داخل حلقة، يمكنك الاستفادة من الـIterator مباشرة بدون إنشاء List جديدة فورًا.

هل أستخدم sum() لتسطيح القائمة؟

ستجد أحيانًا هذا الحل:

nested = [
    [1, 2],
    [3, 4],
    [5, 6]
]

flat = sum(nested, [])

print(flat)

وهو يعطي النتيجة الصحيحة لقائمة من القوائم:

[1, 2, 3, 4, 5, 6]

لكن لا أنصح بجعله الحل الافتراضي. كل عملية جمع للقوائم قد تنشئ قائمة جديدة وتنسخ العناصر المتراكمة، ولذلك يصبح الحل أقل كفاءة كلما كبرت البيانات. حتى توثيق بايثون الرسمي يقترح itertools.chain() عند الحاجة إلى ربط سلسلة من Iterables بدل الاعتماد على sum().

{alertWarning} استخدم sum(nested, []) فقط إذا كنت تفهم تكلفته وكانت البيانات صغيرة جدًا. للمقال التعليمي والمشاريع، List Comprehension أو extend() أو chain.from_iterable() أوضح وأفضل.

ماذا لو كانت القائمة متداخلة بأكثر من مستوى؟

المشكلة تبدأ عندما تصبح البيانات هكذا:

nested = [
    1,
    [2, 3],
    [4, [5, 6]],
    7
]

هنا List Comprehension البسيطة التي افترضت أن كل عنصر في المستوى الأول عبارة عن List لن تعمل كما نريد، لأن بعض العناصر أرقام مباشرة وبعضها قوائم بداخلها قوائم أخرى.

في هذه الحالة نحتاج إلى Recursion: إذا كان العنصر List، نعيد تشغيل الدالة عليه؛ وإذا كان قيمة عادية، نضيفه إلى النتيجة.

def flatten_list(items):
    flat = []

    for item in items:
        if isinstance(item, list):
            flat.extend(flatten_list(item))
        else:
            flat.append(item)

    return flat


nested = [
    1,
    [2, 3],
    [4, [5, 6]],
    7
]

print(flatten_list(nested))

الناتج:

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

نسخة أفضل باستخدام yield from

بدل إنشاء List جديدة داخل كل استدعاء Recursive، يمكن كتابة Generator باستخدام yield from:

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


nested = [
    1,
    [2, 3],
    [4, [5, 6]],
    7
]

flat = list(flatten_list(nested))

print(flat)

النتيجة نفسها:

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

هذه النسخة مناسبة عندما تريد إنتاج العناصر تدريجيًا. لكنها أكثر تقدمًا من الحلول الأولى، لذلك لا تحتاجها إذا كانت قائمتك متداخلة بمستوى واحد فقط.

تسطيح قائمة متداخلة متعددة المستويات باستخدام Recursion في بايثون

ماذا يحدث مع القوائم الفارغة؟

وجود List فارغة داخل القائمة المتداخلة لا يمثل مشكلة في الحلول الخاصة بالمستوى الواحد:

nested = [
    [],
    [1, 2],
    [],
    [3]
]

flat = [
    item
    for sublist in nested
    for item in sublist
]

print(flat)

الناتج:

[1, 2, 3]

القوائم الفارغة لا تضيف أي عنصر، لذلك يتم تجاوزها طبيعيًا.

خطأ شائع: استخدام الطريقة نفسها مع بيانات غير منتظمة

هذا المثال مختلف:

items = [
    [1, 2],
    3,
    [4, 5]
]

الحل التالي سيفشل:

flat = [item for group in items for item in group]

لأن الرقم 3 ليس Iterable يمكن المرور عليه بهذه الطريقة، وسيظهر TypeError. إذا كانت البيانات قد تحتوي على قيم عادية وقوائم بمستويات مختلفة، استخدم الحل Recursive الذي يتحقق من isinstance(item, list).

مقارنة بين طرق Flatten List

الطريقة مناسبة لمستوى واحد؟ مناسبة لعمق غير معروف؟ متى أستخدمها؟
for + extend() نعم لا أفضل بداية للمبتدئ وكود واضح.
حلقتان + append() نعم لا لفهم العملية خطوة بخطوة.
List Comprehension نعم لا حل مختصر وواضح للبنية البسيطة.
chain.from_iterable() نعم لا مفيد مع Iterators والبيانات الكبيرة نسبيًا.
sum(..., []) نعم لا يعمل، لكن لا أجعله الخيار المفضل.
Recursion نعم نعم عندما يكون عمق التداخل متغيرًا أو غير معروف.

ما أفضل طريقة لتسطيح List في بايثون؟

إذا كان لديك:

[[1, 2], [3, 4], [5, 6]]

فأرشح أحد هذين الحلين:

flat = [item for sublist in nested for item in sublist]

أو، إذا أردت كودًا أسهل للقراءة للمبتدئ:

flat = []

for sublist in nested:
    flat.extend(sublist)

أما إذا كانت البيانات:

[1, [2, [3, 4]], 5]

فاستخدم Recursion ولا تحاول إجبار List Comprehension ذات المستوى الواحد على حل مشكلة مختلفة.

مثال عملي: دمج مجموعات درجات الطلاب

لنفترض أن لديك الدرجات مقسمة إلى مجموعات:

grade_groups = [
    [85, 90, 78],
    [92, 88],
    [75, 81, 95]
]

all_grades = [
    grade
    for group in grade_groups
    for grade in group
]

print(all_grades)

الناتج:

[85, 90, 78, 92, 88, 75, 81, 95]

بعد تحويل البيانات إلى قائمة واحدة يمكنك تطبيق sum() أو max() أو أي معالجة أخرى عليها بسهولة.

تمرين لك

حوّل القائمة التالية إلى List واحدة:

words = [
    ["Python", "is"],
    ["easy", "to"],
    ["learn"]
]

النتيجة المطلوبة:

["Python", "is", "easy", "to", "learn"]

جرّب أولًا باستخدام for وextend()، ثم اكتب الحل مرة أخرى باستخدام List Comprehension.

روابط مفيدة من بايثون العرب

مصادر رسمية للتوسع

الخلاصة

تحويل قائمة متداخلة إلى قائمة واحدة في بايثون يعتمد أولًا على شكل البيانات.

  • استخدم extend() إذا أردت حلًا واضحًا ومباشرًا.
  • استخدم List Comprehension إذا كان لديك مستوى واحد وتريد كودًا مختصرًا.
  • استخدم chain.from_iterable() إذا كنت تريد العمل مع Iterator من عدة قوائم.
  • لا تجعل sum(nested, []) خيارك الافتراضي للبيانات الكبيرة.
  • استخدم Recursion إذا كان التداخل متعدد المستويات أو غير معروف العمق.
{alertSuccess} القاعدة الأهم: قبل اختيار الكود، انظر إلى شكل القائمة. List of Lists بمستوى واحد ليست نفس المشكلة التي تمثلها قائمة مثل [1, [2, [3, 4]]].

أسئلة شائعة

كيف أحول قائمة متداخلة إلى قائمة واحدة في بايثون؟

إذا كانت القائمة متداخلة بمستوى واحد، يمكنك استخدام List Comprehension مثل [item for sublist in nested for item in sublist] أو استخدام extend() داخل حلقة.

ما أفضل طريقة لعمل Flatten لقائمة من القوائم؟

List Comprehension مناسبة للحل المختصر، وextend() أوضح للمبتدئ، بينما itertools.chain.from_iterable() مفيدة عندما تريد Iterator بدل بناء النتيجة كلها مباشرة.

هل يمكن استخدام sum لتسطيح القوائم؟

نعم مع قائمة من القوائم باستخدام sum(nested, [])، لكنه ليس الخيار المفضل للبيانات الكبيرة لأن عمليات دمج القوائم قد تسبب نسخًا متكررًا للعناصر.

كيف أسطح قائمة متداخلة بأكثر من مستوى؟

استخدم دالة Recursive تتحقق من كل عنصر؛ إذا كان List تستدعي نفسها عليه، وإذا كان قيمة عادية تضيفها إلى النتيجة.

هل chain.from_iterable تدعم التداخل العميق تلقائيًا؟

لا. هي تجمع مستوى واحد من Iterables. إذا كان لديك Lists داخل Lists داخل Lists، تحتاج إلى معالجة إضافية مثل Recursion.

ما الفرق بين append وextend في هذه المسألة؟

append() تضيف القائمة الداخلية نفسها كعنصر واحد، بينما extend() تضيف عناصرها واحدًا واحدًا، ولهذا تكون extend() مناسبة لتسطيح مستوى واحد.

إرسال تعليق

أحدث أقدم