أقنعة البتات: الضبط والمسح والتبديل والتحقق
نفّذ دوال مساعدة لضبط البتات الفردية ومسحها وتبديلها والتحقق منها، وطبّق أقنعة البتات لتمثيل المجموعات الجزئية في مسائل تعدادها.
أقنعة البتات: الضبط والمسح والتبديل والتحقق درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ما هي أقنعة البتات؟
قناع البتات هو عدد صحيح يُستخدم لتحديد بتات معينة في عدد صحيح آخر أو تعديلها أو اختبارها. يحتوي القناع على قيم 1 في المواضع التي تهمكم، وعلى قيم 0 في المواضع الأخرى. وبدمج الأقنعة مع العوامل على مستوى البتات، يمكنكم تنفيذ عمليات دقيقة على البتات دون التأثير في البتات الأخرى.
عمليات الأقنعة الأساسية الأربع هي: الضبط (تشغيل بت)، والمسح (إيقاف بت)، والتبديل (قلب بت)، والتحقق (اختبار ما إذا كان البت يساوي 1). تستخدم كل عملية عاملًا مختلفًا — OR وAND-NOT وXOR وAND على الترتيب — مع القناع 1 << k.
# The four fundamental bit mask operations
def set_bit(n, k): return n | (1 << k) # OR to set
def clear_bit(n, k): return n & ~(1 << k) # AND-NOT to clear
def toggle_bit(n, k): return n ^ (1 << k) # XOR to toggle
def check_bit(n, k): return (n >> k) & 1 # shift+AND to check
n = 0b10110101 # 181
print(f'n = {bin(n)}')
print(f'set bit 1: {bin(set_bit(n, 1))}')
print(f'clear bit 2: {bin(clear_bit(n, 2))}')
print(f'toggle bit 0: {bin(toggle_bit(n, 0))}')
print(f'check bit 4: {check_bit(n, 4)}')ضبط بت: تشغيل بت
لـضبط البت k (أي إجباره على أن يساوي 1 بصرف النظر عن قيمته الحالية)، نفّذوا OR على العدد مع القناع 1 << k. بما أن 0 OR 1 = 1 و1 OR 1 = 1، تصبح قيمة البت المستهدف 1. أما جميع البتات الأخرى، فيُنفّذ عليها OR مع 0، مما يبقيها دون تغيير.
ضبط البت عملية تكرارية ثابتة — فاستدعاؤها عدة مرات له التأثير نفسه لاستدعائها مرة واحدة. إذا كان البت k يساوي 1 بالفعل، فلن تتغير النتيجة. وتُعد هذه الخاصية مهمة عند إدارة الرايات، عندما تريدون تفعيل ميزة دون القلق بشأن حالتها الحالية.
def set_bit(n, k):
mask = 1 << k
return n | mask
# Set various bits
n = 0b00001010 # 10
print(f'Original: {bin(n)} = {n}')
for k in [0, 3, 6, 7]:
result = set_bit(n, k)
print(f'Set bit {k}: {bin(result)} = {result}')
# Idempotence: setting already-set bit does nothing
n = 0b1111
print(f'\nAlready set: {bin(set_bit(n, 2))} = {bin(n)} (unchanged)')
# Setting multiple bits at once with a combined mask
mask = (1 << 0) | (1 << 2) | (1 << 4) # bits 0, 2, 4
print(f'Set bits 0,2,4: {bin(0 | mask)} = {0 | mask}')مسح بت: إيقاف بت
لـمسح البت k (أي إجباره على أن يساوي 0 بصرف النظر عن قيمته الحالية)، نفّذوا AND على العدد مع متممة القناع: n & ~(1 << k). تحتوي المتممة ~(1 << k) على قيم 1 في جميع البتات باستثناء البت k، الذي تكون قيمته 0. يؤدي تنفيذ AND مع 0 إلى إجبار البت المستهدف على أن يساوي 0، بينما يحافظ تنفيذ AND مع 1 على جميع البتات الأخرى.
مثل الضبط، المسح عملية تكرارية ثابتة. فمسح بت يساوي 0 بالفعل لا يغير العدد. في Python، تعمل ~(1 << k) بصورة صحيحة لأي قيمة k، لأن Python تتعامل تلقائيًا مع تمديد الإشارة — إذ تكون جميع البتات الأعلى مفعّلة نظريًا في المتممة.
def clear_bit(n, k):
mask = ~(1 << k) # all 1s except bit k
return n & mask
n = 0b11111111 # 255: all bits set
print(f'Original: {bin(n)} = {n}')
for k in [0, 3, 6, 7]:
result = clear_bit(n, k)
print(f'Clear bit {k}: {bin(result)} = {result}')
# Clear multiple bits with combined mask complement
def clear_bits(n, positions):
mask = 0
for k in positions:
mask |= (1 << k)
return n & ~mask
result = clear_bits(0b11111111, [1, 3, 5, 7])
print(f'Clear bits 1,3,5,7: {bin(result)} = {result}') # 0b01010101 = 85تبديل بت: قلب بت
لـتبديل البت k (قلبه من 0 إلى 1 أو من 1 إلى 0)، نفّذوا XOR على العدد مع القناع 1 << k. يؤدي تنفيذ XOR مع 1 إلى قلب البت، بينما يؤدي تنفيذه مع 0 إلى إبقائه دون تغيير. وهذه هي الخاصية الأساسية لـ XOR عند تطبيقها على بت واحد.
التبديل هو العملية الوحيدة من العمليات الأربع التي ليست تكرارية ثابتة — فاستدعاؤها مرتين يعيد القيمة الأصلية. لذلك فهي مثالية للميزات التي تتناوب بين حالتين، مثل مفتاح تشغيل/إيقاف أو راية منطقية في تمثيل صحيح مضغوط.
def toggle_bit(n, k):
return n ^ (1 << k)
n = 0b10101010 # 170
print(f'Original: {bin(n)}')
print(f'Toggle bit 0: {bin(toggle_bit(n, 0))}') # off->on: 10101011
print(f'Toggle bit 1: {bin(toggle_bit(n, 1))}') # on->off: 10101000
print(f'Toggle bit 7: {bin(toggle_bit(n, 7))}') # on->off: 00101010
# Toggle is its own inverse: two toggles = no change
result = toggle_bit(toggle_bit(n, 3), 3)
print(f'Double toggle bit 3: {bin(result)} == original {bin(n)}? {result == n}')
# Toggle all lower k bits
def toggle_lower_k(n, k):
mask = (1 << k) - 1 # k ones in the lowest positions
return n ^ mask
print(f'Toggle lower 4 bits of {bin(n)}: {bin(toggle_lower_k(n, 4))}')التحقق من بت: اختبار ما إذا كان البت مفعّلًا
لـالتحقق مما إذا كان البت k مفعّلًا، أزيحوا n إلى اليمين بمقدار k من المواضع ثم نفّذوا AND مع 1: (n >> k) & 1. ينقل هذا البت k إلى الموضع 0 ويحجب جميع البتات الأعلى، فلا يبقى سوى 0 (إذا كانت قيمة البت k تساوي 0) أو 1 (إذا كانت تساوي 1). وبديلًا عن ذلك، استخدموا bool(n & (1 << k)) للحصول على نتيجة True/False.
التحقق من البت عملية غير مدمّرة — فهي لا تعدّل n. يمكنكم التحقق من عدة بتات عبر إزاحة كل موضع وتطبيق القناع عليه بشكل مستقل. ويُعد هذا أساسًا للتكرار على التمثيل الثنائي لعدد، وهو ما يُستخدم في تعداد المجموعات الجزئية والبرمجة الديناميكية باستخدام حالات أقنعة البتات.
def check_bit(n, k):
return (n >> k) & 1
def is_bit_set(n, k):
return bool(n & (1 << k))
n = 0b10110101 # 181
print(f'n = {bin(n)} = {n}')
for k in range(8):
print(f'Bit {k}: {check_bit(n, k)} ({"set" if check_bit(n, k) else "clear"})')
# Count set bits using check_bit
def count_set_bits(n):
return sum(check_bit(n, k) for k in range(n.bit_length()))
print(f'\nSet bits in {n}: {count_set_bits(n)}')
# Get bit representation as list (LSB first)
def to_bit_list(n, width=8):
return [check_bit(n, k) for k in range(width)]
print(f'Bit list (LSB first): {to_bit_list(n)}')أقنعة البتات لتمثيل المجموعات الجزئية
يمكن لعدد صحيح مكوّن من n بتات أن يمثل مجموعة جزئية من مجموعة تحتوي على n عنصرًا: تكون قيمة البت k هي 1 إذا كان العنصر k موجودًا في المجموعة الجزئية، و0 خلاف ذلك. يضغط هذا التمثيل المجموعة الجزئية في عدد صحيح واحد، مما يتيح عمليات بزمن O(1): اختبار العضوية (mask & (1 << k))، وإضافة عنصر (mask | (1 << k))، وإزالة عنصر (mask & ~(1 << k))، واتحاد وتقاطع المجموعات (mask1 | mask2 وmask1 & mask2).
مع وجود n عنصرًا، توجد 2^n مجموعة جزئية ممكنة، يمثل كل منها عدد صحيح مكوّنًا من n بتات بشكل فريد، وتتراوح هذه الأعداد من 0 إلى 2^n - 1. يؤدي التكرار على جميع الأعداد من 0 إلى 2^n - 1 إلى تعداد جميع المجموعات الجزئية.
# Subset representation with bitmasks
elements = ['A', 'B', 'C', 'D']
n = len(elements)
def subset_from_mask(mask):
return [elements[k] for k in range(n) if (mask >> k) & 1]
# Enumerate all 2^n subsets
print('All subsets:')
for mask in range(1 << n): # 0 to 15 for n=4
print(f' {mask:04b}: {subset_from_mask(mask)}')
# Set operations
mask_ab = 0b0011 # {A, B}
mask_bc = 0b0110 # {B, C}
print(f'\nUnion: {subset_from_mask(mask_ab | mask_bc)}')
print(f'Intersection: {subset_from_mask(mask_ab & mask_bc)}')
print(f'Difference A\\B: {subset_from_mask(mask_ab & ~mask_bc & 0b1111)}')التكرار على جميع المجموعات الجزئية لقناع
في البرمجة الديناميكية باستخدام أقنعة البتات، تحتاجون غالبًا إلى التكرار على جميع المجموعات الجزئية لقناع معين. تتمثل حيلة شائعة في البدء باستخدام sub = mask ثم التكرار باستخدام sub = (sub - 1) & mask حتى يصل sub إلى 0. ينتج عن كل تكرار قناع فرعي مختلف. وتكون الكلفة الإجمالية لهذه العملية O(3^n) عبر جميع الأقنعة، لأن كل عنصر يمكن أن يكون موجودًا في القناع الخارجي دون القناع الفرعي، أو موجودًا في كليهما، أو غير موجود في أي منهما.
تظهر هذه التقنية في مسائل مثل 'تقسيم المصفوفة إلى مجموعات جزئية ذات XOR متساوية' أو 'إيجاد أكبر AND لأي مجموعة جزئية'. وتُعد القدرة على تعداد الأقنعة الفرعية بكفاءة سمة مميزة للبرمجة الديناميكية المتقدمة باستخدام أقنعة البتات.
def all_submasks(mask):
submasks = []
sub = mask
while sub > 0:
submasks.append(sub)
sub = (sub - 1) & mask
submasks.append(0) # empty subset
return submasks
mask = 0b1011 # {0, 1, 3}
elements = ['A', 'B', 'C', 'D']
def show(m): return '{' + ','.join(elements[k] for k in range(4) if (m>>k)&1) + '}'
print(f'All submasks of {bin(mask)} = {show(mask)}:')
for sub in all_submasks(mask):
print(f' {bin(sub):6s}: {show(sub)}')
print(f'Total: {len(all_submasks(mask))} submasks (should be 2^{bin(mask).count("1")} = {2**bin(mask).count("1")})')البرمجة الديناميكية باستخدام قناع البتات: لمحة عن مسألة البائع المتجول
تحل البرمجة الديناميكية باستخدام قناع البتات المسائل التي تتضمن حالتها مجموعة فرعية من العناصر التي تمت زيارتها. والمثال الكلاسيكي هو مسألة البائع المتجول (TSP): إيجاد جولة بأقل تكلفة تزور n مدينة. الحالة هي dp[mask][city] = أقل تكلفة لزيارة المدن الموجودة في mask والانتهاء عند city. ومع وجود n مدينة، يكون لدينا 2^n × n حالة، ما يعطي زمنًا قدره O(n^2 × 2^n)، وهو مناسب عندما تكون n ≤ 20.
يعمل القناع كمجموعة مضغوطة للمدن التي تمت زيارتها. ويقابل ضبط البتات ومسحها والتحقق منها زيارة المدن وإزالتها والاستعلام عنها. وهذه هي الفكرة الأساسية للبرمجة الديناميكية باستخدام قناع البتات: استخدام البتات كمجموعة مدمجة لتمثيل الحالة.
# TSP with bitmask DP
import sys
def tsp(dist):
n = len(dist)
INF = float('inf')
# dp[mask][v] = min cost to reach v having visited cities in mask
dp = [[INF] * n for _ in range(1 << n)]
dp[1][0] = 0 # start at city 0, only city 0 visited (mask=1=0b0001)
for mask in range(1 << n):
for v in range(n):
if dp[mask][v] == INF: continue
if not (mask >> v) & 1: continue # v must be in mask
for u in range(n):
if (mask >> u) & 1: continue # u must not be visited
new_mask = mask | (1 << u)
dp[new_mask][u] = min(dp[new_mask][u], dp[mask][v] + dist[v][u])
full_mask = (1 << n) - 1
return min(dp[full_mask][v] + dist[v][0] for v in range(1, n))
dist = [[0,10,15,20],[10,0,35,25],[15,35,0,30],[20,25,30,0]]
print('TSP minimum tour cost:', tsp(dist)) # should be 80التقنيع متعدد البتات: استخراج حقل
تحتاجون أحيانًا إلى استخراج أكثر من بت واحد، بل حقل متعدد البتات، أي نطاق متصل من البتات. لاستخراج البتات من الموضع start إلى الموضع start+length-1، أنشئوا قناعًا يتكون من length بتات متتالية قيمتها 1: mask = (1 << length) - 1، ثم استخدموا (n >> start) & mask.
تُستخدم هذه التقنية في تحليل تنسيقات الأعداد الصحيحة المعبأة، مثل عناوين IP وبيانات البكسلات وسجلات العتاد، حيث تُخزَّن عدة قيم صغيرة داخل عدد صحيح واحد. فعلى سبيل المثال، يخزّن بكسل RGB565 ذي 16 بتًا اللون الأحمر في البتات 15-11، والأخضر في 10-5، والأزرق في 4-0.
def extract_field(n, start, length):
mask = (1 << length) - 1 # e.g., length=3 => mask=0b111
return (n >> start) & mask
# RGB565 pixel format: RRRRRGGGGGGBBBBB
pixel = 0b1111100111001000 # 63432
red = extract_field(pixel, 11, 5) # bits 15-11
green = extract_field(pixel, 5, 6) # bits 10-5
blue = extract_field(pixel, 0, 5) # bits 4-0
print(f'Pixel: {hex(pixel)}')
print(f'Red: {red} ({bin(red)})')
print(f'Green: {green} ({bin(green)})')
print(f'Blue: {blue} ({bin(blue)})')
# Packing values back
def pack_rgb565(r, g, b):
return (r << 11) | (g << 5) | b
packe = pack_rgb565(red, green, blue)
print(f'Repacked: {hex(packed) if (packed := pack_rgb565(red,green,blue)) else 0}')أقنعة البتات في مسائل المقابلات التقنية
تظهر أقنعة البتات عادةً في أنواع مسائل المقابلات التقنية التالية:
- تعداد المجموعات الفرعية: تكرار جميع المجموعات الفرعية البالغ عددها 2^n باستخدام الأقنعة من 0 إلى 2^n-1
- البرمجة الديناميكية بضغط الحالة: ترميز مجموعة من العُقد أو العناصر التي تمت زيارتها في صورة قناع بتات ضمن حالة DP
- أنظمة الصلاحيات: دمج علامات READ/WRITE/EXECUTE باستخدام OR، والتحقق منها باستخدام AND
- تتبع الخلايا التي تمت زيارتها في الشبكات: تخزين الخلايا التي تمت زيارتها في شبكة صغيرة داخل عدد صحيح واحد
ومن المؤشرات المهمة على فائدة أقنعة البتات أن تتضمن المسألة مجموعة صغيرة (n ≤ 20 عنصرًا) وأن تتطلب تتبع تركيبات الانتماء. أما المجموعات الأكبر فتتطلب تمثيلات مختلفة.
# Subset sum with bitmask enumeration
def subset_sum_exists(nums, target):
n = len(nums)
for mask in range(1 << n):
total = sum(nums[k] for k in range(n) if (mask >> k) & 1)
if total == target:
subset = [nums[k] for k in range(n) if (mask >> k) & 1]
print(f'Found subset {subset} summing to {target}')
return True
return False
subset_sum_exists([3, 1, 4, 1, 5], 10) # finds a subset summing to 10
# Check if permutation covers all required elements (bitmask approach)
required = 0b11111 # need all 5 elements
visited = 0b01101 # visited elements 0, 2, 3
all_visited = (visited & required) == required
print(f'All required visited: {all_visited}') # False: missing bits 1 and 4حيل فعالة لتعداد البتات
عند التكرار على البتات المضبوطة في قناع، تُستخدم تقنيتان شائعتان. الأولى هي طريقة الإزاحة والتحقق: الإزاحة إلى اليمين والتحقق من LSB. والثانية هي طريقة عزل أقل بت مضبوط: عزل أقل بت مضبوط باستخدام n & -n، ومعالجته، ثم مسحه باستخدام n &= n - 1. لا تزور الطريقة الثانية إلا البتات المضبوطة، ولذلك تكون أسرع عندما يكون القناع متناثرًا.
في Python، يمكنكم أيضًا استخدام bin(n).count('1') أو n.bit_count() (3.10+) لحساب popcount. وللحصول على موضع أعلى بت مضبوط، استخدموا n.bit_length() - 1.
# Iterate over set bit positions
def set_bit_positions(n):
positions = []
k = 0
while n:
if n & 1:
positions.append(k)
n >>= 1
k += 1
return positions
# Faster: use lowest-set-bit isolation
def set_bit_positions_fast(n):
positions = []
while n:
lsb = n & -n # isolate lowest set bit
k = lsb.bit_length() - 1 # position of that bit
positions.append(k)
n &= n - 1 # clear lowest set bit
return positions
mask = 0b10110101
print(f'Set positions (naive): {set_bit_positions(mask)}')
print(f'Set positions (fast): {set_bit_positions_fast(mask)}')
print(f'Bit count: {bin(mask).count("1")}')
print(f'Highest set bit: {mask.bit_length() - 1}')اختبار سريع
اختبروا فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep في هذا الدرس.
مراجعة الدرس
تعلمتم في هذا الدرس أن: عمليات قناع البتات الأساسية الأربع هي الضبط (OR)، والمسح (AND-NOT)، والتبديل (XOR)، والتحقق (shift-AND)، وأن الأعداد الصحيحة يمكنها تمثيل مجموعات فرعية بحيث يرمّز كل بت إلى انتماء عنصر واحد، ما يتيح تعداد 2^n مجموعة فرعية، وأن استخراج الحقول متعددة البتات والبرمجة الديناميكية باستخدام قناع البتات يعتمدان على مبادئ التقنيع نفسها لترميز حالات أكثر تعقيدًا. بعد ذلك سنستكشف عدّ البتات والأعداد المفقودة وعكس البتات باستخدام تقنيات هذا الدرس والدرس السابق.
الأسئلة الشائعة
هل درس «أقنعة البتات: الضبط والمسح والتبديل والتحقق» مجاني؟
نعم — نص درس «أقنعة البتات: الضبط والمسح والتبديل والتحقق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «أقنعة البتات: الضبط والمسح والتبديل والتحقق»؟
نفّذ دوال مساعدة لضبط البتات الفردية ومسحها وتبديلها والتحقق منها، وطبّق أقنعة البتات لتمثيل المجموعات الجزئية في مسائل تعدادها. تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟
لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «أقنعة البتات: الضبط والمسح والتبديل والتحقق»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟
نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- العوامل على مستوى البت: AND وOR وXOR وNOT والإزاحات
- الرقم الوحيد وخصائص XOR
- أقنعة البتات: الضبط والمسح والتبديل والتحقق
- عدّ البتات والرقم المفقود وعكس البتات