问题的来历
在群里面一个小萝莉非要说拜我为师,呵呵,对于程序媛我一向--嗯嗯觉得程序不如人好看,再加上该名萝莉大学还没毕业,术语都多半没有听过,于是就想着拒绝,当时嘴一贱,就说了一句:你用一个For循环做个99表出来。
当然,这个对于小萝莉们来说,已经足够形成挑战了,但是对于群里的一众大佬们来说,自然是不在话下,3下5除二就搞定了。我又异想天开一下,如果不用判断语句,是不是也完成呢?粗想想是可以的,于是动手摆了几行代码,确实可以。于是就不断加码,不断增加新的完成条件,于是就形成了下面的问题,挑战极限这个定语,有一定的博眼球的意思,实际上不是那么难了。
挑战极限的问题
今天我想挑战一下OSCHINA的亲们的编程能力,出一道百度、谷歌不到答案的问题,第一个挑战成功的,直接奖励现金100元RMB(本人也是苦B码农,纯属意思一下)。
以前发过一条循环语句打印螺旋矩阵和蛇型矩阵的博客,今天我们来挑战只出现一条循环语句来打印99表。
注意,此题是考思想的,用“*”之外的运算符,如 "& | ^ >> << / % "的,虽然确实可以有解,但是代码逻辑与我倡导的:"一个好的算法首先是简单易懂的,其次是清晰明了的,再个一定是充满美感的"是相违背的。为什么下面条件这么多,实在是亲们的创意无限,我防不胜防哦。
特别声明:
- n可以是任意正整数,只要N的平方不要溢出都可以
- 一行一行print结果的无效
- 不允许出现if,switch,?:语句及判断语句的变体,也就是只允许循环变量做条件比较以确定循环次数,不允许其它变量进行条件判断
- 不允许出现异常
- 循环语句中只能有一个变量
- 代码行数超过100行的无效
- 如果 @红薯 能抢先给出答案,奖金跃升为250元RMB
- 提交问题并关注本人,回答才有效
- 答案是否有效解释权归悠然所有
问题如下
不管是什么编程语言,只要是在程序中只用了一条循环语句正确的输出了99表,那么就算挑战成功。
下面是我的测试用例:
测试1:
public static void main(String[] args) {
new Test99().print(9);
}
运行结果:
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
8 16 24 32 40 48 56 64
9 18 27 36 45 54 63 72 81
测试2:
public static void main(String[] args) {
new Test99().print(5);
}
运行结果:
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
第一个回答正确的人将获得奖励,以时间为准。
攻城狮的答案
来自各种语言的攻城狮们,对偶的问题进行了长时间的轰炸,有的群大部分时间在讨论这个题有木有?半夜两点继续苦占的有没木有?避木不出的有没有(@红薯 ,别用帽子盖你的脑袋,偶看到你了)?志有必得的有没有(工程的名字就叫GetMoney)?茶不思饭不香,没有思绪加班的有木有?
下面我们就一睹攻城狮们的风采:
利用语言特性搞定的
def main(row_count): for row_idx in range(1, row_count+1): row = map(lambda x:x*row_idx, range(1, row_idx+1)) print(str(row)[1:-1].replace(",","")) if __name__ == ‘__main__‘: print("-"*25) main(5) print("-"*25) main(9) print("-"*25) main(99)
这里有个技巧是
range(1, row_count+1):实际上是利用语言的特性来做了一个循环,不过,确实是非常简练的实现。
相当不错的Java版实现
public static void print(int n){ int i = 1; int j = 1; String flag[] = {"", "\n"}; while(i <= n){ System.out.print((i*j) + " "); int nextj = (j % i)+1; int nexti = (j / i) + i; System.out.print(flag[nexti - i]); i = nexti; j = nextj; } }
这个实现也是非常不错的,唯一的就是利用了"% /"小小的违规了一把,而且非常漂亮的把if语句给回避了-实际上是转换成上数组下标了。
依然不错的Java实现
public class MultiplicationTable2 { int line = 1; int cursor; public void print(final int maxLine) { for (cursor = 1; cursor <= line || enter() <= maxLine; cursor++) { System.out.print(cursor * line + "\t"); } } public int enter() { System.out.print("\n"); line++; cursor = 1; return line; } public static void main(String[] args) { new MultiplicationTable2().print(9); } }
这个实现的也非常不错,唯一违规的地方是在For语句中夹带了判断语句。
一个for里面搞两个循环变量的
for (int i = 1, j = 1; j <= 9; i++) { System.out.printf("%d*%d=" + i * j + " ", i, j); if (i == j) { i = 0; j++; System.out.println(); } }
当然,里面还多一个if判断
一不小心用了两个For的解法
package everydaydo; public class OSC { public static void main(String[] args) { OSC.fun1(9); } public static void fun1(int n){ StringBuilder sb = new StringBuilder(); for(int i=1;i<=n;i++){ sb.append(fun2(i)); } System.out.println(sb.toString()); } public static String fun2(int m){ StringBuilder sb = new StringBuilder(); for(int i=1;i<=m;i++){ sb.append(i*m+"\t"); } sb.append("\n"); return sb.toString(); } }
有把历史实现直接过过冲奖的
class MulTable{ public enum MulTableEnum{ ASC, DESC } public void showMulTable(final int count, MulTableEnum e){ if(MulTableEnum.ASC == e){ ascMulTable(count); }else{ descMulTable(count); } } private void descMulTable(final int count){ if(count<1){ i=0; return; } if(i!=count){ i++; System.out.print(i+"*"+count+"="+i*count+"\t"); descMulTable(count); }else{ i=0; System.out.println(); descMulTable(count-1); } } private void ascMulTable(final int count){ if(j>count){ j=1; return; } if(i!=j){ i++; System.out.print(i+"*"+j+"="+i*j+"\t"); }else{ i=0; j++; System.out.println(); } ascMulTable(count); } private int i=0; private int j=1; } public class Test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub MulTable mul = new MulTable(); mul.showMulTable(9, MulTable.MulTableEnum.DESC); mul.showMulTable(9, MulTable.MulTableEnum.ASC); } }
亲,你这题目也不仔细看看,怎么可能对得上要求呢?
Java版递归来了
public void show(int n){ if (n == 0) { return ; }else{ show(--n); } for(int i = 1; i <= n ; i++){ System.out.print(i*n + " "); } System.out.println(); }
唯一的就是用了if语句,唉,楼上的那位兄弟,你那递归和这个相比,是不是有点偏Low了?
某攻城狮的再战作品
public class asdfasf { public static void main(String[] args) { int n = 9; int i = 1; int line = 1; int lastSq = 0; while (i <= n * n) { System.out.print(i + " "); int sq = (int) Math.sqrt(i); if (sq * sq == i && sq > lastSq) { line++; System.out.println(); i = line; lastSq = sq; } else { i = i + line; } } } }
这个代码可读性稍差一点,当然,里面有if判断,违规了
JavaScript版来了
我们的前端攻城狮也不甘寂寞,冲上来了
$(document).ready(function () { $("body").html(Print(5)); }); var PrintHtml = ""; function Print(num) { var ThisHtml = ""; for (var i = 1; i <= num; i++) { ThisHtml += (i * num).toString() + " "; } PrintHtml = ThisHtml + "<br>" + PrintHtml; (num - 1) > 0 ? Print((num - 1)) : PrintHtml; return PrintHtml; }
嗯嗯,用了?:
某大神的第三次冲刺
public class asdfasf { public static void main(String[] args) { int n = 9; n = n * n; int i = 1; int line = 1; int times = line; while (i <= n) { System.out.print(i + " "); times = times - 1; try { System.out.println(1 / times); i = i + line; } catch (Exception e) { line++; times = line; i = line; System.out.println(); } } } }
这次居然用了反人类的异常来替换IF,确实是让我大大吃了一斤,亲我给归到使用if变体里了。
JavaScript再战
$(document).ready(function () { Print(5); $("body").html(PrintHtml); }); var PrintHtml = ""; function Print(num) { var ThisHtml = ""; for (var i = 1; i <= num; i++) { ThisHtml += (i * num).toString() + " "; } PrintHtml = ThisHtml + "<br>" + PrintHtml; while ((num - 1) > 0) { Print(num - 1); num = 0; } }
嗯嗯,用了两个循环
一个距离成功只有一步之遥的答案
public class Test99 { public static void main(String[] args) { Test99.print(20); } private static void print(int n) { for (int i = 1; i <= n; i++) { print2(i); } } private static void print2(int k) { for (int i = 1; i <= k; i++) { System.out.print(i * k + " "); } System.out.println(); } }
这个答案,实际上熟悉模式的同学,大致看看就能想出怎么把两个for语句干掉一个的办法,可惜了一点儿。
实践比想法困难的代表
某大神第4次挑战
public class MultiplicationTable { public static void main(String[] args) { char space = ‘ ‘; char enter = ‘\n‘; int n = 9; int i = 1; int line = 1; int col = 0; int current = 1; while (i <= (1 + n) * n / 2) { System.out.print(current); // col = col + 1; int isZero = ((line - col - 2) >> 31 & 1); line = line + 1 * isZero; col = (col + 1) - (col + 1) * isZero; current = line + current + (-current) * isZero; System.out.print((char) (space + (enter - space) * isZero)); i++; } } }
嗯嗯,里面有/有>>有&,总之各种小技巧。偶的答复:
亲,我觉得一个好的算法,首先是简单的,再一个是容易理解的,再一个是有技巧的。
中间省略N个重复了的代码
C#版的来了
#include <iostream> using namespace std; typedef void (*pFun)(int m, int k); void f0(int m, int k) { cout<<endl; } void f1(int m, int k) { pFun fun[2] = {f0, f1}; cout<<m * k<<" "; fun[(k<m)](m, k+1); } void printLine(int m) { f1(m, 1); } void run(int m) { int i = 1; while (i <= m) { printLine(i); i++; } } int main() { run(9); return 0; }
里面用到了k<m判断语句
SCALA版来了
def mul(n: Long): String = (1L to n).map { i => (1L to i).map(_ * i).mkString(" ") }.mkString(System.getProperty("line.separator"))
太精练了!!!就是那to了两次是不是两个循环?
非常不错的实现
def test99(n): i = 1 j = 1 while i <= n: print ‘%d ‘ % (i * j), nj = (j % i) + 1 ni = (j / i) + i print ‘\n‘ * (ni - i), i = ni j = nj
唯一就是小小的违规了。
这是啥语言版来着?
package object test { def main(args: Array[String]) { _99(9) } def _99(m: Int) { var i = 1; while (i <= m) { printLine(i); i += 1 } } def printLine(m: Int) { printResult(m, 1); } def printResult(m: Int, k: Int) { print(m * k + "\t") matchResult(m - k > 0, m, k); } def matchResult(in: Any, m: Int, k: Int) = in match { case true => printResult(m, k + 1) case _ => println() } }
里面有判断语句。
JavaScript版的解法
/** javascript 版, chrome 浏览器, CTRL+SHIFT+I 打开调试器 -> console选项卡 粘贴代码并回车: **/ function _99x(n){ function iterate(n, callback){ for(var idx = 1; idx <= n; idx++) callback(idx, n); } iterate(n, function(start, end){ iterate(start, function(start, end){ console.log(start * end); }) console.log(‘-------‘) }) }
原来没有仔细看,以为callback是系统函数,今天仔细看,原来这个是OK的,唯一的问题是换行怎么处理的?
NIM版
## test.nim for i in 1..9: for j in 1..i: write stdout, j * i write stdout, ‘\10‘ ## $ nim c -r test.nim
呵呵,标准的就是这么写的。
这个是啥版?
proc p(n: int) = proc walki(j: int, i: int) = if j <= i: write(stdout, j * i) walki(j + 1, i) else: write(stdout, ‘\10‘) proc walk(i: int) = if i <= n: walki(1, i) walk(i + 1) walk(1) p(9)
明显有判断了。
C语言版来了
#include <stdio.h> //输出一行 int print_one_row(int i, int n) { return (i <= n) && ((printf("%d ", i*n), print_one_row(i+1, n))); } //整体输出 int print_all(int n) { return (n && (print_all(n-1), print_one_row(1, n), printf("\n"))); } int main(int argc, char *argv[]) { print_all(1); //测试1行 print_all(9); //测试9行 print_all(11); //测试11行 return 0; }
用了if变体
也不是错的实现
import sys, math def test99(a): for i in xrange((1 + a) * a / 2): i = i + 1 q = int(math.sqrt(i * 2)) b = q * (q + 1) / 2 - i sys.stdout.write(str(max(q * -b + -b, q * -b + q * q))) sys.stdout.write(chr(10 - max(abs(b), 0) / max(abs(b), 1))) test99(5)
这个性能确实有点差的,sqrt,* / max 好多运算哦
号称邪恶的解法
一个变量是挡不住邪恶的我的, 你是不是要考虑限制内存的使用?
package com.test; public class Test99 { public void print(Integer... oneVar){ oneVar = new Integer[]{oneVar[0], 1, 1}; while(oneVar[1]<=oneVar[0] && System.out.printf(oneVar[1]*oneVar[2]+"\t")!=null &&((oneVar[1] == oneVar[2] && (oneVar[1]++>0 && System.out.printf("\n")!=null) && (oneVar[2]=0)==0) || true) ){ oneVar[2]++; } } }
简单的说,是把判断隐藏在while里了。
超级技巧版来了
function fun(n){ (n - 1) && fun(n - 1); for(var idx = 1; idx <= n; idx++) process.stdout.write(idx * n + ‘ ‘) process.stdout.write(‘\n‘) } fun(process.argv[2]);
这里的技巧相当高明,隐藏得非常深。
C# 简短版
static int s = 9; public static void print(int k=1) { int m = 1; while (m <= k && k <= s) { Console.Write("{0}*{1}={2}\t", m, k, m * k); m++; } Console.WriteLine(); if(m<=s) print(m); }<span></span>
C#版的,想来想去都要一个if来判断边界啊。
==嗯嗯,如果有一个if,这题就不难了:)
世界上最好的语言PHP版也来了
<?php //生成1-9的数组,我只是快速定义好数组..这不算循环吧。23333 $number = range(1, 9); //数组递归.. array_walk($number, function($param, $key) { for ($i = 1; $i < $key + 2; $i++) { echo $param * $i. " "; } echo ‘<br/>‘; });
range函数是干啥的,里面有没有个循环语句帮你干活?
版本帝的第N版来了
public class MultiplicationTable3 { /** * 主要是使用了数组初始化后,未赋值的位置为0,来实现复位的效果。<br> * cursors数组运行中第一位总是0,变化过程如:<br> * {0,0,0,0,0,0,0,0,0}<br> * {0,1,0,0,0,0,0,0,0}<br> * {0,1,2,0,0,0,0,0,0}<br> * {0,1,2,3,0,0,0,0,0}<br> * * @param n */ public void print(final int n) { // 游标控制,多一位,容错 int[] cursors = new int[n + 1]; // 基数,系数 int[] factor = new int[n]; factor[0] = 1; // 字符后缀 char[] suffixChars = new char[n]; suffixChars[0] = ‘\n‘; int i = 0; while (factor[0] <= n) { int colValue = (cursors[i] + 1); System.out.print(colValue * (factor[0]) + " "); int temp = i; i = cursors[i + 1]; cursors[temp + 1] = cursors[temp] + 1; factor[i] = factor[i] + 1; System.out.print(suffixChars[i]); } } public static void main(String[] args) { new MultiplicationTable3().print(9); } }
JS版改进型
这里的undefined:部分是不是判断的变体呢?
多线程版来了
public class Test99 { private int rowNum; private int[] arr; private int i; public Test99(int rowNum, int[] arr, int i) throws InterruptedException { this.rowNum = rowNum; //初始化一个长度为N+1的数组,如rowNum=5,数组为[0,0,0,0,0,0] this.arr= arr; this.i = i; try { arr[i+1] = arr[i] + 1; PrintThread printThread = new PrintThread(arr[++i], rowNum); printThread.start(); new Test99(rowNum, arr, i); } catch (Exception e) { Thread.sleep((arr[--i] + 1) * 100); Thread.interrupted(); } } } class PrintThread extends Thread { int length = 0; int rowNum = 0; public PrintThread (int length, int rowNum) { this.length = length; this.rowNum =rowNum; } @Override public void run() { try { Thread.sleep(length * 100); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 1; i <= length; i++) { System.out.print(i * length + "\t"); } System.out.println(); } }
居然有线程,居然有异常,偶的心好凌乱....让偶缕缕
韦达定理版
public class Test99 { public void print(int n) { for (int i = 0; i < n * (n + 1) / 2; i++) { int row = (int)((Math.sqrt(1 + 8 * i) - 1) / 2); int column = i - row * (row + 1) / 2; int end = 10 + (int)Math.ceil(1.0 * (row - column) / (row + 1)) * 22; System.out.printf("%d%c", (row + 1) * (column + 1), end); } } public void printOneline(int n) { for (int i = 0; i < n * (n + 1) / 2; i++) { System.out.printf("%.0f%c", (Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) + 1) * (i - Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) * (Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) + 1) / 2 + 1), (int)(10 + (int)Math.ceil(1.0 * (Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) - i + Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) * (Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) + 1) / 2) / (Math.floor((Math.sqrt(1 + 8 * i) - 1) / 2) + 1)) * 22)); } } public static void main(String[] args) { new Test99().print(5); new Test99().printOneline(9); } }
亲,但是不允许用/,而且这个性能也太慢了........
至尊版来了,此版一出,天地为之变色,世人为之瞩目
data segment a db 1 b db ? data ends code segment assume cs:code,ds:data start:mov ax,data mov ds,ax lop1: mov al,a mov cl,al mov b,1 lop: mov ah,2 or b,30h mov dl,b int 21h mov dl,‘*‘ int 21h or a,30h mov dl,a mov ah,2 int 21h mov dl,‘=‘ int 21h sub a,30h sub b,30h mov al,a mov bl,b mul bl mov bl,10 div bl mov bh,al mov bl,ah or bx,3030h mov ah,2 cmp bh,30h je lop2 mov dl,bh int 21h lop2: mov dl,bl int 21h mov dl,‘ ‘ int 21h inc b loop lop mov dl,13 int 21h mov dl,10 int 21h inc a cmp a,10 jb lop1 mov ah,4ch int 21h code ends end start
破绽在这里 cmp bh,30h je lop2
尽管你换了个马甲,照样认识你。
正解来了
function _99x(n){ function iterate(n, callback){ for(var idx = 1; idx <= n; idx++) callback(idx, n); } iterate(n, function(start, end){ iterate(start, function(start, end){ process.stdout.write(start * end + ‘ ‘); }); process.stdout.write(‘\n‘); }) }
作者自述:
我第一次写的这个实际上跟此答案思路相似,都是模板方法,横向或纵向都是遍历, 不同的是遍历过程中的处理逻辑(一个是算乘积, 另一个是输出换行以及启动横向遍历过程), 实质上就是把循环逻辑抽取出来(比如一个叫"遍历器"或者叫"遍历模板"的东西), 留个回调方法, 做不同的逻辑处理,如果此题通过,我写的也算通过了
说白了,就是模板模式的应用。
悠然的解法
悠然的解法,说实际的,只花了几分钟的时间考虑,写了一个,估计已经算不得最优解了。当时我和群里的同学们有说过:如果我最后拿不出来一个解,估计被人干人等骂死了。
public class Test99 { public static void main(String[] args) { Iterator.iterate(5,new IAction()); } } abstract class Iterator<T> { abstract void process(T n); static void iterate(int n, Iterator action) { for (int i = 1; i <= n; i++) { action.process(i); } } } class IAction extends Iterator<Integer> { public void process(Integer n) { iterate(n, new JAction(n)); System.out.println(); } } class JAction extends Iterator<Integer> { private final int x; JAction(int n) { this.x = n; } public void process(Integer n) { System.out.print(" "+x * n); } }
解题思路解释:
实际上,只用一个For语句打印99表,无非是以下几种方案:
- 一个循环语句来完成:这种方案肯定需要一些其它指令来配合,比如/ % sqrt等等,同时换行符处理也非常难处理,如果没有IF的话,是非常难以解决的。
- 递归方式:递归方式一般来说肯定需要有退出条件判断,否是就变成死循环了
- 把一个For语句使用两次
再来看看我的注意事项:
注意,此题是考思想的,用“*”之外的运算符,如 "& | ^ >> << / % "的,虽然确实可以有解,但是代码逻辑与我倡导的:"一个好的算法首先是简单易懂的,其次是清晰明了的,再个一定是充满美感的"是相违背的。为什么下面条件这么多,实在是亲们的创意无限,我防不胜防哦。
这里,所有的限制都是为了把攻城狮向第3种方案逼,同时里面也有提醒:是考思想的。
接下来解释一下我的代码:
- Iterator是一个迭代类,它的功能就是去做循环,然后干它应该干的事情,实际上为重用For语句埋好了伏笔。
- IAction用于完成ForFor实现方式的第一层循环,从语义上来讲就是打印每一行的内容,然后换行
- JAction用于完成ForFor实现方式的第二层循环,输出每行的计算式
里面没有任何和判断相关的代码,里面只出现了一次For循环语句,同时也没有乱七八糟的其它运算符和库函数。
代码扣除测试相关代码,也就是20行代码左右。
实际上,这个时候与上面说的正解的解法对比,仅仅是由于语言的特性导致的实现形式不一样而已,实际上思想是完全一致的。
大结局
感谢所有参与此项挑战的同学,不管你们采用的是什么语言,不管你们的方法是不是挑战成功,你们都是成功的,你们对自己的极限的挑战,对未知世界的探索的精神都是我学习的榜样。限于篇幅,有些同学的答案没有贴上来,在此表示歉意。
同时,对于我们来说,也有非常大的收获:
- 要敢于对我们已经习惯的方式进行挑战:有时候,我们已经习惯于遵守习惯,却丧失了打破习惯的勇气
- 知道不代表拥有:如果直接告诉你这个是应用什么什么模式来解决的,我想大多数的同学都是知道的,也是可以搞得定,但是只有想用就能用得出来的东西才是自己的。
- 群众们的表现,所有的参与者都学到了许多许多,再一次认识到自己的渺小,所以不要相信自己是最NB的几乎永远是对的。
实际上,在这次挑战活动的收益最大的正是我自己,再次感谢所有同学,尤其是@红薯 为了我们搭建了这么好的平台,对了那100块奖金能不能给报销了?