[JAVA] Try to imitate the idea of a two-dimensional array with a one-dimensional array

Introduction

I think that two-dimensional arrays are often used as a way to manage data with two-way indexes.

Two-dimensional array example


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

a.png I thought that if you manage with a two-way index, you can manage the data even if it is a one-dimensional array if you prepare a two-way index, so I decided to imitate the idea of a two-dimensional array with a one-dimensional array. [^ Experiment].

Preparation

First, define two variables that indicate the width and height of the two-dimensional array. Hereafter, $ width $ will be referred to as ** array width **, and $ height $ will be referred to as ** array height **.

python


int width = 5;
int height = 5;

Then, prepare a one-dimensional version of the two-dimensional array mentioned earlier [^ Person class].

python


String[] names = {
    "Yamauchi", "Woods", "Izumi", "Yoshikawa", "Shibuya",
    "Matsumoto", "Kono", "Mizuno", "Kawaguchi", "Ushio",
    "Kato", "Nishimura", "Taketa", "Yoshida", "Ito",
    "Kawano", "Kato", "Yamada", "Sasaki", "Fujita",
    "Shibata", "Ozaki", "Omori", "hill", "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 Here is a diagram of the array. This array is an array with both ** array width ** and ** array height **. If the horizontal index of each row is $ x $ and the vertical index of each column is $ y $, then [^ x and y], for example, the index indicating "Ushio" is $ x = 4, \ y = 1 . It becomes. Also, the horizontal index on the last element of each row is $ x = width-1 $, and the vertical index on the last element of each column is $ y = height-1 $$.

Practice

Scanning elements

The element is scanned using the following code.

Scan code


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

Scans all the elements of the array of interest (from index 0 to ʻarray.length). This time we are defining a people array, so replace ʻarray.length with people.length. The counter variables declared in the for statement are:

variable meaning
i Array index
x A value that indicates the horizontal index of the array
y A value that indicates the vertical index of the array

ʻIf (x == width --1) {...}incrementsy if it is the last element of the line after processing the ... part of the scan code, and then Means to move to the line of. x = -1` is for adjustment.

Extracting and replacing elements

Using this, for example, in the ... part of the scanning code

python


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

If you define, you can output "Ushio".

Extraction example


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

Also,

python


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

You can replace "Ushio" with "Hoge" by defining.

Replacement example


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

I thought it would be a hassle to define it manually every time, so I modularized it [^ dirty].

Matrix class

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("The array taken as the first argument is empty or null.");
        if (array.length != width * height)
            throw new IllegalArgumentException("The length of the array taken as the first argument and the value of "second argument x third argument" do not match.");
        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("Define sequential processing.");
        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;
    }
}

I assumed both no return value and with return value. Specify the array type in the T part of<T, R> of (...). For the R part, if there is a return value, specify the return type. If there is no return value, specify java.lang.Void and specify that there is no return value [^ null Void].

Usage example (no return value)


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();

Define the ... part of the scan code with the setAction method before calling the run method. It uses a functional interface, replacing ʻi, x, and ywithm.i, m.x, and m.y, respectively. m does not have to be m` because it is the argument name [^ m] of the lambda expression.

If there is a return value, it is a little complicated, but it uses a functional interface with return as follows. Define and use to return a non-null object when some condition is met. Also, if it does not match the condition, it will return null [^ null].

Usage example (with return value)


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]

These operations are implemented as get method and put method.

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]

Insert and shift elements

Since it uses a one-dimensional array, you can insert an element by defining it as follows. Since the length of the array is fixed, the elements before "Yamada" are shifted one by one to the left, and the first element (Yamauchi) disappears.

Insert a "hoge" between "Yamada" and "Sasaki" and shift to the left.


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();

On the contrary, if you define as follows, the elements after "Yamada" will be shifted to the right one by one, and the last element (Yaguchi) will disappear. If you operate with only one array, the element shift when $ x = 0 $ is specified will not be as expected due to the problem of reference copy, so duplicate the array once and operate from that array to the original array. I am.

Insert "Hoge" between "Kato" and "Yamada" and shift to the right.


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();

These operations are implemented as insertShiftLeft method and insertShiftRight method.

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」を挿入し、右にシフトする。

Summary

Even with a one-dimensional array, we were able to manage data in a two-dimensional array. If you want to change the array width or array height later, you can handle it by simply changing the values of $ width $ and $ height $.

python


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

Alternatively, if you want to do it, you can define a method in the Matrix class that extracts one non-null element on the far left of each row, like this: By using the variables of the scanning code, you can output the elements on the right side, the upper side, and the lower side.

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

By applying this, it is also possible to pinpoint the squares with collision detection as shown in the following figure.

c.png

Postscript

Modify the Matrix class

@ sdkei's comment pointed out the Matrix class. Thank you! Based on the improvement measures I received, I modified the Matrix class as follows.

Matrix class

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("The array taken as the first argument is empty or null.");
        if (array.length != width * height)
            throw new IllegalArgumentException("The length of the array taken as the first argument and the value of "second argument x third argument" do not match.");
        return new Matrix<>(array.clone(), width, height); //Defensive copy
    }

    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(); //Defensive copy
    }

    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("Define sequential processing.");
        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;
    }
}

Correction points

By creating my own functional interface, I was able to make the encapsulation stronger. To be honest, I was impressed.

In addition, a runOnly method has been added to take advantage of the variability of the runner and supplier fields.

runOnly method

python


public Matrix<T, R> runOnly() {
    if (Objects.isNull(runner))
        throw new IllegalStateException("Define sequential processing.");
    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;
}
Example of use

python


@test 
public void testRunOnly() {
    List<String> people = new ArrayList<>();
    matrix.setAction((i, x, y) -> {
            if (x == 0 &&
                (this.people[i].name.contains("Mountain") || this.people[i].name.contains("river"))
                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"));
}

I think that it is not very practical to process sequentially, but after performing one process with runOnly (), once throw the Matrix object to another method or object, different actions within the other method or object After setting with the setAction method, if you use it by calling runOnly () and performing different processing, I feel that the implementation of processing will be somewhat wider.

[^ Experiment]: It's just an experiment with curiosity. It is by no means an anti-two-dimensional array. [^ Person class]: In the Person class, we define a field that represents the name and override the toString method to output the name. [^ x and y]: Both $ x and \ y $ shall be incremented (incremented) by 1 in the positive direction. [^ Dirty]: I introduced a functional interface assuming no return value and with a return value, so the code has become very dirty. If you have any suggestions on how to improve readability, I would appreciate it if you could let me know. [^ null Void]: It's just explicit, so it actually returns a null Void object. [^ m]: Here, it is used as an acronym for Matrix. [^ null]: If you return anything other than null when the condition is not met, the object will always be returned. [^ Immutable class]: The runner and supplier fields are not completely immutable classes as they assume variability.

Recommended Posts

Try to imitate the idea of a two-dimensional array with a one-dimensional array
How to find the total value, average value, etc. of a two-dimensional array (multidimensional array)-java
How to divide a two-dimensional array into four with ruby
Learning Ruby with AtCoder 13 How to make a two-dimensional array
Try to imitate marshmallows with MiniMagick
The story of toString () starting with passing an array to System.out.println
The secret to the success of IntelliJ IDEA
[Java] How to turn a two-dimensional array with an extended for statement
How to convert an array of Strings to an array of objects with the Stream API
Make a margin to the left of the TextField
Set the time of LocalDateTime to a specific time
Convert a string to a character-by-character array with swift
Try to summarize the common layout with rails
How to get the ID of a user authenticated with Firebase in Swift
Send a notification to slack with the free version of sentry (using lambda)
Rails6 I want to make an array of values with a check box
Speed comparison when the value side of Hash wants to retrieve all with array
Convert the array of errors.full_messages to characters and output
The story of making a reverse proxy with ProxyServlet
How to request by passing an array to the query with HTTP Client of Ruby
[Docker] How to see the contents of Volumes. Start a container with root privileges.
The idea of quicksort
I got stuck in a clone of a two-dimensional array
Let's implement a function to limit the number of access to the API with SpringBoot + Redis
A memorandum because I was addicted to the setting of the Android project of IntelliJ IDEA
The idea of jQuery
The story of pushing a Docker container to GitHub Package Registry and Docker Hub with GitHub Actions
I tried to make the sample application into a microservice according to the idea of the book "Microservice Architecture".
Display a balloon message in BarButtonItem of NavigationBar with a size according to the amount of text.
How to take a screenshot with the Android Studio emulator
Port C code with a lot of typecasts to Swift
[Beginner] Try to make a simple RPG game with Java ①
A story packed with the basics of Spring Boot (solved)
Check the operation of two roles with a chat application
The idea of C # (lambda expression, for statement) to chew
A story addicted to toString () of Interface proxied with JdkDynamicAopProxy
A series of steps to create portfolio deliverables with Rails
How to move another class with a button action of another class.
[Ruby] How to retrieve the contents of a double hash
How to add elements without specifying the length of the array
How to add the same Indexes in a nested array
Explain the benefits of the State pattern with a movie rating
Be sure to compare the result of Java compareTo with 0
[jsoup] How to get the full amount of a document
Find the number of days in a month with Kotlin
[Ruby] Extracting a two-dimensional array
I tried to express the result of before and after of Date class with a number line
[Java] How to search for a value in an array (or list) with the contains method
I tried to take a look at the flow of Android development environment construction with Android Studio
Read the data of Shizuoka point cloud DB with Java and try to detect the tree height.
The basics of the process of making a call with an Android app
I tried to solve the problem of "multi-stage selection" with Ruby
A solution to the problem of blank spaces at the beginning of textarea
[Swift5] How to get an array and the complement of arrays
I tried to build the environment of PlantUML Server with Docker
A story of connecting to a CentOS 8 server with an old Ansible
Try Hello World with the minimum configuration of Heroku Java spring-boot
Try to make a cross-platform application with JRuby (jar file generation)
Logic to draw a circle with ASCII art on the console
Connecting to a database with Java (Part 1) Maybe the basic method
I made a gem to post the text of org-mode to qiita