0Pricing
DSA Interview Prep · درس

أقنعة البتات: الضبط والمسح والتبديل والتحقق

نفّذ دوال مساعدة لضبط البتات الفردية ومسحها وتبديلها والتحقق منها، وطبّق أقنعة البتات لتمثيل المجموعات الجزئية في مسائل تعدادها.

أقنعة البتات: الضبط والمسح والتبديل والتحقق درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA 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) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «أقنعة البتات: الضبط والمسح والتبديل والتحقق»؟

نفّذ دوال مساعدة لضبط البتات الفردية ومسحها وتبديلها والتحقق منها، وطبّق أقنعة البتات لتمثيل المجموعات الجزئية في مسائل تعدادها. تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟

لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «أقنعة البتات: الضبط والمسح والتبديل والتحقق»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟

نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. العوامل على مستوى البت: AND وOR وXOR وNOT والإزاحات
  2. الرقم الوحيد وخصائص XOR
  3. أقنعة البتات: الضبط والمسح والتبديل والتحقق
  4. عدّ البتات والرقم المفقود وعكس البتات
← العودة إلى DSA Interview Prep