zoukankan      html  css  js  c++  java
  • Java实现图案打印: 记法与解释器

          摘要: 通过引入一套简洁的记法和相应的解释器, 提高了打印图案的重用性。

          难度: 初级

          童鞋们应该对“打印星形图案”的编程题记忆犹新吧? 那就像我们的编程童年一样的美好。 打印边长为 n 个菱形, HO, 有的人立马就刷刷刷写出来了:

          

    package patterns.interpretation;
    
    public class FigurePrinting {
        
        /**
         * 根据给定 n 值打印菱形图案
         * @param n 菱形的边数
         */
        public void printDiamond(int n)
        {
            if (n < 2) {
                throw new IllegalArgumentException("参数 n = " + n + " 错误, 给定参数必须不小于 2!");
            }
            int midRow = n-1; 
            for (int row = 0; row <= midRow; row++) {
                printLine(new int[] { midRow-row , midRow+row}, 2*n-1, '*');
            }
            for (int row = midRow+1; row < 2*n-1; row++) { // 对称性: row = midRow+i 与 row = midRow-i 的图案是一样的
                printLine(new int[] { row - midRow, midRow + (2*midRow - row)}, 2*n-1, '*');
            }
        }
    
        /**
         * printLine: 在指定的若干位置上,打印符号; 不打印符号的位置打印空格
         * @param indexes 指定要打印符号 symbol 的位置数组
         * @param n 一行总共要打印的字符数
         * @param symbol 要打印出的符号
         */
        public void printLine(int[] indexes, int n, char symbol)
        {
            if (indexes == null || indexes.length == 0) { // 如果位置数组为空,则直接换行
                System.out.println();
                return ;
            }
            if (n < indexes.length) {
                throw new IllegalArgumentException("要打印的符号数超过一行的总字符数!");
            }
            int k = 0;
            for (int i=0; i < n; i++) 
            {
                if (i != indexes[k]) {
                    System.out.printf("%c", ' ');
                }
                else {
                    System.out.printf("%c", symbol);
                    k++;
                    if (k == indexes.length) { 
                        for (i = indexes[k-1]; i < n; i++) {
                            System.out.printf("%c", ' ');
                        }
                    }
                }
            }
            System.out.println();
        }
    }
    package patterns.interpretation;
    
    public class FigurePrintingTester {
        
        private static FigurePrinting fp = new FigurePrinting();
        
        public static void testPrintLine()
        {
            fp.printLine(null, 3, '*');
            fp.printLine(new int[0], 3, '*');
            fp.printLine(new int[] {}, 3, '*');
            fp.printLine(new int[3] , 3, '*');
            fp.printLine(new int[] {2,3}, 3, '*');
            fp.printLine(new int[] { 0, 1, 2 }, 3, '*');
        }
        
        public static void testPrintDiamond()
        {
            for (int k = 1; k < 5; k++) {
                try {
                    System.out.printf("边长为 %d 的菱形: \n", k);
                    fp.printDiamond(k);
                } 
                catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
        }
        
        public static void main(String[] args)
        {
            testPrintLine();
            testPrintDiamond();
        }
    
    }
    
    

             不过, 上述方式确有一些不足: 

             1.  对需要打印的位置要有精细的控制, 稍一不小心就容易出错;  2. 几乎没有多少可重用性。 一旦要打印更复杂或无规则图形,则要重新设计控制结构。

              那么,有没有其它的解决方案呢?

              有的, 这里,我们引入记法和解释器。 所谓记法, 就是对要打印图案的位置信息的记录或者指令; 而解释器,则对这种记录或指令进行解析。 比如: 打印符号 H

              X         X

              X   X   X  

              X         X

            可以记做: 1Line 1X 3Blank 1X \n  1Line 1X 1Blank 1X 1Blank 1X \n 1Line 1X 3Blank 1X, 或者简记为: 1l 1x 3b 1x \n 1l1x1b1x1b1x \n 1l 1x 3b 1x \n. 类似于行程长度编码。 看上去, 这样的记法似乎有点繁琐, 但是,它的灵活性是非常高的, 几乎对于任意图形, 都可以写出这样的记法, 而对于 1x 1b 1x 1b ... 这样的常用记法, 可以通过编写常用例程生成。 

           这样, 我们所需要的中心任务, 就转化为确定记法的规则及相应的解释器。 

           通过对各种图案的归纳, 可以归纳出描述一行图案的记法的几个规则: 

            1.  记法中可以含三种组成成分: (a) [合法数值] [l 或 L] ,  (b) [合法数值] [b 或 B ] , (c) [合法数值] [X 或 x] ; 三种组成成分用多个空格符隔开;

            2.  如果记法中不存在行标记 l 或 L , 则视为 一行 的简记;  记法中若仅含 l 或 L, 则视为一个或多个空行;

            3.  (a) 总是出现在最前面; 接下来 , (b) 或 (c) 可以任意组合出现或者不出现。

            相应的正则表达式描述是: 

             [零个或多个空格符] ([合法数值] [lL]) ?  [零个或多个空格符]  ( [零个或多个([合法数值][BbXx][零个或多个空格]) 的组合) [零个或多个空格]. 

             其中, 合法数值是不带前导零的数值, 即: [1-9] | [1-9][0-9]+

             实现代码如下: 

             

    package patterns.interpretation;
    
    import java.util.Scanner;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class Interpreter {
        
        private static final String PART1_REGEX = "(([1-9]|[1-9][0-9]+)[lL])?";
        private static final String PART2_REGEX = "((([1-9]|[1-9][0-9]+)[BbXx]\\s*)*)";
        private static final String LINE_PATTERN_REGEX = "\\s*" + PART1_REGEX + "\\s*" + PART2_REGEX + "\\s*";
        
        private static final Pattern LINE_PATTERN = Pattern.compile(LINE_PATTERN_REGEX);
        private static final Pattern PART_PATTERN = Pattern.compile("([1-9]|[1-9][0-9]+)([bBxX])");
        
        /**
         * interpretFigure: 解释给定图形标记,必须由 \n 或 \r 隔开的多个行标记 
         * @param figureNote
         */
        public void interpretFigure(String figureNote)
        {
            System.out.println("要打印的图形为: ");
            for (String lineNote: figureNote.split("\\s*[\r\n]\\s*")) {
                interpretLine(lineNote, false);
            }
        }
        
        /**
         * 解释给定行的图形标记
         * @param lineNote 给定行的图形标记
         */
        public void interpretLine(String lineNote, boolean tipFlag)
        {
            if (tipFlag) {
                System.out.println("行标记为: " + lineNote);
            }
            Matcher matcher = LINE_PATTERN.matcher(lineNote);
            if (!matcher.matches()) {
                throw new IllegalArgumentException("不合法的图形标记,请检查!");        
            }
            int lineNum = 0;
            if (matcher.group(1) == null) {
                lineNum = 1;
            } 
            else {
                lineNum = Integer.parseInt(matcher.group(2));
            }
            String lineInfo = matcher.group(3);
            for (int i = 0; i < lineNum; i++) {
                printLine(lineInfo);    
                System.out.println();
            }
        }
        
        /**
         * 根据给定的行标记信息打印行
         * @param lineInfo 行标记信息
         */
        public void printLine(String lineInfo)
        {
            if (lineInfo == null || lineInfo.equals("")) {
                return ;
            }
            String partNote = "";
            Matcher matcher = PART_PATTERN.matcher("");
            String flag = null;
            int num = 0;
            Scanner scanner = new Scanner(lineInfo);
            while (scanner.hasNext(PART_PATTERN)) {
                partNote = scanner.next();
                matcher.reset(partNote);
                if (matcher.matches()) {
                    num = Integer.parseInt(matcher.group(1));
                    flag = matcher.group(2);
                    if (flag.equals("x") || flag.equals("X")) {
                        printNChars('x', num);
                    }
                    else {
                        printNChars(' ', num);
                    }
                }
            }
            
        }
        
        /*
         * 打印 n 个 由 ch 指定的符号
         */
        private void printNChars(char ch, int n)
        {
            if (n <= 0 ) { return ; }
            while (n-- > 0) {
                System.out.printf("%c", ch);
            }
        }
        
    
    }
    package patterns.interpretation;
    
    public class InterpreterTester {
    
        private static Interpreter ip = new Interpreter();
        
        public static void main(String[] args)
        {
            testInterpretLine();
            printDiamond();
            printCharH();    
            printCharC();
        }
        
        public static void printDiamond()
        {
            String figureNote = "2b 1x 2b \n 1b 1x 1b 1x 1b \n 1x 3b 1x \n 1b 1x 1b 1x 1b \n 2b 1x 2b \n";
            ip.interpretFigure(figureNote);
        }
        
        public static void printCharH()
        {
            String figureNote = "2l 1x 3b 1x \n 5x \n 2l 1x 3b 1x\n";
            ip.interpretFigure(figureNote);
        }
        
        public static void printCharC()
        {
            String figureNote = "1b 5x \n 3l 1x \n 1b 5x \n";
            ip.interpretFigure(figureNote);
        }
        
        public static void testInterpretLine()
        {
            String exString1 = "3l \n 3L \n 1l 4b \n 1l 3x \n 3l 2b 2x \n ";
            String exString2 = "3l 5b 2x 3b\n  3l 5x 3b 5x \n";
            String exString3 = "03l 23x 4b \n 3l 023x 43b \n 3l 23x 043b \n 5b  \n 3x \n  5b 3x \n 3x 5b \n";
            String totalString = exString1 + exString2 + exString3;
            String[] multLines = totalString.split("\n");
            
            for (String line: multLines) {
                try {
                   ip.interpretLine(line, true);    
                } 
                catch (Exception e) {
                   System.out.println(e.getMessage());
                }
    
                     }
        }
        
    }
    
    

            总结:

          通过引入一套简洁的记法和相应的解释器, 我们提高了打印图案的重用性。 对于任意要打印的图案, 只要遵循这种记法写出相应的指令, 就可以轻易地打印出期望的图案。 并且, 这种记法对于非技术性普通用户也是易于理解的。 这说明了, 记法与解释器, 确实一种另辟蹊径的求解方案。


  • 相关阅读:
    我的算法日志:数据结构之顺序队列与循环队列
    我的算法日志:排序算法之快速排序
    算法:冒泡排序
    算法:桶排序(简易版)
    Android:配置LitePal 3.0
    Android:简单粗暴的二维码生成与扫描
    Linux
    Python
    Linux
    Python
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/4037838.html
Copyright © 2011-2022 走看看