Печать алмаза ASCII

Это принимает ширину, заданную пользователем, и печатает алмаз этой ширины. Он использует только три цикла for, но могу ли я уменьшить это дальше? Есть ли более элегантное решение?

public class Diamond {

    static boolean cont = true;

    public static void main (String[] args) {
        Scanner input = new Scanner(System.in);
        while (cont) {
            System.out.print("Width: ");
            int width = input.nextInt();
            int lines = width;
            System.out.println();
            for (int line = 0; line < lines; line++) {
                for (int spaces = 0; spaces < Math.abs(line - (lines / 2)); spaces++) {
                    System.out.print(" ");
                }
                for (int marks = 0; marks < width - 2 * (Math.abs(line - (lines / 2))); marks++) {
                    System.out.print("x");
                }       
                System.out.println();
            }
            System.out.println();
        }
    }
}
29 голосов | спросил tadamson 30 Jam1000000amThu, 30 Jan 2014 05:58:30 +040014 2014, 05:58:30

5 ответов


27

Есть несколько вещей, которые мы можем сделать, чтобы очистить это.

  • Это то, что я извлечу из собственного метода. Обработайте ввод пользователя в методе main(), а затем передайте его методу drawDiamond().

  • Контуры for разделены на итерации по строкам, пробелам и Метки. Мы можем упростить это до простых строк и столбцов, где мы можем перебирать каждую отдельную единицу за раз. Это также устранит один из ваших System.out.println() s в конечном методе.

  • Мы можем упростить математику, где печатать кусок алмаза.

    if ((column == Math.abs(row - half)) || (column == (row + half)) || (column == (sqr - row + half - 1)))
    

Конечный метод:

void drawDiamond(int sqr)
{
    int half = sqr/2;
    for (int row=0; row<sqr; row++)
    {
        for (int column=0; column<sqr; column++)
        {
            if ((column == Math.abs(row - half)) || (column == (row + half)) || (column == (sqr - row + half - 1)))
            {
                System.out.print("*");
            }
            else System.out.print(" ");
        }
        System.out.println();
    }
}
ответил syb0rg 30 Jam1000000amThu, 30 Jan 2014 06:19:46 +040014 2014, 06:19:46
19

Вы знаете, приятно видеть код, который делает то, что он говорит, и приятная простая задача, которая по-прежнему требует некоторой царапин на голове ... но я предполагаю, что вы начинающий Java.

Основы

Просматривая некоторые основные вещи ...

  • у вас есть переменная cont, объявленная как статическая переменная вне метода, но единственное место, которое она использует, находится внутри метода. В этом случае вы должны переместить декларацию внутри метода main. Кроме того, ничто не изменяет это состояние, поэтому программа просто запускается и запускается, что нормально (как новичок).
  • вы не закрываете сканер input. Опять же, это, вероятно, потому, что программа никогда не завершается, но в Java7 есть хорошие способы, чтобы убедиться, что это происходит аккуратно и без особых усилий.
  • вам следует, вероятно, проверить ввод пользователя. Если пользователь вводит отрицательные целые числа, это на самом деле нормально (программа ничего не делает). Более того, если пользователь вводит 2000000000. Вы должны установить верхнюю границу.
  • вы называете эту форму «бриллиантом», но на самом деле это квадрат. Ширина и высота - это одинаковое количество символов. Вам нужна только одна переменная, lines или width, а не оба.
  • сбивает с толку, что у вас есть как переменная line, так и lines. Нет необходимости в lines, если вместо этого вы используете width, поэтому избавитесь от него (также, поскольку приглашение пользователя - «Ширина:»).

Хорошо, это довольно простой материал. Размещая свой код, используя приведенные выше предложения, я получаю:

public static void main (String[] args) {
    boolean cont = true;

    try (Scanner input = new Scanner(System.in)) {
        while (cont) {
            System.out.print("Width: ");
            int width = input.nextInt();
            System.out.println();

            if (width > 100) {
                System.out.println("Width too wide, reducing to 100");
                width = 100;
            }

            for (int line = 0; line < width; line++) {
                for (int spaces = 0; spaces < Math.abs(line - (width / 2)); spaces++) {
                    System.out.print(" ");
                }
                for (int marks = 0; marks < width - 2 * (Math.abs(line - (width / 2))); marks++) {
                    System.out.print("x");
                }       
                System.out.println();
            }
            System.out.println();
        }
    }
}

Алгоритм

Хорошо, теперь некоторые алгоритмические вещи:

  • System.out.print(...) и println, на самом деле очень медленные. Вызов их из внутренних циклов является реальной проблемой для производительности, и это плохая привычка учиться. Эти методы блокируют вывод консоли, а также не подходят для других потоков. Там, где это возможно, вы всегда должны доставлять печать символов в более крупный оператор.
  • Иногда убирать вещи проще, чем добавлять его ... (критический намек).

Мы можем решить большую сложность в ваших циклах, выполнив несколько трюков. Вот предложение:

  1. Создайте две строки, одно из пробелов и другое из символов «x». Каждый из них должен быть по крайней мере до тех пор, пока нам не понадобится самое длинное значение.
  2. прокручивать строки и использовать части каждой из двух вышеперечисленных строк.

Логика точно такая же, как у вас кроме . У меня есть большие вещи, в которых я использую часть, тогда как вы ее постепенно наращиваете. Важной частью являются утверждения Math.abs (...), идентичные предыдущей версии ... они являются пределом значений, которые мы строим.

Вот как это сделать:

public static void main (String[] args) {
    boolean cont = true;

    try (Scanner input = new Scanner(System.in)) {
        while (cont) {
            System.out.print("Width: ");
            int width = input.nextInt();
            System.out.println();

            if (width > 100) {
                System.out.println("Width too wide, reducing to 100");
                width = 100;
            }

            char[] spaces = new char[width / 2];
            char[] exes = new char[width];
            Arrays.fill(spaces, ' '); // now an array of spaces
            Arrays.fill(exes, 'x'); // now an array of 'x'

            for (int line = 0; line < width; line++) {
                String pad = new String(spaces, 0, Math.abs(line - (width / 2)));
                String fill = new String(exes, 0, width - 2 * (Math.abs(line - (width / 2))));
                System.out.println(pad + fill);
            }
            System.out.println();
        }
    }
}

Вот что вам нужно подумать .....

ответил rolfl 30 Jam1000000amThu, 30 Jan 2014 06:40:29 +040014 2014, 06:40:29
12

Люди перешли во много важных вещей о синтаксисе, структуре программы и т. д. Но я думаю, что вы могли бы сделать ваши циклы намного проще и понятнее:

int halfheight = (width + 1) / 2;
int spaces = halfheight;
int exes = width - 2 * spaces;

for (int i = 0; i < halfheight; i++)
{
    spaces--;
    exes += 2;

    // You could use the approaches suggested by other folk here instead of inner loops
    for (int s = 0; s < spaces; s++)
        System.out.print(" ");
    for (int x = 0; x < exes; x++)
        System.out.print("X");
    System.out.print("\n");
}

for (int i = 0; i < halfheight - 1; i++)
{
    spaces++;
    exes -= 2;

    for (int s = 0; s < spaces; s++)
        System.out.print(" ");
    for (int x = 0; x < exes; x++)
        System.out.print("X");
    System.out.print("\n");
}

(Я считаю, что это обрабатывает нечетные и четные ширины точно так же, как и ваши, за исключением того, что он не выводит начальную пустую строку в случае равномерной ширины. Я предположил, что это был артефакт, а не часть спецификации. )

Единственная «математика», которую я делаю, - это инициализация, затем я зацикливаю на суммы, которые полностью понятны без каких-либо вычислений, и меняет количество exes и пробелов на каждой итерации таким образом, который полностью очевиден. Может показаться, что различные вычисления с помощью abs и целочисленного деления не являются сложными, однако, когда вы отлаживаете или пытаетесь слегка изменить свой код, вы будете тратить время на размышления в разных случаях (положительные, отрицательные, нечетная, четная, первая итерация, последняя итерация), и, вероятно, вы просто испытаете разные значения something, something + 1, something - 1 и т. д., вместо того, чтобы видеть точное взаимодействие каждой переменной в крайних случаях.

Обратите внимание, что у меня есть 6 для циклов, и код намного длиннее соответствующих частей вашего или кого-либо еще. Это не уменьшает их, что делает код понятным. Это тот факт, что легко понять, что делает каждый, не ссылаясь на (много) материал за пределы цикла, который помогает сопровождающему. ИМХО (и я знаю, что некоторые не согласятся), повторение использовалось экономно и симметрично, так как оно здесь более элегантно, чем было бы, если бы я угадал его.

ответил jwg 30 Jpm1000000pmThu, 30 Jan 2014 17:13:32 +040014 2014, 17:13:32
10

Неплохая практика поместить весь ваш код в main(). Здесь у вас есть дополнительная проблема, заключающаяся в том, что main() выполняет три функции: запрос ввода, печать алмаза и цикл. (Кстати, у вас нет безошибочного способа выхода из бесконечного цикла.) Смешение этих задач с одной функцией сделало бы невозможным, например, повторное использование вашей процедуры печати алмазов каким-либо другим способом (например, создание непрерывная вертикальная струна из нескольких алмазов).

@ syb0rg предложил один способ разделить процедуру ввода из процедуры печати. Я бы пошел дальше и предположил, что объектно-ориентированный интерфейс будет хорошей привычкой строить на Java. Вот один из способов:

private static int promptWidth(Scanner input) {
    System.out.print("Width: ");
    return input.hasNextInt() ? input.nextInt() : 0;
}

public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    int width;
    // Exit cleanly on EOF, or if anything other than a positive
    // integer is entered.
    while ((width = promptWidth(input)) > 0) {
        System.out.println();
        new Diamond(width).draw(System.out);
        System.out.println();
    }
    input.close();
}

Другими словами, Diamond умеет рисовать себя в System.out.

Вот еще один подход:

public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    int width;
    // Exit cleanly on EOF, or if anything other than a positive
    // integer is entered.
    while ((width = promptWidth(input)) > 0) {
        int width = input.nextInt();
        System.out.println();
        System.out.println(new Diamond(width));
        System.out.println();
    }
    input.close();
}

Это зависит от метода Diamond .toString(), который вам нужно будет реализовать с помощью StringBuilder.

ответил 200_success 30 Jpm1000000pmThu, 30 Jan 2014 16:23:44 +040014 2014, 16:23:44
10

Вы можете сделать это в одном цикле. Поскольку я не говорю на Java, поэтому я буду использовать синтаксис C #.

public static void Main()
{
    string valueString;
    int width;
    do
    {
        Console.Write("Width:");
        valueString = Console.ReadLine();
    } while (!int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out width) && width > 0 && width <= 100);
    int half = (int)((double)width/2+0.5);
    string pattern = new string(' ',width)+new string('*',width);
    for (int row = 1; row <= width; row++)
    { 
        int spaces = width-Math.Abs(half - row);
        Console.WriteLine(pattern.Substring(spaces, spaces));
    }
}

Перевод на Java для вашего удобства ....

public static void main (String[] args) {

    try (Scanner scanner = new Scanner(System.in)) {
        int width;
        do
        {
            System.out.print("Width:");
            width = scanner.nextInt();
        } while (width < 0 || width > 100);

        char[] blanks = new char[width];
        char[] exes = new char[width];
        Arrays.fill(blanks, ' ');
        Arrays.fill(exes, 'x');
        String pattern = new String(blanks) + new String(exes);

        int half = (int)((double)width / 2 + 0.5);
        for (int row = 1; row <= width; row++)
        {
            int spaces = width - Math.abs(half - row);
            // Java substring has arguments (first, last), not (first, length).
            System.out.println(pattern.substring(spaces, spaces + spaces));
        }                

        System.out.println();
    }
}
ответил Marc Selis 30 Jpm1000000pmThu, 30 Jan 2014 16:21:45 +040014 2014, 16:21:45

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132