W drugim poście poświęconym algorytmowi sortowania bąbelkowego wrócę do stworzenia metod, które pozwolą posortować elementy o typie string. Dodatkowo zostanie ukazane to w jaki sposób można wykorzystać interfejs.
Sortowanie elementów typu string w TypeScript
Aby posortować elementy o typie string przy użyciu klasy BubbleSort musimy stworzyć nową klasę, która będzie bliźniaczo podobna do klasy NumbersCollection. Klasa ta będzie także miała wbudowane trzy metody czyli length, compare oraz swap.
Przypomnijmy najpierw jak wyglądała klasa NumbersCollection.
// NumbersCollection.ts
export class NumbersCollection {
constructor(public data: number[]) {};
get length(): number {
return this.data.length;
}
compare(leftIndex: number, rightIndex: number): boolean {
return this.data[leftIndex] > this.data[rightIndex]
}
swap(leftIndex: number, rightIndex: number): void {
const leftHand = this.data[leftIndex];
this.data[leftIndex] = this.data[rightIndex];
this.data[rightIndex] = leftHand;
}
}
Wytłumaczenie poszczególnych metod znajdują się w poprzednim poście.
Stwórzmy teraz nową klasę i nazwijmy ją CharactersCollection.
W konstruktorze musimy zmienić typ danych, z któe będzie przyjmowała instancja klasy. Następnie w metodzie compare musimy użyć metod wbudowanych w język czyli .toLowerCase(). Używamy ich z racji tego, że porównanie stringów w JavaScript opiera się na ich indeksach z tablicy ASCII co sprawia, że przykładowo duże A jest mniejsze od małego (proces ten został opisany w poprzednim poście).
Ostatnia metoda najbardziej się różni od tej z klasy sortującej liczby. Tworzymy zmienna characters i dzielimy przekazany tekst na tablicę liter oddzielonych przecinkiem (linia 15). Następnie tworzymy zmienną leftHand, która przypisujemy pierwszy element każdej pary przekazanej do metody.
W linii 16 nadpisujemy pierwszy argument otrzymany w funkcji drugim argumentem otrzymanym. A następnie w linii 17 nadpisujemy drugi element podmienianej pary elementem leftHand, który wcześniej został zapisany w pamięci.
Na końcu nadpisujemy element data naszą tablicą characters usuwając z niej przecinki.
// CharactersCollection.ts
export class CharactersCollection {
constructor(public data: string) {};
get length(): number {
return this.data.length;
}
compare(leftIndex: number, rightIndex: number): boolean {
return
this.data[leftIndex].toLowerCase()
> this.data[rightIndex].toLowerCase()
}
swap(leftIndex: number, rightIndex: number): void {
const characters = this.data.split('');
const leftHand = characters[leftIndex]
characters[leftIndex] = characters[rightIndex];
characters[rightIndex] = leftHand;
this.data = characters.join('');
}
}
Technicznie rzecz biorąc ta klasa jak i wcześniejsza (NumbersCollection) jedyne co wykonuje na przekazanych danych to:
- sprawdza długość przekazanych elementów
- porównuje wielkością dwa argumenty
- podmienia argumenty stronami.
Interfejsy
Jeżeli posiadamy już stworzone dwie klasy to wiemy o tym, że ich metody nazywają się tak samo i robią te same zadania na innych typach danych. Aby ułatwić korzystanie z klasy BubbleSorter możemy stworzyć interfejs, który umożliwi nam korzystanie z tej klasy bez względu na to co jest zaimplementowane w poszczególnych metodach a elementami, które muszą się zgadzać są typy przekazywanych argumentów oraz to co one zwracają.
Wróćmy do klasy BubbleSorter.
// BubbleSorter.ts
import { CharactersCollection } from './CharactersCollection';
class BubbleSorter {
constructor(public collection: CharactersCollection) {}
sortElements(): void {
const { length } = this.collection;
for (let i = 0; i < length; i++) {
for (let j = 0; j < length - i - 1; j++) {
if (this.collection.compare(j, j + 1)) {
this.collection.swap(j, j + 1);
}
}
}
}
}
const characterToSort = new CharactersCollection('zaAbcX');
const bubbleSorter = new BubbleSorter(characterToSort);
bubbleSorter.sort(); // => ['AabcXz']
Aby skorzystać z funkcji musimy zaimportować klase CharactersCollection. W konstruktorze musimy przekazać jako typ argumentu collection wpisujemy nazwę klasy, która posiada metody length, compare oraz swap. Na końcu odpowiednio wywołujemy klasę tworząc nową instancję CharactersCollection oraz BubbleSorter.
Teraz aby wykorzystać klasę posiadającą metody do sortowania liczb musielibyśmy podmieniać wszystkie argumenty aby wykorzystać odpowiednie metody.
Na ratunek przychodzą jednak interfejsy, które są jednym z głównych elementów wbudowanych w TypeScript. Aby stworzyć interefejs korzystamy z słowa kluczowego interface i przypisujemy nazwę. W środku definiujemy nasze metody, to jaki typ zwracają oraz ich argumenty.
interface SortableMethods {
length: number;
compare(leftIndex: number, rightIndex: number): boolean;
swap(leftIndex: number, rightIndex: number): void;
}
Taki interfejs umieszczamy w konstruktorze klasy BubbleSorter zamiast przekazywania klasy NumbersCollection lub CharactersCollection.
interface SortableMethods {
length: number;
compare(leftIndex: number, rightIndex: number): boolean;
swap(leftIndex: number, rightIndex: number): void;
}
class BubbleSorter {
constructor(public collection: SortableMethods) {}
sortElements(...)
}
Teraz za każdym razem gdy będziemy chcieli skorzystać z klasy BubbleSorter musimy jej przekazać instancję klasy, która będzie zgadzała się z interfejsem SortableMethods i będzie posiadała zadeklarowane w niej metody z odpowiednimi typami. Interfejs będzie patrzył tylko na typ przekazanych argumentów natomiast ich nazwy mogą się różnić.