一点一点前进...

0%

对于有理数,分成有限小数和无限循环小数。比如1/3 = 0.33333…
对于无限循环小数,循环节表示后续循环的部分。比如1/6 = 0.166666…,循环节是6,长度是1。1/7 = 0.1428571428571428…,循环节是142857,长度是6。

欧拉项目的第二十六题很有意思。基于以上知识,对于任意d < 1000,找到分数1/d循环节最长的那个d。

如何计算循环节长度呢?
想想我们手算,如果余数比除数小,我们会在后面补0然后再除。也就是说补零之后的数是下一次的被除数。如果被除数重复出现,除数确定的,那么商和余数也就是一样的。这时,循环节就出现了。
下面的函数分为两部分,

  1. 补零操作
  2. 查找有没有同样的被除数存在,如果有,就找到了循环节,计算循环节长度并返回。在没有找到的前提下,把当前被除数记录下来,并得到余数作为下一次的被除数。
    这里需要注意,如果某一次出现了除尽的情况,说明该分数是有限小数,循环节长度是0。
    下面是函数代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // n/m
    static int GetRecurringCycle(int n, int m)
    {
    List<int> dividends = new List<int>();
    while (true)
    {
    while (n < m)
    {
    n *= 10;
    }
    // search pattern.
    int index = dividends.IndexOf(n);
    if (index >= 0)
    {
    return dividends.Count - index;
    }
    dividends.Add(n);
    n %= m;
    // n is divisible by m.
    if (n == 0)
    {
    return 0;
    }
    }
    }

能够计算循环节长度,得到循环节最长的那个d就易如反掌了。

今天需要用到一个全排列算法,Google了一下,找了好几个,觉得下面的写法最NB,最ZB,最简洁。
最核心的地方在于那个for循环和foreach那里。
遍历每个元素,把这个元素从source里面取出来,递归的求得剩余元素的全排列,把这个取出来的元素放到剩余元素全排列的前面。
That’s all!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Returns all permutations of the input <see cref="IEnumerable{T}"/>.
/// </summary>
/// <param name="source">The list of items to permute.</param>
/// <returns>A collection containing all permutations of the input <see cref="IEnumerable<T>"/>.</returns>
public static IEnumerable<IEnumerable<T>> Permutations<T>(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException("source");
// Ensure that the source IEnumerable is evaluated only once
return permutations(source.ToArray());
}

private static IEnumerable<IEnumerable<T>> permutations<T>(IEnumerable<T> source)
{
var c = source.Count();
if (c == 1)
yield return source;
else
for (int i = 0; i < c; i++)
foreach (var p in permutations(source.Take(i).Concat(source.Skip(i + 1))))
yield return source.Skip(i).Take(1).Concat(p);
}

角谷猜想:该猜想由日本数学家角谷静夫发现,是指对於每一个正整数,如果它是奇数,则对它乘3再加1,如果它是偶数,则对它除以2,如此循环,最终都能够得到1。
这是一个很有名的问题,貌似在06年有一个定论,太高深,没看懂。

欧拉项目网站第14题就是源于这个猜想。在小于一百万的数字里面,哪一个数字算到1所需要的序列最长。
比如,13-40-20-10-5-16-8-4-2-1,那么13对应的序列长度就是10。需要注意的是,虽然数字是小于100万的,但是计算过程中数字可能比100万大。

这个题要稍微用一下动态规划的思想,把已经算好的值保存在一个数组里面。不要使用最朴素的想法,每个值都是直接计算序列长度,那么时间复杂度将是100万乘以序列平均长度(计算得到该值是132)。计算量数以亿计,对现代计算机来说,算完也是分分钟的事情,但是如果稍稍改进下算法,或许,1秒就出来结果了,岂不快哉。

给定一个整数,算出它对应的序列长度,注意,要利用之前计算出来的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int[] termsLength;

static int GetChainLength(this long number)
{
int length = 1;
while (number != 1)
{
if (number % 2 == 0)
{
number /= 2;
}
else
{
number = 3 * number + 1;
}

if (number < 1000000 && termsLength[number] != 0)
{
return length + termsLength[number];
}
length++;
}
return length;
}

下面是主函数,从小往大,依次计算每个数对应序列的长度。找出最大的一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
termsLength = new int[1000000];

int maxLength = 0;
long maxNumber = 0;
for (long i = 1; i < 1000000; i++)
{
int tempLength = i.GetChainLength();
termsLength[i] = tempLength;
if (tempLength > maxLength)
{
maxLength = tempLength;
maxNumber = i;
}
}
return maxNumber;

欧拉项目题目52:找到一个最小整数x,使得2x,3x,4x,5x,6x和x具有相同的数字,只是顺序不同。

因为上面的题目可以编程去解决。我就去思考如何写出高效的程序解决它。因为网站上有提示,解决欧拉项目的问题,如果算法足够好,1分钟之内就能出结果。
下面是我的解题思路:

  1. 3x能被三整除,那么x也能被三整除。
  2. 5x能被五整除,那么x肯定要含有5或0。
  3. x首位是1,且小于1是若干个6,因为x和6x的位数一样多。
  4. x首位是1,2x的首位就是2或者3,所以x肯定要包含2或3。
  5. x首位是1,4x的首位是4或5或6,所有x肯定要包含4或5,或6。
  6. x首位是1,6x的首位大于等于6,所有x肯定要有一个数字大于等于6。
  7. 根据上面的2,4,6三条,x至少三位。虽然我的直觉告诉我x是6位的数字。
    阅读全文 »

大约在两年前,我在太极做项目,写完了code,为了通过验收(因为做的是外包项目),开始写Unit Testing case。按照要求,写一个成功的,写一个失败的。当时的感想有两点,写Unit Testing case几乎没有必要,除非针对非常复杂或者自己拿不准的函数才写,二是像太极那样写case就更加没有意义了。

现在觉得上述第二点仍然是正确的,因为那种case是为了有case而写,完全没有必要,还浪费了大量的人力。对提高代码质量没有任何意义,相反,还加入了一堆没有用的测试code。

第一点完全不正确。
重构,毋庸置疑,是提高代码质量很好的一种方式。但是,简单到重命名一个变量名,复杂到改变类的结构,如何能保证你的修改正确呢?那就是做测试。

这两天在修复一个Bug,顺手重构了两个函数。很简单,就是改变了for循环if条件判断的结构,使得代码更简单,更易懂。二是将方法修改成扩展方法,使得调用的code和自然语言更为接近。三是根据现有业务逻辑(不会影响到原有的设计,是一个兼容的修改)修改了某个分支的判断条件。

之后我在测试函数里面添加了两个新的用例,测试一下新的逻辑。顺手右键跑了一下针对这两个函数的测试case,得到一个红色的叉叉。以为是新的用例写的不对,结果是之前的用例没有通过。我勒个去。。。
仔细检查了一下code,发现重构的时候漏写了一个感叹号(非运算符)。改过来,继续做测试,得到一个绿色的对勾。

即使再简单的重构,有一个Unit Testing跑一下,确保修改无误,更安心。因为人毕竟是人,做什么事情都有可能出错。而有了完善的case,不费多少事,就能确保code重构之后的行为和之前一致。相反,没有这些保障,那么就只能靠重构的人就小心翼翼的处理各个环节,伤神不说,还不能保证是100%正确。

我在想,如果没有Unit Testing,这个少了个运算符的错误应该也会被发现。运气好,code review的时候由其他人发现和原有逻辑不完全一致,提出个疑问,我会修改一下。其实这种可能性微乎其微的。之后按照流程部署Edog,让测试测试是否修复了该bug,并看一下是否引起了regression。这个时候应该能发现这个问题。但是意味着这个时候再修改代码,build,部署Edog,再让测试去测试。
我损失了时间(同时也浪费了测试的时间),虽然build和部署时间也就一个小时多,而且敲命令看各种信息的时间不会超过15分钟,但是,势必打断了自己当前的工作,回头还要再次进入状态,浪费精力。有这时间和精力,看看书,或者多干点活都不错。哪怕是休息去了,那咱也是为了以后更好的工作。

我这里发生的是一个小事,但是也能体现出Unit Testing的重要性。遇到更复杂的案例,充分并且及时的测试才能保证代码质量,才能提高生产力。没有充分且及时的测试,虽然短期看来省了时间,但是之后的维护工作更加棘手,会出现更多的问题,耗费更多的精力和时间去弥补。出来混,早晚都是要还的。测试很重要,开发人员顺手写一个Unit Testing case,就能避免很多不必要的麻烦,向贡献高质量代码走出一小步。

过去两周,参加了校招新员工培训。学到了很多东西。
特别是上周,连续几天加班到9点10点去做项目,不过结果还错,我们team获得了Best UI和Best Presentation两个奖哈哈。

下面总结了一些新的体会:

  1. 数据库操作非常慢,耗时长,应该尽可能去优化。
    比如,
    所有的查询都有时间参数,那么用时间建立索引,以节约查询的时间。
    经过测试,发现提交一次查询很慢,查询的结果集大小几乎没有影响,改进是大量减少查询次数。

  2. 方法很重要,值得花时间去实现更快的方法。(当然,要权衡寻找新方法所花的时间和新方法所能节省的时间)
    比如,
    将分析完的数据从HDFS导入SQL Database。花了近2个小时找了更快的方式,节约的时间远远超出2小时。
    更重要的是,周四晚发现一个bug,如果没有更好的方法,那么到周五很可能不能导出所有的数据。
    如果新方法是替代手动操作,就更有必要了。

  3. 任务的分拆数量等于独立模块的数量。
    也就是说,一个人负责一个独立的模块。如果两个人在一个独立的模块,效率的提高很有限。所谓的人月神话。
    手术室模型在我们团队也很适用,一个人主力去解决一个问题,其他人为他提供支持,做一些外围工作。

  4. 不到最后时刻,不言弃。
    我们到了周五中午,还没有做好search功能,大家协作努力,最后还是发布了search功能。同时还fix了几个bug。

  5. 多个原型问题。
    周五和hoyle商量ppt的时候,告诉他我们的Plan A还没有完全ready,不过有Plan B和C。
    但是hoyle的回答很不一样,说他刚入职的时候也会像我们这样,但是现在不会了。原因是太多手准备说明我们时间安排的不合理。
    关于这个问题,求解释。

  6. 技术的重要性
    技术重要吗?很重要,但是没有我想象的那么重要(相比培训前)。
    写代码本身是很简单的事情,几乎处于整个项目最不重要的地位。
    更重要的是利用相关的资源达成自己目的,如何去得到并运用资源很重要。
    解决一个问题,技术本身不具备不可替代性,很多不同的技术都能解决同一个问题。
    所以解决问题本身才是重要的,而不是纠结用什么技术。
    学习技术很容易,只要你愿意花精力去钻研,总会弄明白。
    那么,重要的事情是学习能力,执行能力等等。
    或许,这也是为什么微软找人看中的是一个人的潜力,而不是这个人目前会做什么。

  7. 视野
    视野决定了很多东西。
    做事之前要多想,想想问题和其他问题的联系。做事前要多问问自己如何能做的更好。
    要看的更多,更远,而不仅仅局限于解决当前问题。只有这样,才能掌握更多的东西,理解更多。

  8. 其他
    时间紧迫导致理想和现实的差距。刚开始大家想的都很好,理想很丰满,但是实际做的事情和构建一个可维护可运行的软件相去甚远,现实很骨感。
    事物的波浪式前进。每当我们感觉要最终成型的时候,又从各个角落冒出来一大堆问题。

空谷幽兰,一本讲述老外寻山拜访隐士的书。

古诗有云,寻隐者不遇。隐士既然隐居,就是不想让大家找到,那么,如何能够找到隐士呢?这真是一个棘手的问题。作者不懈的努力,还是让他找到了,还找到了很多很多。

终南山,自古以来就是隐居圣地,本书也以此为中心。不过我最熟悉的关于终南山的故事来自金庸的小说,终南山后,活死人墓,神雕侠侣,绝迹江湖。
隐士,来终南山是为了寻道。
这里关于道的最早的故事是老子西出函谷关,下面摘自百度百科,括弧是我自己的见解。
传说,当年函谷关总兵尹喜见到紫气东来(其实尹喜先看到紫气东来,才专门去谋求总兵职位,想第一时间见到圣人),老子骑青牛而至,便拜老子为师,辞官随老子沿秦岭终南山神仙路西行,昼行夜宿,不几日来到将军山下,只见此处:祥云缭绕,四季如春,溪流纵横,鱼翔浅底,百鸟争鸣,龙飞凤舞,牡丹竞放,泉水叮咚,真乃世外桃源。老子抬头望时,只见一巨石十分奇异,如有人形,豹头环眼,铁面虬鬓,一手执剑,一手执扇,五蝠飞舞,正气浩然,不尽叹到:”道可道,非常道,宇宙造物,天地之始,万物之母,欲观其妙,常有也。。。”洋洋洒洒五千言,由尹喜记录,世谓之《道德经》是也。
这里作者提到,在文化大革命期间,红卫兵破坏了这里的建筑,更甚,破坏了宗教,思想,文化。本书后面多次提到了红卫兵的事迹,哎,那个特殊时期,文化的损失巨大,甚至有人说文革之后,再无文化。
访问了当地一个道长,回答之中,讲述了在当前中国,政治和宗教的关系。社会是复杂的,但是道长保持着清心寡欲的心,无欲无求

华山,中国道家思想最早的发源地之一。访问了一个道士,提到目前中国的道家大师不到200人,但是在几十年前,有数千人,这对道家的发展很不利。
这本书不仅仅说了地理,说了风景,还讲了人文,讲了故事,很好的一本旅游书籍。

佛教,另外一个很大的宗教,很多分支。

插一句,访问隐士,他们的对话在书中完整的做了呈现,回答往往富含智慧,展现了他们对人生的态度。

再插一句,在古代,道士和尚都云游四海,处处作诗,很有文化。

作者访问了很多和尚尼姑,他们多数都生活困难,但是心里很充实,有信仰。相比起那些在少林寺这种地方的和尚,没有世俗的争斗,没有利益的争斗。

太白山,作者去的那个年代很危险,还有野兽,野猪,熊,金丝猴。这里很危险,在山里,经常有人被这些野兽伤着,但是这里的人们心境很好,坚韧的活着,过着世外桃源般的生活,不追求世俗的名与利。不过二十年过去了,不知道这里还有没有野兽了,毕竟,生态被破坏了。

隐居不仅仅是穷人的事情,还有一个很有名的富人,王维。
我们小时候知道的王维是一个诗人,在我看完这本书之后,王维是一个艺术家,懂音乐,会画画。虽然,他写诗不如李白杜甫。

最后一章详细的讲了八仙的故事,道是非常深奥的,不可道,需要有人指点你,点醒你,当然,自己也要有悟性,最终达到开悟,成道升仙。

隐居的另外一种境界是大隐隐于市。作者在寻完各种山之后,在长安(西安)也发现了隐居者。这个隐居者在大城市保持着不喧嚣,令人吃惊的是,他能说出终南山上所有的隐士(回想之前的内容,修行需要去山里住一段时间,需要有个师傅教你如何修行),当然,包括作者所拜访的。大隐隐于市,这才是境界,令人佩服。

今天看了一篇文章,是”程序员怎么了”,觉得非常受用,这里把里面的观点总结一下:

  1. 程序员和其他行业一样,需要一个态度,最基本的,要有想做好这件事的态度。如果你连这个最基本的态度都没有,那就不要说做程序员没有前途,因为这样的态度去什么行业都是没有前途的。
  2. 程序员的前途是自己创造的,都是有深层次的因果关系的。如果目前认为程序员没有前途,很可能是自己做的还不够好。
  3. 多读书,读好书。更重要的是,不只读一遍。
  4. 在这个行业,要认识这个行业的牛人,比自己牛的人,多和他们交流,从他们那里,能学到很多。
  5. 每天都会遇到问题,或者上网查资料,或者查书,解决问题之后要多总结。写博客是很不错的选择,不仅仅是总结,还能让自己的条理更清楚,有可能的话,别人能从你这里得到帮助。

我也是一个程序员,非常喜欢新东西,非常喜欢去解决问题,看了这个文章,需要做的是不浮躁,好好做事,努力做好一个程序员。

给定一个正整数,写一个函数判断该整数是否是回文。
一个很常见的思路,把整数转化成字符串,然后判断字符串是否是回文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static bool IsPalindrome_solution1(int number)
{
return IsPalindrome(number.ToString());
}

static bool IsPalindrome(string s)
{
if (string.IsNullOrEmpty(s))
{
return true;
}

if (s.Length == 1)
{
return true;
}

return s[0] == s[s.Length - 1] && IsPalindrome(s.Substring(1, s.Length - 2));
}

这个题目限定了输入是一个整数,所以我们可以通过循环把这个数的回文数值计算出来,判断原先的整数和反转之后的整数是否相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
static bool IsPalindrome_solution2(int number)
{
int copy = number;
int reverted = 0;

while (number != 0)
{
reverted = reverted * 10 + number % 10;
number /= 10;
}

return copy == reverted;
}

关于测试用例,由于是正整数,不用考虑null或者””这种字符串里面的特殊情况。但是应包含以下三个基本测试用例:

  1. 不是回文,比如123
  2. 是回文,长度是奇数,比如121
  3. 是回文,长度是偶数,比如1221

又读了整整一章,几何的大厦。

看到尺规作图这一章,让我回忆起初中学习几何的时候。
那个时候数学很好,习题/考试题中的尺规作图都完全不是什么问题。
不过看了这个上面的题目,嗯,很有难度。
看的时候尽可能跟上作者的思路,每一步都找到理论支持,完全搞明白。
这样才能真正享受什么是思考。

不过,最让人感觉到nb的是,
从尺规作图到圆规作图…
再到锈规作图…
无所不能…
还有火柴棍作图和折纸作图,都很强很强。

至于最后一节,剪接图形,开启一个思路,太多让人称奇的地方了。