Back home

ARTES #014

ARTES #014

ARTES es una actividad iniciada por 由左耳朵耗子--陈皓: Haga al menos una pregunta sobre el algoritmo Leetcode cada semana, lea y comente al menos un artículo técnico en inglés, aprenda al menos una habilidad técnica y comparta un artículo con opiniones y pensamientos. (Es decir, Algoritmo, Revisión, Sugerencia y Compartir se denominan ARTS) y persisten durante al menos un año.

##ARTES 014

este es el articulo 14

Pregunta sobre el algoritmo del algoritmo

290. Patrón de palabra

Dificultad: Fácil

Dado un pattern y una cadena str, encuentre si str sigue el mismo patrón.

Aquí seguir significa una coincidencia completa, de modo que hay una biyección entre una letra en pattern y una palabra no vacía en str.

Ejemplo 1:

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

**Ejemplo 2:**

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

Ejemplo 3:

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

**Ejemplo 4:**

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

Notas: Puede asumir que pattern contiene solo letras minúsculas y str contiene letras minúsculas separadas por un solo espacio.

Solución

Idioma: C

Cuando vi este algoritmo por primera vez, sentí que se podía resolver con un diccionario, pero no hay ningún diccionario entre los tipos de datos básicos en lenguaje C y no podía pensar en un método simple, así que leí algunas respuestas y luego, dos días después, según mi propio entendimiento, lo implementé nuevamente. El algoritmo inicial es el siguiente:

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

Después de ejecutarlo, descubrí que hay casos de prueba que no se pueden pasar, como por ejemplo: Cuando char* patrón = “abba”; char* str = “perro perro perro perro”;

Al principio pensé que entendía los algoritmos de otras personas, pero aun así cometí errores cuando los implementé. Todavía no pude hacerlo bien. Analicé las razones:

Leí los requisitos de la pregunta nuevamente y luego noté que el patrón y str deberían tener una relación de uno a uno, y mi implementación es una relación de uno a muchos, y el diccionario es una relación de uno a muchos, lo que significa que mi implementación solo puede garantizar Para las mismas letras en diferentes posiciones del patrón, solo se puede garantizar que las palabras en la misma posición correspondientes a str también sean las mismas. Por ejemplo, la primera a del patrón corresponde a perro, y la cuarta a también debería corresponder a perro. Sin embargo, no hay garantía de que para la misma palabra en diferentes posiciones de str, solo pueda haber un carácter de patrón correspondiente. La implementación mejorada es la siguiente:


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

De hecho, es solo agregar el juicio correspondiente uno a uno.


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 la pena aprender las siguientes implementaciones:

Implementación 1, esta es la primera implementación que vi, por lo que mi implementación es muy similar, pero hay diferencias. Este algoritmo hace un juicio primero antes de agregar las palabras en str a la tabla anterior. Si las palabras en str ya existen en la tabla, devuelve falso directamente, mientras que mi implementación primero agrega las palabras en str a la tabla y finalmente las 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;
}

Implementación 2. Esta implementación es en realidad exactamente la misma que el algoritmo anterior. La diferencia es que la tabla ya no está llena de palabras en str, sino de valores hash. Además, este algoritmo hash es relativamente simple, es decir, convierte palabras en números enteros.


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

Implementación 3, esta implementación es un poco más complicada, principalmente porque no usa la función strtok y la implementa yo mismo.


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

Implementación 4: Esta implementación nunca me ha quedado clara. Aunque se puede ejecutar con éxito en LeetCode, creo que habrá problemas de colisión 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;
}

Implementación 5: tengo algo de tiempo para familiarizarme con el siguiente algoritmo.


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

Revisión

Este artículo proviene de https://medium.com/@JimmyMAndersson/ios-development-and-the-wrong-kind-of-mvc-4e3e2decb82e,. La primera parte de este artículo habla sobre cómo usar el patrón de diseño MVC en el desarrollo de iOS, pero luego habla sobre la herencia. La calidad del blog es media.

Desarrollo de iOS y el tipo equivocado de MVC

Al comenzar como desarrollador de iOS, escuchará mucho sobre el patrón MVC (Modelo-Vista-Controlador) y cómo es el mejor al desarrollar aplicaciones móviles. El patrón MVC es una buena manera de pensar en ciertos problemas cuando se trata de desarrollar aplicaciones con una interfaz gráfica de usuario. Le proporciona una forma de pensar que le permite dividir su código en paquetes manejables, donde cada paquete manejará su propio dominio de problema.

Cuando comience como desarrollador de iOS, escuchará mucho sobre el patrón MVC (Modelo-Vista-Controlador) y cómo es mejor al desarrollar aplicaciones móviles. El patrón MVC es una buena manera de pensar en ciertas cuestiones al desarrollar aplicaciones con una interfaz gráfica de usuario. Le brinda una forma de pensar que le permite dividir su código en paquetes manejables, donde cada paquete manejará su propio dominio de problema.

¿Cuáles son estos paquetes de los que estás hablando?

Hay tres paquetes conceptuales diferentes en MVC que se supone que manejan problemas como este: Hay tres paquetes conceptuales diferentes en MVC que deberían manejar problemas como este:

Modelo: El paquete Model maneja lo que los desarrolladores llaman The Business Logic. Lo que es The Business Logic depende de su aplicación particular, pero puede generalizarlo vagamente para que signifique “las cosas en su aplicación que administran sus datos y los mantienen actualizados y precisos”. Esto significa que el trabajo de los Modelos es realizar un seguimiento de la posición de los personajes de tu juego, saber cuántas veces has reproducido una canción en iTunes, guardar tus fotos editadas en un formato que puedas compartir en las redes sociales… El Modelo es lo que hace que tu aplicación sea inteligente.

Los paquetes modelo manejan lo que los desarrolladores llaman lógica empresarial. La lógica empresarial depende de su aplicación específica, pero puede resumirla en términos generales como “lo que hay en su aplicación que administra los datos y los mantiene actualizados y precisos”. Esto significa que sus modelos funcionan para rastrear la ubicación de tu personaje del juego, saber cuántas veces has reproducido una canción en iTunes, guardar fotos editadas en un formato que puedes compartir en las redes sociales… modelos que hacen que tus aplicaciones sean más inteligentes. .

Ver: La Vista en una aplicación MVC representará los datos que se envían desde el Modelo y los mostrará al usuario. Se supone que una Vista es “tonta”, lo que significa que solo debe saber cómo dibujar o mostrar los datos que recibe, pero nunca debe realizar ningún ajuste ni siquiera ser consciente de qué tipo de datos son.

La vista en una aplicación MVC representará los datos enviados desde el modelo y los mostrará al usuario. La vista debe ser “tonta”, lo que significa que solo debe saber cómo dibujar o mostrar los datos que recibe, pero no debe realizar ningún ajuste, ni siquiera saber qué tipo de datos son.Controlador: El paquete Controller es lo que hace que una aplicación sea divertida de usar. Es responsable de interpretar los toques, presiones, clics e inclinaciones de su dispositivo y enviarlos para realizar actualizaciones en el Modelo o la Vista. El controlador debe ser “delgado”, lo que significa que simplemente debe interpretar la entrada y pasar la señal adecuada al lugar correcto, para que la aplicación realice ajustes de acuerdo con lo que espera el usuario. Cuando la información fluye entre el Modelo y la Vista, el Controlador actuará como enlace. Los paquetes de controladores hacen que el uso de las aplicaciones sea divertido. Es responsable de interpretar clics, presiones, clics e inclinaciones del dispositivo y enviarlos al modelo o vista para actualizaciones. El controlador debe ser “delgado”, lo que significa que debe interpretar la entrada y enviar las señales apropiadas a los lugares correctos para que la aplicación se ajuste a las expectativas del usuario. Los controladores actúan como enlaces cuando la información fluye entre el modelo y la vista.

Entonces, ¿qué pasa con iOS y “el tipo incorrecto de MVC”? Entonces, ¿qué pasa con iOS y el “MVC incorrecto”? Al desarrollar para iOS, el patrón Modelo-Vista-Controlador a menudo tomará la forma de un patrón MassiveViewController. Al desarrollar para iOS, el patrón Modelo-Vista-Controlador a menudo toma la forma del patrón MassiveViewController. Eche un vistazo a este ejemplo de una clase de controlador.

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

Estoy seguro de que esto te resulta familiar y que es posible que hayas escrito algo como esto, lo sé. Entonces, ¿cuál es el problema aquí? Estoy seguro de que esto te resulta familiar, probablemente hayas escrito algo como esto, lo sé. Entonces, ¿cuál es el problema?

El problema con este archivo es que no separa las preocupaciones. No adopta el patrón MVC y, por lo tanto, la clase se vuelve masiva y será muy difícil de mantener si realizamos cambios en la aplicación en el futuro. El problema con este documento es que no separa las preocupaciones. No sigue el patrón MVC, por lo que las clases se vuelven muy grandes y serán difíciles de mantener si hacemos cambios en la aplicación en el futuro.

Al observar el código, puede ver que se supone que es una clase de Controlador, pero también asume responsabilidades de la Vista, configurando elementos visuales que solo están ahí para mostrar datos al usuario. Al observar el código, puede ver que debería ser una clase de Controlador, pero también asume la responsabilidad de la vista, configurando elementos visuales que se usan solo para mostrar datos al usuario.

¿Cómo lo mejoramos?


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

Arriba se muestra el mismo código, pero dividido en fragmentos que corresponden a una única responsabilidad de MVC. Tenga en cuenta que la clase Controlador se redujo bastante. Sin embargo, la clase Ver tiene casi el mismo tamaño que el archivo original. Entonces, ¿qué ganamos realmente? Esta es la parte complicada de MVC, porque si necesita escribir más código, puede que no siempre parezca que está obteniendo algo de él. Arriba está el mismo código, pero dividido en partes que corresponden a responsabilidades individuales de MVC. Observe que la clase de controlador se reduce un poco. Sin embargo, el tamaño de la clase de vista es casi el mismo que el tamaño del archivo original. Entonces, ¿qué ganamos exactamente? Esta es la parte más complicada de MVC porque si necesita escribir más código, es posible que no siempre parezca que obtuvo algo.

Al refactorizar el código de esta manera, se cumplirá el principio de separación de preocupaciones. La clase Controlador solo se ocupa de los problemas del Controlador y, además de inicializar el objeto Vista correspondiente, está completamente separada de todo lo relacionado con mostrar cosas al usuario. Esta será una característica valiosa a medida que la aplicación crezca, porque algún día es posible que desee reemplazar una clase de Controlador por una nueva y mejor. Si el Controlador está separado de la Vista, eso significa menos trabajo para usted y la probabilidad de errores y fallas disminuye. También tenga en cuenta que definimos dos métodos en nuestra Vista para ayudar a los Controladores que quieran escuchar eventos de tap en cualquiera de las partes “interesantes” de la vista, para no violar la Ley de Demeter. Al refactorizar el código de esta manera, se cumplirá el principio de separación de preocupaciones. La clase de controlador solo maneja problemas del controlador y está completamente separada de todo lo relacionado con mostrar contenido al usuario, excepto la inicialización del objeto de vista correspondiente. Esta será una característica valiosa a medida que su aplicación crezca, ya que algún día querrá reemplazar la clase de controlador con una clase de controlador nueva y mejor. Si el controlador está desacoplado de la vista, tendrá menos trabajo que hacer y se reducirá la probabilidad de errores. También tenga en cuenta que definimos dos métodos en la vista para ayudar a los controladores que quieran escuchar eventos en las partes “interesantes” de la vista para evitar violar la Ley de Demeter.

¿Es eso todo?

Bueno, si nos alejamos del aspecto MVC puro, podemos ver que nuestra clase Controlador depende en gran medida de una única implementación de una clase Vista. Esto va en contra de los principios de extensibilidad y acoplamiento, así que intentemos solucionarlo con algunos genéricos y abstracciones.

Bueno, si dejamos el lado MVC puro, puedes ver que nuestra clase de controlador depende en gran medida de una única implementación de una clase de vista. Esto va en contra de los principios de extensibilidad y acoplamiento, así que intentemos solucionarlo con algunos genéricos y abstracciones.

    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

Ahora tenemos 4 archivos de repente (7 archivos, si separa los protocolos en sus propios archivos). ¿Es esto realmente necesario? Ahora de repente tenemos 4 archivos (7 archivos si separa los protocolos en sus propios archivos). ¿Es esto realmente necesario?Lo que sucedió aquí es que abstrajimos cualquier posible comportamiento común de las clases Controlador y Vista, y los pusimos en una clase Base. En realidad, la clase base no está destinada a inicializarse por sí sola, sino que define y proporciona operaciones y flujos comunes que cada subclase compartirá. Lo que sucede aquí es que abstraemos cualquier posible comportamiento público del controlador y de las clases de vista y los colocamos en una clase base. En realidad, la clase base no está destinada a inicializarse a sí misma, sino que define y proporciona operaciones y flujos comunes que cada subclase compartirá.

Esto significa que todas las subclases futuras de Controlador y Vista serán más pequeñas, más fáciles de leer y de mantener, ya que no tendrá que duplicar todo el comportamiento compartido. Agradecerás a las estrellas si decides, por ejemplo, cambiar la imagen de fondo de todas las vistas. En lugar de cambiar la misma línea en 20 clases, solo tendrás que cambiarla en una clase Base. ¡Fácil! Esto significa que todas las subclases futuras de controlador y vista serán más pequeñas, más fáciles de leer y de mantener porque no es necesario duplicar todo el comportamiento compartido. Por ejemplo, si decides cambiar la imagen de fondo de todas las vistas, se lo agradecerás a las estrellas. No es necesario cambiar la misma línea en 20 clases, solo en una clase base. ¡Simple!

Esto también significa que si desea cambiar qué tipo de clase de Vista está asociada con un determinado Controlador, será mucho menos trabajo. Dado que adoptamos el protocolo MVCViewActionDelegate, cambiar la clase Vista será tan fácil como cambiar el nombre de la clase en la restricción genérica e implementar los nuevos métodos de delegado de acción (si hay alguno que acompañe a la nueva Vista). Esto también significa que si desea cambiar el tipo de clase de vista asociada con un determinado controlador, tendrá mucho menos trabajo. Debido a que hemos adoptado el protocolo MVCViewActionDelegate, cambiar de clase de vista es tan simple como cambiar el nombre de la clase en una restricción genérica e implementar el nuevo método de delegado de acción (si la nueva vista incluye alguna acción).

Con suerte, la forma de pensar en MVC al desarrollar aplicaciones iOS se ha vuelto un poco menos abrumadora y un poco más clara después de leer esto. No dude en comentar si tiene preguntas y síganos para recibir notificaciones sobre artículos futuros. Con suerte, después de leer este artículo, el enfoque para pensar en MVC al desarrollar aplicaciones iOS se vuelve menos desalentador y más claro. Si tiene alguna pregunta, no dude en dejar un comentario y permanecer atento a futuros artículos.

Consejos

imgView.contentMode = UIViewContentModeScaleAspectFill; Si establece esta propiedad, establezca imgView.clipsToBounds = YES; de lo contrario, si la imagen no es apropiada, desaparecerá de la vista.

Recientemente hice una solicitud para configurar una imagen para UIImageView. El tamaño de UIImageView es 375X88 o 375X64 y el tamaño de la imagen es 1000X400. Se requiere que el ancho de la imagen no pueda interceptarse y que la altura solo pueda interceptarse desde la parte superior de la imagen. Lo completé con mi propia captura de pantalla. El método de implementación es el siguiente:

    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;

Entonces, ¿se puede cumplir este requisito utilizando contentMode de UIImageView? Aunque este modelo se ha utilizado antes, no se ha resumido. Aprovecho para resumirlo: Demostración de prueba: 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];
    
}

Compartir

Resuma la sensación de hacer preguntas de algoritmos:

La semana pasada trabajé en una pregunta sobre algoritmos durante casi un día, pero no lo logré. Seguí rascándolo. La eficiencia era realmente baja, por lo que en el futuro me tomaría hasta media hora para resolver una pregunta. Si no pudiera resolverlo, miraría las implementaciones de otras personas y luego las implementaría nuevamente yo mismo. Esta semana seguí esta rutina.

¿Por qué no resolví esta pregunta esta semana?

  1. No estoy familiarizado con los conocimientos básicos del lenguaje C y las bibliotecas correspondientes, como strcpy, strtok, strcmp. 2 Pero todavía practico muy poco y hago muy poco. Todavía necesito leer más libros.

Pensé que había entendido los algoritmos de otras personas, pero aun así cometí errores cuando los implementé yo mismo, ¡así que debo practicar!

Ahora que hay cada vez más preguntas en LeetCode, es casi imposible resolverlas todas. ¿Qué tengo que hacer? Creo que puede ser así: Clasifique las preguntas, primero haga algunas de cada tipo de preguntas y luego resuma la dirección del pensamiento para resolver dichas preguntas. Puedes hacer algunas preguntas cada semana según tu propia situación. De todos modos, no se puede utilizar la fuerza bruta ni el punto muerto. Debes volver a la intención original de entrenar la forma de pensar. Una pregunta lleva media hora 1. Si no puede descubrir cómo hacerlo en 0 minutos, simplemente mire las ideas de otras personas, impleméntelas usted mismo y luego observe las excelentes respuestas de otras personas. Mientras responde las preguntas, debe completar rápidamente algunos algoritmos básicos (como clasificación, búsqueda, árboles binarios, gráficos, etc.) y leer libros de algoritmos.