6.6. الفهرسة والتقطيع

يُعنوَن ndarray بأربع طرق: الفهارس المفردة، والشرائح، والأقنعة المنطقية، وصيغ الإسناد لكلٍّ منها.

6.6.1. العناصر المفردة

تُعيد الفهرسة بالأقواس المربعة القيمة في الموضع المعطى:

a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1])      # 0 9
print(a[1], a[-2])      # 1 8

تَعُدّ الفهارس السالبة من النهاية، تمامًا كما هو الحال في list الخاصة بـ Python. والفهرس الخارج عن النطاق يُطلِق IndexError.

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

m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1])          # 4
print(m[2, 0])          # 6

عندما تُمرَّر فهارس أقل من المحاور، تُترَك المحاور غير المفهرسة سليمة. والنتيجة هي مشهد مخفَّض الرتبة للمصدر:

print(m[0])             # the first row, as a 1-D view of m

6.6.2. الشرائح

تُعيد الشريحة start:stop:step مشهدًا للمصفوفة. ويشارك المشهد مخزن البيانات الأساسي مع المصدر؛ والكتابة عبر المشهد تكتب إلى المصدر:

a = np.arange(10, dtype=np.uint8)
v = a[::2]              # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

عندما تكون هناك حاجة إلى مخزن مؤقت مستقل، تُنتج copy() واحدًا صراحةً.

يمتدّ التقطيع طبيعيًا إلى الأبعاد الأعلى. ويأخذ كل محور شريحته الخاصة:

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]], dtype=np.uint8)

m[0]            # first row
m[0, :2]        # first two elements of row 0
m[:, 0]         # column 0 (still 2-D in ulab)
m[-1]           # last row
m[::2, ::2]     # every other row, every other column

إنّ المزج بين عدد صحيح (فهرس مفرد، يُسقِط المحور) وشريحة (تُبقي المحور) مسموح به، وهو الأسلوب المعتاد لكتابة الوصول إلى صف مفرد أو عمود مفرد.

6.6.3. الأقنعة المنطقية

تختار مصفوفة منطقية بالشكل نفسه للمصدر العناصرَ حيث يكون القناع True. تعمل الفهرسة المنطقية حاليًا على المصفوفات أحادية البعد؛ والمدخلات الأعلى رتبة تُطلِق NotImplementedError

a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])

الخرج:

array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)

القناع هو ndarray عادي من نوع bool، لذا فإنّ أيّ تعبير يُنتج واحدًا منها يعمل:

b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])

تُعيد الفهرسة المنطقية نسخة. تقع العناصر المختارة عند أيّ مواضع يكون فيها القناع True -- وليس على خطوة منتظمة عبر المصدر -- لذا لا يوجد واصف يمكن لمشهدٍ استخدامه لعنونتها، وتُجسَّد النتيجة في مخزنها المؤقت الخاص.

6.6.4. الفهرسة بمصفوفة أعداد صحيحة

إنّ تمرير قائمة أو مصفوفة من الفهارس بين الأقواس يلتقط تلك العناصر في خطوة واحدة:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)

النتيجة هي نسخة؛ ولم تعد العناصر الملتقَطة تشارك التخزين مع المصدر. وتعمل الصيغة نفسها على الجانب الأيسر من الإسناد:

a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)

إنّ take() (المشروحة في الانتقاء وإعادة الترتيب) هي صيغة الدالة للعملية نفسها، وتقبل الكلمة المفتاحية out= للاستخدام الخالي من التخصيص في حلقة دفق.

6.6.5. إسناد التقطيع

تظهر الشرائح والأقنعة على يسار الإسناد كما تظهر على يمينه. وقد يكون الطرف الأيمن عددًا قياسيًا، أو مصفوفة أخرى، أو مشهدًا:

m = np.zeros((3, 3), dtype=np.uint8)
m[0]      = 1            # whole row 0 set to 1
m[:, 2]   = 3            # whole column 2 set to 3
m[1, 1:3] = [7, 8]       # row 1, columns 1 and 2

تستبدل الأقنعة المنطقية على اليسار العناصرَ التي تستوفي الشرط:

a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)

a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)

6.6.6. لماذا يهمّ إسناد التقطيع على الكاميرا

يكتب إسناد التقطيع عبر مصفوفة موجودة بالفعل. ولا تُخصَّص أيّ مصفوفة جديدة. وذلك هو الفرق بين:

out = a + b              # makes a new array the size of a
out = out * 2            # makes another new array

و:

out[:] = a               # writes into the existing out
out   += b               # in place
out   *= 2               # in place

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

تغطّي الأداء هذا النمط بالتفصيل. والقاعدة المهمة الآن هي أنّ إسناد التقطيع، والمُعامِلات الحسابية في المكان (+=، *=، ...)، والكلمة المفتاحية out= على الدوال الكونية هي الأدوات الثلاث التي تجعل التحديثات الخالية من التخصيص ممكنة.