Back home

الفنون رقم 014

الفنون رقم 014

ARTS هو نشاط بدأه 由左耳朵耗子--陈皓: قم بإجراء سؤال واحد على الأقل عن خوارزمية Leetcode كل أسبوع، واقرأ مقالًا تقنيًا واحدًا على الأقل باللغة الإنجليزية وعلق عليه، وتعلم مهارة فنية واحدة على الأقل، وشارك المقالة مع الآراء والأفكار. (أي أن الخوارزمية والمراجعة والنصائح والمشاركة يشار إليها باسم ARTS) وتستمر لمدة عام واحد على الأقل.

الفنون 014

هذه هي المادة 14

سؤال خوارزمية الخوارزمية

290. نمط الكلمات

الصعوبة: سهل

بالنظر إلى pattern وسلسلة str، اكتشف ما إذا كان str يتبع نفس النمط.

تعني follow هنا تطابقًا كاملاً، بحيث يكون هناك اعتراض بين حرف في pattern وكلمة غير فارغة في str.

مثال 1:

**Input:** pattern = `"abba"`, str = `"dog cat cat dog"`
**Output:** true```

**مثال 2:**

**Input:**pattern = "abba", str = "dog cat cat fish" Output: false```

مثال 3:

**Input:** pattern = `"aaaa"`, str = `"dog cat cat dog"`
**Output:** false```

**مثال 4:**

Input: pattern = "abba", str = "dog dog dog dog" Output: false```

ملاحظات: قد تفترض أن pattern يحتوي على أحرف صغيرة فقط، وأن str يحتوي على أحرف صغيرة مفصولة بمسافة واحدة.

الحل

اللغة: ج

عندما رأيت هذه الخوارزمية لأول مرة، شعرت أنه يمكن حلها باستخدام القاموس، ولكن لا يوجد قاموس بين أنواع البيانات الأساسية في لغة C، ولم أتمكن من التفكير في طريقة بسيطة، لذلك قرأت بعض الإجابات، ثم بعد يومين، بناءً على فهمي الخاص، قمت بتنفيذها مرة أخرى. الخوارزمية الأولية هي كما يلي:

bool wordPattern0(char* pattern, char* str) {
    int str_len = strlen(str);
    int pattern_len = strlen(pattern);
    char buffer[str_len + 1];
    strcpy(buffer, str);
    
    char *s = strtok(buffer, " ");
    char *tables[26] ;
    memset(tables, '\0', 26 * sizeof(char *));
    int i =0;
    for (; s; s = strtok(NULL, " ")) {
        if (i>=pattern_len) {
            return false;
        }
        char aa = pattern[i];
        char *s1 = tables[aa - 'a'];
        
        if (!s1) {
            tables[aa - 'a'] = s;
        }
        else{
            if (strcmp(s, s1)) {
                 return false;
            }
        }
        i++;
    }
    
    if (i!=pattern_len) {
        return false;
    }
    return true;
}

وبعد تشغيله وجدت أن هناك حالات اختبار لا يمكن اجتيازها مثل: عندما يكون نمط شار* = “آبا”؛ char* str = “كلب كلب كلب كلب”;

اعتقدت في البداية أنني فهمت خوارزميات الآخرين، لكنني ما زلت أرتكب الأخطاء عندما نفذتها. ما زلت لا أستطيع الحصول على الحق. لقد قمت بتحليل الأسباب:

قرأت متطلبات السؤال مرة أخرى، ثم لاحظت أن النمط والسلسلة يجب أن يكون لهما علاقة رأس برأس، وأن تنفيذي هو علاقة رأس بأطراف، والقاموس عبارة عن علاقة رأس بأطراف، مما يعني أن تنفيذي لا يمكن أن يضمن إلا لنفس الحروف في مواضع مختلفة من النمط، ولا يمكن ضمان إلا أن الكلمات الموجودة في نفس الموضع المقابل لـ str هي نفسها أيضًا. على سبيل المثال، أول نمط a يتوافق مع كلب، والرابع a يجب أن يتوافق أيضًا مع كلب. ومع ذلك، ليس هناك ما يضمن أنه بالنسبة لنفس الكلمة في مواضع مختلفة من str، يمكن أن يكون هناك حرف نمط واحد فقط يتوافق معها. التنفيذ المحسن هو كما يلي:


bool wordPattern0(char* pattern, char* str) {
    int str_len = strlen(str);
    int pattern_len = strlen(pattern);
    char buffer[str_len + 1];
    strcpy(buffer, str);
    
    char *s = strtok(buffer, " ");
    char *tables[26] ;
    memset(tables, '\0', 26 * sizeof(char *));
    int i =0;
    for (; s; s = strtok(NULL, " ")) {
        if (i>=pattern_len) {
            return false;
        }
        char aa = pattern[i];
        char *s1 = tables[aa - 'a'];
        
        if (!s1) {
            tables[aa - 'a'] = s;
        }
        else{
            if (strcmp(s, s1)) {
                 return false;
            }
        }
        i++;
    }
    
    if (i!=pattern_len) {
        return false;
    }
    
    for (int i =0; i<26; i++) {
        if (!tables[i]) {
            continue;
        }
        for (int j =i+1; j<26; j++) {
            if (!tables[j]) {
                continue;
            }
            if (!strcmp(tables[i], tables[j])) {
                return false;
            }
        }
    }
    
    return true;
}

في الواقع، إنه مجرد إضافة حكم واحد لواحد.


for (int i =0; i<26; i++) {
        if (!tables[i]) {
            continue;
        }
        for (int j =i+1; j<26; j++) {
            if (!tables[j]) {
                continue;
            }
            if (!strcmp(tables[i], tables[j])) {
                return false;
            }
        }
    }

التطبيقات التالية تستحق التعلم:

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


bool wordPattern(char* pattern, char* str) {
    int str_len = strlen(str);
    char buffer[str_len + 1];
    strcpy(buffer, str);
    
    char *p = pattern;
    char *s = strtok(buffer, " ");
    
    char *table[26];
    memset(table, '\0', 26 * sizeof(char *));
    
    do {
        if (*p == '\0') return false;
        int input = *p++ - 'a';
        
        if (table[input] == NULL) {
           ////加入table之前先判断table中是否已经存在,如果存在直接返回false
            for (int i = 0; i < 26; i++) {
                if (table[i] == NULL)
                    continue;
                if (strcmp(table[i], s) == 0)
                    return false;
            }
            
            table[input] = (char *) malloc((strlen(s) + 1) * sizeof(char));
            strcpy(table[input], s);
        } else {
            if (strcmp(table[input], s))
                return false;
        }
        
        s = strtok(NULL, " ");
    } while (s != NULL);
    
    for (int i = 0; i < 26; i++)
        free(table[i]);
    
    if (*p != '\0')
        return false;
    return true;
}

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


bool wordPattern(char* pattern, char* str)
{
    unsigned int table[26] = {0};
    
    for (; *pattern && *str; pattern++) {
        unsigned int hash = 2139062143;
        
        for(; *str && *str != ' '; str++)
            hash = 37 * hash + *str;
        
        if (*str)
            str++;
        
        if (!table[*pattern - 'a']) {
          ////加入table之前先判断table中是否已经存在,如果存在直接返回false
            for (int i = 0; i < 26; ++i)
                if (table[i] == hash)
                    return false;
            table[*pattern - 'a'] = hash;
        } else
            if (table[*pattern - 'a'] != hash)
                return false;
    }
    
    return !*str && !*pattern;
}

التنفيذ 3، هذا التنفيذ أكثر تعقيدًا بعض الشيء، ويرجع ذلك أساسًا إلى أنه لا يستخدم وظيفة strtok وينفذها بنفسي.


bool wordPattern2(char* pattern, char* str) {
    int patternLength = strlen(pattern);
    int tokens = 0;
    for (int i = 0; str[i] != 0; i++) {
        if (str[i] == ' ') {
            tokens++;
        }
    }
    tokens++;
    if (patternLength != tokens) {
        return false;
    }
    char *dict[26];
    for (int i = 0; i < 26; i++) {
        dict[i] = NULL;
    }
    for (int i = 0; pattern[i] != 0; i++) {
        int size = 0;
        int capacity = 8;
        char *ithTok = malloc(sizeof(char) * 8);
        int spacesToSkip = i;
        char *stringPointer = str;
        while (spacesToSkip) {
            if (*stringPointer == ' ') {
                spacesToSkip--;
            }
            stringPointer++;
        }
        //这个for循环相当于strtok函数,取出str中的单词
        for (int i = 0; stringPointer[i] != ' ' && stringPointer[i] != 0; i++) {
            ithTok[i] = stringPointer[i];
            size++;
            if (size >= capacity) {
                ithTok = realloc(ithTok, sizeof(char) * capacity * 2);
                capacity *= 2;
            }
        }
        
        //下面的判断和上面两个算法一样
        ithTok[size] = 0;
        if (dict[pattern[i] - 'a'] == NULL) {
           ////加入table之前先判断table中是否已经存在,如果存在直接返回false
            for (int j = 0; j < 26; j++) {
                if (dict[j] != NULL) {
                    if (strcmp(dict[j], ithTok) == 0) {
                        return false;
                    }
                }
            }
            dict[pattern[i] - 'a'] = ithTok;
        }
        else {
            if (strcmp(dict[pattern[i] - 'a'], ithTok) != 0) {
                return false;
            }
        }
    }
    return true;
}

التنفيذ 4: لم يكن هذا التنفيذ واضحًا بالنسبة لي أبدًا. على الرغم من أنه يمكن تشغيله بنجاح في LeetCode، إلا أنني أعتقد أنه ستكون هناك مشكلات تصادم تجزئة.



#define alphabetSIZE 26  //a~z 26 letters
#define hashtableSIZE 3533  //randomly choose a large prime number
struct Node{
    int count;
    char *str;
    struct Node *next;
};
void initialize_hashtable(struct Node *hashtable)
{
    int i;
    for(i=0;i<hashtableSIZE;i++)
    {
        hashtable[i].count = 0;
        hashtable[i].next = NULL;
    }
}

long hash (char *str)
{
    int c;
    unsigned long hash = 0;
    while(c = *str++)
        hash = (hash<<5) + hash + c;
    return hash%hashtableSIZE;
}

void hashtable_free(struct Node *hashtable)
{
    int i;
    for(i=0;i<hashtableSIZE;i++)
    {
        struct Node *tail = hashtable[i].next;
        while(tail)
        {
            struct Node *temp = tail;
            tail = tail->next;
            free(temp);
        }
    }
    free(hashtable);
}

bool wordPattern(char* pattern, char* str) {
    int alphabet[alphabetSIZE]={0},i,patternSize = strlen(pattern);
    struct Node *hashtable = (struct Node *)malloc(sizeof(struct Node)*hashtableSIZE);
    initialize_hashtable(hashtable);
    struct Node *node;
    char buffer[patternSize + 1];
    strcpy(buffer, str);
    char *word = strtok(buffer," ");
    for(i=0;word;i++)
    {
        if(i>=patternSize)    return 0;    // the number of word in str is more than the length of pattern
        node = &hashtable[hash(word)];

        if(alphabet[pattern[i]-'a'] != node->count)
            return 0;
        alphabet[pattern[i]-'a'] = node->count = i+1;
        word = strtok(NULL," ");
    }
    hashtable_free(hashtable);
    return i==patternSize?1:0;
}

التنفيذ 5: لدي بعض الوقت للتعرف على الخوارزمية التالية.


bool wordPattern4(char* pattern, char* str){
    bool follow = false;
    int sl = strlen(str);
    int pl = strlen(pattern);
    char c = ' ';
    char** strArray;
    int i = 0;
    int j = 0;
    int k = 0;
    int temp = 0;
    
    if ((pl==1)) return true;
    bool endofstr = false;
    strArray =  malloc(pl*sizeof(char*));
    
    for (j = 0; endofstr == false; j++){
        strArray[j] = malloc(10*sizeof(char));
        k = 0;
        while( i < sl){
            if (str[i]!=c)
                strArray[j][k] = str[i];
            else{
                strArray[j][k] = '\0';
                i++;
                break;
            }
            k++;
            i++;
        }
        if (i == sl) endofstr = true;
        printf("%d sub-string, %s\n", j, strArray[j]);
    }
    
    //    for (i = 0; i <= j; i++){
    //        printf("%d:%s\n",i,strArray[i]);
    //    }
    
    if (pl != j) return false;
    
    for (i = 0; i < pl-1; i++){
        for (k = i+1; k < pl; k++){
            
            temp = strcmp(strArray[i], strArray[k]);
            if ((pattern[i] == pattern[k]) && !temp)
                follow = true;
            else if ((pattern[i] != pattern[k]) && temp)
                follow = true;
            else
                return false;
        }
    }
    return follow;
}

مراجعة

تأتي هذه المقالة من https://medium.com/@JimmyMAndersson/ios-development-and-the-wrong-kind-of-mvc-4e3e2decb82e, يتحدث الجزء الأول من هذه المقالة عن كيفية استخدام نمط تصميم MVC في تطوير iOS، ولكنه يتحدث بعد ذلك عن الميراث. جودة المدونة متوسطة.

تطوير iOS والنوع الخاطئ من MVC

عندما تبدأ كمطور iOS، ستسمع الكثير عن نمط MVC (Model-View-Controller) وكيف أنه الأفضل عند تطوير تطبيقات الهاتف المحمول. يعد نموذج MVC طريقة جيدة للتفكير في بعض المشكلات عندما يتعلق الأمر بتطوير التطبيقات باستخدام واجهة مستخدم رسومية. فهو يوفر لك طريقة تفكير تتيح لك تقسيم التعليمات البرمجية الخاصة بك إلى حزم يمكن التحكم فيها، حيث ستتعامل كل حزمة مع مجال المشكلة الخاص بها.

عندما تبدأ كمطور iOS، ستسمع الكثير عن نمط MVC (Model-View-Controller) وكيف يكون أفضل عند تطوير تطبيقات الهاتف المحمول. يعد نمط MVC طريقة جيدة للتفكير في مشكلات معينة عند تطوير التطبيقات باستخدام واجهة مستخدم رسومية. فهو يوفر لك طريقة تفكير تسمح لك بتقسيم التعليمات البرمجية الخاصة بك إلى حزم يمكن التحكم فيها، حيث ستتعامل كل حزمة مع مجال المشكلة الخاص بها.

ما هي هذه الباقات التي تتحدث عنها؟

هناك ثلاث حزم مفاهيمية مختلفة في MVC من المفترض أن تتعامل مع مشكلات مثل: هناك ثلاث حزم مفاهيمية مختلفة في MVC والتي يجب أن تتعامل مع مشكلات مثل هذه:

نموذج: تتعامل حزمة النموذج مع ما يسميه المطورون منطق الأعمال. يعتمد معنى منطق الأعمال على التطبيق الخاص بك، ولكن يمكنك تعميمه بشكل فضفاض ليعني “الأشياء الموجودة في تطبيقك والتي تدير بياناتك وتبقيها محدثة ودقيقة”. هذا يعني أن وظيفة النماذج هي تتبع موضع شخصيات لعبتك، ومعرفة عدد المرات التي قمت فيها بتشغيل أغنية على iTunes، وحفظ صورك المعدلة بتنسيق يمكنك مشاركته على وسائل التواصل الاجتماعي… النموذج هو ما يجعل تطبيقك ذكيًا.

تتعامل الحزم النموذجية مع ما يسميه المطورون منطق الأعمال. يعتمد منطق العمل على تطبيقك المحدد، ولكن يمكنك تلخيصه بشكل فضفاض على أنه “ما يوجد في تطبيقك والذي يدير البيانات ويبقيها محدثة ودقيقة.” وهذا يعني أن نماذجها تعمل على تتبع موقع شخصيتك في اللعبة، ومعرفة عدد المرات التي قمت فيها بتشغيل أغنية على iTunes، وحفظ الصور المعدلة بتنسيق يمكنك مشاركته على وسائل التواصل الاجتماعي… نماذج تجعل تطبيقاتك أكثر ذكاءً. .

عرض: سيمثل العرض في تطبيق MVC البيانات المرسلة من النموذج ويعرضها للمستخدم. من المفترض أن تكون طريقة العرض “غبية”، مما يعني أنها يجب أن تعرف فقط كيفية رسم أو عرض البيانات التي تتلقاها ولكن لا ينبغي أبدًا إجراء أي تعديلات عليها أو حتى أن تكون على دراية بنوع البيانات.

سيمثل العرض في تطبيق MVC البيانات المرسلة من النموذج ويعرضها للمستخدم. يجب أن يكون العرض “غبيًا”، أي أنه يجب أن يعرف فقط كيفية رسم أو عرض البيانات التي يتلقاها، ولكن لا ينبغي إجراء أي تعديلات عليها، أو حتى معرفة نوع البيانات التي هي عليها.المراقب المالي: حزمة وحدة التحكم هي ما يجعل استخدام التطبيق ممتعًا. إنه مسؤول عن تفسير النقرات والضغطات والنقرات وإمالة جهازك وإرسالها لإجراء تحديثات على النموذج أو العرض. يجب أن تكون وحدة التحكم “رفيعة”، مما يعني أنه يجب عليها فقط تفسير الإدخال وتمرير الإشارة المناسبة إلى المكان الصحيح، حتى يتمكن التطبيق من إجراء التعديلات وفقًا لما يتوقعه المستخدم. عندما تتدفق المعلومات بين النموذج والعرض، سيعمل جهاز التحكم كحلقة وصل. تعمل حزم وحدات التحكم على جعل التطبيقات ممتعة في الاستخدام. وهو مسؤول عن تفسير النقرات والضغطات والنقرات وإمالة الجهاز وإرسالها إلى النموذج أو العرض للحصول على التحديثات. يجب أن تكون وحدة التحكم “رفيعة”، مما يعني أنها يجب أن تفسر المدخلات وتوصيل الإشارات المناسبة إلى الأماكن الصحيحة بحيث يتكيف التطبيق مع توقعات المستخدم. تعمل وحدات التحكم كروابط عندما تتدفق المعلومات بين النموذج وطريقة العرض.

إذن، ماذا عن نظام التشغيل iOS و"النوع الخاطئ من MVC"؟ إذن، ماذا عن iOS و"MVC الخاطئ"؟ عند التطوير لنظام iOS، غالبًا ما يتخذ نموذج Model-View-Controller Pattern شكل MassiveViewController Pattern. عند التطوير لنظام التشغيل iOS، غالبًا ما يأخذ نمط Model-View-Controller شكل نمط MassiveViewController. ألق نظرة على هذا المثال لفئة وحدة التحكم.

import UIKit

class MassiveViewController: UIViewController {
  
  private var stackView: UIStackView = {
    let stack = UIStackView()
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.axis = .vertical
    stack.alignment = .center
    stack.distribution = .fillProportionally
    return stack
  }()
  
  private var photoView = UIImageView(image: UIImage(named: "SomeImage"))
  
  private var descriptionLabel: UILabel = {
    let label = UILabel()
    label.textColor = .black
    label.text = "Descriptive text"
    return label
  }()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    self.stackView.addArrangedSubview(self.photoView)
    self.stackView.addArrangedSubview(self.descriptionLabel)
    self.view.addSubview(self.stackView)
    
    self.stackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
    self.stackView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    self.stackView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    self.stackView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor).isActive = true
    
    let photoTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handlePhotoViewTap(_:)))
    self.photoView.addGestureRecognizer(photoTapRecognizer)
    
    let labelTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDescriptionLabelTap(_:)))
    self.descriptionLabel.addGestureRecognizer(labelTapRecognizer)
  }
  
  @objc private func handlePhotoViewTap(_ sender: UITapGestureRecognizer) {
    // Do something mind blowing
  }
  
  @objc private func handleDescriptionLabelTap(_ sender: UITapGestureRecognizer) {
    // Do some other cool thing
  }
}

MassiveViewController.swift

أنا متأكد من أن هذا يبدو مألوفًا وأنك ربما كتبت شيئًا كهذا، وأنا أعلم أنني فعلت ذلك. إذن ما هي المشكلة هنا؟ أنا متأكد من أن هذا يبدو مألوفًا، ربما تكون قد كتبت شيئًا كهذا، وأنا أعلم أنني فعلت ذلك. إذن ما هي المشكلة؟

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

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

كيف يمكننا تحسينه؟


class PlainMVCView: UIView {
  
  private var stackView: UIStackView = {
    let stack = UIStackView()
    stack.translatesAutoresizingMaskIntoConstraints = false
    stack.axis = .vertical
    stack.alignment = .center
    stack.distribution = .fillProportionally
    return stack
  }()
  
  private var photoView = UIImageView(image: UIImage(named: "SomeImage"))
  
  private var descriptionLabel: UILabel = {
    let label = UILabel()
    label.textColor = .black
    label.text = "Descriptive text"
    return label
  }()
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    setupView()
  }
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupView()
  }
  
  private func setupView() {
    self.stackView.addArrangedSubview(self.photoView)
    self.stackView.addArrangedSubview(self.descriptionLabel)
    self.addSubview(self.stackView)
    
    self.stackView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor).isActive = true
    self.stackView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor).isActive = true
    self.stackView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
    self.stackView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true
  }
  
  public func addPhotoViewGestureRecognizer(_ recognizer: UIGestureRecognizer) {
    self.photoView.addGestureRecognizer(recognizer)
  }
  
  public func addDescriptionLabelGestureRecognizer(_ recognizer: UIGestureRecognizer) {
    self.descriptionLabel.addGestureRecognizer(recognizer)
  }
}

عاديMVCView.swift


class PlainMVCViewController: UIViewController {

  override func loadView() {
    self.view = PlainMVCView()
  }
  
  override func viewDidLoad() {
    let photoTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handlePhotoViewTap(_:)))
    // This will work for now, because we know the specific type of the view.
    // However, force casting should be avoided in general. More on that in the next example.
    (self.view as! PlainMVCView).addPhotoViewGestureRecognizer(photoTapRecognizer)
    
    let labelTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDescriptionLabelTap(_:)))
    (self.view as! PlainMVCView).addDescriptionLabelGestureRecognizer(labelTapRecognizer)
  }
  
  @objc private func handlePhotoViewTap(_ sender: UITapGestureRecognizer) {
    // Do something mind blowing
  }
  
  @objc private func handleDescriptionLabelTap(_ sender: UITapGestureRecognizer) {
    // Do something a little less mind blowing, but still cool
  }
}

عاديMVCViewController.swift

أعلاه هو نفس الكود، ولكنه مقسم إلى أجزاء تتوافق مع مسؤولية MVC واحدة. لاحظ أن فئة وحدة التحكم تقلصت قليلاً. ومع ذلك، فإن فئة العرض هي تقريبًا بنفس حجم الملف الأصلي. إذن ما الذي فزنا به حقًا؟ هذا هو الجزء الصعب في MVC، لأنه إذا كنت بحاجة إلى كتابة المزيد من التعليمات البرمجية، فقد لا يبدو دائمًا أنك تحصل على شيء ما منها. أعلاه هو نفس الكود، ولكنه مقسم إلى أجزاء تتوافق مع مسؤوليات MVC الفردية. لاحظ أن فئة وحدة التحكم قد تقلصت قليلاً. ومع ذلك، فإن حجم فئة العرض هو تقريبًا نفس حجم الملف الأصلي. إذن ما الذي فزنا به بالضبط؟ هذا هو الجزء الأكثر صعوبة في MVC لأنه إذا كنت بحاجة إلى كتابة المزيد من التعليمات البرمجية، فقد لا يبدو دائمًا أنك حصلت على أي شيء منه.

من خلال إعادة بناء الكود بهذه الطريقة، فإنه سيلتزم بمبدأ فصل الاهتمامات. تتعامل فئة وحدة التحكم فقط مع مشكلات وحدة التحكم، وبصرف النظر عن تهيئة كائن العرض المقابل، فهي منفصلة تمامًا عن كل ما يتعلق بعرض الأشياء للمستخدم. ستكون هذه ميزة قيمة مع نمو التطبيق، لأنه في يوم من الأيام قد ترغب في استبدال فئة وحدة التحكم بفئة جديدة أفضل. إذا تم فصل وحدة التحكم عن العرض، فهذا يعني تقليل العمل بالنسبة لك وتقليل احتمال حدوث الأخطاء والأخطاء. لاحظ أيضًا أننا حددنا طريقتين في طريقة العرض الخاصة بنا لمساعدة المتحكمين الذين قد يرغبون في الاستماع للنقر على الأحداث في أي من الأجزاء “المثيرة للاهتمام” من العرض، حتى لا ينتهك قانون ديميتر. من خلال إعادة هيكلة الكود بهذه الطريقة، فإنه سوف يلتزم بمبدأ فصل الاهتمامات. تتعامل فئة وحدة التحكم مع مشكلات وحدة التحكم فقط وهي منفصلة تمامًا عن كل ما يتعلق بعرض المحتوى للمستخدم، باستثناء تهيئة كائن العرض المقابل. ستكون هذه ميزة قيمة مع نمو تطبيقك، حيث قد ترغب يومًا ما في استبدال فئة وحدة التحكم بفئة تحكم جديدة أفضل. إذا تم فصل وحدة التحكم عن طريقة العرض، فسيكون لديك عمل أقل للقيام به وتقل احتمالية الأخطاء والأخطاء. لاحظ أيضًا أننا حددنا طريقتين في العرض لمساعدة وحدات التحكم التي قد ترغب في الاستماع إلى الأحداث في الأجزاء “المثيرة للاهتمام” من العرض لتجنب انتهاك قانون ديميتر.

هل هذا هو؟

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

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

    protocol Controller {
associatedtype ViewType
var currentView: ViewType { get }
}

class BaseViewController<ViewType: UIView>: UIViewController, Controller {

override func loadView() {
  self.view = ViewType()
}

var currentView: ViewType {
  /*
   Because of how we implemented loadView(), this cast should never fail.
   However, linters and such tools may complain if we force cast and we want
   to be good developers, so we perform Optional binding just to be safe.
   */
  if let view = self.view as? ViewType {
    return view
  } else {
    let view = ViewType()
    self.view = view
    return view
  }
}
}
BaseController.swift 
protocol View {
func setupView()
}

class BaseView: UIView, View {

override init(frame: CGRect) {
  super.init(frame: frame)
  setupView()
}

required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  setupView()
}

func setupView() {
  // Use this method to do any set up that is shared between all your views,
  // for example drawing backgrounds, etc.
}
}

BaseView.swift 
@objc protocol MVCViewActionDelegate: AnyObject {
func photoViewWasTapped()
func descriptionLabelWasTapped()
}

class MVCView: BaseView {

private var stackView: UIStackView = {
  let stack = UIStackView()
  stack.translatesAutoresizingMaskIntoConstraints = false
  stack.axis = .vertical
  stack.alignment = .center
  stack.distribution = .fillProportionally
  return stack
}()

private var photoView = UIImageView(image: UIImage(named: "SomeImage"))

private var descriptionLabel: UILabel = {
  let label = UILabel()
  label.textColor = .black
  label.text = "Descriptive text"
  return label
}()

public weak var actionDelegate: MVCViewActionDelegate?

override func setupView() {
  super.setupView()
  
  self.stackView.addArrangedSubview(self.photoView)
  self.stackView.addArrangedSubview(self.descriptionLabel)
  self.addSubview(self.stackView)
  
  self.stackView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor).isActive = true
  self.stackView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor).isActive = true
  self.stackView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
  self.stackView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true
  
  let photoTapEvent = UITapGestureRecognizer(target: self.actionDelegate, action: #selector(MVCViewActionDelegate.photoViewWasTapped))
  self.photoView.addGestureRecognizer(photoTapEvent)
  
  let labelTapEvent = UITapGestureRecognizer(target: self.actionDelegate, action: #selector(MVCViewActionDelegate.descriptionLabelWasTapped))
  self.descriptionLabel.addGestureRecognizer(labelTapEvent)
}
}

MVCView.swift

class MVCViewController: BaseViewController<MVCView>, MVCViewActionDelegate {

override func viewDidLoad() {
  super.viewDidLoad()
  self.currentView.actionDelegate = self
}

func photoViewWasTapped() {
  // Do some mind blowing stuff
}

func descriptionLabelWasTapped() {
  // Do some other cool junk
}
}
MVCViewController.swift

أصبح لدينا الآن 4 ملفات فجأة (7 ملفات، إذا قمت بفصل البروتوكولات إلى ملفات خاصة بها). هل هذا ضروري حقا؟؟ أصبح لدينا الآن فجأة 4 ملفات (7 ملفات إذا قمت بفصل البروتوكولات إلى ملفات خاصة بها). هل هذا ضروري حقا؟؟ما حدث هنا هو أننا استخرجنا أي سلوك مشترك محتمل لكل من فئتي وحدة التحكم والعرض، ووضعناهما في فئة أساسية. لا يُقصد من الفئة الأساسية أن تتم تهيئتها من تلقاء نفسها، ولكنها تحدد وتوفر العمليات والتدفقات المشتركة التي ستشاركها كل فئة فرعية. ما يحدث هنا هو أننا نستبعد أي سلوك عام محتمل من وحدة التحكم ونعرض الفئات ونضعها في فئة أساسية. ليس المقصود من الفئة الأساسية في الواقع تهيئة نفسها، ولكنها تحدد وتوفر العمليات والتدفقات المشتركة التي ستشاركها كل فئة فرعية.

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

وهذا يعني أيضًا أنه إذا كنت تريد تغيير نوع فئة العرض المرتبطة بوحدة تحكم معينة، فسيكون ذلك يتطلب جهدًا أقل بكثير. نظرًا لأننا اعتمدنا بروتوكول MVCViewActionDelegate، فإن تبديل فئة العرض سيكون سهلاً مثل تغيير اسم الفئة في القيد العام، وتنفيذ أساليب تفويض الإجراء الجديدة (إذا كان هناك أي طريقة مصاحبة للعرض الجديد). وهذا يعني أيضًا أنه إذا كنت تريد تغيير نوع فئة العرض المرتبطة بوحدة تحكم معينة، فلديك عمل أقل بكثير. نظرًا لأننا اعتمدنا بروتوكول MVCViewActionDelegate، فإن تبديل فئات العرض يعد أمرًا بسيطًا مثل تغيير اسم الفئة في قيد عام وتنفيذ طريقة تفويض الإجراء الجديدة (إذا كان العرض الجديد مصحوبًا بأي إجراءات).

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

نصائح

imgView.contentMode = UIViewContentModeScaleAspectFill; إذا قمت بتعيين هذه الخاصية، قم بتعيين imgView.clipsToBounds = YES; وإلا، إذا كانت الصورة غير مناسبة، فسوف تختفي عن الأنظار.

لقد تقدمت مؤخرًا بطلب لتعيين صورة لـ UIImageView. حجم UIImageView هو 375X88 أو 375X64، وحجم الصورة هو 1000X400. ويشترط ألا يمكن اعتراض عرض الصورة، ولا يمكن اعتراض ارتفاعها إلا من أعلى الصورة. لقد أكملتها مع لقطة الشاشة الخاصة بي. طريقة التنفيذ هي كما يلي:

    CGFloat height =  UIImageView.height / (UIImageView.width) * image.size.width;
    CGImageRef imageRef = image.CGImage;
    CGImageRef imageRefRect = CGImageCreateWithImageInRect(imageRef, CGRectMake(0, image.size.height - height, image.size.width, height));
    UIImage *newImage = [[UIImage alloc] initWithCGImage:imageRefRect];
    CGImageRelease(imageRefRect);
    UIImageView.image = newImage;

فهل يمكن تلبية هذا المطلب باستخدام contentMode الخاص بـ UIImageView؟ وعلى الرغم من أن هذا النموذج قد تم استخدامه من قبل، إلا أنه لم يتم تلخيصه. وسوف أغتنم هذه الفرصة لتلخيص ذلك: العرض التجريبي: https://github.com/dandan2009/UIImageViewContentMode

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //最近做了个需求,给UIImageView的设置图片,UIImageView的大小是375X88或375X64,图片的大小是1000X400,要求图片宽度不能截取,高度只能截取图片的顶部,我是用自己截图完成的,实现方法如下的:
//    伪代码 image就是图片
//    CGFloat height =  UIImageView.height / (UIImageView.width) * image.size.width;
//    CGImageRef imageRef = image.CGImage;
//    CGImageRef imageRefRect = CGImageCreateWithImageInRect(imageRef, CGRectMake(0, image.size.height - height, image.size.width, height));
//    UIImage *newImage = [[UIImage alloc] initWithCGImage:imageRefRect];
//    CGImageRelease(imageRefRect);
//    UIImageView.image = newImage;
//    那么用UIImageView的contentMode 能不能完成这个需求呢?虽然这个模式之前也用过,但是没有总结过,趁着这个机会总结一下
    
    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.frame = self.view.bounds;
    scrollView.backgroundColor = [UIColor greenColor];
    scrollView.contentSize = CGSizeMake(self.view.frame.size.width, 1200);
    [self.view addSubview:scrollView];
    
    UIImage *iconImage = [UIImage imageNamed:@"icon"];
    CGFloat imageWidth = iconImage.size.width;
    CGFloat imageheight = iconImage.size.height;
    
    UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(10, 30, 370, 20)];
    lab.text = @"1 UIImageView的尺寸刚好等于image的尺寸";
    [scrollView addSubview:lab];
    
    UIView *base1 = [[UIView alloc] initWithFrame:CGRectMake(lab.left, lab.bottom + 5, imageWidth + 10, imageheight + 10)];
    base1.backgroundColor = [UIColor redColor];
    [scrollView addSubview:base1];
    UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, imageWidth , imageheight )];
    imageView1.image = iconImage;
    imageView1.backgroundColor = [UIColor orangeColor];
    [base1 addSubview:imageView1];
    
    UILabel *labsec1 = [[UILabel alloc] initWithFrame:CGRectMake(base1.left, base1.bottom + 10, 370, 20)];
    labsec1.text = @"2 以下测试是UIImageView的尺寸小于image的尺寸";
    [scrollView addSubview:labsec1];
    
    /// 默认contentMode UIViewContentModeScaleToFill
    // 可以看到这个是把图片的拉伸或压缩,使图片的大小刚好等于imageview的大小
    UILabel *lab2 = [[UILabel alloc] initWithFrame:CGRectMake(labsec1.left, labsec1.bottom + 10, 370, 20)];
    lab2.text = @"2.1 默认contentMode UIViewContentModeScaleToFill";
    [scrollView addSubview:lab2];
    
    UIView *base2 = [[UIView alloc] initWithFrame:CGRectMake(lab2.left, lab2.bottom + 5, imageWidth + 10, imageheight + 10)];
    base2.backgroundColor = [UIColor redColor];
    [scrollView addSubview:base2];
    UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, imageWidth-20 , imageheight-40 )];
    imageView2.backgroundColor = [UIColor orangeColor];
    imageView2.image = iconImage;
    [base2 addSubview:imageView2];
    
    ///ModeScaleAspectFit 图片在不超出UIImageView的情况下,保证图片不变形,就有可能导致UIImageView不被image冲满,有空白留出
    UILabel *lab3 = [[UILabel alloc] initWithFrame:CGRectMake(base2.left, base2.bottom + 10, 370, 20)];
    lab3.text = @"2.2 contentMode == ModeScaleAspectFit";
    [scrollView addSubview:lab3];
    
    UIView *base3 = [[UIView alloc] initWithFrame:CGRectMake(lab3.left, lab3.bottom + 5, imageWidth + 10, imageheight + 10)];
    base3.backgroundColor = [UIColor redColor];
    [scrollView addSubview:base3];
    UIImageView *imageView3 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, imageWidth-20 , imageheight-40 )];
    imageView3.contentMode = UIViewContentModeScaleAspectFit;
    imageView3.image = iconImage;
    imageView3.backgroundColor = [UIColor orangeColor];
    [base3 addSubview:imageView3];
    
    
    /// ModeScaleAspectFill 图片在保证UIImageView被填满的情况下 ,保证图片不变形,就有可能导致image超出UIImageView
    UILabel *lab4 = [[UILabel alloc] initWithFrame:CGRectMake(base3.left, base3.bottom + 10, 370, 20)];
    lab4.text = @"2.3 contentMode == ModeScaleAspectFill";
    [scrollView addSubview:lab4];
    
    UIView *base4 = [[UIView alloc] initWithFrame:CGRectMake(lab4.left, lab4.bottom + 5, imageWidth + 10, imageheight + 10)];
    base4.backgroundColor = [UIColor redColor];
    [scrollView addSubview:base4];
    UIImageView *imageView4 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, imageWidth-20 , imageheight-40 )];
    imageView4.backgroundColor = [UIColor orangeColor];
    imageView4.contentMode = UIViewContentModeScaleAspectFill;
    imageView4.image = iconImage;
    [base4 addSubview:imageView4];
    
    //ModeBottom 图片在保证UIImageView被填满, 且UIImageView底部不被超出的情况下,image按自己的实际大小完全展示,有可能导致image超出UIImageView
    UILabel *lab5 = [[UILabel alloc] initWithFrame:CGRectMake(base4.left, base4.bottom + 10, 370, 20)];
    lab5.text = @"2.4 contentMode == ModeBottom";
    [scrollView addSubview:lab5];
    
    UIView *base5 = [[UIView alloc] initWithFrame:CGRectMake(lab5.left, lab5.bottom + 35, imageWidth + 10, imageheight + 10)];
    base5.backgroundColor = [UIColor redColor];
    [scrollView addSubview:base5];
    UIImageView *imageView5 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, imageWidth-20 , imageheight-40 )];
    imageView5.backgroundColor = [UIColor orangeColor];
    imageView5.contentMode = UIViewContentModeBottom;
    imageView5.image = iconImage;
    [base5 addSubview:imageView5];
    
}

شارك

لخص الشعور بحل أسئلة الخوارزمية:

في الأسبوع الماضي، عملت على سؤال خوارزمي لمدة يوم تقريبًا، لكنني لم أتمكن من حله. لقد ظللت أخدشها. كانت الكفاءة منخفضة حقًا، لذا في المستقبل، سأستغرق ما يصل إلى نصف ساعة لحل سؤال ما. إذا لم أتمكن من حلها، فسوف أنظر إلى تطبيقات الآخرين، ثم أنفذها مرة أخرى بنفسي. لقد اتبعت هذا الروتين هذا الأسبوع.

لماذا لم أجيب على هذا السؤال هذا الأسبوع؟

  1. لست على دراية بالمعرفة الأساسية للغة C والمكتبات المقابلة لها، مثل strcpy وstrtok وstrcmp 2 لكنني لا أزال أمارس القليل جدًا وأقوم بالقليل جدًا. ما زلت بحاجة لقراءة المزيد من الكتب.

اعتقدت أنني فهمت خوارزميات الآخرين، لكنني ما زلت أرتكب أخطاء عندما نفذتها بنفسي، لذلك يجب أن أتدرب!

الآن بعد أن أصبح هناك المزيد والمزيد من الأسئلة في LeetCode، يكاد يكون من المستحيل الانتهاء منها جميعًا. ماذا علي أن أفعل؟ أعتقد أنه يمكن أن يكون مثل هذا: قم بتصنيف الأسئلة، وقم بإجراء عدد قليل من كل نوع من الأسئلة أولاً، ثم قم بتلخيص اتجاه التفكير لحل هذه الأسئلة. يمكنك الإجابة على بعض الأسئلة كل أسبوع وفقًا لحالتك الخاصة. على أية حال، لا يمكنك استخدام القوة الغاشمة أو الجمود. يجب عليك العودة إلى النية الأصلية المتمثلة في تدريب طريقة التفكير. سؤال واحد يستغرق نصف ساعة 1 إذا لم تتمكن من معرفة كيفية القيام بذلك في 0 دقيقة، فما عليك سوى إلقاء نظرة على أفكار الآخرين، وتنفيذها بنفسك، ثم إلقاء نظرة على الإجابات الممتازة للأشخاص الآخرين. أثناء الإجابة على الأسئلة، يجب عليك إكمال بعض الخوارزميات الأساسية بسرعة (مثل الفرز والبحث والأشجار الثنائية والرسوم البيانية وما إلى ذلك) وقراءة كتب الخوارزميات.