Ładny sposób na iterację i porównanie obiektów w JS / Angular

0

Hej, może coś mi umknęło, może o czymś nie pamiętam, ale nie przychodzi mi nic lepszego do głowy. Ciekaw jestem czy da się poniższy kod napisał ładniej.

Mam 2 kolekcje: boardP1: BoardCellModel[][] oraz forbiddenCells: BoardCellModel[]. Chcę się dowiedzieć, czy w pierwszej się znajdują rekordy odpowiadające drugiej. W C# bym wykorzystał do tego LINQ. Ciekaw jestem czy w JS jest jakiś odpowiednik? Na chwilę obecną mój kod wygląda tak:

private compareBoardWithForbiddenCells(
    forbiddenCells: Array<BoardCellModel>
  ): boolean {
    for (let i = 0; i < 10; i++) {
      for (let j = 0; j < 10; j++) {
        for (let f = 0; f < forbiddenCells.length; f++) {
          if (
            this.boardP1[i][j].col == forbiddenCells[f].col &&
            this.boardP1[i][j].row == forbiddenCells[f].row &&
            this.boardP1[i][j].value == 1
          ) {
            return false;
          }
        }
      }
    }

    return true;
  }

Czy da się to ładniej napisać?

0
for (let f = 0; f < forbiddenCells.length; f++) {
          if (
            this.boardP1[i][j].col == forbiddenCells[f].col &&
            this.boardP1[i][j].row == forbiddenCells[f].row &&
            this.boardP1[i][j].value == 1
          ) {
            return false;
          }

pętlę for w tym przypadku możesz zastąpić metodą find
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Coś w tym stylu:

    const boardCell = this.boardP1[i][j]; // dla większej czytelności
    return forbiddenCells[f].find(cell => (
        boardCell.col == cell.col && boardCell.row == cell.row && boardCell.value == 1
    ));
0

Możesz skorzystać z reduce + find + some, wtedy byś miał coś takiego:

board.reduce((prev,curr)=> prev.concat(curr), []).find(cell => forbiddenCells.some(forbiddenCell => /* warumek*/))

Jeżeli potrzebujesz tablicę obiektów, możesz wykorzystać filter.

0

@Aisekai: Wygląda dużo lepiej niż moje rozwiązanie :) Choć nigdy nie korzystałem z .reduce() i na chwilę obecną wydaje mi się zbyt abstrakcyjnym rozwiązaniem. Podejrzewam też, że niekoniecznie właściwym przy wielowymiarowej tablicy. Bo nie mam pomysłu jak go zastosować z drugim wymiarem tablicy. Czy się mylę?

@LukeJL: Do końca nie wiem skąd w Twoim rozwiązaniu by się brały i oraz j? Wnioskuję, że zostawiamy poniższe pętle, a Twój drugi blok kodu umieszczamy w środku?

for (let i = 0; i < 10; i++) {
      for (let j = 0; j < 10; j++) {

Wydaje mi się, że to nie upraszcza rozwiązania. Na chwilę obecną dodałem jedynie między dwoma pierwszymi forami:
if (this.boardP1[i][j].value !== 1) continue;

Pozwala to pominąć wykonanie ostatniego fora jeśli jeden z warunków nie jest spełniony.

1

Do końca nie wiem skąd w Twoim rozwiązaniu by się brały i oraz j? Wnioskuję, że zostawiamy poniższe pętle, a Twój drugi blok kodu umieszczamy w środku?

tak, moje rozwiązanie dotyczyło tylko zamiany tej wewnętrznej pętli for (let f = 0; f < forbiddenCells.length; f++) {.......} na .find, a dwie pozostałe pętle for by zostały tak, jak napisałeś (i z nich by było i oraz j tak jak teraz masz).

Ale moim zdaniem, jakbym ja to pisał, to tak:

  1. logika chodzenia po mapie powinna być oddzielona od reszty aplikacji. Czyli jeśli masz jakieś pętle for, czy cokolwiek innego (.find, .reduce czy jakiekolwiek inne rozwiązanie), to aplikacja o tym nie powinna wiedzieć. To powinien być szczegół implementacyjny obiektu reprezentującego mapę, który mógłby udostępniać metody do manipulacji nią. Wtedy nawet jak będziesz mieć trochę brzydki kod, to nie będzie miało to aż takiego znaczenia, jeśli zrobisz dobrą enkapsulację. Bo ten brzydki kod będzie w jednym miejscu (i będzie można później łatwo go poprawić bez przerabiania całej aplikacji)

  2. zwykle nie trzymam danych tego typu mapy w postaci wielowymiarowej tablicy, tylko bardziej robię coś w stylu słownika (gdzie kluczem są współrzędne x, y np.

const map = Object.create(null); // pusty obiekt, który nie dziedziczy znikąd (bo np. {} dziedziczy domyślnie z Object)
map[x + ";" + y] = something;
// albo uzywajac obiektów Map
const map = new Map();
map.set(x + ";" + y, something);

Tym sposobem nie jestem zależny od wymiarów (i mapa może być duża w rozmiarze, ale mieć mało obiektów itp.).
Plus łatwiej można iterować

const map = Object.create(null);
for (let k in map) {..... }
Object.keys(map).forEach(......)

albo

const map = new Map();
for (let [key, value] of map.entries()) {........ }

dodatkowo tak jak napisałem - zrobiłbym to tak, żeby odizolować samą logikę mapy. Ale również do tworzenia kluczy też bym wydzielił prywatną funkcję typu getKey, która by robiła nawet to głupie x + ";" + y, żeby nie powtarzać tej logiki.

Ogólnie chodzi o to, żeby od strony aplikacji obsługa tego byłaby prosta. Żeby od strony aplikacji nie robić takiego czegoś:

 this.boardP1[i][j].

tylko bardziej

this.boardP1.getCell(i, j);

no i ta cała funkcja private compareBoardWithForbiddenCells powinna być częścią samej mapy, a nie komponentu (tzn. nie wiem, czy to komponent, ale w tytule wątku jest "angular"). Komponent nie powinien się zajmować szczegółami technicznymi mapy.

Innymi słowy - napisałbym to w OOP, stosując enkapsulację.

1 użytkowników online, w tym zalogowanych: 0, gości: 1