[JAVA] Essayez d'imiter l'idée d'un tableau à deux dimensions avec un tableau à une dimension

introduction

Je pense que les tableaux bidimensionnels sont souvent utilisés pour gérer les données avec des index bidirectionnels.

Exemple de tableau bidimensionnel


String[][] names = {
    {"Yamauchi", "les bois", "Izumi", "Yoshikawa", "Shibuya"},
    {"Matsumoto", "Kono", "Mizuno", "Kawaguchi", "Ushio"},
    {"Kato", "Nishimura", "Taketa", "Yoshida", "Ito"},
    {"Kawano", "Kato", "Yamada", "Sasaki", "Fujita"},
    {"Shibata", "Ozaki", "Omori", "colline", "Yaguchi"}
};
System.out.println(names[1][4]); //Ushio

a.png Je pensais que si vous gérez avec un index bidirectionnel, vous pouvez gérer les données même s'il s'agit d'un tableau unidimensionnel si vous préparez un index bidirectionnel, j'ai donc décidé d'imiter l'idée d'un tableau bidimensionnel avec un tableau unidimensionnel. [^ Expérience].

Préparation

Tout d'abord, définissez deux variables qui indiquent la largeur et la hauteur du tableau à deux dimensions. Ci-après, $ width $ sera appelé ** largeur du tableau ** et $ height $ sera appelé ** hauteur du tableau **.

python


int width = 5;
int height = 5;

Ensuite, préparez à l'avance une version unidimensionnelle du tableau bidimensionnel ci-dessus [^ Person class].

python


String[] names = {
    "Yamauchi", "les bois", "Izumi", "Yoshikawa", "Shibuya",
    "Matsumoto", "Kono", "Mizuno", "Kawaguchi", "Ushio",
    "Kato", "Nishimura", "Taketa", "Yoshida", "Ito",
    "Kawano", "Kato", "Yamada", "Sasaki", "Fujita",
    "Shibata", "Ozaki", "Omori", "colline", "Yaguchi"
};
Iterator<String> nameTokens = Arrays.stream(names).iterator();
Person[] people = IntStream.range(0, width * height)
    .mapToObj(i -> new Person(nameTokens.next()))
    .toArray(Person[]::new);

b.png Voici un schéma de l'arrangement. Ce tableau est un tableau avec à la fois ** largeur du tableau ** et ** hauteur du tableau **. Si l'index horizontal de chaque ligne est $ x $ et l'index vertical de chaque colonne est $ y $, alors [^ x et y], par exemple, l'index indiquant "Ushio" est $ x = 4, \ y = 1 . Ce sera. De plus, l'index horizontal sur le dernier élément de chaque ligne est $ x = largeur-1 $, et l'index vertical sur le dernier élément de chaque colonne est $ y = hauteur-1 $$.

Entraine toi

Analyse des éléments

Scannez les éléments à l'aide du code suivant.

Scan code


for (int i = 0, x = 0, y = 0; i < array.length; i++, x++) {
    ...
    if (x == width - 1) {
        x = -1;
        y++;
    }
}

Analyse tous les éléments du tableau cible (de l'index 0 à ʻarray.length). Cette fois, nous définissons un tableau de personnes, remplacez donc ʻarray.length par people.length. Les variables de compteur déclarées dans l'instruction for sont:

variable sens
i Index du tableau
x Une valeur qui indique l'index horizontal du tableau
y Une valeur qui indique l'index vertical du tableau

ʻIf (x == width --1) {...} incrémente y s'il s'agit du dernier élément de la ligne après avoir traité la partie ... `du code de balayage, puis Signifie passer à la ligne de. «x = -1» est pour l'ajustement.

Extraire et remplacer des éléments

En utilisant ceci, par exemple, dans la partie ... du code de numérisation

python


if (y == 1 && x == 4) System.out.println(people[i]);

Si vous définissez, vous pouvez afficher "Ushio".

Exemple d'extraction


for (int i = 0, x = 0, y = 0; i < array.length; i++, x++) {
    if (y == 1 && x == 4) System.out.println(people[i]); // Person [name=Ushio]
    if (x == width - 1) {
        x = -1;
        y++;
    }
}

Aussi,

python


if (y == 1 && x == 4) array[i] = new Person("Hoge");

Si vous définissez, vous pouvez remplacer "Ushio" par "Hoge".

Exemple de remplacement


for (int i = 0, x = 0, y = 0; i < array.length; i++, x++) {
    if (y == 1 && x == 4) array[i] = new Person("Hoge");
    if (x == width - 1) {
        x = -1;
        y++;
    }
}

Je pensais que ce serait un problème de le définir manuellement à chaque fois, alors je l'ai modularisé [^ dirty].

<détails>

Classe Matrix </ summary>

python


class Matrix<T, R> {
    private T[] array;
    public final int width;
    public final int height;
    public int i;
    public int x;
    public int y;
    private Consumer<Matrix<T, R>> action;
    private Function<Matrix<T, R>, R> function;

    private Matrix(T[] array, int width, int height) {
        this.array = array;
        this.width = width;
        this.height = height;
    }

    static <T, R> Matrix<T, R> of(T[] array, int width, int height) {
        if (ObjectUtils.isEmpty(array))
            throw new IllegalArgumentException("Le tableau pris comme premier argument est vide ou nul.");
        if (array.length != width * height)
            throw new IllegalArgumentException("La longueur du tableau prise comme premier argument et la valeur de «deuxième argument x troisième argument» ne correspondent pas.");
        return new Matrix<>(array, width, height);
    }

    Matrix<T, R> setAction(Consumer<Matrix<T, R>> action) {
        if (Objects.nonNull(function)) function = null;
        this.action = action;
        return this;
    }

    Matrix<T, R> setAction(Function<Matrix<T, R>, R> function) {
        if (Objects.nonNull(action)) action = null;
        this.function = function;
        return this;
    }

    @SuppressWarnings("unchecked")
    R get(int xIndex, int yIndex) {
        if (isIllegalIndex(xIndex, yIndex)) return null;
        return setAction(m -> {
            if (m.y == yIndex && m.x == xIndex) return (R) array[m.i];
            return null;
        })
        .run();
    }

    Matrix<T, R> put(T obj, int xIndex, int yIndex) {
        if (isIllegalIndex(xIndex, yIndex)) return this;
        setAction(m -> {
            if (m.y == yIndex && m.x == xIndex) array[m.i] = obj;
        })
        .run();
        return this;
    }

    T[] array() {
        return array;
    }

    Matrix<T, R> insertShiftLeft(T obj, int xIndex, int yIndex) {
        if (isIllegalIndex(xIndex, yIndex)) return this;
        setAction(m -> {
            if (m.i < width * yIndex + xIndex) array[m.i] = array[m.i + 1];
            if (m.y == yIndex && m.x == xIndex) array[m.i] = obj;
        })
        .run();
        return this;
    }

    Matrix<T, R> insertShiftRight(T obj, int xIndex, int yIndex) {
        if (isIllegalIndex(xIndex, yIndex)) return this;
        T[] cloneArray = array.clone();
        setAction(m -> {
            if (width * height - m.i - 1 > width * yIndex + xIndex)
                array[width * height - m.i - 1] = cloneArray[width * height - m.i - 2];
            if (m.y == yIndex && m.x == xIndex) array[m.i] = obj;
        })
        .run();
        return this;
    }

    R run() {
        if (Objects.isNull(action) && Objects.isNull(function))
            throw new IllegalStateException("Définissez le traitement séquentiel.");
        R result = null;
        Supplier<R> l_action = null;
        if (Objects.nonNull(action))
            l_action = () -> {action.accept(this); return null;};
        else if (Objects.nonNull(function))
            l_action = () -> function.apply(this);
        for (int i = 0, x = 0, y = 0; i < array.length; i++, x++) {
            this.i = i;
            this.x = x;
            this.y = y;
            result = l_action.get();
            if (Objects.nonNull(result)) break;
            if (x == width - 1) {
                x = -1;
                y++;
            }
        }
        return result;
    }

    private boolean isIllegalIndex(int xIndex, int yIndex) {
        if (xIndex < 0 || width  - 1 < xIndex) return true;
        if (yIndex < 0 || height - 1 < yIndex) return true;
        return false;
    }
}

J'ai supposé à la fois aucune valeur de retour et une valeur de retour. Spécifiez le type du tableau dans la partie T de <T, R> de (...). Pour la partie R, spécifiez le type de retour s'il existe une valeur de retour. S'il n'y a pas de valeur de retour, spécifiez java.lang.Void et spécifiez qu'il n'y a pas de valeur de retour [^ null Void].

Exemple d'utilisation (pas de valeur de retour)


Matrix.<Person, Void>of(people, width, height)
    .setAction(m -> {
        if (m.y == 1 && m.x == 4) System.out.println(people[m.i]); // Person [name=Ushio]
    })
    .run();

Définissez la partie ... du scan code avec la méthode setAction avant d'appeler la méthode run. Il utilise une interface fonctionnelle et remplace respectivement «i», «x» et «y» par «m.i», «m.x» et «m.y». m n'a pas besoin d'être m car c'est le nom d'argument [^ m] de l'expression lambda.

S'il y a une valeur de retour, c'est un peu compliqué, mais il utilise une interface fonctionnelle avec return comme suit. Il est défini et utilisé pour renvoyer un objet non nul lorsqu'une condition est remplie. De plus, si la condition n'est pas remplie, null est renvoyé [^ null].

Exemple d'utilisation (avec valeur de retour)


String personName = Matrix.<Person, String>of(people, width, height)
    .setAction(m -> {
        if (m.y == 1 && m.x == 4) return people[m.i].toString();
        return null;
    })
    .run();
System.out.println(personName); // Person [name=Ushio]

Ces opérations sont implémentées comme méthode get et méthode put.

python


Matrix<Person, Person> m = Matrix.of(people, width, height);
System.out.println(m.get(0, 3)); // Person [name=Kawano]
System.out.println(m.get(3, 2)); // Person [name=Yoshida]
System.out.println(m.get(4, 1)); // Person [name=Ushio]

m.put(new Person("Hoge"), 0, 3);
System.out.println(m.get(0, 3)); // Person [name=Hoge]

Insérer et décaler des éléments

Puisqu'il utilise un tableau unidimensionnel, vous pouvez insérer des éléments en le définissant comme suit. Puisque la longueur du tableau est fixe, les éléments avant "Yamada" sont décalés un par un vers la gauche, et le premier élément (Yamauchi) disparaît.

Insérez "Hoge" entre "Yamada" et "Sasaki" et déplacez-vous vers la gauche.


int xIndex = 2;
int yIndex = 3;
Matrix.<Person, Void>of(people, width, height)
    .setAction(m -> {
        if (m.i < width * yIndex + xIndex) array[m.i] = array[m.i + 1];
        if (m.y == yIndex && m.x == xIndex) array[m.i] = new Person("Hoge");
    })
    .run();

Au contraire, s'il est défini comme suit, les éléments après "Yamada" seront décalés vers la droite un par un, et le dernier élément (Yaguchi) disparaîtra. Si vous opérez avec un seul tableau, le décalage d'élément lorsque $ x = 0 $ est spécifié ne sera pas comme prévu en raison du problème de copie de référence, donc dupliquez le tableau une fois et opérez de ce tableau au tableau d'origine. Je suis.

Insérez "Hoge" entre "Kato" et "Yamada" et déplacez-vous vers la droite.


int xIndex = 2;
int yIndex = 3;
Person[] cloneArray = people.clone();
Matrix.<Person, Void>of(people, width, height)
    .setAction(m -> {
        if (width * height - m.i - 1 > width * yIndex + xIndex)
            array[width * height - m.i - 1] = cloneArray[width * height - m.i - 2];
        if (m.y == yIndex && m.x == xIndex) array[m.i] = new Person("Hoge");
    })
    .run();

Ces opérations sont implémentées en tant que méthode insertShiftLeft et méthode insertShiftRight.

python


Matrix.<Person, Void>of(people, width, height)
    .insertShiftLeft(new Person("Hoge"), 2, 4); // 「大森」と「丘」の間に「Hoge」を挿入し、左にシフトする。

python


Matrix.<Person, Void>of(people, width, height)
    .insertShiftRight(new Person("Hoge"), 2, 4); // 「尾崎」と「大森」の間に「Hoge」を挿入し、右にシフトする。

Résumé

Même avec un tableau à une dimension, nous avons pu gérer les données dans un tableau à deux dimensions. Si vous souhaitez modifier la largeur ou la hauteur du tableau ultérieurement, vous pouvez le gérer en changeant simplement les valeurs de $ width $ et $ height $.

python


int width = 7;
int height = 2;
String[] names = {
    "Yamauchi", "les bois", "Izumi", "Yoshikawa", "Shibuya", "Matsumoto", "Kono",
    "Kato", "Nishimura", "Taketa", "Yoshida", "Ito", "Kawano", "Kato"
};
Iterator<String> nameTokens = Arrays.stream(names).iterator();
Person[] people = ... //réduction
Matrix<Person, Person> m = Matrix.of(people, width, height);
System.out.println(m.get(3, 1)); // Person [name=Yoshida]

Sinon, si vous souhaitez le faire, vous pouvez définir une méthode dans la classe Matrix qui extrait les éléments non nuls sur le côté gauche de chaque ligne un par un comme suit. En utilisant les variables du code de numérisation, vous pouvez générer les éléments sur le côté droit, le côté supérieur et le côté inférieur.

python


@SuppressWarnings("unchecked")
public T[] getNonNullRightElements() {
    T[] elements = (T[]) Array.newInstance(array.getClass().getComponentType(), height);
    setAction(m -> {
        if (Objects.nonNull(array[m.i])) elements[m.y] = array[m.i];
    })
    .run();
    return ArrayUtils.removeAllOccurrences(elements, null);
}

En appliquant cela, il est également possible de localiser les cellules qui ont un jugement de hit, comme illustré dans la figure suivante.

c.png

Postscript

Classe de matrice fixe

Un commentaire de @sdkei a souligné la classe Matrix. Je vous remercie! Sur la base des mesures d'amélioration que j'ai reçues, j'ai modifié la classe Matrix comme suit.

<détails>

Classe Matrix </ summary>

python


public final class Matrix<T, R> {
    private final T[] array;
    private final int width;
    private final int height;
    private MatrixRunner runner;
    private MatrixSupplier<R> supplier;

    private Matrix(T[] array, int width, int height) {
        this.array = array;
        this.width = width;
        this.height = height;
    }

    public static <T, R> Matrix<T, R> of(T[] array, int width, int height) {
        if (ObjectUtils.isEmpty(array))
            throw new IllegalArgumentException("Le tableau pris comme premier argument est vide ou nul.");
        if (array.length != width * height)
            throw new IllegalArgumentException("La longueur du tableau prise comme premier argument et la valeur de «deuxième argument x troisième argument» ne correspondent pas.");
        return new Matrix<>(array.clone(), width, height); //Copie défensive
    }

    public Matrix<T, R> setAction(MatrixRunner runner) {
        if (Objects.nonNull(supplier)) supplier= null;
        this.runner = runner;
        return this;
    }

    public Matrix<T, R> setAction(MatrixSupplier<R> supplier) {
        if (Objects.nonNull(runner)) runner= null;
        this.supplier = supplier;
        return this;
    }

    @SuppressWarnings("unchecked")
    public R get(int xIndex, int yIndex) {
        if (indexOutOfBounds(xIndex, yIndex)) return null;
        return (R) array[width * yIndex + xIndex];
    }

    public Matrix<T, R> put(T obj, int xIndex, int yIndex) {
        if (indexOutOfBounds(xIndex, yIndex)) return this;
        array[width * yIndex + xIndex] = obj;
        return this;
    }

    public T[] array() {
        return array.clone(); //Copie défensive
    }

    public Matrix<T, R> insertShiftLeft(T obj, int xIndex, int yIndex) {
        if (indexOutOfBounds(xIndex, yIndex)) return this;
       setAction((i, x, y) -> {
            if (i < width * yIndex + xIndex) array[i] = array[i + 1];
            if (y == yIndex && x == xIndex) array[i] = obj;
        })
        .run();
        return this;
    }

    public Matrix<T, R> insertShiftRight(T obj, int xIndex, int yIndex) {
        if (indexOutOfBounds(xIndex, yIndex)) return this;
        T[] cloneArray = array.clone();
        setAction((i, x, y) -> {
            if (width * height - i - 1 > width * yIndex + xIndex)
                array[width * height - i - 1] = cloneArray[width * height - i - 2];
            if (y == yIndex && x == xIndex) array[i] = obj;
        })
        .run();
        return this;
    }

    public R run() {
        if (Objects.isNull(runner) && Objects.isNull(supplier))
            throw new IllegalStateException("Définissez le traitement séquentiel.");
        R result = null;
        MatrixSupplier<R> l_supplier = null;
        if (Objects.nonNull(runner))
           l_supplier = (i, x, y) -> { runner.run(i, x, y); return null; };
        else if (Objects.nonNull(supplier))
            l_supplier = (i, x, y) -> supplier.get(i, x, y);
        for (int i = 0, x = 0, y = 0; i < array.length; i++, x++) {
            result = l_supplier.get(i, x, y);
            if (Objects.nonNull(result)) break;
            if (x == width - 1) {
                x = -1;
                y++;
            }
        }
        return result;
    }

    @FunctionalInterface
    public interface MatrixRunner {
        void run(int i, int x, int y);
    }

    @FunctionalInterface
    public interface MatrixSupplier<R> {
        R get(int i, int x, int y);
    }

    private boolean indexOutOfBounds(int xIndex, int yIndex) {
        if (xIndex < 0 || width  - 1 < xIndex) return true;
        if (yIndex < 0 || height - 1 < yIndex) return true;
        return false;
    }
}

Points de correction

  • Limitation de la variabilité (classe finale, partie du champ final privé, implémentation de la copie défensive) [^ classe immuable].
  • Réduction du poids des méthodes get and put
  • Introduction des interfaces MatrixRunner et MatrixSupplier. Ceci élimine le besoin d'avoir une valeur comme champ public pour appeler les variables de code de balayage.
  • Modifiez les autres noms de méthodes, rendez les méthodes de classe publiques, etc.

En créant ma propre interface fonctionnelle, j'ai pu renforcer l'encapsulation. Pour être honnête, j'ai été impressionné.

De plus, une méthode runOnly a été ajoutée pour tirer parti de la variabilité des champs coureur et fournisseur.

méthode runOnly

python


public Matrix<T, R> runOnly() {
    if (Objects.isNull(runner))
        throw new IllegalStateException("Définissez le traitement séquentiel.");
    for (int i = 0, x = 0, y = 0; i < array.length; i++, x++) {
        runner.run(i, x, y);
        if (x == width - 1) {
            x = -1;
            y++;
        }
    }
    return this;
}
Exemple d'utilisation

python


@test 
public void testRunOnly() {
    List<String> people = new ArrayList<>();
    matrix.setAction((i, x, y) -> {
            if (x == 0 &&
                (this.people[i].name.contains("Montagne") || this.people[i].name.contains("rivière"))
                people.add(this.people[i].name);
        })
        .runOnly()
        .setAction((i, x, y) -> {
            if (x == 3 && (this.people[i].name.contains("Kichi")))
                people.add(this.people[i].name);
        })
        .runOnly();
    assertThat(people, contains("Yamauchi", "Kawano", "Yoshikawa", "Yoshida"));
}

Je pense qu'il n'est pas très pratique de traiter séquentiellement, mais après avoir effectué un processus avec runOnly (), une fois lancer l'objet Matrix vers une autre méthode ou un autre objet, différentes actions dans l'autre méthode ou objet Après avoir défini avec la méthode setAction, si vous appelez runOnly () et effectuez un traitement différent, je pense que l'implémentation du traitement sera un peu plus large.

[^ Expérience]: C'est juste une chose expérimentale qui consiste en de la curiosité. Ce n'est en aucun cas un tableau anti-bidimensionnel. [^ Person class]: la classe Person définit un champ qui représente un nom et remplace la méthode toString pour imprimer le nom. [^ x et y]: $ x et \ y $ doivent être incrémentés de 1 dans le sens positif. [^ Dirty]: J'ai introduit une interface fonctionnelle en supposant aucune valeur de retour et avec une valeur de retour, donc le code est devenu beaucoup sale. Si vous avez des suggestions pour améliorer la lisibilité, je vous serais reconnaissant de bien vouloir me le faire savoir. [^ null Void]: C'est juste une déclaration explicite, donc il renvoie en fait un objet Void nul. [^ m]: Ici, il est utilisé comme un acronyme pour Matrix. [^ null]: Si une valeur non nulle est renvoyée lorsque la condition n'est pas remplie, l'objet sera toujours renvoyé. [^ Classe immuable]: les champs du runner et du fournisseur ne sont pas complètement immuables car ils sont censés être mutables.

Recommended Posts