קוד כניסה (פייתון)

20/04/2018

בכניסה לבניינים רבים יש היום את המנעול הזה שצריך להקליד קוד כדי שהדלת תיפתח. שמתי לב שכמעט בכולם המנגנון כתוב עקום: אם לדוגמא קוד הכניסה שלכם הוא 13579 והתחלתם להקליד 13 ועכשיו בטעות לחצתם 4, צריך להמשיך וללחוץ על עוד שני כפתורים, לקבל הודעה שהקוד שגוי ורק אז להקליד הכל מהתחלה.

הקוד שמפעיל את המנגנון כנראה משתמש בחוצץ (Buffer) בעל גודל קבוע ובכל פעם שלוחצים על מספר המספר הזה נכנס לחוצץ. כשהחוצץ מתמלא בודקים את הקוד במלואו: אם הקוד נכון פותחים את הדלת, אחרת מרוקנים את החוצץ ומתחילים מהתחלה. הנה תוכנית פייתון קצרה שממחישה איך זה (אולי) בנוי:

import sys
class CodePanel:
    def __init__(self, code):
        self.code = code
        self.guess = Buffer(len(code))

    def check(self):
        if self.guess.get_as_string() == self.code:
            print('welcome')
            sys.exit(0)
        else:
            print('wrong code. try again')
            self.guess = Buffer(self.guess.size)

    def type_char(self, ch):
        self.guess.write(ch)
        if self.guess.is_full():
            self.check()

class Buffer:
    def __init__(self, size):
        self.buf = [''] * size
        self.at = 0
        self.size = size

    def write(self, ch):
        self.buf[self.at] = ch
        self.at += 1

    def is_full(self):
        return self.at == self.size

    def get_as_string(self):
        return ''.join(self.buf)


panel = CodePanel('13579')
code = '123413579'
for ch in code:
    panel.type_char(ch)

בכל פעם שמשתמש לוחץ על כפתור תיקרא הפונקציה CodePanel#type_char ותוסיף את האות לחוצץ עד שהחוצץ מתמלא, ורק אז נבדוק את הקוד ונדפיס הודעה. אפשר לראות שניסיתי להקליד את הרצף 123413579 ונשארתי בחוץ.

שינוי קטן בקוד היה יכול לשפר משמעותית את החוויה בלי לדרוש יותר משאבים.

מה יקרה אם במקום להסתכל על המספרים שאנחנו מקלידים כל פעם בקבוצות של 5, ננסה להסתכל על ה-5 מספרים האחרונים שנלחצו? כלומר לא משנה מה כתבת קודם אם ה-5 כפתורים האחרונים שלך היו 13579 הדלת תיפתח. המפתח לשינוי כזה יהיה מעבר לחוצץ מעגלי.

חוצץ מעגלי (Circular Buffer) ישמור את הספרות בהתחלה לפי הסדר ואז כשיתמלא הספרות החדשות ישמרו בתחילת החוצץ במקום הספרות הישנות. עבור אותו רצף מספרים שתיארתי כך יראה החוצץ אחרי כל סיפרה:

1
1, 2
1, 2, 3
1, 2, 3, 4
1, 2, 3, 4, 1
3, 2, 3, 4, 1
3, 5, 7, 9, 1

תמיד נשמור את חמשת הספרות האחרונות שהוכנסו וכך ברגע שמישהו הקליד את הקוד הנכון אפשר לפתוח את הדלת, לא משנה אם לפני זה הוא היה באמצע הקלדה של קוד לא נכון. קוד פייתון עם מימוש פשוט לחוצץ מעגלי נראה כך:

import sys

class CircularBuffer:
    def __init__(self, size):
        self.size = size
        self.buf  = [''] * size
        self.at   = 0

    def write(self, ch):
        self.buf[self.at] = ch
        self.at = (self.at + 1) % self.size

    def get_as_string(self):
        res = []
        for i in range(self.size):
            res.append(self.buf[(self.at + i) % self.size])
        return ''.join(res)

class CodePanel:
    def __init__(self, code):
        self.code = code
        self.guess = CircularBuffer(5)

    def check(self):
        if self.guess.get_as_string() == self.code:
            print(f'welcome: {self.guess.get_as_string()}')
            sys.exit(0)

    def type_char(self, ch):
        self.guess.write(ch)
        self.check()


panel = CodePanel('13579')
code = input()
for ch in code:
    panel.type_char(ch)

השינוי היחיד במחלקה CodePanel הוא שעברנו לבדוק את הקוד אחרי כל לחיצה. אם בדיקת הקוד איטית יכול להיות שזו הסיבה שמתכנני הקודים בדלתות בחרו במנגנון הראשון. עיקר השינוי הוא במחלקה Buffer שהפכה ל CircularBuffer ומייצגת כעת חוצץ מעגלי.

אגב לפייתון עצמה יש מחלקה בשם collections.deque שעושה את אותה העבודה כמו החוצץ המעגלי אבל בנויה כרשימה מקושרת ולא כמערך. מאחר וכל מודול collections בנוי ב C שימוש במחלקה זו ייתן שיפור משמעותי בביצועים כמו גם יחסוך למתכנת עבודה. זה הקוד:

class CircularBuffer:
    def __init__(self, size):
        self.size = size
        self.buf  = deque([''], size)
        self.at   = 0

    def write(self, ch):
        self.buf.append(ch)

    def get_as_string(self):
        return ''.join(list(self.buf))

אם אתם מכירים אנשים שכותבים מנגנונים לקודנים בדלתות כניסה תהיו טובים ותעבירו להם את הפוסט הזה. אולי בעתיד המכשירים האלה יהיו יותר סובלניים לטעויות של אנשים.