Back home

ARTES #014

ARTES #014

ARTS é uma atividade iniciada por 由左耳朵耗子--陈皓: Faça pelo menos uma pergunta sobre o algoritmo Leetcode toda semana, leia e comente pelo menos um artigo técnico em inglês, aprenda pelo menos uma habilidade técnica e compartilhe um artigo com opiniões e pensamentos. (Ou seja, Algoritmo, Revisão, Dica e Compartilhamento são chamados de ARTS) e persistem por pelo menos um ano.

ARTES 014

Este é o artigo 14

Pergunta sobre algoritmo de algoritmo

290. Padrão de palavra

Dificuldade: Fácil

Dado um pattern e uma string str, descubra se str segue o mesmo padrão.

Aqui seguir significa uma correspondência completa, de modo que haja uma bijeção entre uma letra em pattern e uma palavra não vazia em str.

Exemplo 1:

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

**Exemplo 2:**

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

Exemplo 3:

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

**Exemplo 4:**

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

Notas: Você pode presumir que pattern contém apenas letras minúsculas e str contém letras minúsculas separadas por um único espaço.

Solução

Idioma: C

Quando vi esse algoritmo pela primeira vez, senti que ele poderia ser resolvido com um dicionário, mas não existe dicionário entre os tipos de dados básicos na linguagem C e não consegui pensar em um método simples, então li algumas respostas e, dois dias depois, com base em meu próprio entendimento, implementei-o novamente. O algoritmo inicial é o seguinte:

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;
}

Depois de executá-lo, descobri que existem casos de teste que não podem ser aprovados, como: Quando char* padrão = “abba”; char* str = “cachorro cachorro cachorro cachorro”;

Originalmente, pensei que tinha entendido os algoritmos de outras pessoas, mas ainda cometi erros ao implementá-los. Eu ainda não consegui acertar. Analisei os motivos:

Li os requisitos da pergunta novamente e percebi que padrão e str deveriam ter um relacionamento um-para-um, e minha implementação é um relacionamento um-para-muitos, e o dicionário é um relacionamento um-para-muitos, o que significa que minha implementação só pode garantir Para as mesmas letras em diferentes posições do padrão, só pode ser garantido que as palavras na mesma posição correspondente a str também sejam as mesmas. Por exemplo, o primeiro a no padrão corresponde a cachorro, e o quarto a também deve corresponder a cachorro. No entanto, não há garantia de que para a mesma palavra em diferentes posições de str, só possa haver um caractere padrão correspondente. A implementação aprimorada é a seguinte:


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;
}

Na verdade, trata-se apenas de adicionar um julgamento correspondente um a um.


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;
            }
        }
    }

Vale a pena aprender as seguintes implementações:

Implementação 1, esta é a primeira implementação que vi, então minha implementação é muito parecida com ela, mas há diferenças. Este algoritmo faz um julgamento primeiro, antes que as palavras em str sejam adicionadas à tabela anterior. Se as palavras em str já existirem na tabela, ela retornará false diretamente, enquanto minha implementação primeiro adiciona as palavras em str à tabela e, finalmente, compara.


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;
}

Implementação 2. Esta implementação é exatamente igual ao algoritmo anterior. A diferença é que a tabela não é mais preenchida com palavras em str, mas com valores hash. Além disso, esse algoritmo hash é relativamente simples, ou seja, converte palavras em inteiros.


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;
}

Implementação 3, essa implementação é um pouco mais complicada, principalmente porque não utiliza a função strtok e a implementa sozinho.


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;
}

Implementação 4: Esta implementação nunca foi clara para mim. Embora possa ser executado com sucesso no LeetCode, acho que haverá problemas de colisão de hash.



#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;
}

Implementação 5: tenho algum tempo para me familiarizar com o algoritmo a seguir.


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;
}

Revisão

Este artigo vem de https://medium.com/@JimmyMAndersson/ios-development-and-the-wrong-kind-of-mvc-4e3e2decb82e, A primeira parte deste artigo fala sobre como usar o padrão de design MVC no desenvolvimento iOS, mas depois fala sobre herança. A qualidade do blog é média.

Desenvolvimento iOS e o tipo errado de MVC

Ao começar como desenvolvedor iOS, você ouvirá muito sobre o padrão MVC (Model-View-Controller) e como ele é o melhor no desenvolvimento de aplicativos móveis. O Padrão MVC é uma boa maneira de pensar sobre certos problemas quando se trata de desenvolver aplicações com interface gráfica de usuário. Ele fornece uma maneira de pensar que permite dividir seu código em pacotes gerenciáveis, onde cada pacote lidará com seu próprio domínio de problema.

Ao começar como desenvolvedor iOS, você ouvirá muito sobre o padrão MVC (Model-View-Controller) e como ele é melhor no desenvolvimento de aplicativos móveis. O padrão MVC é uma boa maneira de pensar sobre certas questões ao desenvolver aplicações com interface gráfica de usuário. Ele oferece uma maneira de pensar que permite dividir seu código em pacotes gerenciáveis, onde cada pacote lidará com seu próprio domínio de problema.

De que pacotes são esses que você está falando?

Existem três pacotes conceituais diferentes no MVC que devem lidar com problemas como estes: Existem três pacotes conceituais diferentes no MVC que devem lidar com problemas como este:

Modelo: O pacote Model lida com o que os desenvolvedores chamam de Business Logic. O que é The Business Logic depende de seu aplicativo específico, mas você pode generalizá-lo vagamente para significar “aquilo em seu aplicativo que gerencia seus dados e os mantém atualizados e precisos”. Isso significa que é trabalho dos Modelos acompanhar a posição dos personagens do jogo, saber quantas vezes você tocou uma música no iTunes, salvar suas fotos editadas em um formato que você possa compartilhar nas redes sociais… O Modelo é o que torna seu aplicativo inteligente.

Os pacotes de modelos lidam com o que os desenvolvedores chamam de lógica de negócios. A lógica de negócios depende do seu aplicativo específico, mas você pode resumi-la vagamente como “o que há no seu aplicativo que gerencia os dados e os mantém atualizados e precisos”. Isso significa que seus modelos funcionam para rastrear a localização do seu personagem no jogo, saber quantas vezes você tocou uma música no iTunes, salvar fotos editadas em um formato que você pode compartilhar nas redes sociais… modelos que tornam seus aplicativos mais inteligentes. .

Ver: A Visualização em um aplicativo MVC representará os dados que são enviados do Modelo e os exibirá ao usuário. Uma View deve ser “burra”, o que significa que ela só deve saber como desenhar ou exibir os dados que recebe, mas nunca deve fazer nenhum ajuste neles ou mesmo estar ciente de que tipo de dados se trata.

A visualização em um aplicativo MVC representará os dados enviados do modelo e os exibirá ao usuário. A visualização deve ser “burra”, ou seja, deve saber apenas desenhar ou exibir os dados que recebe, mas não deve fazer nenhum ajuste neles, nem mesmo saber que tipo de dados se trata.Controlador: O pacote Controller é o que torna um aplicativo divertido de usar. É responsável por interpretar os toques, pressionamentos, cliques e inclinações do seu dispositivo e enviá-los para fazer atualizações no Modelo ou na Visualização. O Controlador deve ser “fino”, ou seja, deve apenas interpretar a entrada e passar o sinal apropriado para o local correto, para que a aplicação faça os ajustes de acordo com o que o usuário espera. Quando a informação flui entre o Modelo e a Visualização, o Controlador atuará como um link. Os pacotes de controlador tornam os aplicativos divertidos de usar. Ele é responsável por interpretar cliques, pressionamentos, cliques e inclinações do dispositivo e enviá-los ao modelo ou visualização para atualizações. O controlador deve ser “fino”, o que significa que deve interpretar a entrada e entregar os sinais apropriados aos locais certos para que a aplicação se ajuste às expectativas do usuário. Os controladores atuam como links quando as informações fluem entre o modelo e a visualização.

Então, e quanto ao iOS e “o tipo errado de MVC”? Então, e quanto ao iOS e ao “MVC errado”? Ao desenvolver para iOS, o padrão Model-View-Controller geralmente assume a forma de um padrão MassiveViewController. Ao desenvolver para iOS, o padrão Model-View-Controller geralmente assume a forma do padrão MassiveViewController. Dê uma olhada neste exemplo de uma classe Controller.

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

Tenho certeza de que isso parece familiar e que você pode ter escrito algo assim, eu sei que escrevi. Então qual é o problema aqui? Tenho certeza que isso parece familiar, você provavelmente escreveu algo assim, eu sei que escrevi. Então qual é o problema?

O problema com este arquivo é que ele não separa preocupações. Ele não está adotando o padrão MVC e, portanto, a classe fica massiva e será muito difícil de manter se fizermos alterações no aplicativo no futuro. O problema deste documento é que ele não separa preocupações. Ele não segue o padrão MVC, então as classes ficam muito grandes e serão difíceis de manter caso façamos alterações na aplicação no futuro.

Olhando para o código, você pode ver que esta deveria ser uma classe Controller, mas também assume responsabilidades de View, configurando elementos visuais que existem apenas para exibir dados ao usuário. Olhando o código, você pode perceber que esta deveria ser uma classe Controller, mas ela também assume a responsabilidade da view, configurando elementos visuais que servem apenas para exibir dados ao usuário.

Como podemos melhorar isso?


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)
  }
}

PlainMVCView.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
  }
}

PlainMVCViewController.swift

Acima está o mesmo código, mas dividido em partes que correspondem a uma única responsabilidade do MVC. Observe que a classe Controller diminuiu bastante. No entanto, a classe View tem quase o mesmo tamanho do arquivo original. Então, o que realmente ganhamos? Essa é a parte complicada do MVC, porque se você precisar escrever mais código, nem sempre parecerá que você está conseguindo algo com isso. Acima está o mesmo código, mas dividido em partes que correspondem às responsabilidades individuais do MVC. Observe que a classe do controlador diminuiu um pouco. No entanto, o tamanho da classe de visualização é quase igual ao tamanho do arquivo original. Então, o que exatamente ganhamos? Esta é a parte mais complicada do MVC porque se você precisar escrever mais código, nem sempre parecerá que você conseguiu algo com isso.

Ao refatorar o código dessa forma, ele aderirá ao Princípio da Separação de Preocupações. A classe Controller lida apenas com problemas de Controller e, além de inicializar o objeto View correspondente, é completamente separada de tudo relacionado à exibição de coisas ao usuário. Este será um recurso valioso à medida que o aplicativo crescer, porque um dia você poderá querer substituir uma classe Controller por uma nova e melhor. Se o Controlador estiver separado da Visualização, isso significa menos trabalho para você e a probabilidade de bugs e erros diminui. Observe também que definimos dois métodos em nossa Visualização para ajudar os Controladores que desejam ouvir eventos de toque em qualquer uma das partes “interessantes” da visualização, para não violar a Lei de Deméter. Ao refatorar o código dessa forma, ele aderirá ao princípio da separação de interesses. A classe do controlador trata apenas de problemas do controlador e é completamente separada de tudo relacionado à exibição de conteúdo ao usuário, exceto a inicialização do objeto de visualização correspondente. Este será um recurso valioso à medida que seu aplicativo crescer, pois um dia você poderá querer substituir a classe do controlador por uma classe de controlador nova e melhor. Se o controlador estiver desacoplado da visualização, você terá menos trabalho a fazer e a probabilidade de bugs e erros será reduzida. Observe também que definimos dois métodos na visão para ajudar os controladores que desejam ouvir eventos nas partes “interessantes” da visão para evitar violar a Lei de Deméter.

É isso?

Bem, se nos afastarmos do aspecto MVC puro, você poderá ver que nossa classe Controller depende muito de uma única implementação de uma classe View. Isso vai contra os princípios de extensibilidade e acoplamento, então vamos tentar consertar isso com alguns genéricos e abstrações.

Bem, se deixarmos o lado MVC puro, você pode ver que nossa classe de controlador depende muito de uma única implementação de uma classe de visualização. Isso vai contra os princípios de extensibilidade e acoplamento, então vamos tentar consertar isso com alguns genéricos e abstrações.

    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

Agora temos 4 arquivos de repente (7 arquivos, se você separar os protocolos em seus próprios arquivos). Isso é realmente necessário?? Agora, de repente, temos 4 arquivos (7 arquivos se você separar os protocolos em seus próprios arquivos). Isso é realmente necessário??O que aconteceu aqui é que abstraímos qualquer possível comportamento comum das classes Controller e View e os colocamos em uma classe Base. A classe base não foi criada para ser inicializada sozinha, mas define e fornece operações e fluxos comuns que cada subclasse compartilhará. O que está acontecendo aqui é que abstraímos qualquer comportamento público possível do controlador e das classes de visualização e os colocamos em uma classe base. Na verdade, a classe base não se destina a ser inicializada, mas sim a definir e fornecer operações e fluxos comuns que cada subclasse compartilhará.

Isso significa que todas as futuras subclasses Controller e View serão menores, mais fáceis de ler e mais fáceis de manter, pois você não terá que duplicar todo o comportamento compartilhado. Você agradecerá às estrelas se decidir, por exemplo, alterar a imagem de fundo de todas as visualizações. Em vez de alterar a mesma linha em 20 classes, você só terá que alterá-la em uma classe Base. Fácil! Isso significa que todas as subclasses futuras de controladores e visualizações serão menores, mais fáceis de ler e mais fáceis de manter porque você não precisará duplicar todo o comportamento compartilhado. Por exemplo, se decidir alterar a imagem de fundo de todas as visualizações, você agradecerá às estrelas. Não há necessidade de alterar a mesma linha em 20 classes, apenas em uma classe base. Simples!

Isso também significa que se você quiser alterar o tipo de classe View associada a um determinado Controller, será muito menos trabalhoso. Como adotamos o protocolo MVCViewActionDelegate, trocar a classe View será tão fácil quanto alterar o nome da classe na restrição genérica e implementar os novos métodos delegados de ação (se houver algum acompanhando a nova View). Isso também significa que se você quiser alterar o tipo de classe de visualização associada a um determinado controlador, terá muito menos trabalho. Como adotamos o protocolo MVCViewActionDelegate, alternar as classes de visualização é tão simples quanto alterar o nome da classe em uma restrição genérica e implementar o novo método delegado de ação (se a nova visualização vier com alguma ação).

Esperamos que a maneira de pensar sobre MVC ao desenvolver seus aplicativos iOS tenha se tornado um pouco menos assustadora e um pouco mais clara depois de ler isto. Sinta-se à vontade para comentar se tiver dúvidas e siga para receber notificações sobre artigos futuros. Esperamos que depois de ler este artigo, a abordagem para pensar em MVC ao desenvolver aplicativos iOS se torne menos assustadora e mais clara. Se você tiver alguma dúvida, fique à vontade para deixar um comentário e fique ligado em artigos futuros.

Dicas

imgView.contentMode = UIViewContentModeScaleAspectFill; Se você configurar esta propriedade, configure imgView.clipsToBounds = YES; caso contrário, se a imagem for inadequada, ela desaparecerá de vista.

Recentemente fiz uma solicitação para definir uma imagem para UIImageView. O tamanho do UIImageView é 375X88 ou 375X64 e o tamanho da imagem é 1000X400. É necessário que a largura da imagem não possa ser interceptada e a altura só possa ser interceptada a partir do topo da imagem. Eu completei com minha própria captura de tela. O método de implementação é o seguinte:

    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;

Então, esse requisito pode ser atendido usando o contentMode do UIImageView? Embora este modelo tenha sido usado antes, ele não foi resumido. Aproveito para resumir: Demonstração de teste: 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];
    
}

Compartilhar

Resuma a sensação de fazer perguntas sobre algoritmos:

Na semana passada, trabalhei em uma questão de algoritmo por quase um dia, mas não consegui. Eu apenas continuei coçando. A eficiência era muito baixa, então, no futuro, eu levaria até meia hora para resolver uma questão. Se eu não conseguisse resolver, examinaria as implementações de outras pessoas e depois implementaria novamente. Essa semana segui essa rotina.

Por que não resolvi essa questão esta semana?

  1. Não familiarizado com o conhecimento básico da linguagem C e das bibliotecas correspondentes, como strcpy, strtok, strcmp 2 Mas ainda pratico muito pouco e faço muito pouco. Ainda preciso ler mais livros.

Achei que tinha entendido os algoritmos de outras pessoas, mas ainda cometi erros quando os implementei, então preciso praticar!

Agora que há cada vez mais perguntas no LeetCode, é quase impossível terminar todas elas. O que devo fazer? Acho que pode ser assim: Classifique as questões, faça primeiro algumas de cada tipo de questões e depois resuma a direção do pensamento para resolver tais questões. Você pode fazer algumas perguntas toda semana de acordo com sua situação. De qualquer forma, você não pode usar força bruta ou impasse. Você deve retornar à intenção original de treinar o modo de pensar. Uma pergunta leva meia hora. 1 Se você não conseguir descobrir como fazer isso em 0 minutos, basta olhar as ideias de outras pessoas, implementá-las você mesmo e depois observar as excelentes respostas de outras pessoas. Ao fazer as perguntas, você deve completar rapidamente alguns algoritmos básicos (como classificação, pesquisa, árvores binárias, gráficos, etc.) e ler livros de algoritmos.