Wait the light to fall

Posts categorized in ‘rakulang’ (533)

接口多态性被认为是可爱的

先说点题外话。在写这篇文章的过程中,我遭遇了系统管理员最可怕的噩梦:服务器丢失,然后是备份不好。直到最后一刻,我都有充分的理由担心自己根本无法完成这篇文章。幸运的是,我与生命做了休战,得到了暂时的喘息。结论是什么?不要在 ESXi 上使用 bareos。或者,可能就是不要使用裸 OS… 在为我之前的 advent 日志领取 RFC 的时候,我完全把注意力放在了语言对象部分。我花了几遍时间才找到合适的内容。但在这期间,我发现列表中居然少了一个非常重要的主题。“不可能!” - 我自言自语道,随后又继续搜索。然而,无论是搜索"抽象类",还是搜索"角色",都没有任何结果。我本想放弃,并做出结论,这个想法是后来才有的,当时写提纲的时候还是这样的。 但是,等等,OO 相关的 RFC 中提到的主题是什么接口?哦,那个接口! 就像请求正文中所说的那样。 增加一个机制来声明类的接口 还有一个方法来声明一个类实现了上述接口。 此时我再次意识到,现在已经落后了整整20年。这段文字来自于很多人认为 Java 是唯一正确的 OO 实现的时代! 而事实上,通过进一步的阅读,我们发现下面的说法,很可能是受到当时一些流行观点的影响。 现在,如果一个接口文件试图做任何事情,而不是预先声明方法,这都是一个编译时错误。 这让人想起了什么,不是吗?然后,在 RFC 的最后,我们又发现了一个问题。 Java 是一种使用接口多态性的语言。不要被它吓倒–如果我们一定要从 Java 中偷点东西,那就偷点好东西吧。 好东西?好东西?哦,我的……Java 试图通过简单地否认 C++ 多重继承方式来解决它的问题,这是让我从一开始就远离这门语言的原因。早在90年代初,我就受够了 Pascal 控制我的写作风格! 幸运的是,那些参与早期 Perl6 设计的人一定和我一样对这个问题有相同的看法(此外,Java 本身也发生了很大的变化)。所以,我们现在有了角色。它们与抽象类和现代接口的共同点是,一个角色可以定义一个接口来与一个类进行通信,也可以提供一些特定角色行为的实现。它还可以做得更多一些,不仅仅是这些! 角色的不同之处在于 Raku OO 模型中角色的使用方式。一个类不会实现一个角色;也不会像抽象类那样从它那里继承。相反,它做的是角色;或者我喜欢用另一个词来形容:它消耗一个角色。从技术上讲,这意味着角色被混入类中。这个过程可以形象地描述为:如果编译器将角色的类型对象所包含的所有方法和属性,重新植入到类中。类似这样。 role Foo { has $.foo = 42; method bar { say "hello!" } } class Bar does Foo { } my $obj = Bar.

改造 tie 以支持可扩展性

2000年9月7日提出,2000年9月20日冻结,取决于 RFC 159: True Polymorphic Objects 2000年8月25日提出,2000年9月16日冻结,也是由Nathan Wiger提出的,之前已经在博客中提到过。 tie 到底是什么? RFC 200 是关于扩展 Perl 提供的 tie 函数。 Perl 中的这个功能允许人们将程序逻辑注入到系统对标量、数组和哈希等的处理中。这是通过给数组等数据结构分配一个包的名称来实现的(也就是绑定)。然后,该包要提供一些子程序(如 FETCH 和 STORE),这些子程序将被系统调用,以实现对给定数据结构的某些效果。 因此,它被 Perl 的一些核心模块(如线程)和 CPAN 上的许多模块(如 Tie::File)所使用。Perl 的 tie 函数仍然存在 RFC 中提到的问题。 都是绑定的 在 Raku 中,所有的东西都是一个对象,或者可以被认为是一个对象。系统需要对一个对象做的一切事情,都是通过它的方法来完成的。在这个意义上,你可以说,Raku 中的所有东西都是一个绑定的对象。幸运的是,Rakudo(Raku 编程语言最先进的实现)可以识别出对象上的某些方法实际上是系统提供的方法,并在编译时实际创建捷径(例如,当分配给一个有标准容器的变量时:它实际上不会调用 STORE 方法,而是使用一个内部子程序来达到预期的效果)。 但除此之外,Rakudo 还具有在程序执行过程中识别热点代码路径的能力,并对这些路径进行实时优化。 Jonathan Worthington 就这个过程做了两场非常精彩的演讲。从2017年的去优化如何帮助我们更快,以及2019年的性能更新。 因为在 Raku 中所有的东西都是一个对象,并且通过这些对象的类的方法进行访问,这使得编译器和运行时能够更好地掌握程序中实际发生的事情。从而获得更好的优化能力,甚至在某些时候优化到机器语言级别。 因为在 Raku 中一切都被 “绑定"了(用 Perl 过滤过的眼镜来看),将程序逻辑注入到系统对数组和哈希的处理中,可以简单到只需子类化系统的类,并提供一个系统使用的标准方法的特殊版本。假设你想在你的程序中看到当一个元素从一个数组中获取时,只需要添加一个自定义的 AT-POS 方法: class VerboseFetcher is Array { # subclass core's Array class method AT-POS($pos) { # method for fetching an element say "fetching #$pos"; # tell the world nextsame # provide standard functionality } } my @a is VerboseFetcher = 1,2,3; # mark as special and initialize say @a[1]; # fetching #1␤2 Raku 文档中包含了一个概述,说明要模拟一个 Array 和模拟一个 Hash 需要提供哪些方法。顺便说一下,关于通过索引或键来访问数据结构元素的整个词条是推荐给想要了解 Raku 内部的这些方面的人阅读的。

带退出码的增强

categories: [“Raku”] 在我的上一篇文章中,我找到了一个很好的方法来匹配 exitcode。我想把它扩展到与 STDERR 匹配,如果 exitcode 是非零。我已经有了一种方法来捕获一个管道的所有错误流。 px<find /tmp» |» px<your-script-here> |» @a :stderr(Capture); 我使用 Capture(类型对象)的方式和我们使用 * 或 Whatever 一样。它只是表示,神奇的东西应该发生。这个神奇的东西归结为把所有 STDERR 流粘到一个二维数组中。如果我想处理错误,我可能想对 exitcode、shell 命令的名称和它输出到 STDERR 的部分进行匹配。像下面这样的语法会很好。 my $ex = Exitcode.new: :STDERR(<abc def ghi>), :exitint(42), :command<find>; given $ex { when ‚find‘ & 42 & /def\s(\S+)/ { note „find terminated with 42 and $0“; } } 事实证明,在 Junction 中得到对 Str、Numeric 和 Regex 的匹配是很容易做到的。我们需要做的就是增强 Regex。 augment class Regex { multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) { ?

线程

RFC 1, 作者 Bryan C. Warnock: 线程 在 Perl 中对真正的多线程架构的需求, 可能是, 也可能不是, 这才是最初被称为简单的 Perl, 然后是 Perl 6, 最后是 Raku 的真正动机。 大概是在 90 年代末或 10 年代初, 我们与一家大公司签订了合同, 该公司需要快速从网上下载东西。我们需要这些线程, 而它们最终在 Perl 5.8.8 中出现了。然而, 我们的线程是非常基本的, 不需要任何形式的通信, 只是光秃秃的并行的东西, 在它们下面, 使用的是操作系统进程, 在 Perl 虚拟机层面没有真正的线程。而它们是非常需要的。这就是为什么 RFC1 的由来: Perl 中线程的实现 它最初是在8月1日提出的(所以才有了20周年纪念日的事情), 几个月后, 到9月28日终于被冻结了。 它基本上提出了一种实现低级线程的方法, 包括新的命名空间(全局的, 用于线程之间共享变量)以及 Threads 类, 有这个例子: use Threads; # the main thread has all four above in its arena my $thread2 = Threads->new(\&start_thread2); #... sub start_thread2 { .

显示缺席

categories: [“Raku”] 我不能在 Stackoverflow 上自作聪明,因为我和他们使用的许可证不兼容。但这并不能阻止我阅读 uzlxxxx 提出的问题。在他最后的知识收获中,他试图用 Nil 来表示链接列表末尾没有值。Nil 是这个博客的老朋友了,我花了很长时间才喜欢上它。 Nil 确实可以表示没有值。表示是一个相当活跃的词,可以问一个问题,谁在向谁表示。我相信 Nil 是在向编译器表示没有值。 my $var = "value"; $var = Nil; dd $var; # Any $var = Any 对调试器来说(也就是你和我,调试器不会删除任何错误。我们会删除。),缺失用 Any 表示。正如 jnthn 指出的那样,在链表中的 Node 的情况下,一个与该列表相关联的类型对象更有意义。这不是 Rakudo 正在做的事情。 my constant IterationEnd = nqp::create(Mu); # eqv to Mu.new; 它使用的是 Mu 的实例,这就带来了一些问题。 my $var = Mu.new; say [$var.Bool, $var.defined]; # OUTPUT: [True True] 请求列表末尾的元素不应该是 True,也不应该是 defined。我们可以通过在其中混入一个角色来解决这个问题。 my \Last = Mu.new but role { method defined { False }; method Bool { False } }; say [.

非递归

categories: [“Raku”] Moritz 不满意 Raku 给他的权力,让他与名单搏斗。他说的没错。如果简单的事情很容易,就不需要摔跤了。这让我想到了我在上一篇博文中构建的数据结构。它是一个列表和一个 Proc::Async 的对。 [[[Proc::Async],Proc::Async],Proc::Async] 其中,列表中混入了 .start 方法。这样我就可以按顺序连接 shell 命令,并按相反的顺序启动它们,而不需要特殊的套管来让 .start 被调用。毕竟在启动一对shell命令之前,我需要连接 STDOUT 和 STDIN。然而,任何形式的反省都会成为一种负担。而且我需要检查一个 Array 是否不在一个管道链的开始或结束。 @a |> $grep |> $sort; # this is fine $find |> $sort |> @a; # this too $find |> @a |> $sort; this can not work 数组不是一个并发的数据结构。链的左边和右边是。所以我们不能把它们混在一起。(我相信当 R#3778 修复后,我可以让这个工作。) 所以我重写了目前的内容。作为一个副作用,我们可以存储一个管道,并在以后手动启动它,并提供一个很好的要领。 my $find = Proc::Async.new('/usr/bin/find', '/tmp'); my $sort = Proc::Async.new('/usr/bin/sort'); my @a; my $p = $find |> $sort |> @a; say $p; #OUTPUT: find ↦ sort ↦ @a 其中 $p 包含一个 Shell::Pipe,它有 @.

为什么我喜欢 Raku

我一直在家里静静地玩着每周挑战,本周的第一个任务是: 写一个脚本,找到第一个至少有5个不同数字的平方数。 解决这个问题的方法是(很明显!)懒洋洋地把每一个数字从1到无穷大的平方,然后梳理每个平方的数字,寻找5个或更多独特的数字,并立即输出你找到的第一个这样的平方。 这就直接翻译成 Raku: 1..∞ ==> map {$^n²} ==> first {.comb.unique ≥ 5} ==> say(); 但那个解决方案的优雅并不是我喜欢 Raku 的原因。 我爱 Raku 是因为,如果那个解决方案对你来说太可怕了(太无限、太懒惰、太并发、太管道化、太 Unicode、太声明式、太函数化、太像 Erlang 大师会写的代码),那么 Raku 同样允许你写一个简单的版本:一个命令式的、迭代的、块结构的、变量驱动的、纯 ASCII 的、或多或少完全和你在 Perl 中写的一样的版本,甚至在C中: loop (my $n=1 ;; $n++) { my $n_squared = $n ** 2; my %unique-digits; for (split '', $n_squared, :skip-empty) { %unique-digits{$_}++ } if (%unique-digits >= 5) { say $n_squared; last; } } 或者你也可以很容易地写出一个介于这两个极端之间的解决方案,无论其复杂程度和分解程度如何,都是你个人舒适区的甜蜜点。比如说: sub find_special_square { for 1..Inf -> $n { return $n² if $n².

简单化

上周每周挑战的第二个任务是列出某年每个月最后一个星期五的日期。 许多参与者花了很大的力气来创建高效而准确的解决方案:跟踪每个月的最后一天,正确检测闰年,计算出特定日期的月日公式,管理所需的复杂日期运算。 但是,鉴于 Raku 中内置了强大的 Date 类,这些令人钦佩的劳作其实大部分都没有必要。 整个任务可以通过简单地从给定年份的1月1日到12月31日来完成,首先检查每个日期是否是星期五(即"星期几"的值是5),然后检查下一个星期五(即正好一周后的日期)是否恰好在不同的月份。 如果这两个条件都为真,那么我们就有一个"本月最后一个星期五",所以我们只需打印出来。 换句话说: for Date.new($year,1,1) .. Date.new($year,12,31) -> $date { if $date.day-of-week == 5 && $date.later(:1week).month != $date.month { say $date; } } 我们不需要明确地担心每个月有多长,一年是否是闰年,或者如何计算出某一天是否是星期五;Date 类为我们解决了所有这些问题。 而这不是运气,完全是设计好的。Raku 有大量类似这样的内置数据类型;它们是另一种"合适的工具,就在手边"。 当然,因为 Raku 是多范式的,所以有很多其他的方法可以将解决方案包裹在 Date 类的便利性中,这取决于你喜欢如何思考这个世界。 如果你喜欢纯韩式式的解决方案,可以这样写: say join "\n", grep { .later(:1week).month != .month }, grep { .day-of-week == 5 }, Date.new($year,1,1) .. Date.new($year,12,31); 或者你更喜欢一个(潜在的)并行的管道,然后通过一系列独立的过滤器和处理器来过滤日期序列: Date.new($year,1,1) .. Date.new($year,12,31) ==> grep( {.day-of-week == 5} ) ==> grep( {.

编程之更全面的工具集

叹息。它总是这样,不是吗? 你很快就写完了关于拥有正确的工具如何使某一特定的编码任务变得微不足道…当你意识到,就在隔壁,有一个更好的例子,你可以用它来表达同样的观点。 我在上一篇文章中谈到的"最长初始子路径"的例子是上周每周挑战的第2项挑战。但那周的挑战1让我更清楚地看到,正确的工具可以简化一项任务。 挑战1是找到最小的不是质数的欧几里得数。第 N 个欧几里得数是由前 N 个质数的乘积加上1给出的。所以欧几里得数的序列是: (2)+1, (2*3)+1, (2*3*5)+1, (2*3*5*7)+1, (2*3*5*7*11)+1, ... 如果有一个简单的方法来建立另一个列表的部分乘积的序列(例如质数的部分乘积),这个序列就很容易计算了: (2), (2*3), (2*3*5), (2*3*5*7), (2*3*5*7*11), ... 当然,Raku 也有这样一个工具内置在核心语言中。它叫做三角还原元操作符。 在 Raku 中,一个普通的还原运算符(如 [*]): $product = [*] @nums; 在列表的每两个元素之间插入方括号内的操作符,就像已经写好的一样: $product = @nums[0] * @nums[1] * @nums[2] * ... * @nums[*-1]; 在这个例子中,这计算的是所有数字的乘积。 另一方面,三角还原元运算符(例如,“三角积"运算符:[/*]): @products = [\*] @n; 或多或少地做了同样的事情,但它不是只返回最后的计算结果,而是从每一个连续的乘法步骤中返回一个部分结果的渐进列表,就像它被写的那样: @products = (@n[0], @n[0]*@n[1], @n[0]*@n[1]*@n[2], ...); 这似乎是一个神秘而无用的功能,但是,当然,这正是我们实际需要的神秘而无用的功能,以便从质数列表中建立欧几里得数的列表。 记住,我们要找的是第一个非质数欧几里得数。也就是:从所有大于从2到无穷大的所有质数的累计部分乘积序列的数中,选出第一个非质数。 而这一描述,又一次直接转化为一行 Raku: say first !*.is-prime, # Print the first non-prime from map *+1, # all numbers one greater than [\*] # the cumulative products of grep &is-prime, # all the prime numbers 2.

编程之全套工具

几年前,我在 Raku 中创建了一个关于 “transparadigm 编程"的演讲(以及后来的整个课程)。 基本的前提是,当一些语言限制你只能使用一个单一的锤子(或者更糟的是:一个装满锤子的盒子)时,Raku 被设计成一个完整的工具箱:整合了 OO、函数式、并发、声明式和过程式工具,使你能够为每个工作选择正确的组合。 最近,这个想法又在我脑海中全面浮现。在上周的每周挑战中,第二个任务是把文件路径列表,找到最长的共同初始子路径(即它们都共享的最深的目录)。 各个注册参与者提供的解决方案都非常简洁,而且通常既高效又优雅。然而他们中的大多数都是同一个程序解决方案的变体。在目录分隔符上拆分每条路径,然后对于1到N: 比较所有第N个组件,如果它们不一样就退出。类似于: my @components = @list.map({.split('/')}; for 1..* -> $n { next if all(@components).elems > $n && [~~] @components.values».[$n]; say @components.first.[0..$n-1].join('/'); last; } 并不是说这种方法有什么不好。 只是感觉比实际需要的"体力劳动"多了很多;很多低级的程序化的"告诉我怎么做”,而不是高级的声明性或功能性的"告诉我做什么"。 当我自己来解决这个问题的时候,我是这样想的。 我的搜索空间是所有可能的初始子路径的所有路径。在这个空间内,我需要找到所有路径共享的最长的初始子路径。换句话说,我需要将每条路径转换成一组越来越长的子路径,然后找到这些集合的交集,再找到该交集中最长的元素。它可以直接翻译成 Raku,比如这样: @list.map({m:ex{^.*\/}».Str}).reduce({$^a∩$^b}).keys.max(*.chars).say; 或(对于喜欢注释的人)喜欢这样: @list\ # In the list... .map({m:ex{^ .* '/'}\ # Find all initial subpaths ».Str})\ # ...as lists of strings .reduce({$^a ∩ $^b})\ # Then find all shared subpaths .keys.max(*.chars)\ # Then find the longest .

加密

第15周挑战赛的第二个任务是实现 Vigenère 密码的编码器和解码器。但这比看起来要复杂一些,因为以 Blaise de Vigenère 命名的密码其实并不是他发明的,而 Vigenère 真正发明的密码也不是以他的名字命名的。 所以我们应该实现维根埃密码…还是维根埃的密码?为什么不两者兼而有之呢! Vigenère 密码是由 Giovan Battista Bellaso 在1553年设计的,然后在大约三百年后被误认为是 Vigenère 发明的。它使用了一个 tabula rēcta 来将信息文本翻译成密码文本,然后再翻译回来。 给定一个用户提供的密钥(如 “BELLASO”),我们通过匹配密钥和信息中各自的字母来加密信息(如 “VIGENEREDIDNOTINVENTTHIS”),然后用它们作为两个索引,在 tabula rēcta 的相应列和行中查找相应的密文字符。而如果密钥比信息短,我们只需回收密钥,次数不限。 比如说: Key: B E L L A S O B E L L A S O B E L L A S O B E L L A... Text: V I G E N E R E D I D N O T I N V E N T T H I S Table: ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ Cipher: W M R P N W F F H T O N G H J R G P N L H I M D 换句话说,tabula rēcta 的每一列都是一个独立的凯撒密码(或 ROT-N 转录),钥匙的每一个连续字母都会选择对信息中的相应字母进行哪种替换。

工欲善其事必先利其器

上周每周挑战的第一个任务是打印前十个强质数和弱质数。如果一个质数 pn 大于其两个相邻质数的平均数(即 pₙ > (pₙ₋₁+pₙ₊₁)/2),则该质数为"强"质数。如果一个质数小于其两个相邻质数的平均值,那么它就是"弱"质数。 当然,如果我们碰巧有一个所有质数的列表,这个挑战就变得微不足道了。那么我们只需要过滤掉前十个强数,和前十个弱数。事实上,如果我们碰巧有一个强质数的列表和一个弱质数的列表,那就更简单了。那我们就只需要打印出每个的前十个。 但是,质数和弱质数的数量是无限的(也有可能是强质数,虽然这还只是猜测),所以在大多数编程语言中建立一个完整的质数亚种列表是不切实际的。 然而,我喜欢 Raku 的另一个原因是它能很好地处理无限列表和序列。不用担心有限的上限,简化了大量的任务…这就是其中之一。 质数序列只是正整数序列,经过过滤(用 .grep)只保留那些质数。当然,Raku 已经有一个质数测试器:内置的 &is-prime 函数。质数序列永远不会改变,所以我们可以把它声明为一个常数: constant p = [ (1..∞).grep( &is-prime ) ]; 现在我们只需要提取强质数和弱质数。鉴于前面的"强"和"弱"的定义,我们得到: sub strong (\n) { n > 0 && p[n] > p[n-1, n+1].sum/2 } sub weak (\n) { n > 0 && p[n] < p[n-1, n+1].sum/2 } 请注意,p[n-1,n+1] 是 p[n] 的两个邻域的列表,然后我们把它们加在一起(.sum)和平均(/2)。 这两个强度测试是在质数的索引上操作的,而不是它的值,所以要生成强质数和弱质数的完整列表,我们需要取这些索引(p.keys),过滤它们,只保留强或弱的索引(另一个 .grep),然后将每个索引映射回对应的质数。 这就得到了: constant strong-p = [ p.keys.grep(&strong).map(->\n { p[n] }) ]; constant weak-p = [ p.

越简单越好

上周每周挑战的第一个任务是生成范埃克序列: 0, 0, 1, 0, 2, 0, 2, 2, 1, 6, 0, 5, 0, 2, 6, 5, 4, 0,... 第一个挑战是要理解什么是范埃克序列,因为网上的各种解释都没有即时的帮助。 范埃克序列是一个从零开始的整数列表,序列中的下一个数字由当前数字与该数字最近的前一个出现的距离给出。 例如,如果当前指数N处的数字(我们称它为:Aₙ)是7,那么为了计算指数N+1处的数字,我们通过序列回溯7的最近一次出现(在某个较早的指数M处)。那么序列中的下一个数字就是这两个7的出现之间的距离: N - M 唯一复杂的是,如果当前数字在前面的序列中没有出现,那么下一个数字就是零。这并不像你想象的那样武断:我们可以把这个零值看作是 Aₙ 到自身的距离。也就是说,如果没有前面的出现,那么"前面"出现的唯一可能的候选者只是 Aₙ 本身,所以这种情况下的距离是 N - N,也就是零。 好了,这就是范埃克序列。现在我们如何在 Raku 中生成它? 如果你一直在关注我最近的博客文章,那么了解到 Raku 有一个适当的内置工具,它将让我们通过一个单一的语句来完成整个任务,可能不会有太多的惊喜。这个工具就是序列运算符。 ... 序列操作符接受一个初始值列表,然后是一个代码对象(即一个块或子例程),接着是一个终止条件。 my @sequence = @initial-list, &code-obj ... $termination; 它从初始值开始建立一个序列(字面意思是 Seq 类的对象),然后通过在初始列表的最后元素上反复调用代码对象,在初始列表之后生成额外的值。每次调用代码对象时,它返回的值都会被追加到序列中,直到返回一个与终止条件智能匹配的值,这时序列就完成了。例如 # initial code-obj termination my @odd-nums = 1, 3, 5, {$^n + 2} ... 999; my @N-powers = $N, $N², {$^x * $N} .

数列

斐波那契数列(Fibonacci sequence) lazy my \sequence = 1, 1, {$^a + $^b } ... *; say sequence.raku; # 1, 1, 2, 3, 5... say [+] sequence[0..^10]; # 143 等差数列(arithmetic sequence) 通项为 n lazy my \sequence = 1, 2, 3 ... *; say sequence.raku; # 1, 2, 3, 4, 5... say [+] sequence[0..^10]; # 55 通项为 2n lazy my \sequence = 2, 4, 6 ... *; say sequence.raku; # 2, 4, 6, 8, 10 say [+] sequence[0.

使用 %% 提取文本块儿

数据样例 section.txt 中的本文为样例数据: 123,456,789 =begin code 999,333,666 145,123,120 =end code 10,20,30 10,10,10 =begin code 567,555,578 678,679,665 710,720,715 =end code 321,654,987 =begin code 312,555 =end code 要求把 =begin code 和 =end code 之间的所有数字分别提取出来。 Grammar Grammar 的结构如下, 其中 Section 目录下分别是 Grammar 和 Action 模块, data 目录下是样例数据 section.txt: ├── Section │ ├── Actions.pm6 │ └── Grammar.pm6 ├── data │ ├── section.txt ├── extract-section.p6 use Grammar::Debugger; use Grammar::Tracer; unit grammar Section::Grammar; token TOP { ^ <section>+ %% <separator> $ } token section { <line>+ } token line { ^^ [\d+]+ %% ',' $$ \n } token separator { | ^^ '=begin code' $$ \n | ^^ '=end code' $$ \n* } 其中 Grammar::Debugger 和 Grammar::Tracer 模块用于调试 grammar, 需要放在 grammar 模块的行首:

再用 %% 提取文本块儿

数据样例 Here's some unimportant text. =begin code This code block is what we're after. We'll use 'ff' to get it. =end code More unimportant text. =begin code I want this line. and this line as well. HaHa. =end code More unimport text. =begin code Let's to go home. =end code Grammar use Grammar::Debugger; use Grammar::Tracer; unit grammar Range::Grammar; token TOP { ^ <un-important-line>+ %% <section> $ } token section { <begin> ~ <end> <line>+?

提取文本块儿

数据样例 Here's some unimportant text. =begin code This code block is what we're after. We'll use 'ff' to get it. =end code More unimportant text. =begin code I want this line. and this line as well. HaHa =end code More unimport text. =begin code Let's to go home. =end code 要求提取 =begin code 和 =end code 之间的文本块儿。 Grammar grammar ExtractSection { token start { ^^ '=begin code' \n } token finish { ^^ '=end code' \n } token line { ^^ \N+)> \n } token section { <start> ~ <finish> <line>+?

子解析

子解析 游标不一定要到达字符串的末尾才算成功。也就是说,它不一定要匹配整个字符串。 subparse 总是返回一个 Match 对象 method subparse( $target, :$rule = 'TOP', Capture() :$args = \(), Mu :$actions = Mu, *%opt ) Grammar grammar RepeatChar { token start($character) { $character+ } } 解析 say RepeatChar.subparse( 'bbbabb', :rule('start'), :args(\('b')) ); # 「bbb」 say RepeatChar.parse( 'bbbabb', :rule('start'), :args(\('b')) ); # Nil say RepeatChar.subparse( 'bbbabb', :rule('start'), :args(\('a')) ); # <failed match> say RepeatChar.subparse( 'bbbabb', :rule('start'), :args(\('a')), :pos(3) ); # 「a」

解析结构化文本

数据样例 [28/04/2015 12:32] Title1 content line 1 content line 2 content line 3 content line 4 content line 5 [28/04/2015 12:16] Title2 content line 6 content line 7 [27/04/2015 17:30] ​Title3 content line 8 content line 9 content line 10 Grammar grammar StructedText { token TOP { ^ <entry>+ $ } token entry { <head> \s* # 每一项有一个标题 <line>+ \s* # 每个标题下面有很多行 } token head { '[' <datetime> ']' \s+ <title> } token datetime { <filedate> \s+ <filetime> } token filedate { [\d+]+ % '/' } token filetime { [\d+]+ % ':' } token title { \N+ } token line { [ <!

计算器

数据样例 x = 40 + 2; print x; y = x - (5/2); print y; z = 1 + y * x; print z; print 14 - 16/3 + x; 目录结构如下: . ├── Lang │ ├── Actions.pm6 │ └── Grammar.pm6 ├── data │ ├── calc.lang Grammar Grammar.pm6 的内容如下: unit grammar Lang::Grammar; rule TOP { ^ <statements> $ } rule statements { <statement>+ %% ';' } rule statement { | <assignment> | <printout> } rule assignment { <identifier> '=' <expression> } rule printout { 'print' <expression> } rule expression { | <term>+ %% $<op>=(['+'|'-']) | <group> } rule term { <factor>+ %% $<op>=(['*'|'/']) } rule factor { | <identifier> | <value> | <group> } rule group { '(' <expression> ')' } token identifier { (<:alpha>+) } token value { ( | \d+['.

解析 JSON

数据样例 { "country": "Austria", "cities": [ "Wien", "Salzburg", "Innsbruck" ], "population": 8353243 } Grammar grammar JSON::Tiny::Grammar { rule TOP { ^[ <object> | <array> ]$ } rule object { '{' ~ '}' <pairlist> } rule pairlist { <pair>* % [ \, ] } rule pair { <string> ':' <value> } rule array { '[' ~ ']' [ <value>* % [ \, ] ] } proto token value { <...> }; token value:sym<number> { '-'?

检测 CSV 是否有效

数据样例 Year,Make,Model,Length 1997,Ford,E350,2.34 2000,Mercury,Cougar,2.38 Grammar grammar CSV { token TOP { [ <line> \n? ]+ } token line { ^^ # Beginning of a line <value>* % \, # Any number of <value>s with commas in `between` them $$ # End of a line } token value { [ | <-[",\n]> # Anything not a double quote, comma or newline | <quoted-text> # Or some quoted text ]* # Any number of times } token quoted-text { \" [ | <-["\\]> # Anything not a " or \ | '\"' # Or \", an escaped quotation mark ]* # Any number of times \" } } 解析 say "Valid CSV file!

解析带 Action 的纸牌游戏

数据样例 a♥ a♥ 7♦ 8♣ j♥ a♥ 7♥ 7♦ 8♣ j♥; 10♥ j♥ q♥ k♥ a♦ Grammar grammar CardGame { rule TOP { ^ <deal> $ } rule deal { :my %*PLAYED = (); <hand>+ % ';' } rule hand { [ <card> ]**5 } token card {<face><suit>} proto token suit {*} token suit:sym<♥> {<sym>} token suit:sym<♦> {<sym>} token suit:sym<♣> {<sym>} token suit:sym<♠> {<sym>} token face {:i <[2..9]> | 10 | j | q | k | a } } Action class CardGame::Actions { method card($/) { my $card = $/.

解析纸牌游戏

数据样例 2♥ 5♥ 7♦ 8♣ 9♠ 2♥ a♥ 7♦ 8♣ j♥ Grammar grammar CardGame { rule TOP { ^ <deal> $ } rule deal { <hand>+ % ';' } rule hand { [ <card> ]**5 } token card {<face><suit>} proto token suit {*} token suit:sym<♥> {<sym>} token suit:sym<♦> {<sym>} token suit:sym<♣> {<sym>} token suit:sym<♠> {<sym>} token face {:i <[2..9]> | 10 | j | q | k | a } } 解析 say CardGame.

Alaways Succeed Assertion

数据样例 255 435 777 123 456 789 098 764 125 Grammar grammar Digifier { rule TOP { [ <.succ> <digit>+ ]+ } token succ { <?> } token digit { <[0..9]> } } Action class Devanagari { has @!numbers; method digit ($/) { @!numbers.tail ~= <零 一 二 三 四 五 六 七 八 九>[$/] } method succ ($) { @!numbers.push: '' } method TOP ($/) { make @!numbers[^(*-1)] } } 解析 say Digifier.

解析校名

数据样本 [Wang, Zhiguo; Zhao, Zhiguo] Hangzhou Normal Univ, Ctr Cognit & Brain Disorders, Hangzhou, Zhejiang, Peoples R China; [Wang, Zhiguo; Theeuwes, Jan] Vrije Univ Amsterdam, Dept Cognit Psychol, Amsterdam, Netherlands Grammar grammar University { token TOP { ^ <university> $ } token university { [ <bracket> <info> ]+ % '; ' } token bracket { '[' <studentname> '] ' } token studentname { <stdname=.info>+ % '; ' } token info { <field>+ % ', ' } token field { <-[,\]\[;\n]>+ } } grammar MyUniversity is University { token university { <info>+ % '; ' } } Action class MyUniversityAction { .

解析键值对儿

数据样例 version=6.d backend=MoarVM disto=Rakudo Star Grammar grammar KeyValuePairs { token TOP { [<pair> \v+]* } token pair { <key=.identifier> '=' <value=.identifier> } token identifier { \w+ } } Action class KeyValuePairsActions { method pair ($/) { $/.make: $<key>.made => $<value>.made } method identifier($/) { # subroutine `make` is the same as calling .make on $/ make ~$/ } method TOP ($match) { # can use any variable name for parameter, not just $/ $match.

解析数学表达式

数据样例 3 + 4 - 5 3 * 4 * 5 4 + 5 * (1 + 3) Grammar grammar MathExpression { token TOP { <sum> } rule sum { <product>+ % '+' } rule product { <term>+ % '*' } rule term { <number> | <group> } rule group { '(' <sum> ')' } token number { \d+ } } Action class MathEvalAction { method TOP($/) { make $<sum>.made; } method sum($/) { make [+] $<product>».

解析变量名

数据样例 @array %hash $sum Grammar grammar VariableNames { token variable { <sigil> <name> } token sigil { '$' | '@' | '&' | '%' | '::' } # [ ... ] are non-capturing groups token name { <identifier> [ '::' <identifier> ] * } # 标识符以字母开头 token identifier { <alpha> \w+ } } 匹配 my $match = VariableNames.parse("@array",:rule('variable')); say $match; 继承 # we inherit from the original grammar... grammar VARIABLENAMES is VariableNames { # .

解析括号对儿之间的数据

数据样本 [Lue, Fan] [Lou, Man-Li] [Tian, Mijie; Zhou, Lin; Zou, Xiao; Zheng, Qiaoji; Luo, Lingling; Jiang, Na; Lin, Dunmin] Grrammar grammar PairBracket { token TOP { ^ <line>+ $ } token line { \[ <student>+ % <semicolon> \] \n # 换行 \n 是最容易被忽略的地方 } token student { <myname>+ % <comma> # 分隔符也可以是一个 subrule } token myname { <[A..Za..z-]>+ # 字符类的写法 <[...]> } token comma { ',' \s+ # 逗号 } token semicolon { ';' \s+ } } Action class PairBracketAction { .

解析 INI 文件

INI 数据 access=user ;; more details of user below [person] name=john doe address=555 Canndy Lane Grammar grammar IniFile { token key { \w+ } token value { <!before \s> <-[\n;]>+ <!after \s> } token pair { <key> \h* '=' \h* <value> \n+ } token header { '[' <-[ \[ \] \n ]>+ ']' \n+ } token comment { ';' \N*\n+ } token block { [<pair> | <comment>]* } token section { <header> <block> } token TOP { <block> <section>* } } Action class IniFile::Action { method key($/) { make $/.

解析天气预报数据

Name= Jan Mayen Country= NORWAY Lat= 70.9 Long= 8.7 Height= 10 Start year= 1921 End year= 2009 Obs: 1921 -4.4 -7.1 -6.8 -4.3 -0.8 2.2 4.7 5.8 2.7 -2.0 -2.1 -4.0 1922 -0.9 -1.7 -6.2 -3.7 -1.6 2.9 4.8 6.3 2.7 -0.2 -3.8 -2.6 2008 -2.8 -2.7 -4.6 -1.8 1.1 3.3 6.1 6.9 5.8 1.2 -3.5 -0.8 2009 -2.3 -5.3 -3.2 -1.6 2.0 2.9 6.7 7.2 3.8 0.6 -0.3 -1.3 Grammar grammar StationDataParser { token TOP { ^ <keyval>+ <observations> $ } token keyval { $<key>=[<-[=]>+] '=' \h* $<val>=[\N+] \n } token observations { 'Obs:' \h* \n <observation>+ } token observation { $<year>=[\d+] \h* <temp>+ %% [\h*] \n } token temp { '-'?

trips

行程数据 Russia Vladivostok : 43.131621,131.923828 : 4 Ulan Ude : 51.841624,107.608101 : 2 Saint Petersburg : 59.939977,30.315785 : 10 Norway Oslo : 59.914289,10.738739 : 2 Bergen : 60.388533,5.331856 : 4 Ukraine Kiev : 50.456001,30.50384 : 3 Switzerland Wengen : 46.608265,7.922065 : 3 Bern : 46.949076,7.448151 : 1 Grammar grammar SalesExport { token TOP { ^ <country>+ $ } token country { <name> \n <destination>+ } token destination { \s+ <name> \s+ ':' \s+ <lat=.

在 Spark Structured Streaming 中管理 Kafka Offsets

HBase 表 create 'stream_kafka_offsets', {NAME=>'offsets', TTL=>1209600} RowKey 布局: row: <topicName>:<groupID>:<EPOCH_BATCHTIME_MS> column family: offsets qualifier: <PARTITION_ID> value: <OFFSET_ID> /* Save offsets for each batch into HBase */ def saveOffsets(topicName: String, groupID: String, offsetRanges: Array[OffsetRange], hbaseTableName: String, batchTime: org.apache.spark.streaming.Time) = { val hbaseConf = HBaseConfiguration.create() hbaseConf.addResource("src/main/resources/hbase-site.xml") val conn = ConnectionFactory.createConnection(hbaseConf) val table = conn.getTable(TableName.valueOf(hbaseTableName)) val rowKey = topicName + ":" + groupID + ":" + String.valueOf(batchTime.milliseconds) val put = new Put(rowKey.getBytes) for(offset <- offsetRanges){ put.

parquet-format-not-work-in-spark-structured-streaming

使用 Spark Structured Streaming 消费 Kafka 数据并实时保存为 parquet 文件时, 出现一个问题, 代码如下: val res: StreamingQuery = adapterData .repartition(sparkConf.numOfPartitions) .writeStream .format("parquet") .option("path", "/parquet_data/enterprise/") .partitionBy("vintype", "dt") .outputMode(OutputMode.Append()) .trigger(Trigger.ProcessingTime("1200 seconds")) .queryName("enterprise kafka data saving as parquet") .start() 问题: 在线上运行一段时间后, 发现 /parquet_data/enterprise/ 目录下不再有新的文件追加了, 查日志没发现原因。隔一段时间就出现一次。临时解决办法是删除并重建 _spark_metadata 目录和 checkpoint 目录。 这样会丢数据。

用 Apache Flink 创建带状态的流式应用

首先创建项目框架 在终端中执行如下命令: curl https://flink.apache.org/q/quickstart-scala-SNAPSHOT.sh | bash -s 1.10.0 此例子中使用了 flink-table 依赖, 我们在 pom 文件中加入它: <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table</artifactId> <version>${flink.version}</version> <type>pom</type> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.flink/flink-table-common --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-common</artifactId> <version>${flink.version}</version> <scope>provided</scope> </dependency>

为什么你可能会误用 Spark Streaming API

免责声明:是的,我知道这个话题有些争议,而且我知道大多数信息都在 Spark 文档中针对其 Streaming API 进行了介绍,但是当我看到这个错误发生了很多遍之后,我感到写这篇博客的冲动很强烈。 我经常会看到来自 Spark Streaming 的新手在 StackOverflow 上提问,大致如下所示: 问题:“我正在尝试执行 XYZ,但无法正常工作,该怎么办? 这是我的代码:” val sparkContext = new SparkContext("MyApp") val streamingContext = new StreamingContext(sparkContext, Seconds(4)) val dataStream = streamingContext.socketTextStream("127.0.0.1", 1337) dataStream.foreachRDD { rdd => // Process RDD Here } 嗯,好吧,这是怎么了? 当我开始学习 Spark 时,我的第一个着陆点是有关 RDD(弹性分布式数据集)如何工作的解释。通常的例子是单词统计,其中所有操作都在 RDD 上执行。我认为可以肯定的是,这是许多其他学习 Spark 的人的切入点(尽管如今 DataFrame\Sets 已成为初学者的首选方法)。 当人们飞跃使用 Spark Streaming 时,可能有点不清楚 DStream 的附加抽象意味着什么。这导致许多人寻求他们可以掌握的东西,他们遇到的最熟悉的方法是 foreachRDD,该方法将 RDD 作为输入并产生 Unit(典型的副作用方法的结果)。然后,他们可以再次在他们已经感到满意和理解的 RDD 级别上工作。这完全遗漏了 DStreams 的要点,这就是为什么我想简要了解一下我们可以在 DStream 本身上做些什么而无需探究底层 RDD 的原因。

探索 Apache Spark 中的状态流

更新(2017.08.01): Spark v2.2 最近推出了一个叫做 mapGroupsWithState 的有状态流的新抽象, 我最近有一篇博客也是关于它的。我强烈建议你检查一下。 介绍 Apache Spark 由几个模块组成,每个模块都有不同的用途。 它的功能强大的模块之一是 Streaming API,它使开发人员能够在称为 Discretized Stream 或 DStream 的抽象下使用连续流(或准确地说是微批次)。 在这篇文章中,我将深入探讨 Spark Streaming 的一个特殊属性,它是有状态的 Streaming API。 有状态流使我们能够维护微批之间的状态,从而使我们能够形成数据的会话化。 免责声明-为了遵循本文的流程,应该对 Spark 的工作原理有基本的了解,并对 DStream 抽象具有一般的了解。 如果没有,请继续阅读,不用担心,我会等你… 欢迎回来! 让我们继续。 通过例子理解 为了了解如何使用 API​​,让我们创建一个简单的传入数据示例,该示例要求我们进行会话化。我们的输入数据流将是 UserEvent 类型: case class UserEvent(id: Int, data: String, isLast: Boolean) 每个事件描述一个唯一的用户。我们通过用户 ID 标识用户,并用 String 表示发生的事件的内容。我们还想知道用户何时结束会话,因此我们提供了一个 isLast 标志来指示会话结束。 我们负责汇总所有用户事件的状态将是 UserSession 类型的状态: case class UserSession(userEvents: Seq[UserEvent]) 其中包含特定用户发生的事件序列。在此示例中,我们假设数据源是来自于 Kafka 使用的 JSON 编码数据流。 我们的 Id 属性将用作键,而 UserEvent 将是我们的值。两个放在一块,我们得到一个 DStream[(Int, UserEvent)]。

细究 Rust 中的所有权

所以,您想学习 Rust,并不断了解所有权和借用的概念,但不能完全了解它的含义。所有权至关重要,因此在学习 Rust 的过程中尽早理解它是很好的,而且还可以避免遇到导致您无法实现程序的编译器错误。 在上一篇文章中,我们已经从 JavaScript 开发人员的角度讨论了所有权模型。在本文中,我们将仔细研究 Rust 如何管理内存,以及为什么这最终会影响我们在 Rust 中编写代码并保持内存安全的方式。 那么什么是内存安全? 首先,最重要的是要了解在讨论什么使 Rust 成为一种编程语言时,内存安全实际上意味着什么。特别是当来自非系统编程背景,或者主要具有垃圾回收语言的经验时,可能很难理解 Rust 的这一基本功能。 正如威尔·克里顿(Will Crichton)在他的伟大文章《Rust 的内存安全:C 语言案例研究》中所述: “内存安全性是程序的属性,其中所使用的内存指针始终指向有效内存,即已分配的内存和正确的类型/大小。内存安全是一个正确性问题-内存不安全程序可能会崩溃,或者会由于错误而产生不确定的输出。” 实际上,这意味着存在允许我们编写“内存不安全”代码的语言,从某种意义上来说,引入错误非常容易。其中一些错误是: 悬空指针:指向无效数据的指针(一旦我们查看数据在内存中的存储方式,这将更有意义)。您可以在此处阅读有关悬空指针的更多信息。 两次释放:尝试两次释放相同的内存位置,这可能导致“未定义的行为”。在这里查看更多信息。 为了说明悬空指针的概念,让我们看一下下面的 C++ 代码及其在内存中的表示方式: std::string s = "Have a nice day"; 初始化的字符串在内存中通常使用如下的栈和堆表示: buffer / capacity / / length / / / +–––+––––+––––+ stack frame │ • │ 16 │ 15 │ <– s +–│–+––––+––––+ │ [–│––––––––––––––––––––––––– capacity ––––––––––––––––––––––––––] │ +–V–+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+ heap │ H │ a │ v │ e │ │ a │ │ n │ i │ c │ e │ │ d │ a │ y │ │ +–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+ [––––––––––––––––––––––––– length ––––––––––––––––––––––––––] 我们将在一秒钟之内介绍一下栈和堆的内容,但是现在很重要的一点是,要知道存储在栈中的是 std::string 对象本身,它的长度为三个字,固定大小。这些字段是指向堆分配缓冲区的指针,该缓冲区保存实际数据,缓冲区容量和文本长度。换句话说,std::string 拥有其缓冲区。程序销毁该字符串时,也会通过该字符串的析构函数释放相应的缓冲区。

在 Spark Streaming 程序中执行 shell 命令

我有个 Spark FileStreaming 程序,当监控到一个批次完成后,就执行 hdfs 的 mv 命令: import scala.sys.process._ import org.apache.spark.streaming.StreamingContext import org.apache.spark.streaming.scheduler.{StreamingListener, StreamingListenerBatchCompleted} class StreamingMonitor(ssc: StreamingContext) extends StreamingListener { override def onBatchCompleted(batchCompleted: StreamingListenerBatchCompleted): Unit = { try { val day = Seq("sh", "-c", "hdfs dfs -cat /tmp/apps/days.txt | head -1").!!.trim // 取日期文本文件的第一行 Seq("sh", "-c", s""" if hdfs dfs -test -e /a26_adapter_data/nation/vintype=A26/d=${day}; then hdfs dfs -mv /a26_adapter_data/nation/vintype=A26/d=${day} /daily_parquet/nation/vintype=A26 fi """).!! Seq("sh", "-c", s""" if hdfs dfs -test -e /a26_adapter_data/enterprise/vintype=A26/dt=${day}; then hdfs dfs -mv /a26_adapter_data/enterprise/vintype=A26/dt=${day} /daily_parquet/enterprise/vintype=A26 fi """).

Spark Structured Streaming 中的触发器

最近几周,我专注于 Apache Beam 项目。经过一番阅读之后,我发现了 Beam 和 Spark 结构化流之间的许多相似概念(或反之?)。相似之处之一就是触发器。 经过数月的休息后,本文介绍了另一个 Apache Spark 功能,即触发器。第一部分介绍了 Apache Spark 结构化流项目的上下文中的触发器。第二个显示了一些实现细节。最后一部分包含一些学习测试,显示了触发器如何工作。 触发器角色 Spark 触发器与 Apache Beam 中的触发器具有相似的作用,即,它们确定何时开始对累积数据进行处理。该处理的执行显然会向结果表发出新数据。关于 Apache Spark 中基于流的先前版本(基于 DStream),触发器的概念类似于批处理间隔属性。 在描述的版本(2.2.1)中,Spark 中有2种不同的触发器类型。第一种类型基于处理时间。它根据处理时间按固定间隔执行流查询。可以以任何单位时间(ms,s,min,…)定义此间隔。第二种类型称为once,因为它仅执行一次查询。执行后,查询终止,即使有新数据到达,查询也不会再次开始。 默认情况下,Apache Spark 结构化流以 0 ms的基于处理时间的触发器执行查询。这意味着 Spark 将在处理完之前的查询后尽快启动新查询。仅当存在新数据时,才会执行新的执行。 触发器内部 在内部,触发器分组在 org.apache.spark.sql.streaming.Trigger 类中,其中每种触发器类型都由一个或多个工厂方法表示。在处理时间的情况下,我们可以使用以下方法创建触发器:ProcessingTime(长间隔Ms),ProcessingTime(长间隔,TimeUnit timeUnit),ProcessingTime(持续时间间隔)或 ProcessingTime(字符串间隔)。它们全部都从 org.apache.spark.sql.streaming.ProcessingTime 对象调用幕后创建或应用方法。一次触发器由返回 OneTimeTrigger 案例对象的 Once() 表示。 如此创建的触发器实例稍后将在流查询中用作 org.apache.spark.sql.execution.streaming.StreamExecution 属性的一部分。在此实例中,触发器用于构建org.apache.spark.sql.execution.streaming.TriggerExecutor 实现的正确实例,该实现将是 ProcessingTimeExecutor 来处理基于时间的触发器,或者是 OneTimeExecutor 来执行一次触发器。 稍后,流查询由 TriggerExecutor的execute(triggerHandler: ()=> Boolean) 方法执行。此方法的实现取决于触发器类型。对于一次执行的触发器,execute 方法仅启动一次 TriggerHandler 函数。对于ProcessingTimeExecutor,execute 方法是一个长时间运行的过程(while(true)循环),其中触发器在执行查询之前等待间隔时间。 触发器还与 org.apache.spark.sql.execution.streaming.ProgressReporter#finishTrigger(hasNewData: Boolean) 方法中定义的统计信息有关。 触发器范例 下面的学习测试显示了一些触发特性: "once trigger" should "execute the query only once" in { val inputStream = new MemoryStream[Int](1, sparkSession.

Spark Structured Streaming 中的输出模式

结构化流引入了许多有关基于 DStream 的流的新概念。其中之一是输出模式。 这篇文章介绍了 Spark 2.0.0 中引入的用于处理流数据输出的输出模式。第一部分通过简短的理论部分向他们展示。第二部分介绍了它们的 API。最后一部分显示了它们在某些学习测试中的工作方式。 输出模式定义 输出模式指定将数据写入结果表的方式。在可用的输出模式中,我们可以区分: 追加(append)-这里仅将新行写入输出接收器。此模式保留给处理,不进行任何汇总,非常适合不可变的结果。 需要注意的重要一点是与水位的关系。如果在聚合中定义了水位,则它控制何时将附加数据发送到结果表。它仅在“中间”状态(=处理期间更改的状态)完成之后才发生。仅当水位的新值在给定组中的最新条目之前通过时,才会发生这种情况。例如,假设水位设置为19:00:00,并且组“A”的记录的最新值来自 19:00:05。现在,当水位传递到 19:00:06时,组“A”的结果将被发送到结果表。引擎认为不会传递任何新的事件,因此可以安全地将结果发送到输出表。 完成(compelete)-在这种模式下,所有行每次都被写入输出接收器。当流有一些更新时,将进行写入。此模式专用于具有聚合的处理。 更新(update)-与上一个相似,不同之处在于仅将更新的行写入输出接收器。 输出模式 API 输出模式定义发生在 DataStreamWriter#outputMode(outputMode: String)方法中。因此,传递的名称稍后会从 InternalOutputModes 对象转换为相应的 case 对象。 解析的实例主要在 DataStreamWriter 类中使用。它从那里传递到 StreamingQueryManager#startQuery(userSpecifiedName: Option[String], userSpecifiedCheckpointLocation: Option[String], df: DataFrame, sink: Sink, outputMode: OutputMode, useTempCheckpointLocation: Boolean = false, recoverFromCheckpointLocation: Boolean = true, trigger: Trigger = ProcessingTime(0), triggerClock: Clock = new SystemClock()).。顾名思义,此方法开始执行流查询。 在物理执行方面,我们可以在 StateStoreSaveExec 中找到输出模式的轨迹。在那里存储中间状态结果。顺便说一下,我们可以找到许多有关水位的参考,有助于消除过时的结果。如果您想了解更多信息,请转到 Apache Spark 结构化流中有关 StateStore 的文章。 输出模式示例 下表总结了可用于给定类型的处理的模式。在每个测试之后,编写一些测试以显示使用情况和未使用情况: 带水位的聚合 附加模式: 受支持-但结果仅在超过水位后才发出(=输出完成)。如果未将汇总应用于水位的列,则该查询将不起作用。 完整模式: 受支持-与更新不同,不使用水位 更新模式: 支持-水位用于删除太旧的聚合 测试:

Spark Structured Streaming 中的容错

结构化流可通过应用于状态管理,数据源和数据接收器的语义来保证端到端的一次精确交付(以微批处理模式)。关于状态的帖子更详细地介绍了状态,但还有2个其他部分尚待发现。 这篇文章分为2个主要部分。第一部分着眼于数据源,并说明了在微批量处理的情况下,一旦交付,它们将如何对端到端做出贡献。第二部分是关于接收器的部分,而最后一部分则是在一个示例中总结了所有理论要点。 数据源 就正好一次处理而言,源必须是可重播的。也就是说,它必须允许跟踪当前的读取位置,还必须从上一个故障位置开始重新处理。这两个属性都有助于在任意失败(包括 driver 或 executor)后恢复处理状态。可重播源的一个很好的例子是 Apache Kafka 或其基于云的同事 Amazon Kinesis。两者都能够跟踪当前读取的元素-带偏移量的 Kafka 和带序列号的 Kinesis。不可重播源的一个很好的例子是 org.apache.spark.sql.execution.streaming.MemoryStream,在关闭应用程序后由于数据存储在易失性内存中而无法恢复。 借助检查点机制,可以跟踪已处理的偏移量。在结构化流中,检查点文件中存储的项主要是有关当前批次中处理的偏移量的元数据。检查点存储在 checkpointLocation 选项或 spark.sql.streaming.checkpointLocation 配置条目中指定的位置。 对于微批量执行,检查点将集成在以下架构中: 检查点位置通过 StreamingQueryManager的startQuery 和 createQuery 方法从 DataStreamWriter#startQuery() 方法传递到 StreamExecution 抽象类。 StreamExecution 初始化对象 org.apache.spark.sql.execution.streaming.OffsetSeqLog。该对象表示 WAL日志,该日志记录每个已处理批次中存在的偏移量。 该字段表示处理数据源中偏移量的逻辑。当前微批处理的偏移量(我们称其为 N)始终在处理完成之前写入。该事实还假定来自先前微批处理(N-1)的所有数据均已正确写入输出接收器。在以下来自 org.apache.spark.sql.execution.streaming.MicroBatchExecution#constructNextBatch() 的代码段中进行了表示: updateStatusMessage("Writing offsets to log") reportTimeTaken("walCommit") { assert(offsetLog.add( currentBatchId, availableOffsets.toOffsetSeq(sources, offsetSeqMetadata)), s"Concurrent update to the log. Multiple streaming jobs detected for $currentBatchId") logInfo(s"Committed offsets for batch $currentBatchId. " + s"Metadata ${offsetSeqMetadata.toString}") // NOTE: The following code is correct because runStream() processes exactly one // batch at a time.

Spark Structured Streaming 中流之间的内连接

Apache Kafka Streams 支持流之间的连接,社区对 Apache Spark 期望相同。此功能已在最新的 2.3.0 版本中实现并发布,在此之后的几个月后,现在是讨论该功能的好时机。 该帖子由 2 个部分组成。第一个介绍了流管道中的连接的想法。下一个讨论两种受支持的类型之一-内连接。 连接和流 在流上执行连接操作非常具有挑战性,这有很多原因。最明显的是延迟。对于有界数据源,要连接的所有数据都就位,而对于基于流的数据源,数据则在不断移动。有时(通常?)移动速度不同。可能是由于技术方面的问题(例如摄入管道问题),或者仅是由于功能要求,而相关事件并非总是在相似的时间段内生成。这种功能限制之一可能是在电子商务商店中的订购过程,在该过程中,订单很难很快完成。因此,连接操作必须以某种方式管理相关但非常异步的事件的情况。 解决的另一个重要点是状态管理。由于给定事件的数据可能随时(非常晚)到达,并且存储该事件的存储空间有限,因此引擎必须弄清楚如何处理累积状态,尤其是何时丢弃它。此特定时间对应于我们不希望为给定的连接键接收任何新事件的时刻。 Apache Spark 结构化流提供了连接 2 个或更多流的功能,从而解决了 2.3.0 版本中的两个问题。流到流的连接可以通过以下时间轴来表征: 连接语义与批连接相同 找到匹配元素后立即生成输出(内连接) 水位和时间范围查询用于连接较晚的数据,并确定何时不再发生给定键的其他事件(状态丢弃) 支持不同的连接类型:内连和外连 连接可以级联,即应用于两个以上的流 但是,从 Apache Spark 2.3.1 开始,流到流连接具有几个限制: 它只能与 append 输出模式一起使用(已计划支持其他模式) 连接之前仅支持类似 map 的操作,例如我们不能在连接之前进行聚合 流的内连接 支持的第一种流到流连接是内连接。由于当没有匹配的行不发出时是严格的连接,因此连接的列不需要任何时间限制。但是,这样做很危险,因为即使没有匹配的行也可能在状态存储中保留很长时间。这是因为建议使用一种条件来告知特定键的状态应保留多长时间。下面显示了不清除状态时内连接的最简单情况: it should "output the result as soon as it arrives without watermark" in { val mainEventsStream = new MemoryStream[MainEvent](1, sparkSession.sqlContext) val joinedEventsStream = new MemoryStream[JoinedEvent](2, sparkSession.sqlContext) val stream = mainEventsStream.toDS().join(joinedEventsStream.toDS(), $"mainKey" === $"joinedKey") val query = stream.

Spark Structured Streaming 中的外连接

以前,我们在 Apache Spark 中发现了流到流的内连接,但是它们不是唯一受支持的类型。另一个是外连接,使我们可以在不匹配行的情况下连接流。 这篇文章是关于结构化流模块中的外连接的。它的第一部分介绍了有关这种连接的一些理论观点。第二篇展示了如何通过一些 Scala 示例来实现它。 当匹配是可选的 流式外连接与经典的,类似批处理的连接没有什么不同。有了它们,我们总是从一侧获得所有行,即使其中一些行在连接数据集中没有匹配项也是如此。对于 RDBMS 之类的有限数据源,此类不匹配将直接返回,其中 null 表示另一侧的行。但是,无限来源的逻辑是不同的。由于不同的特性,例如网络延迟影响或脱机设备产生事件,因此在给定时刻我们可能没有所有连接的元素。因此,我们必须能够将物理连接推迟到我们确定要连接的大多数行都将到来的那一刻。为此,我们需要将一侧的行存储在某处。并且,如果您还记得 Apache Spark 结构化流中流之间的内连接的文章中的一些注释,则 Apache Spark 为此使用状态存储。下图从鸟瞰显示了这一点: 该图像清楚地表明,与内连接水位的情况一样,行被缓冲在状态存储中。 外连接还使用水位和范围查询条件的概念来确定何时不应在给定的行中接收第二个流中的任何新匹配。 这就是为什么完全没有水位的外连接是不可能的: it should "fail without watermark and range condition on watermark in the query" in { val mainEventsStream = new MemoryStream[MainEvent](1, sparkSession.sqlContext) val joinedEventsStream = new MemoryStream[JoinedEvent](2, sparkSession.sqlContext) val mainEventsDataset = mainEventsStream.toDS().select($"mainKey", $"mainEventTime", $"mainEventTimeWatermark") .withWatermark("mainEventTimeWatermark", "2 seconds") val joinedEventsDataset = joinedEventsStream.toDS().select($"joinedKey", $"joinedEventTime", $"joinedEventTimeWatermark") .withWatermark("joinedEventTimeWatermark", "2 seconds") val stream = mainEventsDataset.

流到流的状态管理

上周,我们在 Apache Spark 结构化流中发现了 2 种流对流连接类型。如这些帖子所述,有时可能会省略状态管理逻辑(对于内连接),但通常建议减少内存压力。 Apache Spark 提出了 3 种不同的状态管理策略,这些策略将在以下各节中详细介绍。 这篇文章分为 4 部分。第一个回顾了流对流连接情况下的状态特异性。接下来的 3 个讨论 3 种状态管理策略。 状态和流连接 如 Apache Spark 结构化流中的外连接这篇文章所述,每个潜在的可连接行都缓存在状态存储中。每当找到匹配的行时,都会进行连接并发射结果。内连接类型就是这种情况。对于外连接,逻辑略有不同,由于匹配的行或由于过期状态而发出结果。过期状态表示我们不希望收到给定条目的匹配事件的时刻。希望此行为也适用于内连接,但不同之处在于其可选特性。 没有这种“过期状态”的概念,引擎将使行无限期地匹配,并且由于数据源是无界的,因此不可避免地迟早会失败。因此,Apache Spark 提供了 3 种不同的策略来管理状态过期(水位)。 状态键水位 这些策略中的第一个称为状态键水位。在以下情况下将其应用于查询: 在至少一个连接流中之一中定义了一个水位列-它可以是时间戳列或窗口列。如果仅在一侧定义水位,则 Apache Spark 能够从该水位推导另一侧的水位。 水位列在 JOIN 子句中用作相等约束 此策略的名称来自直接在 JOIN 子句条件中使用水位-因此是状态键。为了说明这一点,我们可以用下面的代码片段举几个例子: "state key watermark" should "be built from watermark used in join" in { val mainEventsStream = new MemoryStream[MainEvent](1, sparkSession.sqlContext) val joinedEventsStream = new MemoryStream[JoinedEvent](2, sparkSession.sqlContext) val mainEventsDataset = mainEventsStream.toDS().select($"mainKey", $"mainEventTime", $"mainEventTimeWatermark") .

流和流之间的连接内部研讨

在最近关于Apache Spark结构化流的3篇文章中,我们发现了流连接:内部连接,外部连接和状态管理策略。发现所有这些操作背后发生的事情是总结该系列的一个好方法。 这篇文章首先介绍了流连接过程中涉及的类。接下来是专注于与联接有关的状态管理内部的部分。文章以关于连接机制的一小段结尾。 所涉及的类 在流连接所涉及的类中,我们可以区分3个非常重要的类:SymmetricHashJoinStateManager,StreamingSymmetricHashJoinExec 和 StreamingJoinHelper。所有这些都用于流查询执行的不同阶段。 首先,查询的流式表示形式 IncrementalExecution 实例存储对该状态的引用。如果查询具有某些流间连接,则此状态在每次执行时都表示为 StreamingSymmetricHashJoinExec 的实例。该实例在每次执行中都不同,不同点是偏移量统计信息和状态水位谓词。谓词的计算公式为: def getStateWatermarkPredicates( leftAttributes: Seq[Attribute], rightAttributes: Seq[Attribute], leftKeys: Seq[Expression], rightKeys: Seq[Expression], condition: Option[Expression], eventTimeWatermark: Option[Long]): JoinStateWatermarkPredicates 此方法通过应用不同的规则来计算用于从状态存储中丢弃太晚的行的状态水位谓词。 它首先检查查询的相等性 JOIN 中涉及的所有列中是否至少有一个用水位注释标记的列。 如果是,它将自动认为必须将状态键水位策略应用于迟到的行(您可以在 Apache Spark 结构化流的外连接中了解它们)。 如果不是,则检查一个连接侧是否定义了水位列。 如果满足上述条件之一,并且优先选择前者,则使用常规的 org.apache.spark.sql.execution.streaming.WatermarkSupport#watermarkExpression(optionalWatermarkExpression: Option[Expression], optionalWatermarkMs: Option[Long]) 方法。 JOIN 子句中的相等重要性 流到流连接的实际实现仅接受相等关系作为连接键。 这意味着如果我们有2个流:stream#1(field1[int], field2[timestamp]), stream#2(field10[int], field20[timestamp]), ,则只有 field1 和 field10 之间的相等关系以及 field2 和 field20 将被视为连接键。 如果在 JOIN 的 ON 部分中表示不等式,则会将其转换为 WHERE 条件。 例如,以下查询: val mainEventsDataset = mainEventsStream.toDS().select($"mainKey", $"mainEventTime", $"mainEventTimeWatermark", window($"mainEventTimeWatermark", "3 seconds").

Spark Structured Streaming 和 Kafka 偏移量管理

前段时间,我对 Apache Spark 结构化流中 Apache Kafka 连接器的实现提出了3个有趣的问题。 我将在这篇文章中回答他们。 您可以想象,该帖子分为3个部分。 每个人都会回答一个问题。 在文章结尾,您应该更好地了解谁负责 Apache Kafka 连接器中的内容。 问题1:偏移量跟踪 第一个问题是关于 Apache Kafka 偏移量跟踪的。 谁跟踪他们,driver 或 executor? 在深入探讨该问题之前,让我们回顾一下我在分析结构化流式 Kafka 集成-Kafka源帖子中介绍的一些基础知识。 Apache Kafka 源代码首先从 driver 读取要处理的偏移量,然后将其分配给 executor 以进行实际处理。 因此,我们可以推断出,从这个角度来看,偏移量是由 driver 跟踪的。 您会注意到在 KafkaSource 类内部创建 KafkaSourceRDD 的代码中: // Calculate offset ranges val offsetRanges = topicPartitions.map { tp => // ... } // Create an RDD that reads from Kafka and get the (key, value) pair as byte arrays.

Spark Structured Streaming 中的初始化状态

不久前,Sunil 询问我是否可以像基于 DStream 的 API 一样在 Apache Spark 结构化流中加载初始状态。 由于回应并不明显,因此我决定调查并通过这篇文章分享调查结果。 文章首先简短地回忆了 Apache Spark Streaming 模块中的状态初始化。 下一节将讨论可用于在 Apache Spark 结构化流库中执行相同操作的方法。 流中的初始化状态 在基于 DStream 的库中初始化状态很简单。 您只需要创建一个基于键的 RDD 并将其传递给 StateSpec 的 initialState 方法: "streaming processing" should "start with initialized state" in { val conf = new SparkConf().setAppName("DStream initialState test").setMaster("local[*]") val streamingContext = new StreamingContext(conf, Durations.seconds(1)) streamingContext.checkpoint("/tmp/spark-initialstate-test") val dataQueue = new mutable.Queue[RDD[OneVisit]]() // A mapping function that maintains an integer state and return a UserVisit def mappingFunction(key: String, value: Option[OneVisit], state: State[UserVisit]): Option[(String, String)] = { var visitedPages = state.

Apache Spark 2.4.0 特性 - foreachBatch

当我第一次听说 foreachBatch 功能时,我以为这是结构化流模块中 foreachPartition 的实现。但是,经过一些分析,我发现我错了,因为此新功能解决了其他但也很重要的问题。您会发现更多。 在 Apache Spark 2.4.0 功能系列的这一新文章中,我将展示 foreachBatch 方法的实现。在第一部分中,我将简要介绍有关此功能的要点。我还将在其中添加有关实现的一些详细信息。在接下来的2部分中,我将展示.foreachBatch 数据接收器解决的问题。 定义 在 2.4.0 发行版之前,foreach 是我们可以放置一些自定义逻辑的单一接收器。它很容易使用,因为它看起来像包装在类中的 foreach 循环。另外,foreach 接收器非常适合连续执行,因为我们将重点放在每次一行所带来的信息上。但是,由于基于微批处理的管道的适应性通常更差一些,因为我们经常需要对整个累积的微批处理进行某些处理。 2.4.0 版本使用新的 org.apache.spark.sql.execution.streaming.sources.ForeachBatchSink 接收器解决了微批处理的这些问题。它的主要思想很简单。引擎累积在给定的微批次中处理的数据,并将其作为数据集传递到接收器。这不仅意味着您可以对整个数据应用一种逻辑,而且还可以在流传输管道中执行一些纯批处理,例如将数据写入不可流式的数据存储中。 除了具体的数据集之外,foreachBatch 消费者方法还接受一个名为 batchId 的属性。此参数包含生成数据集的微批处理的 ID。您可以使用此属性实现一次传递语义,因为默认情况下,引擎以至少一次语义运行。 最后,由于 ForeachBatchSink 解决了微批次问题,因此您不能将其与连续触发器一起使用。在 DataStreamWriter 内部进行的简单检查显示: } else if (source == "foreachBatch") { assertNotPartitioned("foreachBatch") if (trigger.isInstanceOf[ContinuousTrigger]) { throw new AnalysisException("'foreachBatch' is not supported with continuous trigger") } 您还可以看到接收器不支持分区管道(.partitionBy(...))。 如果您对引入此逻辑的任务感兴趣,可以在“另请阅读”部分中找到一个链接。 用例:不流式接收器 我已经在上一节中提到了 foreachBatch 的第一个用例。 当您要将处理后的数据保存到关系数据库或键值存储之类的不可流式接收器中时,此新接收器很有用。 为了简单起见,我将使用内存中单例键值存储: "foreachBatch" should "save the data into a key-value memory store" in { val inputStream = new MemoryStream[Int](1, sparkSession.

Spark Structured Streaming 中的持续执行

这些年来,Apache Spark 的流媒体被认为是与微批处理一起工作的。但是,版本 2.3.0 试图对此进行更改,并提出了一种称为“连续”的新执行模型。即使它仍处于实验状态,还是值得更多了解它。 这篇文章介绍了连续流处理的新实验功能。第一部分解释并与微批执行策略进行比较。下一个显示一些内部细节。在第三部分中,我们可以了解至少一次保证。最后一部分在基于比率的源示例中显示了一个简单的连续执行用例。 连续流 毕竟为什么要替换微批​​处理的 Spark 区别标记(与允许连续流处理的 Apache Flink 或 Apache Beam 不同)?答案很简单-延迟。使用微批处理时,延迟很高。在最坏的情况下,等待时间是批处理时间与任务启动时间的总和,估计为几毫秒。连续处理将数据到达与其处理之间的等待时间减少到几毫秒。延迟减少是此(仍)实验特性背后的主要动机。 从鸟瞰来看,连续查询在多个线程中执行。每个线程负责不同的分区。因此,为了保证最佳的并行性,集群中可用核心的数量必须等于要处理的分区的数量。在处理期间,这些线程将结果连续写入结果表。 从技术上讲,可以通过 .trigger(Trigger.Continuous("1 second")) 使用连续触发来启用连续处理。但必须强调的是,在当前(2.3.0)Spark 版本中,并非所有操作都暴露于此模式下。在可用的转换中,我们只能区分投影(select,映射函数)和选择(过滤器子句)。 但是,降低延迟并不是没有代价的。实际上,更快的处理将交付保证从正好一次下降到了至少一次。因此,对于处理延迟比交付保证更为重要的系统,建议执行连续执行。 ContinuousExecution 类 最初,在结构化流中,StreamExecution 包含整个执行逻辑。仅在 2.3.0 版本中,它才成为由 MicroBatchExecution 和 ContinuousExecution 扩展的抽象类。这两个类的出发点都是 org.apache.spark.sql.execution.streaming.StreamExecution#runStream(),在执行一些初始化步骤之后,该类将激活每个执行策略中实现的流查询,方法为 runActivatedStream(sparkSessionForStream: SparkSession)` 方法。 由于以下代码,ContinuousExecution 连续运行查询: do { runContinuous(sparkSessionForStream) } while (state.updateAndGet(stateUpdate) == ACTIVE) 在此 runContinuous 方法内部,发生了许多操作。第一个步骤包括将查询逻辑计划转换为一系列 ContinuousReaders。此接口定义是否可以连续方式读取给定的数据源。当前仅支持 Apache Kafka 和基于速率的源(该源以固定的Y间隔每秒生成 X(行,计数器)行,其中 X 和 Y 是配置参数)。如果某些源或操作不支持连续模式,则在启动查询之前会引发 UnsupportedOperationException。 在创建源之后,将解析起始偏移量,以确定何时开始执行给定执行的处理。如果查询是第一次启动,则偏移量自然为空。但是,再次执行查询时,将从提交日志中检索偏移量。稍后,将为每个映射到读取偏移量的数据源创建一个 StreamingDataSourceV2Relation 实例。接下来,创建数据读取器和写入器,并以与微批处理方法完全相同的方式生成执行计划。 在下一步中,引擎通过 EpochCoordinator 实例以纪元(与微批处理中的批处理类似)进行播放。这是连续处理的杰作。此 RPC 端点负责处理以下消息: 生成新的纪元ID-通过消息 IncrementAndGetEpoch,该纪元 ID 会自动增加。然后,在 runContinuous 方法中返回新值。 返回当前纪元ID-表示为 GetCurrentEpoch 实例的此 getter 消息由定期执行 EpochPollRunnable 中定义的逻辑的线程使用。在此逻辑中,引擎检索当前已知的纪元并将其添加到要处理的消息的内部队列中。在此列表下面的模式中将对此进行详细说明。 在纪元下提交分区-在给定时期内对分区的处理终止时,会将 CommitPartitionEpoch 消息发送给 EpochCoordinator 以发出信号。如果所有纪元的分区都已处理,则 ContinuousExecution 将此纪元标记为已提交。这意味着该纪元被保留在提交日志中,并且在重新处理的情况下,与之关联的偏移量被用作起始偏移量(请参见上一段)。 这些涉及纪元协调器的连续处理逻辑可以在以下简化模式中恢复:

Apache Spark 2.4.0 特性 - Watermark Configuration

有关 Apache Spark 2.4.0 功能的系列继续进行。在上周发现了桶剪枝之后,现在该切换到结构化流模块并查看其主要进展了。 这篇文章介绍了 2.4.0 版本中添加的水位配置功能。第一部分解释了它的变化。第二个给出了实现细节,其中显示了用于保证向后兼容性的策略之一。最后一部分包含说明水位配置功能的测试用例。 最小或最大水位? 2.3.0 版可以连接流。如 Apache Spark 结构化流中的流之间的内联中所述,这样做不是一件容易的事,因为联接的流可能具有不同的延迟。水位是控制哪些数据可以联接的方法之一,即使它来得很晚。在 2.4.0 发行版之前,定义了多个水位时,默认情况下使用最小值。它使我们能够以最慢的流移动,并且不会丢失太多数据: 通过 Apache Spark 2.4.0 中引入的更改,我们可以在这种情况下配置使用的水位,并采用最小或最大水位。 当然,选择最大值将导致应用程序以最快的速度向前移动,从而丢失更多数据: 水位配置实现 水位管理是通过 spark.sql.streaming.multipleWatermarkPolicy 配置条目实现的。它采用“最小”或“最大”值之一。它们中的每一个都导致相应的 MultipleWatermarkPolicy 实例的初始化:MinWatermark 和 MaxWatermark。两者都附带一种解决使用的水位值的方法。根据使用的策略,此方法仅是对 Seq 的 min 或 max 方法的调用。负责调用已配置策略的类为 WatermarkTracker。 向后兼容性由 MultipleWatermarkPolicy 条目的默认值 ‘min’ 保证。除非您明确更改此值,否则您应该能够在新的 Spark 版本上运行旧的流传输管道,而不会出现问题。甚至那些从 Apache Spark 2.4.0 之前的检查点恢复的管道。而且由于我们在谈论检查点,所以要记住的一件事很重要。从检查点还原管道后,我们无法修改水位策略。任何更改都将被忽略。 最大水位示例 为了看到这个新功能的实际效果,我们将执行2个测试用例。前者将具有最小水位,因此可以接受更多迟到数据: object FirstWatermark { var FirstKnownValue = "" } def launchDataInjection(mainEventsStream: MemoryStream[MainEvent], joinedEventsStream: MemoryStream[JoinedEvent], query: StreamingQuery): Unit = { new Thread(new Runnable() { override def run(): Unit = { val stateManagementHelper = new StateManagementHelper(mainEventsStream, joinedEventsStream) var key = 0 val processingTimeFrom1970 = 10000L // 10 sec stateManagementHelper.

Spark Structured Streaming 中的查询指标

长查询的重点之一是跟踪。知道查询的执行方式总是很重要的。在结构化流中,由于有了称为 ProgressReporter 的特殊对象,我们可以追踪此执行。 在本文中,我们将重点介绍 ProgressReporter 对象收集的指标。在第一部分中,我们将解释其生命周期以及一些实现细节。下一部分将涵盖公开的信息,而最后一部分将通过一些测试展示 ProgressReporter 的行为。 ProgressReporter 首先,让我们定义这个著名的 ProgressReporter。它是 org.apache.spark.sql.execution.streaming 中的一个 trait,由 StreamExecution 抽象类直接继承,因此由其实现间接继承:org.apache.spark.sql.execution.streaming.continuous.ContinuousExecution 和 org.apache.spark.sql.execution.streaming.MicroBatchExecution。 ProgressReporter 的作用是提供一个接口,该接口一旦实现,便可以自由用于报告有关流查询执行情况的统计信息。 ProgressReporter 定义了严格的生命周期阶段: 1、一切开始于流查询触发器(处理时间或事件时间)执行时。触发器要做的第一件事是对 ProgressReporter 的 startTrigger 方法的调用。此方法使报告程序准备好累积刚刚开始执行的统计信息。 2、稍后,根据选择的流模式(微批或连续),报告器将记录有关几个不同步骤执行情况的统计信息。下一部分将详细介绍这些步骤。为此,它使用了方法 reportTimeTaken[T](triggerDetailKey: String)(body: => T),该方法将有关执行这些步骤的度量添加到 currentDurationsMs: mutable.HashMap[String,Long]` 字段中。 3、下一步是数据处理,报告者还将收集一些统计信息。 4、将这些统计信息添加到 currentDurationMs 映射后,如果执行微批处理,则 ProgressReporter 调用 finishTrigger(hasNewData: Boolean)。此方法完成触发器的执行,并创建保存执行统计信息的对象,这些统计信息放入 progressBuffer = new mutable.Queue[StreamingQueryProgress]() 中。之后,客户端可以通过公共访问器方法直接从那里检索更新(或最后一个更新)。 在 ProgressReporter 中,我们还可以找到其他一些指标,例如: newData - 它是一个 Map[BaseStreamingSource, LogicalPlan],其中包含每个源的最新数据。 availableOffsets - 这是一个类似于 map 的 StreamProgress 对象,用于存储未提交到接收器的可用于处理的偏移量。 commitOffsets - 类似于 availableOffset。不同之处在于,它存储已处理和已提交数据的偏移量。 currentBatchId - 当前处理批次的 ID。 currentStatus - org.

Spark Structured Streaming 中的状态聚合

https://www.waitingforcode.com/apache-spark-structured-streaming/stateful-aggregations-apache-spark-structured-streaming/read 最近,我们发现了用于处理结构化流中状态聚合的状态存储的概念。但是那时我们还没有花时间在这些聚合上。按照承诺,现在将对其进行描述。 这篇文章开始于聚合与有状态聚合之间的简短比较。在本文结尾之后,直接进行了一些测试,这些测试说明了状态聚合。 聚合与有状态聚合 聚合是从多个值中计算单个值的操作。在 Apache Spark 中,这种操作的示例可以是 count 或 sum 方法。在流式应用程序上下文中,我们谈论有状态聚合,即具有这些值的状态的聚合在一段时间内逐渐增长。 在对结构化流中的输出模式,状态存储和触发器给出了解释之后,就更容易理解状态聚合的特殊性。如所告知的,输出模式不仅确定数据量,而且还决定是否可以丢弃中间状态(如果与水印一起使用的话)。然后,所有这些中间状态都保留在容错状态存储中。上一次触发器执行后,触发器会按固定的时间间隔执行状态计算,这些数据是从上一次触发器执行中累积的。以下架构显示了所有这些部分如何协同工作: 因此,为了简单起见,我们可以将状态聚合定义为结果随时间变化的聚合。 结果计算由触发器启动,并保存在状态存储中,在经过处理的数据通过水印之前(如果已定义),可以从状态存储中删除结果。 有状态聚合示例 两项测试显示了 Apache Spark 结构化流中的状态聚合: "stateful count aggregation" should "succeed after grouping by id" in { val testKey = "stateful-aggregation-count" val inputStream = new MemoryStream[(Long, String)](1, sparkSession.sqlContext) val aggregatedStream = inputStream.toDS().toDF("id", "name") .groupBy("id") .agg(count("*")) val query = aggregatedStream.writeStream.trigger(Trigger.ProcessingTime(1000)).outputMode("update") .foreach( new InMemoryStoreWriter[Row](testKey, (row) => s"${row.getAs[Long]("id")} -> ${row.getAs[Long]("count(1)")}")) .start() new Thread(new Runnable() { override def run(): Unit = { inputStream.

写入到 Elasticsearch

如果你使用 Elasticsearch 6.x 或者更高版本, 那么用 Spark Structured Streaming 写入到 Elasticsearch 就很直截了当。 不像 5.x 之前的版本那样, 为了写入到 Elasticsearch, 你必须实现一个自定义的 sink, 6.x 版本在 Spark Structured Streaming 里面开箱即用。 依赖如下: <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-spark-20_2.11</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_${scala.main}</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql_${scala.main}</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_${scala.main}</artifactId> <version>2.3.1</version> </dependency> 然后写入到 Elasticsearch 就相当简单了。 1、创建带有 Elasticsearch 配置的 SparkSession val spark = SparkSession .builder // run app locally utilizing all cores .master("local[*]") .appName(getClass.getName) .config("es.nodes", "localhost") .config("es.port", "9200") .

自定义会话窗口

Spark Structured Streaming 允许我们以一种直接了当的方式在滑动事件时间窗口上执行聚合。 Spark Structured Streaming 提供开箱即用的事件时间上的窗口操作和水位。 用例 在会话持续时间内求用户收益的总和,当流中有存款事件时,存入当前余额并关闭会话。 窗口和水位 默认情况下,窗口和水位与基于时间的窗口一起使用。在我们的用例中,这不起作用,因为当我们收到一个存放当前余额的信号时,我们想关闭会话。 结果 我们可以通过基于 mapGroupsWithState 函数实现自定义会话窗口来实现. 定义自定义会话类 case class Transaction(sessionId: String, winAmount: Double, deposit: Boolean) case class SessionTrackingValue(totalSum: Double) case class SessionUpdate(sessionId: String, currentBalance: Double, depositCurrentBalance: Boolean) 创建本地 SparkSession val spark: SparkSession = SparkSession .builder .master("local[*]") .appName(getClass.getName) .getOrCreate() 创建 socket 流 val socketStream: DataFrame = spark.readStream // socket as stream input .format("socket") // connect to socket port localhost:9999 waiting for incoming stream .

Apache Spark 2.4.0 特性 - bucket pruning

https://www.waitingforcode.com/apache-spark-sql/apache-spark-2.4.0-features-bucket-pruning/read 桶定义 均衡的分区让我们更快地处理数据。例如,我们可以收集物联网事件,并把它们按照日期分区并存储在树一样的结构中: /events/2018/10/29 /events/2018/10/30 /events/2018/10/31 如果我们想按物联网设备号划分相同的数据,我们该怎么办? 从技术上讲,这是可行的,但从概念上讲,它可能不如基于日期的分区有效。 设备密钥是具有非常高的基数(可能的唯一值的数量)的值,我们最终将得到一棵具有数百或数千个子目录的树。 对于不是分区的最佳选择的值问题,解决方案之一是桶存储,也称为群集。 桶是“分区内的分区”。 区别在于桶的数量是固定的。 在大多数情况下,这些值会使用基于哈希的简单策略分配给存储桶。 您可以在下面找到 Apache Spark 中存储桶的示例: "Spark" should "create buckets in partitions for orders Dataset" in { val tableName = s"orders${System.currentTimeMillis()}" val orders = Seq((1L, "user1"), (2L, "user2"), (3L, "user3"), (4L, "user1")).toDF("order_id", "user_id") orders.write.mode(SaveMode.Overwrite).bucketBy(2, "user_id").saveAsTable(tableName) val metadata = TestedSparkSession.sessionState.catalog.getTableMetadata(TableIdentifier(tableName)) metadata.bucketSpec shouldBe defined metadata.bucketSpec.get.numBuckets shouldEqual 2 metadata.bucketSpec.get.bucketColumnNames(0) shouldEqual "user_id" } 在分布式数据处理框架中,这种方法往往有助于避免洗牌阶段。例如,当桶用于2个数据集与 Spark SQL 中排序合并连接时,因为这两个数据集已经可以位于同一个分区中, 因此洗牌可能没有必要。当然,这两个数据集必须具有相同的分区数并使用哈希分区算法。 桶剪枝实现 在 Apache Spark 2.4.0 之前,当桶列之一参与查询时,Spark 引擎并没有作出任何优化。毕竟,由于桶存储是确定性的,引擎只能读取桶文件存储筛选值。

使用 Spark Structured Streaming 中的 mapGroupsWithState 进行状态转换

mapGroupsWithState 解释 mapGroupsWithState(timeoutConf: GroupStateTimeout)(func: (K, Iterator[V], GroupState[S]) => U) 是一个应用到数据组上的转换。由于它要求数据分组, 所以可能会引入 shuffling 的风险。通过调用 func 参数来计算 state。这个转换可以用在有界数据源和无界数据源上。对于有界数据源, 最终状态会立即被计算出来, 对于无界数据源, 每次触发处理后, 状态都可能会改变。 mapGroupsWithState 的参数 timeoutConf: GroupStateTimeout 这个参数负责处理超时。 mapGroupsWithState 转换接收 3 个不同的超时值: NoTimeout, ProcessingTimeTimeout 和 EventTimeTimeout(只在设置了水位时才有效)。状态过期时间定义在作为 func 参数值的函数里面, 它要么是 org.apache.spark.sql.streaming.GroupState#setTimeoutDuration (processing time), 要么是 org.apache.spark.sql.streaming.GroupState#setTimeoutTimestamp (event time)。 每次至少有一条记录被发送到组里时, 就更新超时时间。状态只有在给定组在指定阈值时间内未接收到任何数据时才被认为过期。 超时时间是在指定 GroupState 对象里面定义的, 所以根据处理的组指定不同的超时配置也是完全可行的。 func: (K, Iterator[V], GroupState[S]) => U - 该函数定义如何处理组里的值以生成状态。 如你所见, 它完全是类型无关的, 例如, 值([V]) 和状态 [S] 都不影响所返回的数据集([U])的类型。 当两个条件之一满足时, 该函数就会被调用: 要么组里有要处理的新值, 要么状态已经过期。在状态过期这种情况下, 会使用一个空列表的值作为第二个参数来调用该函数。 GroupState 另一个重要的与状态管理有关的对象是 org.

给 for 添加 else 分支

标准 Raku 中, for 关键字是没有 else 分支的, 但是 Raku 可以实现 for...else 方言, 新建一个模块, 命名为 Slang::ForElse, 其目录结构如下: Slang/ForElse.pm6 其中 ForElse.pm6 的内容如下: role ForElse::Grammar { rule statement_control:sym<for> { <sym><.kok> {} <.vetPerl5Syntax> <xblock(2)> {} [ 'else' <elseblock=pblock> ]? } rule vetPerl5Syntax { [ <?before 'my'? '$'\w+\s+'(' > <.typed_panic: 'X::Syntax::P5'> ]? [ <?before '(' <.EXPR>? ';' <.EXPR>? ';' <.EXPR>? ')' > <.obs('C-style "for (;;)" loop', '"loop (;;)"')> ]? } } role ForElse::Actions { use nqp; use QAST:from<NQP>; sub lookup(Mu \match, \key) { nqp::atkey( nqp::findmethod(match, 'hash')(match), key ).

chopping-substrings

第18周挑战赛的第一个任务是在一组字符串中找出最长的共同子串,即给定一组字符串如 “ABABC”、“BABCA “和 “ABCBA”,打印出 “ABC”。 “最佳实践"解决方案是一种被称为后缀树的技术,它需要一些中等复杂的编码。然而,我们可以使用一种简单得多的方法,对长达几十万字符的字符串获得非常合理的性能。 让我们从一个非常简洁,但次优的解决方案开始。如果我们有一个每个字符串的所有可能的子串的列表: ABABC: ABABC, ABAB, BABC, ABA, BAB, ABC, AB, BA, BC, A, B, C BABCA: BABCA, BABC, ABCA, BAB, ABC, BCA, BA, AB, BC, CA, B, A, C ABCBA: ABCBA, ABCB, BCBA, ABC, BCB, CBA, AB, BC, CB, BA, A, B, C …那么我们可以把每个列表当作一个集合,取这些集合的交集: ABC, AB, BA, BC, A, B, C …然后只需找到最长的元素: ABC 当然,有时所有可能的子串的交集会有一个以上的最长元素: SHAMELESSLY NAMELESS LAMENESS …所以我们需要确保我们的算法能够找到所有的子串。 鉴于 Raku 语言中内置了集合和集合操作,这里唯一的挑战就是为每个字符串生成所有可能的子串的完整集合。而且,鉴于 Raku 有非常复杂的正则表达式匹配,这也不是什么真正的挑战。 在普通的 regex 匹配中:

beauty-is-best

狂热的,贪婪的本性是太少 20 年前,我首次涉足 Perl 编程的工作之一是使用一种纯文本工具,分析其结构并为给定的线宽整齐地设置其格式的工具。 这是一个中等复杂的换行应用程序,我每天都使用它来整理电子邮件通信,软件文档和博客条目。 因此,第 19 届 Perl 每周挑战赛的第二项任务-实现"贪婪的"换行算法-在许多方面对我来说是一个老朋友。 贪婪的换行只是将输入文本中的每个单词都添加到输出的当前行中,除非这样做会导致输出行超出所需的最大行宽,在这种情况下,它会在该点断开行并继续填充第二行,等等。 因此,一个 45 列的贪婪包装的段落如下所示: It is a truth universally acknowledged, that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters.

with friends like these

http://blogs.perl.org/users/damian_conway/2019/08/with-friends-like-these.html C-o-rr-a-ll-i-n-g d-i-tt-o-e-d l-e-tt-e-r-s 我本打算本周重点关注第20周挑战的第一个任务……但我能说什么呢?这个任务是把一个在命令行中指定的字符串分解成相同的字符: "toolless" → t oo ll e ss "subbookkeeper" → s u bb oo kk ee p e r "committee" → c o mm i tt ee 但是这在 Raku 中就是小菜一碟: use v6.d; sub MAIN (\str) { .say for str.comb: /(.) $0*/ } 或者使用更优雅的方式: .say for $str.comb: /\w+ % <same>/ 而在 Perl 中也几乎一样简单: use v5.30; my $str = $ARGV[0] // die "Usage:\n $0 <str>\n"; say $& while $str =~ /(.

itch-scratch

在我过去写的一些博客中, 我经常遇到 Raku 不能如我所愿的情况。这是件小事儿, 但是像许多其它的小事儿一样, 它不经常发生但确一直刺激到我。 在我之前写的东西中, 我使用了一些 Raku 代码完美地阐述了这一点。我需要为 @values 数组中的每个值创建一个对象, 如果 @values 为空, 则创建一个特殊的对象: for @values Z $label,"",* -> ($value, $label) { Result.new: desc => "$label ($param.name() = $value)", value => timed { $block($value) }, check => { last if .timing > TIMEOUT } } if !@values { Result.new: desc => $label, value => timed { $block(Empty) } } 几乎在同一时间, 在其它我所写的代码中(不在博客里的), 我需要完全相同的结构…处理数组中的每个元素, 如果数组中没有元素, 则做一些不同的处理: for @errors -> $error { note $error if DEBUG; LAST die X::CompilationFailed.

计算微积分常数

𝑒 is for economics 第 21 周挑战的第一个任务是一个非常古老的任务:找出(最终被称为)欧拉数。 故事要追溯到 1683 年, 雅各布·伯努利(Jacob Bernoulli),以及他对高利贷数学的研究。例如,假设你提供一年期的 1000 美元贷款, 利息为 100%, 按年还款。很显然,到了年底, 签收客户要把 1000 美元还给你,再加上($1000×100%)的利息……所以你现在有 2000 美元。我能说什么呢?甜言蜜语! 但是, 你又会想: 假设每 6 个月收取一次利息呢? 在这种情况下, 6 个月后, 他们已经欠你 500 美元的利息($1000 x 100% × 6∕12), 你可以立即开始收取利息! 所以在最后 6 个月后, 他们现在已经欠你原来的 1000 美元加上前 6 个月的利息, 加上后 6 个月的利息, 再加上前 6 个月的利息上的后 6 个月的利息: $1000 + ($1000 × 50%) + ($1000 × 50%) + ($1000 × 50% × 50%)。 也就是 2250 美元。

six slices of pie

第 15 周挑战赛的第一个任务是找到甜品的最佳排队位置。 这是对创造越野车鞭子或牛仔电影或实体书店或碳基能源的老难题的重新表述:什么时候才是选择在一个不断缩小的馅饼中获得一块不断增加的最佳时机? 在这个任务中,馅饼足够大,可以容纳100个人,但排在第一位的人只能得到相当于其中1%的一块。第二个人得到剩下的2%,第三个人得到之后剩下的3%,等等,等等,直到第一百个人得到前面99个人分到自己那份后剩下的100%的碎屑。换句话说,我们要"让苹果派再次伟大!" 实际的任务是确定哪一个是队伍中的最佳位置。是早早地进入,得到一小块还算大的馅饼呢?还是等待,最终获得一块更大的剩饼?让我们用六种不同的方式来回答这个问题。 必需的切片 我的开发者生涯是以C语言程序员的身份开始的,我仍然觉得以命令式模式来处理像这样的小问题是很自然的。所以,我第一次尝试回答这个问题(幸好是用 Raku 而不是C语言)的时候是这样的。 # Start with a full pie, and no idea who gets the most... my $pie = 1.00; # i.e. 100% my $max-share = 0.00; # i.e. 0% my $max-index; # For everyone in line... for 1..100 -> $N { # Take N% of what’s left as the next share... my $share = $pie * $N/100; $pie -= $share; # Remember it if it’s maximal.

在 IDEA 中导入外部库

有一个解析报文的 Java 程序, 里面封装好了可用的函数, 用来解析车辆上的原始报文, kafka 数据如下所示(rawData 字段为报文)。我们用它提供的 libs 来解析该报文。 {"keyId":"XXXXXXXXXXXXX","sampleTime":"1563348447814","msgType":"gbdata","dataList":{"msgType":"gbdata","vehicleModelCode":"1xd9","vehicleSeries":"A76","vin":"LLLLLLLLLLLLLL","rawData":"131302fe4c4e414132414131354b355331303236330101271307110f1b190102010100000000546d0eae26c063010f03e80000050006c3a1fd015f5e080601431018010c100801184a010344070000000000000000000801010eae26c0005c00015c100f101310111010101110101014100b10121010100f1008100e100c100c1010101210111013100e100c1010101310101015101310131015101410141013100e100c100e100d1010100f10131013101410131015100c100d100d100f100e100e101210161015101510431016101710131016101510151016100f101310131016101210101018101010151014101710141014101310131017100e10101013100e100f10111013101410141017101410181016101510161017090101002048474445454545454546444545454545454645464545494a4949464849484849bb","sampleTime":"1563348447814"}} 要在新的项目中使用它们的库, 需要做的是: 在 src 同级目录下, 创建一个 libs 目录, 把 xx.jar 复制到 libs 目录里面 在 src 同级目录下, 创建一个 out 目录, 用于 build Artifact 时存放最后的 jar 包文件 File -> Project Structure -> Artifacts -> + -> Extracted Directory -> 选择外部库.jar File -> Project Structure -> Artifacts -> 勾选 include in projects. (重要) File -> Project Structure -> Libraries -> + -> 选择 libs 目录和外部库.

NQP 练习题

练习 1 本练习将让您在 NQP 中执行一些简单的任务,以熟悉基本语法。 Hello, world 最简单的事情: say('Hello, world'); 现在把它弄错: say 'Hello, world'; 请注意你将如何大声地抱怨。 变量 将值绑定到标量。 使用 say 来输出它. 尝试使用赋值代替绑定。 观察它的失败。 声明一个数组。 使用索引, 例如 @a[0], 去绑定并访问元素。 注意在 Raku 中, 符号是不变的; 使用 $a[0] 则会抱怨未声明的 $a! 尝试绑定一个数组字面量, 例如 [1,2,3] 到一个 @arr 变量上。现在把该数组字面量绑定到一个 $scalar 变量上。注意观察索引是怎样在这两种结构上都工作良好的。 声明一个哈希变量, %h. 尝试使用字面值语法索引 (%h<key>) 和花括号语法索引 (%h{'key'})。 循环 使用 for 来迭代数组, 一次迭代一个元素, 先使用 $_ 迭代, 然后使用 pointy block (for @a -> $val { ... }) 迭代。 你可以一次迭代两个元素吗? 试试看! 迭代散列变量, 打印出该散列每个 pair 对儿的 .

Raku 文档翻译

Raku 文档之 - 语言 起步 number title subtitle progress 1 简介 使用 Raku 官方文档 ✓ 2 通过例子学 Raku 之 P6-101 Raku程序的基本介绍示例 ✓ 迁移指南 number title subtitle progress 3 从 Perl 5 到 Raku 指南 - 简介 我怎么做我以前做的事情? (简而言之,Raku) ✓ 4 从 Perl 5 到 Raku 指南 - 概览 我怎么做我以前做的事情? ✓ 5 从 Perl 5 到 Raku 指南 - 函数 Perl 5 到 Raku 中的内置函数 ✓ 6 从 Perl 5 到 Raku 指南 - 运算符 Perl 5 到 Raku 中的运算符:等价物和变体 ✓ 7 从 Perl 5 到 Raku 指南 - 语法 Perl 5 和 Raku 之间的语法差异 ✓ 8 从 Perl 5 到 Raku 指南 - 特殊变量 Perl 5 Raku 中特殊变量的比较 ✓ 9 从 Haskell 到 Raku - 简介 从 Haskell 学习 Raku,简而言之:我已经知道了什么? ✓ 10 从 Javascript (Node.

Spark 中的 UDAF 函数

上午介绍了 Spark 的用户自定义聚合函数, 其实是官网的例子, 它使用 Spark SQL。下午我们来看一个稍微复杂点的例子, 这次我们在 agg 中使用自定义的聚合函数。 我们的程序读取 JSON数据, 其中 vin 是车架号; timestamp 是信号发生时间, 信号每隔 1 秒发生一次; PtcConsmp 是一个 Long 类型的值。 {"vin":"KLSAP5S09K1214720","timestamp":1543766400000,"PtcConsmp":5000} {"vin":"KLSAP5S09K1214720","timestamp":1543766401000,"PtcConsmp":5000} {"vin":"KLSAP5S09K1214720","timestamp":1543766402000,"PtcConsmp":5000} {"vin":"KLSAP5S09K1214720","timestamp":1543766403000,"PtcConsmp":5000} {"vin":"KLSAP5S09K1214720","timestamp":1543766405000,"PtcConsmp":5000} {"vin":"KLSAP5S09K1214720","timestamp":1543766406000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766407000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766408000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766409000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766410000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766411000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766412000,"PtcConsmp":850} {"vin":"KLSAP5S09K1214720","timestamp":1543766424000,"PtcConsmp":1650} {"vin":"KLSAP5S09K1214720","timestamp":1543766425000,"PtcConsmp":1650} {"vin":"KLSAP5S09K1214720","timestamp":1543766426000,"PtcConsmp":1650} ... 我们规定: 如果一段连续的时间内, PtcConsmp 的值发生变化, 则记为一次 PTC 切换。 如果发生一次时间缺失, 也记为一次 PTC 切换。 计算每个 vin 的 PtcConsmp 切换次数。 我们借助 Spark 的 UDAF 函数来计算这个需求。首先我们定义一个 PTCPwrAgg 对象, 它扩展自 UserDefinedAggregateFunction。所以 PTCPwrAgg 里必须实现如下几个函数: inputSchema 输入数据 bufferSchema 中间状态 dataType 返回值类型 deterministic true initialize 初始化缓冲区 update 更新缓冲区 merge 合并缓冲区 evaluate 计算最终结果 package ohmysummer import org.

用户自定义聚合函数

聚合 内置的 DataFrame 函数提供常见的聚合,如 count(),countDistinct(),avg(),max(),min() 等。虽然这些函数是为 DataFrame 设计的,但 Spark SQL 也有类型安全的版本其中一些在 Scala 和 Java 中使用强类型数据集。此外,用户不限于预定义的聚合函数,并且可以创建自己的聚合函数。 无类型用户自定义函数 用户必须扩展 UserDefinedAggregateFunction 抽象类以实现自定义无类型聚合函数。例如,用户定义的平均值可能看起来像这样: import org.apache.spark.sql.{Row, SparkSession} import org.apache.spark.sql.expressions.MutableAggregationBuffer import org.apache.spark.sql.expressions.UserDefinedAggregateFunction import org.apache.spark.sql.types._ object MyAverage extends UserDefinedAggregateFunction { // 该聚合函数输入参数的数据类型 def inputSchema: StructType = StructType(StructField("inputColumn", LongType) :: Nil) // 聚合缓冲区中值的数据类型 def bufferSchema: StructType = { StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil) } // 返回值的数据类型 def dataType: DataType = DoubleType // 对于相同输入该函数是否始终返回相同输出 def deterministic: Boolean = true // 初始化给定的聚合缓冲区。缓冲区本身是一个 "Row",除了诸如在索引处检索值(例如,get(),getBoolean()) 标准方法之外,提供了更新其值的机会。 // 请注意,缓冲区内的数组和 map 映射仍然是不可变的 def initialize(buffer: MutableAggregationBuffer): Unit = { buffer(0) = 0L // sum 值 buffer(1) = 0L // 元素个数 } // 使用来自 `input` 的新输入数据更新给定的聚合缓冲区 `buffer` def update(buffer: MutableAggregationBuffer, input: Row): Unit = { if (!

Google Guice - 依赖注入

动机 将所有内容连接在一起是应用程序开发的一个单调乏味的部分。有几种方法可以将数据、服务和表示类相互连接起来。为了对比这些方法,我们将为披萨订购网站编写支付代码: public interface BillingService { /** * Attempts to charge the order to the credit card. Both successful and * failed transactions will be recorded. * * @return a receipt of the transaction. If the charge was successful, the * receipt will be successful. Otherwise, the receipt will contain a * decline note describing why the charge failed. */ Receipt chargeOrder(PizzaOrder order, CreditCard creditCard); } 伴随着实现,我们会给我们的代码写单元测试。在测试中,我们需要一个 FakeCreditCardProcessor 来避免支付到真实的信用卡。 直接构造函数调用 以下是我们刚刚刷新信用卡处理器和事务记录器时代码的样子:

Raku 中的快速排序

今天,我们来看看另一个,也许是最着名的数据排序方法,快速排序。 该算法要求您选择所谓的枢轴,其中一个元素来自数据,并将其余部分分成两部分:小于枢轴的元素,以及大于或等于枢轴的元素。然后再次递归地对每个部分进行排序。在每次迭代中,部件变得越来越小,直到子列表是一个或甚至零个元素的平凡数据集。 一个单独的理论是如何选择正确的枢轴。有几种方法,例如,从列表中间取值,或取第一项,或最后一项,或随机项。还有更复杂的方法,您可以调整它以在您的数据集类型上实现最佳性能。 为简单起见,让我们选择第一个元素作为轴点,这是代码 sub quick-sort(@data) { return @data if @data.elems <= 1; my $pivot = @data[0]; my @left; my @right; for @data[1..*] -> $x { if $x < $pivot { push @left, $x; } else { push @right, $x; } } return flat(quick-sort(@left), $pivot, quick-sort(@right)); } my @data = 4, 5, 7, 1, 46, 78, 2, 2, 1, 9, 10; @data = quick-sort @data; say @data; 与之前的冒泡排序示例不同,此程序不会就地排序,而是返回新列表。 现在是时候将代码转换为更具 Raku 风格代码的时候了。

Raku 中的选择排序

sub find-smallest(@arr) { my $smallest = @arr[0]; my $smallest_index = 0; for @arr.kv -> $k, $v { if $v < $smallest { $smallest = $v; # 存储最小的值 $smallest_index = $k; # 存储最小元素的索引 } } return $smallest_index } sub selection-sort(@arr) { my @new-arr = []; for @arr.clone.kv -> $k, $v { # 对数组进行排序 my $smallest_index = find-smallest(@arr); @new-arr.append(@arr.splice($smallest_index,1).head); # 找出数组中最小的元素,并将其加入到新数组中 } return @new-arr } say selection-sort([5, 3, 6, 2, 10]);

使用 Termux 在手机中运行 Raku

之前介绍过在安卓手机上运行 Raku, 那是一种很费劲的折腾, 你得下载一个完整的 Ubuntu 系统, 然后自行编译安装 Raku。虽然现在 Docker 很方便, 但是我不想用它。 有了 Termux 就发现下面这个操作更清凉, 更环保。 我们到 https://github.com/its-pointless/gcc_termux 看到里面的 readme 说得语焉不详, 对于刚接触 Termux 的人来说有点毫无头绪, 但是作者依然提供了一个傻瓜式一键设置脚本, 我拿来改造下: #!/data/data/com.termux/files/usr/bin/sh # Get some needed tools. coreutils for mkdir command, gnugp for the signing key, and apt-transport-https to actually connect to the repo # and clang and make for installing Zef apt-get update apt-get --assume-yes upgrade apt-get --assume-yes install coreutils gnupg wget clang make git # Make the sources.

删除 git 中所有历史提交

把旧项目改一改, 提交到 git 仓库中, 这是很多程序员的学习手段。但是, 旧项目的 history commit 中往往有很多敏感信息, 例如数据库登录信息。我就比较奇特了, 我误 commit 了一个大文件(jar 包, 囧)。 历史提交信息正好我也不想保留了, 我想把改好的这个版本当作一次完整的原子提交。即当作一个新仓库了。做法如下: 1)切换到一个新分支 git checkout --orphan latest_branch 缓存所有文件(.gitignore中的除外) git add -A 提交跟踪后的文件 git commit -am "init commit" 删除 master 分支 git branch -D master 重命名当前分支为 master git branch -m master 提交到远程 master 分支 git push -f origin master

Rust 中的所有权

变量绑定 在 Rust 中, 我们将一些值绑定到一个名字上, 称之为变量绑定。使用 let 来声明一个绑定: fn main() { let x = 5; } 所有权 Rust 中的变量绑定有一个属性: 变量拥有它们所绑定的值的所有权。当绑定超出作用域, 变量绑定的资源就会被释放。例如: fn foo() { let v = vec![1,2,3,4,5]; } 当 v 进入作用域, 会在栈上创建一个新的 vector, 并在堆上为该 vector 的 5 个元素分配空间。当 v 在 foo() 的末尾离开作用域时, Rust 会释放该 vector 占用的资源。 移动语义 在 Rust 中, 一个值一次只能有一个所有者。 let v = vec![1,2,3,4,5]; // ① 声明一个绑定 let s = v; // ② v moved to s println!("v[0] is: {}", v[0]); // ③ error: use of moved value: `v` 除了 ② 里面发生所有权转移(move)外, 另外一种常见的的 move 语义发生在将变量作为参数传递给函数时:

使用 Spark Structured Streaming 重构项目

现在这个项目唯一的乐趣就是使用我学到的新技术重构之前的项目。今天完成了数据适配, 该项目目录结构如下: ├── EnApplication.scala ├── NaApplication.scala ├── conf │ ├── HbaseConfiguration.scala │ ├── KafkaConfiguration.scala │ ├── MysqlConfiguration.scala │ └── SparkConfiguration.scala ├── core │ ├── Adapter.scala │ └── impl │ ├── AdapterImpl.scala │ └── EnAdapterImpl.scala ├── model │ ├── EnSourceData.scala │ └── SourceData.scala ├── module │ ├── EnMainModule.scala │ └── MainModule.scala ├── pipeline │ ├── NaSchema.scala │ ├── NaSparkSession.scala │ └── SourceDataFrame.scala └── util └── XsUdfs.scala 与之前的项目一样, 依旧使用了依赖注入: Kafka 配置 class KafkaConfiguration extends Serializable{ private val config: Config = ConfigFactory.

Flink-Kafka 内置的反序列化 schemas

SimpleStringSchema SimpleStringSchema 把 message 反序列化为字符串。如果你的 message 有键, 则忽略键。 val myConsumer = new FlinkKafkaConsumer( kafkaConf.topic, new SimpleStringSchema(), properties ) JSONDeserializationSchema JSONDeserializationSchema 使用 jackson 将 message 反序列化为 json 格式的消息并返回 com.fasterxml.jackson.databind.node.ObjectNode 对象流。你可以使用 .get("property") 方法访问字段。再一次, 键被忽略。 val myConsumer = new FlinkKafkaConsumer( kafkaConf.topic, new JSONDeserializationSchema(), properties ) JSONKeyValueDeserializationSchema JSONKeyValueDeserializationSchema 与前一个非常类似,但处理带有json编码的键和值的消息。 val myConsumer = new FlinkKafkaConsumer( kafkaConf.topic, new JSONKeyValueDeserializationSchema(true), properties ) 返回的 ObjectNode 包含如下字段: key:键中存在的所有字段 value:所有的 message 字段 metadata(可选):暴露消息的 offset, partition 和 topic (将 true 传递给构造函数以获取元数据) 例如:

Kafka KSQL 用例

有一次, 在威马项目, 项目经理说, xxx, 你能不能给我查一下 kafka 中, 这个车某一时间段的数据? 我说不好查, 查询 Kafka 中某一条记录, 需要 Consumer 程序来消费。虽然 kt 之类的 Kafka 命令行工具可以查询 Kafka 中数据. 但是能力有限, 它只能从某个 offset 开始查询, 满足不了我们的过滤条件。 幸运的是, ksql 可以让我们像查询 sql 一样来查询 kafka, 毕竟写 sql, 谁不会呢?于是我们到它的官网, 看到 ksql 这个产品的宣传是 Streaming SQL for Apache Kafka。 毕竟底层用的是 Kafka-Streams, 所以 ksql 支持流式查询。我们下载最新的 ksql。我们将程序解压到 ~/opt/confluent-5.0.0 启动 zk cd ~/opt/confluent-5.0.0 bin/zookeeper-server-start -daemon etc/kafka/zookeeper.properties 启动 kafka cd ~/opt/confluent-5.0.0 bin/kafka-server-start -daemon etc/kafka/server.properties 创建 topic 和 data confluent 自带了一个 ksql-datagen 工具,可以创建和产生相关的 topic 和数据,

KSQL Stream Processing CookBook

KSQL Stream Processing CookBook 数据过滤 KSQL 流式查询持续运行。您可以使用 KSQL CREATE STREAM AS 语法将流式查询输出持久保存到 Kafka topic。 KSQL 从一个 Kafka topic 中实时获取事件,转换它们并将它们连续写入另一个 topic。 此示例显示如何从入站 topic 过滤数据流数据以排除源自特定地理位置的记录。 指示 在此示例中,使用名为 purchase 的源事件流。 { "order_id": 1, "customer_name": "Maryanna Andryszczak", "date_of_birth": "1922-06-06T02:21:59Z", "product": "Nut - Walnut, Pieces", "order_total_usd": "1.65", "town": "Portland", "country": "United States" } 1、在 KSQL 中, 注册 purchases 流: ksql> CREATE STREAM purchases \ (order_id INT, customer_name VARCHAR, date_of_birth VARCHAR, \ product VARCHAR, order_total_usd VARCHAR, town VARCHAR, country VARCHAR) \ WITH (KAFKA_TOPIC='purchases', VALUE_FORMAT='JSON'); 2、检查收到的前几条消息:

在 IDEA 中运行 Flink 程序

我们需要把 Flink 安装路径中的 opt 和 lib 添加到项目的 Libraries 中。 依次点击 File -> Project Structure -> Project Settings -> Libraries, 然后点击右侧栏中的加号 ➕, 选择 Java, 在弹出的对话框中选择 Flink 安装路径。 我的 Flink 安装在 /usr/local/Cellar/apache-flink/1.8.0/libexec, 默认是隐藏的, 所以要点击 Macintosh HD, 同时按住 Command + Shift + . 即可显示出隐藏目录, 分别添加 opt 目录和 lib 目录到相应的模块的 Libraries 中。这样就可以在 IDEA 中直接运行 Flink 程序了。 tree /usr/local/Cellar/apache-flink/1.8.0/libexec ├── bin │ ├── config.sh │ ├── start-cluster.sh │ ├── ... ├── conf │ ├── flink-conf.yaml │ ├── log4j-cli.

使用 Flink 重写项目

使用 Flink 重写一下之前用 Spark Streaming 做的项目, 发现 Flink 在实时处理上比 Spark Streaming 友好。 结构 目录结构跟之前基本一样: src ├── main │ ├── java │ ├── resources │ │ └── application.conf │ └── scala │ ├── dataset │ └── datastream │ ├── com │ │ └── gac │ │ └── xs6 │ │ ├── BigdataApplication.scala │ │ ├── conf │ │ │ └── KafkaConfiguration.scala │ │ ├── core │ │ │ ├── Adapter.scala │ │ │ └── impl │ │ │ ├── AdapterImpl.

在 Raku 中制作命令行工具

准备 zef install App::Mi6 # 安装模块骨架工具 mi6 new FakeStreaming # 创建一个模块骨架 使用 tree 看一下目录结构: ├── Changes ├── LICENSE ├── META6.json ├── README.md ├── bin │ └── fake-stream ├── dist.ini ├── lib │ └── FakeStreaming.pm6 └── t └── 01-basic.t 我们主要看 META6.json, bin 目录, lib 目录。 META6.json { "authors" : [ "" ], "build-depends" : [ ], "depends" : ["PKafka"], "description" : "a commandline tool to simulate socket streraming mock and kafka producer", "license" : "Artistic-2.

使用 Flink 进行行程划分

准备 ① 启动 Flink 本地模式 /usr/local/Cellar/apache-flink/1.7.0/libexec/bin/start-cluster.sh Starting cluster. Starting standalonesession daemon on host summer. Starting taskexecutor daemon on host summer. ② 模拟实时流数据 raku fake-streaming.pl6 该脚本每隔秒打印一行数据, 每隔 15 秒暂停打印, 然后在继续每隔一秒打印一行, 然后在 sleep 15 秒, 模拟行程间隔。 sub MAIN(Str :$host = '0.0.0.0', Int :$port = 3333) { my $vin = 'LSJA0000000000091'; my $last_meter = 0; react { whenever IO::Socket::Async.listen($host, $port) -> $conn { react { my Bool:D $ignore = True; whenever Supply.interval(15).rotor(1, 1 => 1) { $ignore = !

Threading Nqp Through a Channel

categories: [“Raku”] 考虑到 nqp 比普通 Perl 6 和线程快,两者结合起来应该会给我们带来不错的速度。在上一篇文章中承诺的使用 Supply 并没有真正的帮助。emit 会阻塞,直到 Supply 的内部队列被清空。如果我们想递归处理文件,文件系统可能会在递归线程被解除阻塞后停滞。如果我们在消费者中对文件系统施加压力,我们最好使用一个能迅速填满文件路径的 Channel。 让我们先模拟一个每时每刻都会停滞的消费者,并以 $c 为 Channel。 my @files; react { whenever $c -> $path { @files.push: $path; sleep 1 if rand < 0.00001; } } 如果我们尽可能快地抽出路径,我们可以填满相当多的 RAM,并给 CPU 缓存带来很大的压力。经过一些试验和错误,我发现当有超过64个工作线程等待被放到机器线程上时,在通道上的 .send 之前 sleep 会有帮助。这些信息可以通过 Telemetry::Instrument::ThreadPool::Snap.new<gtq> 访问。 my $c = Channel.new; start { my @dirs = '/snapshots/home-2019-01-29'; while @dirs.shift -> str $dir { my Mu $dirh := nqp::opendir(nqp::unbox_s($dir)); while my str $name = nqp::nextfiledir($dirh) { next if $name eq '.

提取文本块儿的 5 种方法

假设有一段文本, =begin code 和 =end code 把文本分割为一个一个的 section, 我想提取每一个 section 之间的内容。 Grammar 来拯救! my $excerpt = q:to/END/; Here's some unimportant text. =begin code This code block is what we're after. We'll use 'ff' to get it. =end code More unimportant text. =begin code I want this line. and this line as well. HaHa =end code More unimport text. =begin code Let's to go home. =end code END Grammar #use Grammar::Tracer; #use Grammar::Debugger; grammar ExtractSection { rule TOP { ^ <section>+ %% <.

开关

toggle 字面意思是开关, 函数签名如下: method toggle(Any:D: *@conditions where .all ~~ Callable:D, Bool :$off --> Seq:D) 它接收一个数组, 数组中的每个元素都是一个 Callable。 它迭代调用者, 产生一个 Seq, 根据开关是开还是关把接收到的值填充到结果中, 开关是开还是关取决于 @conditions 数组里面调用 Callables 的结果: say (1..15).toggle(* < 5, * > 10, * < 15); # OUTPUT: «(1 2 3 4 11 12 13 14)␤» say (1..15).toggle(:off, * > 2, * < 5, * > 10, * < 15); # OUTPUT: «(3 4 11 12 13 14)␤» 想象一下开关是打开还是关闭(True或False),如果它打开则产生值。默认情况下,该开关的初始状态处于 “on” 位置,除非 :$off 设置为 true 值,在这种情况下,初始状态将为"off"。

使用 Raku Grammars 解压缩 Zelda 3 GFX

grammars Grammar 结合 actions 可以解析字符串并从字符串中产生一些东西。如果说任何压缩后的数据遵循可能由相应的解压缩算法“解析”的结构,那就不足为奇了。 那么为什么不使用 Raku Grammar 进行这类工作呢?特别是我熟悉的压缩。 深入研究任天堂压缩 任天堂在他们的 SNES 游戏中使用了相同的基本压缩格式,并根据游戏使用了一些变体。这非常简单,在 ~2Mhz 的 SNES CPU 上很容易。 它是这样的: <a byte that contains an header> the header is split into 2 parts : the 3 left most bits form a command number The 5 other bits code the lenght associated with the command <An arbitrary number of bytes associated with the command> <repeat until the header is \xFF> 命令取决于游戏,但是 Zelda 3 的命令非常简单

使用rotor操作列表

假设有一个 0..100 的数字区间, 需要分成下面这样的区间: 0..5 5..10 10..15 15..20 20..25 25..30 30..35 35..40 40..45 45..50 50..55 55..60 60..65 65..70 70..75 75..80 80..85 85..90 90..95 95..100 rotor 来拯救: .minmax.say for (0..100).rotor(6 => -1) 我想偷懒用程序生成下面的代码: if (current >=0 && current < 5) "0" if (current >=5 && current < 10) "1" if (current >=10 && current < 15) "2" if (current >=15 && current < 20) "3" if (current >=20 && current < 25) "4" if (current >=25 && current < 30) "5" if (current >=30 && current < 35) "6" if (current >=35 && current < 40) "7" if (current >=40 && current < 45) "8" if (current >=45 && current < 50) "9" if (current >=50 && current < 55) "10" if (current >=55 && current < 60) "11" if (current >=60 && current < 65) "12" if (current >=65 && current < 70) "13" if (current >=70 && current < 75) "14" if (current >=75 && current < 80) "15" if (current >=80 && current < 85) "16" if (current >=85 && current < 90) "17" if (current >=90 && current < 95) "18" if (current >=95 && current < 100) "19" for (0.

使用 Spark Structured Streaming 进行行程划分

使用 Structured Spark Streaming 进行行程划分 我们有一辆车, 车上的传感器每隔 1 秒发出一条 JSON 格式的数据。每条数据包含 3 个字段: field description type unit vin 车辆唯一标识 String 辆 createTime 信号发生时间 Long 毫秒 mileage 当前里程数 Int 千米 下面一段样本数据: {'vin':'LSJA0000000000091','createTime':1546667565,'mileage':0} {'vin':'LSJA0000000000091','createTime':1546667566,'mileage':1} {'vin':'LSJA0000000000091','createTime':1546667567,'mileage':2} {'vin':'LSJA0000000000091','createTime':1546667568,'mileage':3} {'vin':'LSJA0000000000091','createTime':1546667569,'mileage':4} {'vin':'LSJA0000000000091','createTime':1546667570,'mileage':5} {'vin':'LSJA0000000000091','createTime':1546667571,'mileage':6} {'vin':'LSJA0000000000091','createTime':1546667572,'mileage':7} {'vin':'LSJA0000000000091','createTime':1546667573,'mileage':8} {'vin':'LSJA0000000000091','createTime':1546667574,'mileage':9} {'vin':'LSJA0000000000091','createTime':1546667575,'mileage':10} {'vin':'LSJA0000000000091','createTime':1546667576,'mileage':11} {'vin':'LSJA0000000000091','createTime':1546667577,'mileage':12} {'vin':'LSJA0000000000091','createTime':1546667578,'mileage':13} {'vin':'LSJA0000000000091','createTime':1546667579,'mileage':14} {'vin':'LSJA0000000000091','createTime':1546667580,'mileage':15} {'vin':'LSJA0000000000091','createTime':1546667581,'mileage':16} {'vin':'LSJA0000000000091','createTime':1546667582,'mileage':17} {'vin':'LSJA0000000000091','createTime':1546667583,'mileage':18} {'vin':'LSJA0000000000091','createTime':1546667584,'mileage':19} {'vin':'LSJA0000000000091','createTime':1546667585,'mileage':20} {'vin':'LSJA0000000000091','createTime':1546667586,'mileage':21} {'vin':'LSJA0000000000091','createTime':1546667587,'mileage':22} {'vin':'LSJA0000000000091','createTime':1546667588,'mileage':23} {'vin':'LSJA0000000000091','createTime':1546667589,'mileage':24} {'vin':'LSJA0000000000091','createTime':1546667590,'mileage':25} {'vin':'LSJA0000000000091','createTime':1546667591,'mileage':26} {'vin':'LSJA0000000000091','createTime':1546667592,'mileage':27} {'vin':'LSJA0000000000091','createTime':1546667593,'mileage':28} {'vin':'LSJA0000000000091','createTime':1546667594,'mileage':29} {'vin':'LSJA0000000000091','createTime':1546667595,'mileage':30} {'vin':'LSJA0000000000091','createTime':1546667596,'mileage':31} {'vin':'LSJA0000000000091','createTime':1546667597,'mileage':32} {'vin':'LSJA0000000000091','createTime':1546667598,'mileage':33} {'vin':'LSJA0000000000091','createTime':1546667599,'mileage':34} {'vin':'LSJA0000000000091','createTime':1546667600,'mileage':35} {'vin':'LSJA0000000000091','createTime':1546667601,'mileage':36} {'vin':'LSJA0000000000091','createTime':1546667602,'mileage':37} {'vin':'LSJA0000000000091','createTime':1546667603,'mileage':38} {'vin':'LSJA0000000000091','createTime':1546667604,'mileage':39} {'vin':'LSJA0000000000091','createTime':1546667605,'mileage':40} {'vin':'LSJA0000000000091','createTime':1546667606,'mileage':41} {'vin':'LSJA0000000000091','createTime':1546667607,'mileage':42} {'vin':'LSJA0000000000091','createTime':1546667608,'mileage':43} {'vin':'LSJA0000000000091','createTime':1546667609,'mileage':44} {'vin':'LSJA0000000000091','createTime':1546667610,'mileage':45} {'vin':'LSJA0000000000091','createTime':1546667611,'mileage':46} {'vin':'LSJA0000000000091','createTime':1546667612,'mileage':47} {'vin':'LSJA0000000000091','createTime':1546667613,'mileage':48} {'vin':'LSJA0000000000091','createTime':1546667614,'mileage':49} {'vin':'LSJA0000000000091','createTime':1546667615,'mileage':50} {'vin':'LSJA0000000000091','createTime':1546667616,'mileage':51} {'vin':'LSJA0000000000091','createTime':1546667617,'mileage':52} {'vin':'LSJA0000000000091','createTime':1546667618,'mileage':53} {'vin':'LSJA0000000000091','createTime':1546667619,'mileage':54} {'vin':'LSJA0000000000091','createTime':1546667620,'mileage':55} {'vin':'LSJA0000000000091','createTime':1546667621,'mileage':56} {'vin':'LSJA0000000000091','createTime':1546667622,'mileage':57} {'vin':'LSJA0000000000091','createTime':1546667623,'mileage':58} {'vin':'LSJA0000000000091','createTime':1546667624,'mileage':59} {'vin':'LSJA0000000000091','createTime':1546667745,'mileage':60} {'vin':'LSJA0000000000091','createTime':1546667746,'mileage':61} {'vin':'LSJA0000000000091','createTime':1546667747,'mileage':62} {'vin':'LSJA0000000000091','createTime':1546667748,'mileage':63} {'vin':'LSJA0000000000091','createTime':1546667749,'mileage':64} {'vin':'LSJA0000000000091','createTime':1546667750,'mileage':65} {'vin':'LSJA0000000000091','createTime':1546667751,'mileage':66} {'vin':'LSJA0000000000091','createTime':1546667752,'mileage':67} {'vin':'LSJA0000000000091','createTime':1546667753,'mileage':68} {'vin':'LSJA0000000000091','createTime':1546667754,'mileage':69} {'vin':'LSJA0000000000091','createTime':1546667755,'mileage':70} {'vin':'LSJA0000000000091','createTime':1546667756,'mileage':71} {'vin':'LSJA0000000000091','createTime':1546667757,'mileage':72} {'vin':'LSJA0000000000091','createTime':1546667758,'mileage':73} {'vin':'LSJA0000000000091','createTime':1546667759,'mileage':74} {'vin':'LSJA0000000000091','createTime':1546667760,'mileage':75} {'vin':'LSJA0000000000091','createTime':1546667761,'mileage':76} {'vin':'LSJA0000000000091','createTime':1546667762,'mileage':77} {'vin':'LSJA0000000000091','createTime':1546667763,'mileage':78} {'vin':'LSJA0000000000091','createTime':1546667764,'mileage':79} {'vin':'LSJA0000000000091','createTime':1546667765,'mileage':80} {'vin':'LSJA0000000000091','createTime':1546667766,'mileage':81} {'vin':'LSJA0000000000091','createTime':1546667767,'mileage':82} {'vin':'LSJA0000000000091','createTime':1546667768,'mileage':83} {'vin':'LSJA0000000000091','createTime':1546667769,'mileage':84} 开车的时候, 从一个地方到另一个地方, 会有很多行程。

润秒

2019年1月有闰秒吗?(剧透:没有) 今晚我们将在午夜时分从10倒数到0,并来到新的一年。 但是当我们倒数到0时,它会是2019年吗?如果有闰秒怎么办? 快点 - 让我们检查IETF提供的闰秒列表。 不 - 它看起来不错,2017年1月1日是最后一个,并且该列表在2019年6月28日之前是 OK 的。 两年前,情况有所不同。 在“午夜”(UTC)时,它变成了2017年。 ~ $ raku -e 'say DateTime.new("2017-01-01T00:00:00Z").year' 2017 但实际上,从晚上(和年)的11:59:59开始已经过了两秒钟! ~ $ raku -e 'say DateTime.new("2017-01-01T00:00:00Z") - DateTime.new("2016-12-31T23:59:59Z")' 2 在它变成2017年的那一刻,有一个闰秒。now返回的值表明了这一点。 虽然今晚会很好: ~ $ raku -e 'say DateTime.new("2019-01-01T00:00:00Z") - DateTime.new("2018-12-31T23:59:59Z")' 1 如果我们想要更明确,我们可以使用 posix 和 from-posix 将 DateTime 转换为 Instant 并再返回 - 后者将布尔值作为第二个参数来指示是否应该考虑闰秒。 my $date = DateTime.new("2017-01-01T00:00:00Z"); my $instant = Instant.from-posix: $date.posix, True; my $instant2 = Instant.from-posix: $date.posix; say $instant.DateTime.year; say $instant2.

使用 Spark 结构化流探索状态流

在之前的文章中,我们探讨了如何使用带DStream抽象的Sparks Streaming API进行处理状态流。今天,我想和你一起探索Spark 2.2,它在Structured Streaming API下支持状态流。在这篇文章中,我们将看到API如何成熟和发展,看看两种方法(流与结构化流)之间的差异,并了解对API进行了哪些更改。我们将通过上一篇文章中的示例,并使其适应新的API来实现。 回顾 Spark Streaming 状态管理 如果你需要在Spark中使用状态流,你必须在两个抽象之间进行选择(直到Spark 2.2)。updateStateByKey 和 mapWithState后者或多或少是前者的改进(API和性能方面)版本(具有一些不同的语义)。为了利用微批次之间的状态,你提供了一个StateSpec函数,mapWithState对于到达当前微批次的每个键值对,将调用该函数。对于 mapWithState,主要优点是: 状态的初始RDD - 可以加载具有先前保存状态的RDD 超时 - 超时管理由Spark处理。你可以为所有键值对设置单个超时。 部分更新 - 仅迭代当前微批中“触摸”的键以进行更新 返回类型 - 你可以选择任何返回类型。 但事情并不总是完美的…… mapWithState 的痛点 mapWithState 比以前的 updateStateByKey API 有了很大的改进。但是去年在我使用它的过程中,体验过一些缺点: 检查点 为了确保Spark可以从失败的任务中恢复,它必须将数据 checkpoint 到分布式文件系统中,它可以在失败时从中恢复。使用 mapWithState 时,每个 executor 在内存中保存已累积的所有状态的 HashMap。在每个检查点,Spark 每次都会序列化整个状态。如果你在内存中持有很多状态,这可能会导致严重的处理延迟。例如,以下设置: 批处理间隔:4秒 检查点间隔:40秒(4秒批次x 10 常量 spark 系数) 每秒80,000条消息 消息大小:500B - 2KB 5 m4.2xlarge机器(8个vCPU,32GB RAM) 每台机器2个 executor executor 存储大小~7GB(每个) 将数据 checkpoint 到 HDFS 我经历了长达4小时的累积延迟,因为高负荷下的每个检查点在整个状态下花费30秒-1分钟,我们每4秒产生一次批次。我也看到人们在StackOverflow上对此感到困惑,因为实际上并不明白为什么有些批次比其他批次花费的时间要长得多。 如果你计划使用状态流来实现高吞吐量,则必须将此视为一个严重的警告。这个问题非常严重,以至于它让我找到了在Spark中使用内存状态的替代方法。但我们很快就会看到事情看起来很光明;) 在版本更新之间保存状态 软件是一个不断发展的过程,我们总是在改进,增强和实现新的功能要求。因此,我们需要能够从一个版本升级到另一个版本,最好不要影响现有数据。在内存数据中,这变得非常棘手。我们如何保持现状?我们如何确保从中断的地方继续?

🎄 25/25. Raku Golf 代码的提示和想法

欢迎来到第25天,这是Raku One-Liner Advent Calendar的最后一天!传统的 advent calendar 只有24个条目,我们今天的奖励帖子将专注于你可以在Raku高尔夫比赛中使用的一些提示和技巧。 有一个很棒的网站code-golf.io,你可以尝试解决一些问题,并将Raku移到最高分。我怀疑许多问题可以从这个One-Liner Advent Calendar 前几天所涵盖的技术中受益。 省略主题变量 如果在主题变量$_上调用方法,那么 Raku 实际上不需要变量名来理解你在说什么,所以,避免显式命名主题变量: $_.say for 1..10 使用范围进行循环 Perl 中的范围是表达循环细节的好东西:用几个字符,指定循环变量的初始和最终状态。倒装形式通常更短。 for 1..10 {.say} .say for 1..10 想想你是否可以从0开始计数,在这种情况下,可以使用脱字符来获得从0开始的范围。以下代码打印数字0到9: .say for ^10 在范围和序列之间进行选择 在循环中,序列可以与范围完全相同。选择可能取决于Golf软件是计数字节还是计数Unicode字符。在第一种情况下,两个点的范围优于三个点的范围。在第二种情况下,使用Unicode字符: .say for 1..10 .say for 1...10 .say for 1…10 当你需要向下计数时,序列是你的朋友,因为他们可以推断出循环计数器更改的方向: .say for 10…1 使用map而不是循环 在某些情况下,尤其是当你必须使用循环变量进行多个操作时,请尝试使用map迭代所有值: (^10).map: *.say 省略括号 与 Perl 5 不同,Raku 不会强制你在常规形式的条件检查中使用括号: if ($x > 0) {say $x;exit} if $x > 0 {say $x;exit} 有时,你也希望省略函数调用中的括号。 在声明数组或散列时,你都不需要括号。对于数组,使用引号构造:

🎄 24/25. 在 Raku 中用 $*ARGFILES 读取文件

欢迎来到Raku One-Liner Advent Calendar的第24天! 在前几天,我们正在读取文本文件,因此讨论 $*ARGFILES是合乎逻辑的,这是一个内置的动态变量,在处理多个输入文件时可能很方便。 如何读取命令行中传递的两个或多个文件? $ raku work.pl a.txt b.txt 如果您需要将所有文件一起处理,就像它们是单个数据源一样,您可以要求我们今天的变量以单行方式完成工作: .say for $*ARGFILES.lines 在程序内部,您不必考虑循环文件; $*ARGFILES将自动为您做到这一点。 如果命令行中没有文件,则该变量将附加到 STDIN: $ cat a.txt b.txt | raku work.pl 确实很方便,不是吗? 6.d和MAIN 如果你想在更大的程序中使用它,我也必须警告你。考虑以下程序: sub MAIN(*@files) { .say for $*ARGFILES.lines; } 在 Perl 6.d 中, $*ARGFILES 在MAIN子例程内部和它之外的工作方式不同。 这个程序将完全适用于 Raku.c,但不适用于Raku.d. 换句话说,在Rakudo Star中,包括版本2018.10,$*ARGFILES 在命令行中处理文件名,但从 Rakudo Star 2018.12 开始,如果在 MAIN 中使用它,它将始终连接到 $*IN。 这就是今天的 advent 文章的结束,几乎是今年整个日历的结束。不过,明天再来!

🎄 About Translation of Raku One-Liner Advent Calendar

Thanks so much to Andrew Shitov, for writing so much nice articles, for allowing me to translate these posts to Chinese. If you are fluent in english, please read the source posts: Day 1 - https://raku.online/2018/12/01/generating-random-password/ Day 2 - https://raku.online/2018/12/02/grep-dividable-numbers/ Day 3 - https://raku.online/2018/12/03/generating-random-integers-in-perl-6/ Day 4 - https://raku.online/2018/12/04/working-with-big-numbers-in-perl-6/ Day 5 - https://raku.online/2018/12/05/what-is-the-date-today-in-raku/ Day 6 - https://raku.online/2018/12/06/palindrom-testing-in-perl-6/ Day 7 - https://raku.online/2018/12/07/the-joy-of-unicode-in-perl-6/ Day 8 - https://raku.online/2018/12/08/adding-up-even-fibonacci-numbers-in-perl-6/ Day 9 - https://raku.online/2018/12/09/more-on-x-in-perl-6/ Day 10 - https://raku.

🎄 23/25. 用 Raku 计算总数

欢迎来到Raku One-Liner Advent Calendar的第23天!今年年底是人们计算年度结果的时间,Raku 也可以帮助解决这个问题。 今天,我们将看到一个单行程序,用于计算表格列的总数。 这是文件中的一些示例数据: 100.20 303.50 150.25 130.44 1505.12 36.41 200.12 305.60 78.12 这里是为每列打印三个数字总和的代码: put [Z+] lines.map: *.words 该程序打印我们需要的数字: 430.76 2114.22 264.78 从昨天的帖子的更新中,我们知道,裸lines 和 $*IN.lines 是一样的,所以lines.map在输入流中迭代所有行。然后将每一行拆分成由空格分隔的单词子串。 解析输入数据的作业部分已完成。我们得到了许多与输入数据行相对应的序列。对于我们的示例文件,有如下内容: (("100.20", "303.50", "150.25").Seq, ("130.44", "1505.12", "36.41").Seq, ("200.12", "305.60", "78.12").Seq).Seq 现在,将每个序列的第一个元素,每个序列的第二个元素等相加起来。化简运算符和zip元运算符的组合只需要四个代码字符即可完成所有工作:[Z+]。 此时,我们有一个化简后的序列: (430.76,2114.22,264.78).Seq 最后一个简单的步骤是使用 put 例程打印值。如果你昨天做了功课,你会知道say使用gist方法(在序列周围添加括号)来显示结果,而put只是使用Str方法打印值。 还有 让我们在脚本中添加一些字符来演示如何跳过第一列,例如,跳过包含月份名称的列: Jan 100.20 303.50 150.25 Feb 130.44 1505.12 36.41 Mar 200.12 305.60 78.12 您只需要创建一个切片并选择除第一列之外的所有列: put 'Total ', [Z+] lines.map: *.words[1..*] 如您所见,我们甚至不需要自己计算列数。1..* 范围可以完成这样的工作。 这就是今天的 advent 文章的结尾,所以明天再来吧!

第二十三天 - Blin,很快就到圣诞节了!

两年前我已经在出现一篇 advent 文章里提到过 Bisectable,但自那时以来发生了很多变化,所以我觉得是时候简要介绍一下 bisectable 机器人和它的朋友们了。 首先,让我们来定义正在解决的问题。有时会发生提交引入意外更改行为(错误)。通常我们称之为回归,在某些情况下,找出错误并修复它的最简单方法是首先找到引入回归的提交。 Rakudo 2015.12 和 2018.12 之间有9000个提交,尽管它不超过9000,但仍然很多。 幸运的是,我们不需要测试所有修改。假设行为不是一直来回变化,我们可以使用二分法查找。 git bisect 和二分法查找 基本上,给定任何提交范围,我们在范围的“中间”取一个提交提交并测试它。如果它是“坏”或者它显示“新”(现在是不正确的)行为,那么我们就可以抛弃我们范围的后半部分(因为我们知道更改必须在该提交之前发生或完全在该提交之后)。同样,如果它是“好”(或“旧”),我们会扔掉另一半。因此,我们只需检查log n个修改(≈13),而不是测试所有 9000 次提交。 Git 附带了git bisect为您实现二分法查找逻辑的命令。你所要做的就是给它一些起点,然后对于每次提交它跳转过去,告诉它是好还是坏。如果你做了足够多次,它会告诉你哪个提交有问题。 这一切都很好,但有两个问题。 问题❶:跳过 让我们想象一下2 + 2用来返回的情况4(正确!),但现在返回42(……也正确,但不完全对)。 所以你启动了 bisection 过程,git 在修改之间跳转,你测试它们。如果它是4那么good(或old),如果它是42那么它是bad(或new)。但后来你偶然发现了这种行为: > 2 + 2 Merry Christmas! … 怎么办?显然,那个具体修改有点特殊。我们无法判断我们的错误是否存在,我们根本无法知道。是的,它不会打印4,但我们正在寻找一个非常具体的错误,因此它也不会被归类为“新”行为。当然,我们可以抛硬币并随机标记为old或者new,并希望圣诞节奇迹……但是有50%的概率(如果我们只看到其中一个)将二分法查找转移到错误的方向。 对于这些情况,git 提供了一个特殊skip命令。 如果你是手动测试,那么处理这些修改就有点简单(只要你记得你应该跳过(skip)他们)。但是,由于问题❷,很多人都倾向于使用git bisect run脚本自动化过程。也可以使用脚本跳过修改(使用退出代码125),但是如何确定应该跳过哪些修改并不是那么明显。 问题❷:构建时间 让我们用乐观的数字13来估计我们要测试的修改量。请记住,它不包括我们必须跳过的提交,以及可能需要测试的其他额外构建。 构建rakudo所需的时间因硬件而异,但我们乐观地说,在特定的提交中构建rakudo需要2分钟时间并对其进行测试。 13 × 2 = 26 (minutes) 那不是很方便,对吧?如果在此过程中出现问题……你重新开始,然后等待。 Bisectable 在2016年,在看到那些必须手动运行 git bisect 的人(实际上,大部分是我自己)的痛苦之后,我想知道: 有没有人想过为每一次提交构建rakudo,以便你可以快速运行git bisect? 该想法的成本效益分析受到了迅速质疑: AlexDaniel:你认为未来二分法将会很普遍吗? 我提供了非常详细的理由: perlpilot:是的 三天后,机器人加入了频道。这些反应非常有趣: 哇 哦 OooOOOoooh Cooooool

第二十二天 - 使用 Carton 进行 Mojolicious 应用程序部署

你有一个可爱的Mojolicious应用程序,它是时候部署它了! 但是……它不能在生产服务器上运行!到底是怎么回事?哦,不,您所依赖的模块与开发服务器上的版本不同。你能做什么? 实际上,一些模块发展得很快(Hello Mojolicious!),这没有毛病,但可能会导致不兼容的变化。 还有一些可以在版本中解决或引入的错误,如果您的版本错误,则会遇到这些错误。 Cpanfile来救援 Cpanfile是一种用于描述Perl应用程序的CPAN依赖关系的格式。 有了cpanfile,我们可以列出我们需要的模块,但我们也可以强制模块的最小版本,它们的最大版本……或者说“我想要那个模块的确切版本”。 但我们也可以列出可选模块:您可以支持不同的数据库,但如果用户想要使用PostgreSQL,则不必安装与MySQL相关的模块。 这是一个例子cpanfile: # Do not ask for a specific version requires 'DateTime'; # Ask a specific version requires 'Plack', '== 1.0'; # Ask a minimal version requires 'Net::DNS', '>= 1.12'; # Or requires 'Net::DNS', '1.12'; # Ask a maximal version requires 'Locale::Maketext', '< 1.28'; # Give a range requires 'Mojolicious', '>= 7.0, < 8.0'; # Optional modules feature 'postgresql', 'PostgreSQL support' => sub { requires 'Mojo::Pg'; }; feature 'mysql', 'MySQL support' => sub { requires 'Mojo::mysql'; }; feature 'ldap', 'LDAP authentication support' => sub { requires 'Net::LDAP'; }; Cpanfile格式可以做更多(推荐模块,用于特定阶段(需求configure,test…),使用没有公布关于CPAN …模块),但是这是一个关于 Carton 的文章:我让你读cpanfile文档🙂

🎄 22/25. 使用 Raku 反转文件

欢迎来到Raku One-Liner Advent Calendar的第22天!今天,我们将继续使用文件,今天的目标是创建一个单行程序以相反的顺序打印文本文件的行(如tail -r那样)。 第一个单行程序使用 STDIN 流完成工作: .say for $*IN.lines.reverse 运行程序为: $ raku reverse.pl < text.txt 如果要直接从Raku读取文件,请稍微修改程序以从命令行参数创建文件句柄: .say for @*ARGS[0].IO.open.lines.reverse 现在运行如下: $ raku reverse.pl text.txt 重要的是要记住,lines方法的默认行为是从最后的行序列中排除换行符(该方法返回Seq对象,而不是数组或列表)。它可能与你在使用Perl 5时习惯的方式相反。使用chomp是一种常见的做法。 在Raku中,lines方法根据IO::Handle对象的nl-in 属性中存储的值来拆分行。 您可以使用以下小脚本查看行分隔符的当前值: dd $_ for @*ARGS[0].IO.open.nl-in 这是您默认找到的内容: $["\n", "\r\n"] 有趣的是,你可以控制的 lines 的行为,并告诉 Perl 不排除换行符: @*ARGS[0].IO.open(chomp => False).lines.reverse.put 该 chomp 属性由默认设置为 True。您还可以更改默认分隔符: @*ARGS[0].IO.open( nl-in => "\r", chomp => False ).lines.reverse.put 请注意,如果没有 chomping,则不需要对行进行显式的for循环:在最后两个单行中,直接在序列对象上调用.put方法。在早期版本中,字符串不包含换行符,因此它们将打印为单个长行。 今天我将给你留一些小功课:告诉 say 和 put 之间的差异。 明天见!

第二十一天 - 一个小小的圣诞节模板烹饪

Advent Calendar向您展示了许多使用Mojolicious的好方法,因为您已经安装了Mojo,所以除了Web处理之外,您还可以使用它。今天的配方使用模板渲染引擎进行Web响应之外的其他操作。 首先,处理一些字符串模板。这是使用v5.26中发布的波浪形heredoc语法从Mojo::Template中取出的一个示例 use Mojo::Template; my $mt = Mojo::Template->new; say $mt->render(<<~'EOF'); % use Time::Piece; <div> % my $now = localtime; Time: <%= $now->hms %> </div> EOF 带有前导百分号的行是Perl代码。其中一行加载模块,Time::Piece,另一行创建变量$now。该<%= %>插入值内联。您可以自己找出其他模板语法; 这些都在模块文档中。 您可以将其反转,以便值的来源来自模板外部。有时,这比在让表示层中有太多逻辑更可取: use v5.26; use Mojo::Template; my $mt = Mojo::Template->new; use Time::Piece; my $now = localtime; say $mt->render(<<~'EOF', $now->hms ); % my $time = shift; <div> Time: <%= $time %> </div> EOF 如上所示,默认情况下,参数以位置方式传递给模板。然而,通过名称而不是位置来描述变量更为自然。该vars属性打开您将哈希传递给模板的能力; 哈希键成为模板本身的变量名。 use v5.26; use Mojo::Template; my $mt = Mojo::Template->new->vars(1); use Time::Piece; my $now = localtime; say $mt->render(<<~'EOF', { time => $now->hms } ); <div> Time: <%= $time %> </div> EOF 从文件(或许多文件)中获取该模板同样容易。这是我以前使用Template Toolkit执行此操作的一种方式,这是一个非常精细且功能强大的模块,与以前一样好。但是,我已经在使用了Mojo的很多东西,它已经有了一个模板引擎。我可以减少依赖计数并专注于一种模板语言。

开始第一个 Flink 应用程序

创建一个 Flink 应用程序的样板 mvn archetype:generate \ -DarchetypeGroupId=org.apache.flink \ -DarchetypeArtifactId=flink-quickstart-scala \ -DarchetypeVersion=1.6.0 \ -DarchetypeRepository=http://maven.aliyun.com/nexus/content/groups/public 开始 我有一个 Spark 应用程序, 我想把它转为 Flink 应用。在 Spark 应用里我读取 Kafka 中的消息为 DStream, 然后打印出来。我们使用 Flink 来做同样的事情。为了方便起见, 我们使用本地模式: 首先启动一个本地的 Flink 集群 /usr/local/Cellar/apache-flink/1.7.0/libexec/bin/start-cluster.sh 然后在 pom 文件中添加入口类(可选): <mainClass>ohmysummer.BigdataApplication</mainClass> 然后启动这个应用:(如果是其它的 object, 使用 flink run -c className) flink run ./target/stream-word-count-1.0-SNAPSHOT.jar 打开浏览器, 输入网址: localhost:8081 查看 job 的运行状态, 即使你 Ctrl-C 了 flink 提交命令, 依然可以看见任务在运行。如果要取消, 在 UI 界面找到任务后点击 Cancel。 或查看输出: tail -f /usr/local/Cellar/apache-flink/1.7.0/libexec/log/flink-<whoami>-taskexecutor-0-<hostname>.out 停止集群: /usr/local/Cellar/apache-flink/1.7.0/libexec/bin/stop-cluster.sh

🎄 21/25. 在 Raku 中水平地合并文件

欢迎来到Raku One-Liner Advent Calendar的第21天!到本季即将结束的日历只剩下几天了,所以让我们在剩下的日子里收集尽可能多的东西,今天我们将几个文件合并到一个文件中 🙂 我们今天的目标是获取两个(或三个或更多)文件并逐行复制其内容。例如,我们想要合并两个日志文件,知道它们的所有行都相互对应。 文件 a.txt: 2018/12/20 11:16:13 2018/12/20 11:17:58 2018/12/20 11:19:18 2018/12/20 11:24:30 文件 b.txt: "/favicon.ico" failed (No such file) "/favicon.ico" failed (No such file) "/robots.txt" failed (No such file) "/robots.txt" failed (No such file) 我们的第一个单行程序阐述了这个想法: .say for [Z~] @*ARGS.map: *.IO.lines; 假设程序运行如下: $ raku merge.pl a.txt b.txt 对于命令行中的每个文件名(@*.ARGS.map),将创建一个IO::Path对象(.IO),并读取文件中的行(.lines)。 在两个文件的情况下,我们有两个序列,然后使用应用了连接中缀~的zip元运算符Z逐行连接。 在那一步之后,我们得到另一个序列,我们可以逐行打印(.say for)。 2018/12/20 11:16:13"/favicon.ico" failed (No such file) 2018/12/20 11:17:58"/favicon.ico" failed (No such file) 2018/12/20 11:19:18"/robots.txt" failed (No such file) 2018/12/20 11:24:30"/robots.

测试 Dancer

Dancer(及其他)PSGI 应用程序的作者很可能习惯于测试与Plack::Test,虽然这是一个崇敬的选择,这是很裸机。 在去年出现的过程中,我写了一篇关于Test::Mojo的文章,展示了许多简单易用(我敢说)有趣的方法,你可以用它来测试你的Mojolicious应用程序。如果你错过了,那就去看看吧。 我希望至少有一些人能够阅读并思考,“我很乐意使用它,但我不会使用Mojolicious!”; 好吧,你很幸运!只需要一点角色来弥补差距,你也可以使用Test::Mojo来测试你的PSGI应用程序! 安装PSGI应用程序 Mojolicious本身不使用PSGI协议,因为它没有提供某些特性,而且某些异步操作也是必需的。也就是说,你可以使用Mojo::Server::PSGI在PSGI服务器上提供Mojolicious应用程序。当你的基于Mojolicious的应用程序检测到它已在PSGI服务器(例如plackup或Starman)下启动时,将自动使用此Mojolicious核心模块。 虽然在Mojo应用程序和PSGI服务器之间进行转换是核心功能,但相反,在PSGI应用程序和Mojolicious服务器(或应用程序,如你所见)之间进行转换可作为第三方模块使用。Mojolicious::Plugin::MountPSGI,顾名思义,可以将PSGI应用程序安装到基于Mojolicious的应用程序中。为此,它构建了一个新的,空的Mojolicious应用程序,在将任何请求转移到PSGI环境之前,将其转移到任何mount-ed应用程序。 使用Test::Mojo进行测试 一旦你能做到这一点,采用PSGI应用程序,用MountPSGI包装它,并将其设置为与Test::Mojo一起使用的应用程序是微不足道的。尽管如此,为了让它变得更加容易,在Test::Mojo::Role::PSGI中已经完成了所有这些工作。 与任何Mojolicious Role一样,我们可以使用应用角色with_roles创建(主要是匿名)子类。你可以使用快捷方式+代替Test::Mojo::Role::。 use Test::Mojo; my $class = Test::Mojo->with_roles('+PSGI'); 然后使用PSGI应用程序的路径实例化该角色,或者使用PSGI应用程序本身。 由于你使用的是角色,这些角色都与组合有关,因此你还可以应用可能在CPAN上找到的其他角色。 一个例子 作为一个例子,假设我们有一个简单的应用程序脚本(命名app.psgi),可以以不同的方式渲染 "hello world"或"hello $user" 。我将允许纯文本响应,JSON和模板化HTML(使用简单的模板来保持这种简洁)。 use Dancer2; set template => 'simple'; set views => '.'; any '/text' => sub { my $name = param('name') // 'world'; send_as plain => "hello $name"; }; any '/data' => sub { my $name = param('name') // 'world'; send_as JSON => { hello => $name }; }; any '/html' => sub { my $name = param('name') // 'world'; template 'hello' => { name => $name }; }; start; 而模板(hello.

🎄 20/25. 在 Raku 单行程序中使用命令行选项

欢迎来到 Raku One-Liner Advent Calendar 的第20天!到目前为止,我们创建了大约25种不同的单行程序,但从未讨论过Rakudo Raku编译器为我们提供的命令行选项。 -e 使用(Rakudo)Raku时的第一个选项是-e。它需要一个带有Raku单行程序的字符串并立即执行。 例如,打印当前Raku规范的版本: $ raku -e'$ * PERL.version.say' v6.c 注意不要使用Perl 5.10+样式的大写字母 -E,它与-e相同,但也激活诸如 say 之类的功能。在Raku中,选项始终为小写。 -n 此选项为每行输入数据重复运行代码。当您想要处理文件时,这非常方便。例如,这是一个单行程序,将行中的值相加并打印总和: raku -ne'say [+] .split(" ")' data.txt 如果data.txt文件包含以下内容: 10 20 30 40 1 2 3 4 5 6 7 8 然后单行程序的结果是: 100 10 26 这与你是否使用shell的输入重定向没有区别; 以下单行程序也有效: raku -ne'say [+] .split(" ")' < data.txt 确保将 e 选项放在选项列表中的最后一个(因此,不是raku -en'...')或拆分选项:raku -n -e'...'。 -p 此选项类似于-n,但在每次迭代后打印主题变量。 以下单行程序反转文件中的行并将其打印到控制台: raku -npe'.=flip' data.txt 对于相同的输入文件,结果将如下所示: 04 03 02 01 4 3 2 1 8 7 6 5 请注意,您必须更新$_变量,因此键入.

ssh 和 git 笔记

把旧电脑上的 .ssh 目录拷贝到新电脑上, 继续使用之前的 ssh 免密码登录提示 「权限不够或需要输入密码」, 解决办法: chmod 755 ~/.ssh/ chmod 600 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub chmod 644 ~/.ssh/known_hosts git 回滚 git log # 查看提交的 commit 历史 git reset –hard 8ff24a6803173208f3e606e32dfcf82db9ac84d8 git 瘦身 http://blog.mallol.cn/如何给git仓库瘦身删除大文件.html git pull 不输入密码 # vi .git/config [remote "origin"] url = https://Username:Password@github.com/myRepoDir/myRepo.git scp 复制文件 - 权限拒绝 ssh 可以登陆,但是 scp 复制文件时却不行: $ sudo scp -P 52113 ubuntu@123.206.136.59:/tmp/*.txt . Permission denied (publickey) 在 ~/.ssh/config 文件中添加: Host 123.206.136.59 IdentityFile ~/.ssh/ohmycloud

你只需导出俩次

我的Yancy文档站点已经构建,具有自定义登录页面和POD查看器,我只需要部署该站点。我可以使用hypnotoad部署网站,Mojolicious的preforking服务器进行热部署,但这需要我有一台服务器并保持在线。如果我可以像Github一样部署静态网站,就像所有酷炫的人一样,那就好多了。 但要做到这一点,我需要把我的动态网站变成一个静态网站,这是不可能的!真的是吗?为什么我问我自己,我就是那个开辟道路的人:Mojolicious导出命令。 export 命令将一组路径作为输入,获取这些页面,并将结果写入目录。然后它会查看这些页面上的所有链接并编写这些页面。通过这种方式,它将整个 Mojolicious 网站导出为静态文件。 我需要做的就是能够使用 export 命令来安装它: $ cpanm Mojolicious::Command::export 一旦安装完毕,我们现在在我们的应用程序中有了 export 命令,我可以像任何其他 Mojolicious 命令一样使用它。 $ ./myapp.pl export 默认情况下,export命令尝试导出主页(/)并从那里递归工作。如果我的页面没有从其他地方链接,我应该(a)可能添加一些指向该页面的链接,但是(b)可以将它添加到要导出的页面列表中: $ ./myapp.pl export / /private 由于我在我的个人网站的目录下托管这个网站,我需要使用该--base选项重写所有内部链接到正确的路径,我可以使用该--to选项直接写入Web服务器的目录: $ ./myapp.pl export --base /yancy --to /var/www/preaction.me/yancy 而且,如果我愿意,我可以使用Mojolicious Config插件 来更改默认设置,包括要导出的页面,导出目录和 base URL。 最好的部分是export命令处理重定向。因此,当我们使用PODViewer插件并重定向到MetaCPAN时,页面会根据重定向的位置进行更新! 将来,如果将此命令设置为插件,以便它可以具有用于自定义导出内容的钩子或对断开链接的附加检查,那将是很好的。如果有人有兴趣帮助完成这项工作,请告诉我,我可以帮助他们开始工作! 现在,通过Yancy CMS,PODViewer插件和Mojolicious导出命令,我为Yancy提供了一个好看的文档网站!查看完整的应用程序。

给 redis 设置密码

brew install redis To have launchd start redis now and restart at login: brew services start redis Or, if you don't want/need a background service you can just run: redis-server /usr/local/etc/redis.conf 可以编辑 /usr/local/etc/redis.conf, 设置 redis 密码 requirepass 启动: # redis-server redis-server /usr/local/etc/redis.conf 127.0.0.1:6379> pfadd mykey a b c (integer) 1 127.0.0.1:6379> pfcount mykey (integer) 3 127.0.0.1:6379> pfadd mykey2 a b c c d (integer) 1 127.0.0.1:6379> pfcount mykey2 (integer) 4 127.

🎄 19/25. 在 Raku 中使用 map 和 Seq 计算 π 的值

欢迎来到Raku One-Liner Advent Calendar的第19天!今天,我们将使用两种不同的方法计算π的值。这篇博文的目的是使用不同的方法来生成数字序列。 Pre-party 当然,在 Raku 中你不需要自己计算 π 的值,因为 Raku 给出了一些π和pi 形式的预定义常数,以及双倍的值τ和tau。 但为了演示 map 和序列的用法,让我们实现一种最简单的算法来计算 π: 这是检查答案的草案代码: my $pi = 1; my $sign = 1; for 1..10000 -> $k { $sign *= -1; $pi += $sign / ($k * 2 + 1); } say 4 * $pi; 第1部分 现在,让我们使用map来使答案紧凑一点。最好使公式更通用: 这是我们的第一个单行程序: say 4 * [+] (^1000).map({(-1) ** $_ / (2 * $_ + 1)}) 我希望你能理解这段代码里的所有东西。我们在今年的 Advent Calendar 的前几天介绍了该答案的不同部分,例如,在第11天的帖子中。 但是,我仍然想强调你需要-1周围的圆括号。如果输入-1 ** $_,则总是得到 -1,因为减号前缀应用于取幂的结果。所以正确的代码是(-1) ** $_。

第十六天 - 在 Dancer 中使用 Minion

在$work 中,我们使用 Dancer 构建了一个 API,用于生成 PDF 文档和 XML 文件。此 API 是保险登记系统的重要组成部分:生成PDF以立即在Web浏览器中传送给客户端,并且XML一旦可用就会立即传送给运营商。由于XML通常需要花费大量时间来生成,因此在后台生成作业,以免在较长时间内占用应用程序服务器。完成后,开发了一个自行开发的流程管理系统,通过fork() 进程工作,跟踪其 pid,并希望我们以后可以成功地获得完成的过程。 这种方法存在一些问题: 很脆弱 它没有规模 作为开发人员搞砸某事太容易了 在2019年,我们不得不加大承担更大的工作量。目前的解决方案根本无法处理我们预计需要处理的工作量。直到遇见了 Minion。 **注意:**本文中使用的技术在 Dancer或 Dancer2同样适用。 为何选择Minion? 我们研究了Minion的几种替代品,包括beanstalkd和celeryd。然而,使用其中任何一个意味着涉及我们已经过度征用的基础设施团队; 使用Minion允许我们使用我的团队已经拥有的专业知识,而不必向其他人提供帮助。从开发的角度来看,使用Perl开发的产品为我们提供了最快的实施时间。 扩展我们现有的设置几乎是不可能的。处理我们 folk 的进程所消耗的资源不仅难以处理,而且不可能在多个服务器上运行作业。从Minion开始也给了我们一个急需的机会来清理一些需要重构的代码。通过最少量的工作,我们能够清理我们的XML渲染代码并使其在Minion中运行。通过此清理,我们可以更轻松地获取有关XML渲染作业消耗了多少内存和CPU的信息。这些信息对于我们规划未来容量至关重要。 进入 Minion 既然我们是一个 Dancer 商店,而不是 Mojolicious,你从Mojolicious获得的与Minion工作的很多东西都不适合我们。鉴于我们还在商业模式中共享一些基于Minion的代码,我们必须围绕Minion构建一些自己的管道: package MyJob::JobQueue; use Moose; use Minion; use MyJob::Models::FooBar; with 'MyJob::Roles::ConfigReader'; has 'runner' => ( is => 'ro', isa => 'Minion', lazy => 1, default => sub( $self ) { $ENV{ MOJO_PUBSUB_EXPERIMENTAL } = 1; Minion->new( mysql => MyJob::DBConnectionManager->new->get_connection_uri({ db_type => 'feeds', io_type => 'rw', })); }, ); 我们在Minion周围包含了一个简单的Moose类,以便使用我们想要的额外功能轻松添加到任何类或Dancer应用程序。

第十八天 - POD 一览

第18天:对POD的看法 为了让Yancy拥有一个好的文档站点,实际上它需要渲染文档。要在Mojolicious中渲染Perl文档 ,我可以使用 PODViewer 插件(现已弃用的PODRenderer 插件的一个分支 )。 将PODViewer添加到现有站点很容易! use Mojolicious::Lite; plugin 'PODViewer'; app->start; 现在,当我访问http://127.0.0.1:3000/perldoc 时我看到了Mojolicious的 POD::Guides。这很棒,但是这是 Yancy 的文档站点,而不是 Mojolicious 的。让我们调整一些配置来使 Yancy 成为默认模块,并且只允许查看Yancy 模块(尝试查看另一个模块会将用户重定向到MetaCPAN)。 use Mojolicious::Lite; plugin 'PODViewer', { default_module => 'Yancy', allow_modules => [qw( Yancy Mojolicious::Plugin::Yancy )], }; app->start; 在那里,现在Yancy文档显示在首页。 最后,让它看起来更好一些:文档肯定需要使用默认的站点布局,而一些额外的CSS也会使文档看起来好看多了! use Mojolicious::Lite; plugin 'PODViewer', { default_module => 'Yancy', allow_modules => [qw( Yancy Mojolicious::Plugin::Yancy )], layout => 'default', }; app->start; __DATA__ @@ layouts/default.html.ep <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="/yancy/bootstrap.css"> <style> h1 { font-size: 2.

🎄 18/25. 使用 Raku 重命名文件

欢迎来到Raku One-Liner Advent Calendar的第18天!今天,将会有一个真正的单行程序,在某种意义上说,你要作为一个运维从终端中运行它。 我们的任务是重命名命令行参数中传递的所有文件,并以偏爱的格式提供文件序列号。以下是命令行的示例: $ raku rename.pl * .jpg img_0000.jpg 在此示例中,当前目录中的所有图像文件将重命名为img_0001.jpg,img_0002.jpg等。 这是 Raku 中可能的解决方案: @*ARGS[0..*-2].sort.map: *.Str.IO.rename(++@*ARGS[*-1]) 预定义的动态变量@*ARGS包含命令行中的参数。在上面的示例中,shell 将 *.jpg 掩码展开到文件列表中,因此数组中包含所有这些掩码。最后一个元素是重命名样本 img_0000.jpg。 请注意,与 Perl 5 不同,该变量称为 ARGS,而不是 ARGV。 为了遍历所有文件(并使用文件掩码跳过最后一个文件项),我们正在进行@*ARGS的切片。0..*-2 结构创建了索引范围以接收除了最后一个元素的所有元素。 然后对列表进行排序(原始的@*ARGS数组保持不变),我们使用map 方法迭代文件名 。 map 主体包含一个WhateveCode块 ; 它接受当前值的字符串表示形式,从中生成一个IO::Path对象,并调用 rename 方法。请注意,IO方法创建IO::Path类的对象; 而裸 IO是Raku对象系统层次结构中的一个角色。 最后,增量运算符++更改重命名样本(保存在@*ARGS 中的最后一个, 即 *-1 元素)。当操作符应用于字符串时,它会增加它的数字部分,因此我们得到 img_0001.jpg,img_0002.jpg等。 我希望 Raku 不会被称为 star-noise 语言 🙂 。尽管如此,明天还会有另一篇关于 Raku 的短篇小文!

第十六天 - Mojolicious 之圣诞节前的开胃菜 - 儿童故事

您刚刚阅读了 如何在浏览器中减轻体重, 并希望了解减少Mojo应用程序的效果。该过程的一部分是阻止浏览器请求几乎不变的文件。我花了一个充满咖啡因的下午试图用Mojolicious做到这一点。我一直在’围绕房子,扰乱警报我直到最后才找到答案,有点像你最喜欢的圣诞节动画特别与一个小的林地生物叙述“Gruffalo的HTTP标题”。 儿童故事 我们心爱的小型林地生物需要显示一个森林日历,其中包含从数据库中提取的森林事件。Perl 可以获取事件数据并将其打包为JSON feed 流。Mojolicious 可以为每个用户准备具有正确 JSON feed 流的网页。使用一些JavaScript 库来显示 Web 日历,所有这些都可以在森林中使用。 除 JavaScript 库之外的所有内容都是轻量级的。每个人都知道, 如果不是每次都去下载 Javascript, 那么页面重载的速度会更快。那些库几个月都不会改变!如果只有客户端浏览器知道它可以使用上次下载的文件。 当然,秘诀是设置Cache-ControlHTTP 头的字段,但是怎样设置呢? 首先,有一匹马 …… 每个使用 Apache 的人都会考虑使用 mod_expires ,这看起来很容易,除了 Apache 没有被用来为网页提供服务。 …但是马儿提到了一些甜蜜的 Cache-Control指令可以咀嚼,同时继续在早先下载的一些HTTP缓存页面上吃草 。小动物继续前进。 …… 然后有一只蟾蜍 森林生物使用 Mojolicious附带的 Hypnotoad Web服务器来为其页面提供服务。他们发现它非常适合他们的树栖生产环境。 它可以设置HTTP标头将其转换为反向代理,但是流行的设置是将Hypnotoad 置于 Nginx 或Apache/mod_proxy后面。那些服务器应该让你玩Expires标题。但是蟾蜍没有完全具备这种特殊的啮齿动物所需要的东西。 抛开 - 不,我没有提到 Plack。也许如果我今年好,圣诞老人会 告诉我 应该如何使用它。可能与某事有关 Plack::Response->header('Expires' => 'Tue, 25 Dec 2018 07:28:00 GMT'); 但我不知道,我的叙述者也没有。 …… 然后有一个独角兽 嗯,这很容易。只需使用标准的 Mojo::Headers 模块来设置Expires标头。 可是等等!这为一个不是那么大的页面设置了它。我们毛茸茸的朋友只想阻止 JavaScript 文件每次都重新加载,这会破坏 Sciuridae 的移动体验。Hmmmm。

第十七天 - Dancer 和邮件

Web应用程序经常需要向其用户发送电子邮件,例如收据或密码重置链接。Dancer2 的Email插件通过提供email关键字和健全的默认配置简化了此任务。 所以不可避免的“Hello world”示例如下所示: email { from => 'foo@perl.dance', to => 'bar@perl.dance', subject => 'Hello world', text => 'Welcome to the dancefloor!', }; 更常见的情况是使用Web应用程序中的模板并将其转换为HTML电子邮件。 你不是使用template关键字将HTML从路由返回到浏览器,而是生成具有特定布局的 HTML,存储在变量中并发送电子邮件。 post '/welcome' => { my $html = template $template, $tokens, { layout => 'email' }; email { from => 'foo@perl.dance', to => 'bar@perl.dance', subject => 'Welcome to the dancefloor!', type => 'html', body => $html, } redirect '/home'; } UTF-8 如果内容和邮件标题可能包含UTF-8字符,请注意对其进行编码。如果你不这样做,它甚至可能在你的电子邮件客户端中看起来很好,但不一定适用于其他客户端。 email { from => 'foo@perl.

第十七天 - 一个 Yancy 网站

今年,我决定Yancy需要一个网站。而不是建立一个像Statocles这样的静态网站生成器的网站,这些天如此受欢迎,我决定做一些疯狂和不可预测的事情:一个动态网站!幸运的是,我有一个完美的项目,可以轻松建立一个动态的网站:Yancy! 任何动态网站的关键部分都是数据库。由于我只想编写 Markdown 并渲染 HTML,因此我的架构非常简单:存储页面路径的位置,存储页面 Markdown 以进行编辑的位置以及放置渲染 HTML 的位置。我建立了一个 SQLite 数据库并使用Mojo::SQLite::Migrations构建了pages 表 。 #!/usr/bin/env perl use Mojolicious::Lite; use Mojo::SQLite; helper db => sub { state $db = Mojo::SQLite->new( 'sqlite:' . app->home->child( 'docs.db' ) ); return $db; }; app->db->auto_migrate(1)->migrations->from_data(); # Start the app. Must be the code of the script app->start; __DATA__ @@ migrations -- 1 up CREATE TABLE pages ( id INTEGER PRIMARY KEY AUTOINCREMENT, path VARCHAR UNIQUE NOT NULL, markdown TEXT, html TEXT ); 准备好我们的数据库表后,我需要一种编辑页面的方法。Yancy 的内置编辑器带有marked.

🎄 17/25. 在 Raku 中使用素数

欢迎来到 Raku One-Liner Advent Calendar 的第17天!今天,我们将有两个单行程序,它们都产生一些素数。 第1部分 首先,让我们解决欧拉项目的问题7,你需要打印第 10001 个素数(第一个素数是2)。 Raku 擅长素数,因为它有一个 Int 类的内置方法,is-prime。 有几种方法可以生成素数。对于单行程序,最简单的就是最好的, 效率也最低, 方法就是测试每个数字。 say ((1..*).grep: *.is-prime)[10000] 计算结果大约需要半分钟,但代码很短。总有一天,我们将使用所谓的埃拉托色尼筛来解决这个问题,它应该更快,但可能需要更多的代码。 第2部分 在这个 advent 文章的第二部分,让我们娱乐一下并解决 code-golf.io 网站上的相应问题。我们需要打印100以下的所有素数。 我的答案需要22个字符,如下所示: .is-prime&&.say for ^Ⅽ 在 Raku 中没有更短答案了,而在J中,他们做到了只使用11个字符。在 Raku 中,方法名已经消耗了八个字符。我相信,要赢得所有的高尔夫比赛,你需要一种名称很短的特殊语言(J就是)和一组内置例程来生成素数,或斐波那契或任何其他数字序列的列表。它还应该强烈利用Unicode字符空间。 在我们的 Raku 示例中,还有一个 Unicode 字符,Ⅽ。这不是简单的 C,拉丁字母的第三个字母,而是 Unicode 字符 ROMAN NUMERAL ONE HUNDRED(原本是拉丁字母的第三个字母,当然)。使用此符号,我们可以在答案中节省两个字符。 && 技巧是可能的,因为如果第一个操作数是 False,Perl 不会执行布尔表达式的第二部分。请注意,您不能在此处使用单个 &。完整的非优化版本的代码需要额外的空格,如下所示: .say if .is-prime for ^100 这就是今天 Raku 之旅的终点,明天见!

第十六天 - 检查你的列表俩次

从命令行了解 Raku 这是 Sniffles the Elf 的大好机会!在丝带矿山经过多年的苦差事后,他们终于被提升到了清单管理部门。作为一名闪亮的新助理尼斯名单审核员,Sniffles 正在走向重要时刻。 在 Sniffles 到达的第一天,他们的新老板格伦布尔先生正等着他。“好人清单管理很麻烦,当有人在服务器上洒了牛奶和饼干时,我们的数据被意外删除了。我们一直在忙着检查列表,我们忘了检查备份!现在我们必须从头开始重建一切!裁员后,我们有点人手不足,所以由你来挽救这一天。“ Sniffles,特别勤劳,津津乐道于这个问题。经过一些研究,他们意识到他们需要的所有数据都可用,他们只需要收集它。 他们的朋友在丝带矿山中,一位名叫 Hermie 的自称口述历史学家一直在谈论 Raku 有多么伟大。Sniffles 决定尝试一下。 就像拔牙? Sniffles 首先用一种新语言抛出标准的第一个脚本: use v6.d; say "Nice List restored!!!"; 该脚本运行并尽职尽责地打印出消息。距离圣诞节还有几天了,是时候认真对待 Raku文档了。 稍微浏览一下 Sniffles 的 Raku 命令行界面实用程序 页面。他们喜欢它描述的 MAIN 这个特殊子程序的外观。 say 'Started initializing nice lister.'; sub MAIN() { say "Nice List restored!!!" } say 'Finished initializing nice lister.'; 产生: Started initializing nice lister. Finished initializing nice lister. Nice List restored!!! 好吧,至少那是他们的启动代码。Sniffles 抛弃了初始化消息,它们只是噪音。但他们确信这个 MAIN 函数必须有更多的技巧才能让 Hermie 如此兴奋。

🎄 16/25. Raku 中两点之间的距离

欢迎来到 Raku One-Liner Advent Calendar 的第16天!今天,我们将解决一个简单的问题,并找到平面上两点之间的距离。 这是一个有助于制定任务的插图。我们的目标是找到A点和B点之间的距离。 为了使答案更加透明和易于检查,我选择了AB线段,使得它是一个带有边长为3和边长为4的直角三角形的斜边。在这种情况下,第三边的长度将是5。 所以,这是答案: say abs(5.5+2i - (1.5+5i)) 代码使用了复数,一旦你移动到一个复平面,你就可以从平面上两点之间的距离等于这两个数相减的绝对值这一事实中获益。 在这种情况下,其中一个点是复平面上的点 5.5+2i,第二个点是 1.5+5i。在 Raku 中,您可以像在数学中一样编写复数。 如果没有复数的内置支持,你必须明确地使用毕达哥拉斯定理: say sqrt((5.5 - 1.5)² + (2 - 5)²) 家庭作业。修改 Rakudo 的 grammar 以允许以下代码: say √((5.5 - 1.5)² + (2 - 5)²) 这就是今天的一切。明天再来看看另外一个 Raku 单行或两个!

Raku 的正则表达式和文法

前所未有的表现力 Perl 5 正则表达式的强大功能使该语言成为分析文本数据的首选工具。从那时起,许多其他编程语言都复制了 Perl 的正则表达式,这部分地削弱了 Perl 在该领域中优于其他语言的优势。 从 Perl 5 派生的新 Raku 语言创建了一个新的文本匹配模型,该模型源自正则表达式,但功能强大且富有表现力,并且从常规正则表达式中删除它已决定给它们一个新名称,正则表达式。 不仅 Perl 的正则表达式6的机理是它大大高于现有的所有系统的正则表达式更强大,但它被设计成正则表达式相结合,构建 Grammar 语境,也就是说能够实现词汇和语法分析(系统词法和解析)到更复杂的数据,例如 HTML 文本,XML,XHTML,JSON,YAML,其中,不包括简单的情形,都超出正则表达式的范围。这些 Grammar 甚至可以分析所有级别的计算机程序。Raku 程序本身是用 Raku 自己编写的 Grammar 编译的。 虽然它们远不是 Raku 的唯一创新,但我们相信 Raku 正则表达式和 Grammar 将至少与 Perl 的正则表达式一样彻底改变计算机语言,甚至可能更多。他们现在还没有时间这样做。 本教程的讨论在 Perl 论坛上公开,地址如下: 评论 笔者 劳伦特罗森菲尔德 文章 发表于 2015年11月6日 - 更新于2018年 11月4日 PDF, 版离线版, ePub, Azw, Mobi 社交链接 1. 正则表达式入门 在正则表达式(或正则表达式)从数学和将字符串通常已知的形式语言的计算机科学理论概念模式(或模式)来描述一个整体(成品或不)字符串由模式定义的共同特征,根据预定义的语法并且不考虑上下文。的图案(匹配图案匹配)是应用这些模式到文本的样本,以试图找到对应于这些模式的文本片段的过程。 在你的计算机上安装Raku 如果你想使用 Raku,我们建议你在此地址下载 Rakudo Star。有关安装的更多信息,请参阅Perl 5到Raku教程的第一部分- 第1部分:语言基础知识。 在我们更新本文档(2018年10月)时,建议你选择 MoarVM 虚拟机。 1-1 与文件搜索类比 要在目录中搜索名称以字母 “a” 开头且扩展名为 .

实验功能

在 Raku 开发期间,通常可以在设计完成之前为用户提供新功能。最终,这些功能可能成为 Raku 规范的一部分。要使用这些功能,可以在程序源代码中使用 experimental 指令,例如,如下所示: use experimental :macros; 这些功能暂时是实验性的。 pack Pack 是一种允许二进制序列化一般数据结构的功能,并且继承自 Perl 的pack。pack 命令通过以包装字符串给出的特定方式打包数据结构来创建Buf,其中包含 unpack 描述中显示的选项。你可以通过在程序开头插入这个指令来打开它: use experimental :pack; 例如,我们可以打包数字,将它们解释为十六进制(H),重复模式,直到没有更多的元素(*): use experimental :pack; say pack("H*", "414243").contents;# OUTPUT: «(65 66 67)␤» 有一个相应的 unpack 例程正好相反。 use experimental :pack; my $buf=Buf.new(65,66,67); say $buf.unpack("H*"); # OUTPUT: «414243␤» 并非所有上述符号都可以保证实现,并且路线图不包含退出该阶段的固定日期。 请参阅 Blob 页面中的 pack 和 unpack 文档。 宏 宏 是代码生成例程,它们在程序执行之前在编译时生成代码。在 Raku 中,它的使用仍然是实验性的,它需要通过编译指示打开 use experimental :macros; 宏处理在解析时发生。宏生成抽象语法树,将其移植到程序语法树中。 quasi 是执行此任务的例程。 macro does-nothing() { quasi {} }; does-nothing; # OUTPUT: «» 宏是一种例程,因此它们可以以完全相同的方式接受参数,并且也以几乎相同的方式起作用。

🎄 15/25. 在 Raku 中使用斐波那契数

欢迎来到 Raku One-Liner Advent Calendar 的第15天!今天,将有两个单行程序,它们都产生斐波纳契数。 是的,最有可能的是,你从未在实际代码中使用过这样的数字,而且,很可能,你用它们解决了许多教育问题。然而,今天,让我们解决欧拉项目的问题25,并尝试在 Code-Golf.io 站点上寻找最短的 Fibonacci 问题解决方案。 Pre-party 我们如何在 Raku 中形成 Fibonacci 序列呢?使用序列运算符 ...: 0, 1, * + * ... * 如果你想在代码中有一些奇特的味道,你可以用 Inf 或 ∞ 替换最后的那个星号。在任何情况下,结果都是 Seq 类型的惰性序列。 Raku 不会立即计算它(并且它不能,因为右边缘是无限的)。 第1部分 第一个任务是找到第一个 Fibonacci 数的索引,该数字有 1000 个数字。当然,你可以循环上面创建的序列并自己跟踪索引。但是在 Raku 中,有一个选项可以修改 grep 例程簇,并要求它返回匹配项的索引而不是项本​​身。 另外,我们将使用更合适的方法 first 代替 grep。如果我们用 k 键调用该方法,它将返回第一个匹配项或其索引。仅仅提到键就足够了,真的不需要值。 say (0, 1, * + * ... *).first(*.chars >= 1000, :k) 该程序打印一个整数,这是给定问题的正确答案。 第2部分 现在让我们解决一个高尔夫任务并打印前30个斐波那契数字,用单行程序。这次,我们必须在代码中使用尽可能少的字符。 第一种方法相当冗长(即使使用 ^31 代替 0..30,它需要 33 个字符):

Red 中的 Parse

Parse 入门 Rebol 语言最大的特色之一就是它的解析引擎,简称为 Parse。这是卡尔·萨森拉斯(Carl Sassenrath)的一个惊人设计,在过去的15年里,所有 Rebol 用户都免于使用臭名昭著的无法维护的 regexp 的痛苦。现在,Parse 也可用于 Red 用户,而且是增强版! 那么,简而言之,什么是 Parse?它是一个嵌入式 DSL(我们称之为 Rebol 世界中的“方言”),用于使用语法规则解析输入序列。 Parse 方言是 TDPL 家族的增强成员。 Parse 的常见用法是检查,验证,提取,修改输入数据,甚至实现嵌入式和外部 DSL。 parse 函数调用语法很简单: parse <input> <rules> <input>: 任意系列值 (字符串, 文件, 块儿, 路径, ...) <rules>: 块儿! 具有有效 Parse 方言内容的值 这里有几个例子,即使你不懂 Red 和解析方言,你仍然可以“获得”它们中的大多数,不像正则表达式。您可以将它们直接复制/粘贴到 Red 控制台中。 使用语法规则进行字符串或块输入验证的一些简单示例: parse "a plane" [["a" | "the"] space "plane"] ;-- rule 中的空格需要用 space 显式声明 parse "the car" [["a" | "the"] space ["plane" | "car"]] parse "123" ["1" "2" ["4" | "3"]] ;-- rule 中的空格默认被忽略 parse "abbccc" ["a" 2 "b" 3 "c"] ;-- 一个 a, 俩个 b, 三个 c parse "aaabbb" [copy letters some "a" (n: length?

变量

变量名以一个叫做魔符 sigil 的特殊字符开头, 后面跟着一个可选的第二个叫做 twigil 的特殊字符, 然后是一个标识符. Sigils 符号 类型约束 默认类型 Flattens Assignment $ Mu (no type constraint) Any No item & Callable Callable No item @ Positional Array Yes list % Associative Hash Yes list 例子: my $square = 9 ** 2; my @array = 1, 2, 3; # Array variable with three elements my %hash = London => 'UK', Berlin => 'Germany'; 默认类型可以使用 is 关键字设置。 class FailHash is Hash { has Bool $!

第十四天 - Mojo::DOM 实例

随着最新版本的Mojolicious,Mojo::DOM获得了很多力量,我很兴奋尝试,但没有时间。最近,我在实际工作中遇到了一个问题(在工程,采购和施工,或简称EPC),我在很短的时间内用Mojo::DOM(和Mojolicious的其他部分)解决了 - 包括学习如何使用Mojo::DOM,我以前从未做过。 任务 - 简单,但乏味 3D模型和绘图是很棒的工具,但实际上并不是那么完美。建筑公差就是它们的原因,我们公司非常依赖激光扫描,我们在那里进入项目现场并创建一个竣工条件的点云。这会生成数百个需要花费数小时才能处理的文件 - 最近的一个项目有超过30亿个人点。这些对于我们所做的每个项目的工程,建模和构造都至关重要。 问题是当我们的3D建模软件(Tekla Structures)处理点云时,它会将每个文件名从人类可读的东西(例如Pipe Rack Area1)更改为散列,如2e9d52829f973c5b98f60935d8a9fa2b。当一个项目可能有几十个区域时,这不是非常用户友好,而您实际上只想一次加载一个或两个(平均面积为15gb!)。 点云提供的关键信息太难或太昂贵,无法直接建模 幸运的是,Tekla使用了许多任何人都可以编辑的标准文件格式 - 包括使用XML来描述它已处理的每个点云。当然,我可以手工编辑它们来更改名称,但是每次扫描和每个项目都必须这样做 - 这不是一个好的解决方案。 方便的是,XML文件包含哈希名称和原始文件名 - 所以我知道我可以使用Mojo::DOM来解析XML并将点云重命名为人类可读。这个简单的任务就是Mojolicious如何用相对较短的代码完成大量工作的完美例子。结果如下: #!/usr/bin/perl use Mojo::Base -base; use Mojo::Util qw(getopt); use Mojo::File; use Mojo::DOM; getopt 'p|path=s' => \my $path; sub main { # look in xml elements for laserscans that have hashes for names my $file = Mojo::File->new($path, 'pointclouds.xml'); my $dom = Mojo::DOM->new($file->slurp); # if 'Hash' is populated, rename_files(); otherwise ignore for my $e ($dom->find('PointCloudData')->each) { $e->{Folder} = rename_files($e) and $e->{Hash} = '' if $e->{Hash}; } # save xml file so we don't try to rename the pointclouds again $file->spurt($dom); } sub rename_files { # rename pointcloud folder and database file my $e = shift; my $newname = $e->{Folder} =~ s/$e->{Hash}/$e->{Name}/r; say "renaming: $e->{Folder} to:\n$newname"; rename $e->{Folder}, $newname || die $!

🎄 14/25. 昨天问题的另一个解决方案

欢迎来到 Raku One-Liner Advent Calendar 的第14天!今天,我们正在提出我们昨天解决的问题的另一个答案。任务是计算二十世纪第一个月的所有星期日。 昨天,我们只扫描了整个世纪的所有日子,选择星期日( .day-of-week == 7)并且是本月的第一天(.day == 1)。 可以制作更有效的算法。由于我们只对本月的第一天感兴趣,因此无需在100年内扫描所有36525天,而只需要扫描100天,这是1901年到2000年之间每个月的第一天。 因此,我们需要两个嵌套循环:年和月。我们需要两个 for 吗?不必要。我们使用 X 运算符;我们从以前的出版帖子中熟悉它。 这是我们今天的单行: (gather for 1901..2000 X 1..12 { take Date.new(|@_, 1) }).grep(*.day-of-week == 7).elems.say; for 1901..2000 X 1..12 循环在遍历二十世纪的每个月份。对于每个循环变量,我们通过调用具有三个参数的构造函数来创建 Date 对象。 请注意,在循环内部,您可以同时使用 $_[0] 和 $_[1],以及 @_[0] 和 @_[1]。在第一种情况下,$_ 变量包含两个元素的列表,而在第二种情况下,它是一个数组 @_。如果您只是使用点来调用主题(默认)变量上的方法,则可以实现最短的代码:.[0] 和 .[1]。 您可以键入 Date.new(.[0], .[1], 1) 代替 Date.new(|@_, 1)。 |@_ 语法用于展开数组,否则 Raku 会认为您将数组作为第一个参数传递。 在 gather-take 对儿的帮助下,将这些月份的所有第一天收集到序列中。 最后一步是昨天的 grep,但这次我们只需要选择星期日,所以单个 *.day-of-week == 7 条件就足够了。 elems 方法的调用返回列表中元素的数量,即我们正在寻找的星期日数量。因此,请用 say 打印出来。

使用 Spark HBase Connector 读取 HBase

pom 依赖: <!-- https://mvnrepository.com/artifact/com.hortonworks/shc-core --> <dependency> <groupId>com.hortonworks</groupId> <artifactId>shc-core</artifactId> <version>1.1.1-2.1-s_2.11</version> </dependency> 部分代码: package xxxx.xxxx import org.apache.log4j.{Level, Logger} import org.apache.spark.sql.execution.datasources.hbase._ import org.apache.spark.sql.functions._ import org.apache.spark.sql.{DataFrame, SparkSession} /** * 提交方式 * spark2-submit --name HBASE-CONNECTOR --files /etc/hbase/conf/hbase-site.xml --class xxx.xxxx --master yarn --deploy-mode cluster --driver-memory 2g --driver-cores 2 --executor-memory 2g --executor-cores 1 --num-executors 2 wmanxiety-1.0-SNAPSHOT.jar --day 20180810 --repartition 500 --interval 7 */ object MileageAnxiety { def cat = s"""{ |"table":{"namespace":"default", "name":"trip_signal", "tableCoder":"PrimitiveType"}, |"rowkey":"key", |"columns":{ |"rowkey" :{"cf":"rowkey", "col":"key", "type":"string"}, |"vin" :{"cf":"info", "col":"vin", "type":"string"}, |"tripStatus" :{"cf":"info", "col":"tripStatus", "type":"string"}, |"tripStartTime":{"cf":"info", "col":"tripStartTime", "type":"string"}, |"tripEndTime" :{"cf":"info", "col":"tripEndTime", "type":"string"}, |"tripDistance" :{"cf":"info", "col":"tripDistance", "type":"string"}, |"startSoc" :{"cf":"info", "col":"startSoc", "type":"string"}, |"endSoc" :{"cf":"info", "col":"endSoc", "type":"string"}, |"maxSpeed" :{"cf":"info", "col":"maxSpeed", "type":"string"}, |"startMileage" :{"cf":"info", "col":"startMileage", "type":"string"}, |"coordinate" :{"cf":"info", "col":"coordinate", "type":"string"} |} |}""".

Unicode vs. ASCII 符号

可以在 Raku 中使用以下 Unicode 符号,而无需加载任何其他模块。其中一些具有可以使用 ASCII 独有字符键入的等效物。这些变体通常由比 Unicode 版本更多的字符组成,因此它们看起来更大。 下面参考 unicode 码点的各种属性。最终列表可以在这里找到:https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt. 字母字符 任何具有 Ll(字母,小写),Lu(字母,大写),Lt(字母,标题),Lm(字母,修饰符)或 Lo(字母,其他)属性的代码点都可以像任何其他字母一样使用 ASCII 范围内的字符。 数字字符 任何具有 Nd(数字,十进制数字)属性的代码点都可以用作任何数字的数字。例如: my $var = 19; # U+FF11 U+FF19 say $var + 2; # OUTPUT: «21␤» 数字值 任何具有 No(Number,other)或 Nl(Number,letter)属性的代码点都可以单独用作数值,例如 ½ 和 ⅓。 (这些不是十进制数字,因此不能组合。)例如: my $var = ⅒ + 2 + Ⅻ; # here ⅒ is No and Rat and Ⅻ is Nl and Int say $var; # OUTPUT: «14.1␤» 空白字符 除了空格和制表符,您还可以使用具有 Zs(分隔符,空格),Zl(分隔符,行)或 Zp(分隔符,段落)属性的任何其他 unicode 空白字符。

第十三天 - 承担角色

在我之前的 Advent 文章中,我创建了高阶 promise并向您展示了如何使用它们。我没有告诉你它们如何工作的魔力。现在我将从另一个方向开发另一个例子。 有时候我希望 Mojo::File 的行为有点不同。我经常有一个路径,我想只将基本名称与不同的目录结合起来。我最终为两者制作 Mojo::File 对象,然后使用目录对象来获得我想要的东西: use Mojo::File qw(path); my $path = Mojo::File->new( '/Users/brian/bin/interesting.txt' ); my $dir = Mojo::File->new( '/usr/local/bin' ); my $new_path = $dir->child( $path->basename ); say $new_path; # /usr/local/bin/interesting.txt 那很烦人。我不喜欢它需要这么多步骤。我想要一些方法。我宁愿能够像这样编写它,我从有趣的文件开始并继续处理它而不是切换到其他对象: use Mojo::File qw(path); my $new_path = Mojo::File ->new( '/Users/brian/bin/interesting.txt' ) ->rebase( '/usr/local/bin' ); # this isn't a method say $new_path; # /usr/local/bin/interesting.txt 我可以通过各种 Perl 技巧通过猴子补丁或子类化将此方法添加到 Mojo::File。但是,像往常一样,Mojolicious 期待我的愿望并提供一种方法来做到这一点。我可以添加一个角色, 当我跳进它时,你可以自己阅读角色。首先,我创建一个代表我的角色的类。我定义了我想要的方法。我使用我想要影响的包的名称,添加 ::Role::,然后我想使用的名称;它的小写并不重要。 Mojo::Base 设置了当我导入 -role 时所需的一切: package Mojo::File::Role::rebase { use Mojo::Base qw(-role -signatures); sub rebase ($file, $dir) { $file->new( $dir, $file->basename ) } } 我在我想要影响的类上使用 with_roles 来应用我的新功能。因为我使用命名约定通过在它前面加上目标类(Mojo::File),然后是 ::Role::,然后是我想要的短名称。当我应用这个时,我可以省去大部分包裹名称,并使用前面加号的短名称:

🎄 13/25. 本世纪多少天符合条件?

欢迎来到 Raku One-Liner Advent Calendar 的第13天!今天的单行将很长,最好用两行编写,但它会显示 Raku 的 Date 对象的一个​​非常好的功能:它可以很容易地在一个范围内使用。 今天,我们正在解决欧拉项目的问题19。从本质上讲,任务是计算在1901年1月1日至2000年12月31日期间的星期日,这是在第一个月。 Raku 中的 Date 对象实现了 succ 和 prec 方法,它们递增和递减日期。也可以使用两个日期作为范围的边界: say ( Date.new(year => 1901) ..^ Date.new(year => 2001) ).grep({.day == 1 && .day-of-week == 7}).elems 在这里评论一下。 首先,使用单个命名参数(year)创建两个 Date 对象。这是可能的,因为构造函数的签名具有月和日的默认值: multi method new( Date: Int:D() :$year!, Int:D() :$month = 1, Int:D() :$day = 1, :&formatter, *%_) { . . . } 因此,创建1月1日的日期很容易,但是在一年的最后一天你不能这样做。但是 Raku 有一个很好的范围运算符 ..^,它排除了右边界并允许我们节省相当多的字符(而我们还没有玩 Raku Golf :-)。 具有日期的所有显式部分的较长版本将如下所示: say ( Date.

第十三天 - 使用 Cro 和 Debian 从头构建 Web 服务

我和圣诞老人​​谈过,他说他不知道如何在 Debian 上安装 Cro,所以我对自己说:我要帮助他。 如果您对 Apache 等 Web 服务器有一些经验,并且您已经听说过 Raku 强大的并发/响应方面,那么您肯定有兴趣了解 Cro 服务。这篇文章的受众是具有 Debian 基本经验的人,或者在 Raku 新手…就像圣诞老人一样。 Cro 是一个 Raku 模块,它提供了轻松构建反应式和并发服务所需的一切,例如:Web 服务器。 在这篇文章中,我们将看到如何在 Debian 中安装 Cro,这是最受欢迎的 Linux 发行版之一。然后,我将解释 Cro 的演示示例。 我们将使用 Debian 9,64 位(Stretch),我们将在安装后启动它。 安装 Rakudo Raku 编译器 Rakudo 是 Cro 模块运行的当前 Raku 编译器。安装 Rakudo 的常规方法是安装 Rakudo Star,但我更喜欢快速的方式:rakudo-pkg ……怎么样?只需从此 repo 下载并安装相应的 Debian 软件包。在我们的例子中,是来自 Debian 9, 64位的 .deb 文件。 使用 Debian 中的 root 用户,我们可以在 root home 中为 Rakudo 创建一个包文件夹,进入这个目录,下载 Debian 9, 64 位的当前 Rakudo 包,并安装它。就我而言:

使用 Phoenix 读取 HBase

我使用的是 CDH5.13.2, 所以 pom 文件依赖为: <dependency> <groupId>org.apache.phoenix</groupId> <artifactId>phoenix-spark</artifactId> <version>4.14.0-cdh5.13.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.phoenix</groupId> <artifactId>phoenix-core</artifactId> <version>4.14.0-cdh5.13.2</version> </dependency> 这样读取: val df = sqlContext.read.format("jdbc") .option("driver", "org.apache.phoenix.jdbc.PhoenixDriver") .option("url", "jdbc:phoenix:localhost:2181") .option("dbtable", "US_POPULATION") .load() 或者 val df = sqlContext.load( "jdbc", Map("zkUrl" -> "localhost:2181", "url" -> "jdbc:phoenix:localhost:2181", "dbtable" -> "US_POPULATION", "driver" -> "org.apache.phoenix.jdbc.PhoenixDriver") )

第十二天 - 在 Dancer 应用程序中使用 Minion

在 $work,我们使用 Dancer 构建了一个 API,用于生成 PDF 文档和 XML 文件。此 API 是保险登记系统的重要组成部分:生成 PDF 以立即在 Web 浏览器中传送给客户端,并且 XML 一旦可用就会立即传送给运营商。由于XML 通常需要花费大量时间来生成,因此在后台生成作业,以免在较长时间内占用应用程序服务器。完成后,开发了一个自行开发的流程管理系统,并通过 fork() 进程,跟踪其 pid,并希望我们以后可以成功获得完成的流程。 这种方法存在一些问题: - 它很脆弱 - 它不能扩展 - 作为开发人员搞砸某事太容易了 在2019年,我们不得不加大承担更大的工作量。目前的解决方案根本无法处理我们预计需要处理的工作量。z直到遇见 Minion。 注意:本文中使用的技术与 Dancer 或 Dancer2 同样适用。 为何选择 Minion? 我们研究了 Minion 的几种替代品,包括 beanstalkd 和 celeryd。然而,使用其中任何一个意味着涉及我们已经过度征税的基础设施团队;使用 Minion 允许我们使用我的团队已经拥有的专业知识,而不必向其他人提供帮助。从开发的角度来看,使用 Perl 开发的产品为我们提供了最快的实施时间。 扩展我们现有的设置几乎是不可能的。处理我们分叉的进程所消耗的资源不仅难以处理,而且不可能在多个服务器上运行作业。从 Minion 开始也给了我们一个急需的机会来清理一些需要重构的代码。通过最少量的工作,我们能够清理我们的 XML 呈现代码并使其在 Minion 中运行。通过此清理,我们可以更轻松地获取有关XML呈现作业消耗了多少内存和 CPU 的信息。这些信息对于我们规划未来容量至关重要。 进入 Minion 由于我们是一个舞蹈工作室,而不是 Mojolicious,因此我们可以从 Mojolicious 那里获得很多与 Minion 合作的便利。鉴于我们还在商业模式中共享一些基于 Minion 的代码,我们必须围绕 Minion 构建一些自己的管道: package MyJob::JobQueue; use Moose; use Minion; use MyJob::Models::FooBar; with 'MyJob::Roles::ConfigReader'; has 'runner' => ( is => 'ro', isa => 'Minion', lazy => 1, default => sub( $self ) { $ENV{ MOJO_PUBSUB_EXPERIMENTAL } = 1; Minion->new( mysql => MyJob::DBConnectionManager->new->get_connection_uri({ db_type => 'feeds', io_type => 'rw', })); }, ); 我们在 Minion 周围包含了一个简单的 Moose 类,以便使用我们想要的额外功能轻松添加到任何类或 Dancer 应用程序。

Unicode

Raku 对 Unicode 有很高的支持。本文档旨在概述和描述不属于例程和方法文档的 Unicode 功能。 有关 MoarVM 内部字符串表示的概述,请参阅 MoarVM 字符串文档。 文件句柄和输入输出 标准化 默认情况下,Raku 对所有输入和输出应用标准化,但存储为 UTF8-C8 的文件名除外;字形是用户可见的字符形式,将使用标准化表示。这是什么意思?例如,字形数字 á 可以用两种方式表示,或者使用一个代码点: á (U+E1 "LATIN SMALL LETTER A WITH ACUTE") 或两个代码点: a + ́ (U+61 "LATIN SMALL LETTER A" + U+301 "COMBINING ACUTE ACCENT") Raku 将这两个输入转换为一个代码点,如规范化形式 C(NFC)所指定的那样。在大多数情况下,这很有用,意味着两个相同的输入都被视为相同。 Unicode 具有规范等价的概念,它允许我们确定字符串的规范形式,允许我们正确地比较字符串并操纵它们,而不必担心文本丢失这些属性。默认情况下,您处理或从 Raku 输出的任何文本都将采用此“规范”形式,即使在对字符串进行修改或连接时也是如此(请参阅下文,了解如何避免这种情况)。有关规范化表单C和规范等效性的更多详细信息,请参阅Unicode Foundation 的规范化和规范等效性页面。 我们不默认的一种情况是文件名。这是因为必须完全访问文件的名称,就像在磁盘上写入字节一样。 为避免规范化,您可以使用名为 UTF8-C8 的特殊编码格式。将此编码与任何文件句柄一起使用将允许您读取磁盘上的确切字节,而不进行规范化。如果使用 UTF8 句柄打印出来,打印出来时看起来会很滑稽。如果将其打印到输出编码为 UTF8-C8 的句柄,则它将按照您通常的预期进行渲染,并且是字节精确复制的字节。有关 MoarVM 上 UTF8-C8 的更多技术细节, 请参见下文。 UTF8-C8 UTF-8 Clean-8 是一种编码器/解码器,主要用作 UTF-8。但是,遇到一个不能解码为有效 UTF-8 的字节序列,或者由于规范化而不会往返的字节序列时,它将使用 NFG 合成来跟踪所涉及的原始字节。这意味着编码回 UTF-8 Clean-8 将能够重新创建它们最初存在的字节。合成物包含4个代码点:

类型系统

Raku类型的定义 类型通过创建类型对象来定义新对象,该类型对象提供用于创建对象实例或检查值的接口。任何类型对象都是 Any 或 Mu 的子类。通过从这些基类和内省后缀 .^ 继承来提供内省方法。在编译时由以下类型声明符之一或在运行时使用元对象协议将新类型引入当前作用域。所有类型名称的作用域必须是唯一的。 默认类型 如果用户没有提供类型,则 Raku 假定类型为 Any。这包括容器,基类,参数和返回类型。 my $a = 1; $a = Nil; say $a.^name; # OUTPUT: «Any» class C {}; say C.^parents(:all); # OUTPUT: «((Any) (Mu))» 对于容器,默认类型为 Any,但默认类型约束为 Mu。请注意,绑定会替换容器,而不仅仅是值。在这种情况下,类型约束可能会变。 类型对象 要测试对象是否为类型对象,请对使用类型为 smiley 或 .DEFINITE 方法约束的类型使用 smartmatch: my $a = Int; say $a ~~ Mu:U; # OUTPUT: «True» say not $a.DEFINITE; # OUTPUT: «True» 如果调用者是实例,则 .DEFINITE 将返回 True。如果它返回 False,则调用者是一个类型对象。 Undefinedness 未定义的对象在 Raku 中维护类型信息。类型对象用于表示未定义值和未定义值的类型。要提供一般的未定义值,请使用 Any。如果要区分容器和参数的默认类型 Any,则需要使用 Mu。

五个用于提取和探索复杂数据类型的 Spark SQL Helper 实用程序函数

https://docs.databricks.com/_static/notebooks/complex-nested-structured.html 虽然这个深入的博客列出并解释了处理和处理复杂数据类型和格式的概念和动机,但这个笔记本示例通过几个具体示例检查了如何将它们应用于您在使用中可能遇到的数据类型案例。这个简短的笔记本教程展示了一些方法,您可以在其中探索和使用许多新的帮助程序 Spark SQL 实用程序函数和 API 作为org.apache.spark.sql.functions 包的一部分。特别是,它们在执行 Streaming ETL 时派上用场,其中数据是具有复杂和嵌套结构的 JSON 对象:嵌入为 JSON 的 Map 和 Structs: _ get_json_object() _ from_json() _ to_json() _ explode() _ selectExpr() 本简短教程的内容是使用 Spark SQL 实用程序函数对嵌套 JSON 结构进行切片和切块的无数方法。 让我们创建一个带有属性和值的简单 JSON 模式,而不使用任何嵌套结构。 import org.apache.spark.sql.types._ // include the Spark Types to define our schema import org.apache.spark.sql.functions._ // include the Spark helper functions val jsonSchema = new StructType() .add("battery_level", LongType) .add("c02_level", LongType) .add("cca3",StringType) .add("cn", StringType) .add("device_id", LongType) .

🎄 12/25. 在 Raku 中, 0.1 + 0.2 背后的东西

欢迎来到今年 12/25 的 Raku One-Liner Advent Calendar! 的第12天。今天,我们将研究一个计算零的单行程序。 say 0.1 + 0.2 - 0.3 如果您熟悉编程,那么您很清楚,只要开始使用浮点运算,就会失去精度,并且很快就会面对小错误。 您可能还看到了网站,0.30000000000000004.com,它有很多不同的编程语言列表,以及它们如何打印简单的表达式 0.1 + 0.2。在大多数情况下,您没有得到 0.3 这个准确值。通常当你得到零时,它实际上是在打印操作期间舍入的结果。 在 Raku 中,0.1 + 0.2正好是 0.3,今天的单行程序正好打印了一个零。 让我们稍微探讨一下 Raku 的内部结构,看看它是如何工作的。几天前,我们看到 Raku 的 grammar(在 Rakudo 编译器中实现)具有以下检测数字的片段: token numish { [ | 'NaN' >> | <integer> | <dec_number> | <rad_number> | <rat_number> | <complex_number> | 'Inf' >> | $<uinf>='∞' | <unum=:No+:Nl> ] } 假如你很熟悉 Raku,就会知道 Raku 使用有理数来存储浮点数(如0.1)这一事实可以解释上述行为。这是对的,但是看一下 grammar,你会发现这篇文章有点长。 Grammar 中所谓的 rat_number 是用尖括号写的数字:

第十二天 - 构建灵活的 grammar

圣诞老人夫人写了一个基础的 Grammar,以配合 GDPR 无知精灵从世界各地收集的有关今年 naughty 或 nice 的人的简单列表。 每个记录都是一个名称,后跟一个标签,后跟一个地址,然后是一个标签,然后是 naughty 或 nice 的评估,然后用换行符结束。 Batman 1 Batman Street, Gotham Nice Joker 5 Joker Street, Arkham Naughty Riddler 12 Riddler Street, Arkham Naughty Robin 2 Robin Street, Gotham Nice 她希望将 naughty 的人排除在一个列表中,将 nice 的人过滤到另一个列表中,因为 Krampus 将在今年处理所有 naughty 的人。 S.夫人用这样的 grammar 开头: grammar naughty-nice-list { token TOP { <details>+ } # Find one or more records made up of name, address, assessment (defined below) token details { <name> <address> <assessment> } # Find the elements from below, in this order token name { .

第十一天 - 谁在看着 Minions

现在我有一个 Minion 工作队列,我需要妥善处理它。工人是否在工作(他们是否抓住了生产资料)?工作是否顺利完成?有什么错误吗?这些是什么? Minion Jobs Command Minion 带有一个 job 命令,列出了作业及其状态。 $ perl myapp.pl minion job 6 inactive default check_url 5 active default check_url 4 failed default check_url 3 failed default check_url 2 finished default check_url 1 finished default check_url 我可以通过传递工作的ID来查看单个工作的信息。 $ perl minion.pl minion job 1 { "args" => [ "http://mojolicious.org" ], "attempts" => 1, "children" => [], "created" => "2018-11-23T19:15:47Z", "delayed" => "2018-11-23T19:15:47Z", "finished" => "2018-11-23T19:15:48Z", "id" => 1, "notes" => {}, "parents" => [], "priority" => 0, "queue" => "default", "result" => "0.

使用 HBase 的 FilterList 过滤器

package wmstat.trip import org.apache.hadoop.hbase.client._ import org.apache.hadoop.hbase.io.ImmutableBytesWritable import org.apache.hadoop.hbase.mapreduce.TableInputFormat import org.apache.hadoop.hbase.HBaseConfiguration import org.apache.hadoop.hbase.filter._ import org.apache.hadoop.hbase.protobuf.ProtobufUtil import org.apache.hadoop.hbase.util.{Base64, Bytes} import org.apache.spark.sql.SQLContext import org.apache.spark.{SparkConf, SparkContext} import wmutils.WmTimeUtil._ import scala.collection.mutable.ArrayBuffer object HBaseSpark { def main(args:Array[String]): Unit ={ // 本地模式运行,便于测试 val sparkConf = new SparkConf().setMaster("local").setAppName("HBaseTest") // 创建 HBase 扫描器 val scan = new Scan() // val filter=new RowFilter(CompareFilter.CompareOp.GREATER_OR_EQUAL, new RegexStringComparator("^[a-zA-Z0-9]+_20180903[0-9]{6}$")) //使用正则表达式过滤近一个月的 // scan.setFilter(filter) // 过去 7 天 val arrayWeek: ArrayBuffer[String] = lastestNdays("", 7) // filterList val filterList = new FilterList(FilterList.

Traits

在 Raku 中,traits是附加到对象和类的编译器钩子,它们修改了类和对象的默认行为,功能或表示。作为这样的编译器钩子,它们是在编译时定义的,尽管它们可以用于运行时。 通过使用 trait_mod 关键字,已经将几个 traits 定义为语言或 Rakudo 编译器的一部分。接下来列出并解释它们。 is trait 定义为 proto sub trait_mod:<is>(Mu $, |) {*} is 适用于任何类型的标量对象,并且可以接收任意数量的命名参数或位置参数。它是最常用的 trait,取决于第一个参数的类型,采用以下形式。 is 应用于类 最常见的形式涉及两个类,一个正在定义,另一个现有,定义为 defines parenthood。 A is B, 如果两个都是类,则将 A 定义为 B 的子类。 is DEPRECATED 可以应用于类,属性或例程,将它们标记为已弃用并发出警告消息(如果提供了的话)。 is 的几个实例被直接转换为它们引用的类的属性:rw,nativesize,ctype,unsigned,hidden,array_type。 不可实例化的表示 trait 与表示没有多大关系,与特定类可以做什么有关; 它有效地防止以任何可能的方式创建类的实例。 constant @IMM = <Innie Minnie Moe>; class don't-instantiate is repr('Uninstantiable') { my $.counter; method imm () { return @IMM[ $.counter++ mod @IMM.elems ]; } } say don't-instantiate.

🎄 11/25. 在 Raku 中解决34号问题

欢迎来到 Raku One-Liner Advent Calendar 的第11天! 今天,日历文章完全致力于解决欧拉计划的问题34。 如果你想在看到我的答案之前找到自己的答案,请再次暂停阅读。 因此,任务是找到所有数字的总和,它们等于其数字的阶乘的总和。 听起来很清楚? 🙂 你可以看一下单行程序的解决方案,以便更好地理解它。 say [+] (3..50_000).grep({$_ == [+] .comb.map({[*] 2..$_})}) 让我们从……开始吧。 我们循环范围 3 .. 50_000。上边界是基于某些要考虑因素的猜测。我不会在这里解释,但如果你好奇,你可能会试着找到答案。基本上,在某些时候你会理解这个数字要么包含太多数字,要么本身就太大了。请参阅有关 Project Euler 的讨论以获得纯理论的解释。 第二步是 grep。我们正在搜索与总和相等的数字($_ ==)。它是通过第二个化简加号 [+] 计算出来的,但你可以使用 sum 方法代替: {$_ == .comb.map({[*] 2..$_}).sum} 请注意 .comb 是一个在默认变量 $_ 上调用的方法。 comb 方法将数字拆分为单独的数字(作为字符)。 map 方法将每个数字转换为阶乘(再次使用化简运算符 [*],就像我们昨天所做的那样)。 最后,最外层的 [+] 将所有 grepped 的数字相加,并将结果传递给 say 例程。 虽然主要想法是展示一个单行,但在实际操作中,在使用它们之前准备因子是更明智的: my @f = (0..9).map({[*] 1..$_}); say [+] (3..50_000).grep({$_ == [+] .comb.map({@f[$_]})}); 这就是今天故事的结局。这个日历的其余部分还有更多内容!

第十天 - Minion Stands Alone

Minion 作业队列是一个非常有用的工具,但有时我需要将它用于非 Web 项目。那么,如何在不需要 Mojolicious Web 应用程序的情况下使用 Minion? 如果我没有足够大的工作需要 Beam::Minion 提供的任务组织,虽然 Minion 可以用作完全独立的库,但最简单的解决方案就是构建一个 Mojolicious 应用程序。幸运的是,一个 Mojolicious 应用程序可以只有2行: use Mojolicious::Lite; app->start; 现在,如果我从未运行过 web 守护进程,那么这绝不会是一个网站。那么,如果没有人使用网络浏览器访问它,这可以称为 Web 应用程序吗?🤔 Add Minion 要使用 Minion,请将 Minion 插件添加到应用程序。对于这个例子,我将使用 SQLite Minion 后端,这样我就不需要运行单独的数据库进程,但如果你有一个接受远程连接的数据库,Minion 可以在多台机器上工作。 plugin Minion => { SQLite => 'sqlite:' . app->home->child('minion.db'), }; 加载 Minion 插件后,我的应用程序获得了一些新功能: 我可以使用 minion.add_task 助手程序添加 Minion 任务(可运行的代码) 我可以通过多种方式排队工作: 从命令行使用 minion job 命令 从应用程序内部使用 minion.enqueue 助手程序 从任何 Perl 脚本加载 Minion 并使用 enqueue 方法 我可以使用 minion worker 命令运行一个 Minion worker,它将执行任何排队的作业 Create a Task 我将创建一个名为 check_url 的任务来检查下载 URL 所需的时间。 Time::HiRes 核心模块将为我提供高分辨率时间。

使用 Spark 读取 HBase

package wmstat.trip import org.apache.log4j.{Level, Logger} import org.apache.spark.sql.execution.datasources.hbase._ import org.apache.spark.sql.functions._ import org.apache.spark.sql.{DataFrame, SparkSession} import scopt.OptionParser import wmutils.WmTimeUtil._ import scala.collection.mutable.ArrayBuffer /** * 提交方式 * spark2-submit --name HBASE-CONNECTOR --files /etc/hbase/conf/hbase-site.xml --class wmhbase.MileageAnxiety --master yarn --deploy-mode client --driver-memory 2g --driver-cores 2 --executor-memory 2g --executor-cores 1 --num-executors 2 wmanxiety-1.0-SNAPSHOT.jar --day 20180810 --repartition 500 --interval 7 */ object MileageAnxiety { def cat = s"""{ |"table":{"namespace":"default", "name":"trip_signal", "tableCoder":"PrimitiveType"}, |"rowkey":"key", |"columns":{ |"rowkey" :{"cf":"rowkey", "col":"key", "type":"string"}, |"vin" :{"cf":"info", "col":"vin", "type":"string"}, |"tripStatus" :{"cf":"info", "col":"tripStatus", "type":"string"}, |"tripStartTime":{"cf":"info", "col":"tripStartTime", "type":"string"}, |"tripEndTime" :{"cf":"info", "col":"tripEndTime", "type":"string"}, |"tripDistance" :{"cf":"info", "col":"tripDistance", "type":"string"}, |"startSoc" :{"cf":"info", "col":"startSoc", "type":"string"}, |"endSoc" :{"cf":"info", "col":"endSoc", "type":"string"}, |"maxSpeed" :{"cf":"info", "col":"maxSpeed", "type":"string"}, |"startMileage" :{"cf":"info", "col":"startMileage", "type":"string"}, |"coordinate" :{"cf":"info", "col":"coordinate", "type":"string"} |} |}""".

系统交互

通过命令行获取参数 最简单的方法是使用 @*ARGS 变量从命令行获取参数;此数组将包含程序名称后面的字符串。 %*ENV 将包含环境变量,因此如果您使用: export API_KEY=1967196417966160761fabc1511067 ./consume_api.p6 您可以通过以下方式在程序中使用它们: my $api-key = %*ENV<API_KEY> // die "Need the API key"; 如果先前未定义环境变量 API_KEY,则此操作将失败。 Raku 有一个更好的方法来处理命令行参数,如果它们代表文件名:那么使用 $*ARGFILES 动态变量。 for $*ARGFILES.lines -> $l { say "Long lines in {$*ARGFILES.path}" if $l.chars > 72 ; } 例如,你可以用 argf​​iles.p6 *.p6 的方式运行这个程序,每次找到一个超过72个字符的行时,它就会打印一个文件名。 $*ARGFILES 包含命令行中描述的所有文件的文件句柄 - .lines 将依次读取每行文件的一行,每次处理新句柄时都会更改 $*ARGFILES.path 的值。通常,它为处理文件集的脚本提供了非常方便的 API。 以交互方式获取参数 使用 prompt 让一个正在运行的程序向用户查询数据: my UInt $num-iters = prompt "How many iterations to run: "; 同步和异步运行程序 运行外部程序有两个例程:run 和 shell。两者都存在于 IO 角色中,因此包含在混合该角色的所有类中,如 IO::Path。两者都返回一个 Proc 对象,但主要区别在于 run 会尽可能避免系统 shell,而 shell 会通过默认系统 shell 运行命令。

🎄 10/25. Raku 中的化简运算符

欢迎来到 Raku One-Liner Advent Calendar 的第10天!今天,将有三个单行命令而不是一个。 我们今天的客人是一个带有方括号的化简结构。当它们不包围数组索引时,它们就工作在完全不同的领域中了。 例 1 最经典的例子,也是 Raku Calendar 2019,使用化简运算符来计算阶乘: say [*] 1..2019 Raku 中的 [ ] 是一个化简元运算符。名字里面的的「元」告诉我们它可以用作另一个运算符的包层(顺便说一句,不仅仅是作用于运算符)。 在第一个示例中,该运算符包含另一个运算符,并且可以通过将范围注册到列表并将 * 放在其所有元素之间来重写整行: say 1 * 2 * 3 #`(more elements) * 2018 * 2019 例2 现在,让我们解决欧拉项目的问题5,我们需要找到最小的数字,这个数字可以被从1到20的所有数字整除。 让我在 Raku 中给你一个直接的答案: say [lcm] 1..20 此代码看起来与前面的示例非常相似,但使用另一个运算符 lcm 例程,它是 Raku 中的中缀运算符。这个名字代表最小公倍数,但在文档中你还可以读到它返回这两个参数都可以被整除的最小整数。几乎相同的词,用于表达我们要解决的问题。 say 1 lcm 2 lcm 3 lcm 4 lcm 5 lcm 6 lcm 7 # ... and up to 20 例子3 其他已经内置在 Raku 中的中缀运算符也可以非常高效。这是一个只用几个代码字符旋转矩阵的例子:

第九天 - 为你的 Mojolicious 应用添加主题系统

你写了一个很棒的Mojolicious应用程序,人们使用它。奇妙!但是用户可能想要修改应用程序的主题:更改徽标,使用其他CSS框架,这类事情。 修改Mojolicious应用程序的主题非常简单:在公共和模板中添加,修改或删除内容。但是所有这些直接修改可能无法在更新应用程序时继续存在:它们只会被新版本的文件删除。 让我们看看我们如何提供一种在Mojolicious应用程序中拥有主题系统的方法,该方法允许用户在没有痛苦的情况下拥有自定义主题,并且没有丢失更新的风险。 一个新的应用程序 当您使用mojo生成MyApplication创建新的Mojolicious应用程序时,这些是将提供文件及其默认内容的默认目录: $ tree public templates public └── index.html templates ├── example │ └── welcome.html.ep └── layouts └── default.html.ep 2 directories, 3 files public是存储静态文件的地方,而templates是存储模板的地方。 这些路径在$ app-> static-> paths和$ app-> renderer-> paths中的Mojolicious应用程序中注册。幸运的是,这两个对象是数组引用,因此我们可以向它们添加或删除目录。 当提供静态文件时,我们的应用程序在$ app-> static-> paths数组的第一个目录中搜索该文件,如果找不到它,则搜索下一个目录,依此类推。模板渲染也是如此。 让我们改变路由 我们可以将公共模板和模板默认目录保存在应用程序目录的根目录下,但我喜欢在一个名为themes的目录中重新组合所有与主题相关的东西并调用我的默认主题…好吧,默认。 创建新目录并在其中移动默认主题目录: $ mkdir -p themes/default $ mv public templates themes/default 然后,我们需要更改应用程序中的路径。在 lib/MyApplication.pm 中添加: # Replace the default paths $self->renderer->paths([$self->home->rel_file('themes/default/templates')]); $self->static->paths([$self->home->rel_file('themes/default/public')]); 添加使用其他主题的方法 如前所述,Mojolicious在已注册路径的第一个目录中搜索静态文件或模板,如果找不到文件或模板则转到下一个。 因此,我们需要在默认主题路径之前添加新主题路径。 假设我们创建了一个圣诞主题,其中文件位于主题/圣诞节/公共内容中,哪些模板位于主题/圣诞节/模板中。 我们要添加到代码中的代码段变为: # Replace the default paths $self->renderer->paths([$self->home->rel_file('themes/default/templates')]); $self->static->paths([$self->home->rel_file('themes/default/public')]); # Put the new theme first unshift @{$self->renderer->paths}, $self->home->rel_file('themes/christmas/templates'); unshift @{$self->static->paths}, $self->home->rel_file('themes/christmas/public'); 通过这种方式,我们可以重载默认文件。

语法

Raku 借用了人类语言中的许多概念。考虑到它是由语言学家设计的,这并不奇怪。 它重用不同语境中的共同元素,具有名词(术语)和动词(运算符)的概念,是上下文敏感的(在日常意义上,不一定在计算机科学解释中),因此符号可以具有不同的含义取决于名词或动词是否是预期的。 它也是自同步的,因此解析器可以检测大多数常见错误并提供良好的错误消息。 词法约定 Raku 代码是 Unicode 文本。当前的实现支持 UTF-8 作为输入编码。 也参阅 Unicode versus ASCII symbols. 自由形式 Raku 代码也是自由格式的,从某种意义上说,你可以自由选择你使用的空格量,尽管在某些情况下,空格的存在与否具有意义。 所以你可以写 if True { say "Hello"; } 或 if True { say "Hello"; # Bad indentation intended } 或 if True { say "Hello" } 或者甚至 if True {say "Hello"} 虽然你不能省略任何剩余的空白。 Unspace 在编译器不允许空格的许多地方,只要用反斜杠引用,就可以使用任意数量的空格。不支持 token 中的空格。当编译器生成行号时,未空格的换行仍然计算。用于非空格的用例是后缀运算符和例程参数列表的分离。 sub alignment(+@l) { +@l }; sub long-name-alignment(+@l) { +@l }; alignment\ (1,2,3,4).say; long-name-alignment(3,5)\ .say; say Inf+Inf\i; 在这种情况下,我们的目的是让 .

🎄 9/25. 关于 X 的更多东西, Raku 中的 .. 和 …

欢迎来到 Raku One-Liner Advent Calendar 的第9天! 在第6天,我们有一个带有交叉算子的构造,(999...100) X* (999...100)。 今天,我们将从11月开始进入类似的结构: 1..10 X* 1..10 它打印乘积表的项,数字从1到10: (1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100) 关于这样的结构,有几点需要了解。

第八天 - 使用 LDAP 进行身份验证

仍然有不少人在生产中使用LDAP,但对于那些不熟悉它的人来说,LDAP是一个具有树结构的目录,该树结构针对非常快速的查找进行了优化。它曾经是非常常见的集中式身份验证系统,如果您使用的是Active Directory,那么您(主要是)使用LDAP。我漫游在身份验证的荒野中,最后是我的解决方案,如何将LDAP身份验证添加到您的应用程序。 这篇文章是基于我在2018年伦敦Perl研讨会上发表的一篇演讲。我们有点乐观地认为他们会在圣诞节之前编辑所有视频,但我们希望能有一种顿悟。 LDAP只是验证周期的一小部分,所以这篇文章对于你必须编写自己的凭证检查器的情况来说相当好。哦,这篇文章的谈话和写作完全符合我的意图和提出的问题,我没有考虑过这些问题。结果,它开始像爱丽丝的餐厅一样,没有完整的编排和五部分和谐。我希望这个警示故事可以帮助你避免陷入同样的​​陷阱。 在此期间,请参加MojoConf 2018的Lightning Talk。 路由 - lib/MyApp.pm 首先,一个忏悔。我从未真正进入过Lite Apps。我知道很容易将它们发展为完整的应用程序,但是当我开始并且从未回到它时,我面临着制定解决方案的压力。结果是这篇文章是关于认证一个完整的应用程序,并不像其他帖子谈论他们的Lite应用程序那样苗条。 直接进入,我们假设您已经在模板中有一个登录页面,并且它有一个将数据发布到/ login的表单。如果你有这样的路线 $r->post('/login')->name('do_login')->to('Secure#on_user_login'); 将凭据发送到您的控制器。或者,如果你对命名路线很酷,你的模板可能会包含这一行 <form action="<%= url_for 'do_login' %>" method="POST"> 专业提示:你甚至可以简化它 %= form_for 'do_login' 如果路由只处理POST,它会为您完成所有操作,包括方法。 控制器 - lib / MyApp / Controller / Secure.pm 让我们从 Mojolicious Cookbook 中汲取灵感。 package MyApp::Controller::Secure; use Mojo::Base 'Mojolicious::Controller'; sub on_user_login { my $self = shift; my $username = $self->param('username'); my $password = $self->param('password'); if (check_credentials($username, $password)) { $self->render(text => 'Hello Bender!'); } else { $self->render( text => '<h2>Login failed</h2><a href="/login">Try again</a>', format => 'html', status => 401 ); } } sub check_credentials { my ($username, $password) = @_; return $username eq 'Bender' && $password eq 'rocks'; } 存储密码 - MojoX::Auth::Simple 我们同意硬编码用户名和密码是不可持续的。如果您可以连接到数据库,Perl DBI 模块可以连接的任何数据库,那么您可能会认为 MojoX::Auth::Simple 将解决您的问题。进一步阅读将告诉您它只提供帮助方法 log_in,is_logged_in 和 log_out,这些方法对于身份验证周围的所有内容都很有用,但对身份验证本身不起作用。但是,既然您现在正在使用数据库,那么您可以将 check_credentials 更改为比这更好的东西(wot是在周五下午制作而未经过测试)

下标

Subscripts 通过索引或键访问数据结构中的元素。 通常,人们需要引用集合或数据结构中的一个特定的元素(或特定的元素切片)。从数学标记法中偷学到的,向量 v 的组成部分用 v₁, v₂, v₃ 来引用,在 Raku 中这个概念叫做 “下标” (或“索引”)。 Basics Raku 提供了两个通用的下标接口: elements are identified by interface name supported by [ ] zero-based indices Positional Array, List, Buf, Match, ... { } string or object keys Associative Hash, Bag, Mix, Match, ... Positional 下标 (通过 postcircumfix [ ] 通过元素在有序集合中的位置来寻址元素。)索引 0 引用第一个元素, 索引 1 引用第二个元素, 以此类推: my @chores = "buy groceries", "feed dog", "wash car"; say @chores[0]; #-> buy groceries say @chores[1]; #-> feed dog say @chores[2]; #-> wash car Associative 下标 (通过 postcircumfix { }), 不要求集合以任何特定的顺序保存元素 - 相反,它使用一个唯一的键来寻址每个值。键的种类取决于使用的集合: 举个例子, 一个标准的散列 使用字符串作为键, 而一个 Mix 能使用任意的对象作为键, 等等: my %grade = Zoe => "C", Ben => "B+"; say %grade{"Zoe"}; #-> C say %grade{"Ben"}; #-> B+ my $stats = ( Date.

🎄 8/25. 在 Raku 中加总偶数斐波纳契数

欢迎来到今年的 Raku One-Liner Advent Calendar 的第8天。 它大约是整个系列的 ¼,并且不要忘记你可以在 Raku 中键入 ¼ 而不是0.25! 今天,我们正在解决欧拉项目的问题2。 任务是找到所有偶数斐波纳契数都低于四百万的数字的总和。 这是完整的答案: (1, 1, * + * ... * > 4_000_000).grep(* %% 2).sum.say 从左侧或右侧解析代码同样有趣。 我们从左边开始吧。 在第一个圆括号内,我们生成一个斐波纳契数序列,从两个1开始,每个跟随的数字是前两个数之和。 在 Raku 中,您可以使用 WhateverCode 块表达它:* + * 相当于 {$^a + $^b}。 Raku 序列的一个鲜为人知的特征是最终条件。 在许多例子中,你会看到裸星或 Inf。 在我们的示例中,我们使用显式的上边界限制序列。 请注意,您不能简单地写成这样: 1, 1, * + * ... 4_000_000 要更好地将其可视化,请尝试更小的限制,例如100: > (1, 1, * + * ... 100) (1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 当序列越过我们想要的边界时,Raku 没有停止,并继续生成数字。 只有当序列的下一个计算元素完全等于给定数字时,它才会停止它,例如:

第七天 - MetaCPAN, Mojolicious 和 OpenAPI

在这几年meta :: hack 3中,我非常幸运地与Joel Berger合作,通过Mojolicious将OpenAPI与MetaCPAN API集成/记录。 它是什么? OpenAPI是用于设计,记录,验证和驱动RESTful API的规范。它可用于为现有API提供文档,或者在创建新API时提供文档。 OpenAPI规范起源于Swagger规范,并重命名为将API描述格式(OpenAPI)与开源工具(Swagger)分开。该规范已移至新的GitHub存储库,但未更改。 对于MetaCPAN API,我们开始提供现有API的文档,但很快也转向支持API调用的验证。 工具 OpenAPI有许多可用的工具可供帮助,包括有助于编写规范的发现工具。我们选择手动编写定义(当然是vim)并使用工具生成文档并将规范集成到MetaCPAN中。 ReDoc - OpenAPI / Swagger生成的API参考文档 ReDoc创建一个交互式页面,根据OpenAPI规范文件中提供的详细信息提供文档和示例。 ReDoc包含一个HTML模板,用作静态文件,用于自定义文档的显示方式。 Mojolicious :: Plugin :: OpenAPI - Mojolicious的OpenAPI / Swagger插件 读取OpenAPI规范文件,并为基于Mojolicious的应用程序添加适当的路由和验证。 JSON :: Validator - 根据JSON模式验证数据 集成到Mojolicious :: Plugin :: OpenAPI模块中,提供输入和输出验证,以及为规范文件本身提供验证。 入门 在实现MetaCPAN OpenAPI规范时使用以下策略。 OpenAPI 规范文件 通过支持多行属性值,可以更轻松地以更少的格式进行读写,我们选择了YAML。还支持JSON。 # Define the version of the OpenAPI spec to use. Version 2.0 still uses # swagger as the key swagger: "2.0" # general information about the API info: version: "1.

集合、包和混合

简而言之,这些类通常包含无序的对象集合。Set 仅考虑这些对象是否存在,bags 可以容纳多个相同类型的对象,mixes 也允许分数(和负)权重。常规版本是不可变的,Hash 版本是可变的。 让我们详细说明一下。如果要收集容器中的对象但不关心这些对象的顺序,Raku 提供无序集合类型 Set, SetHash, Bag, BagHash, Mix, 和 MixHash. 由于无序,这些容器可以比 Lists 更有效地查找元素或处理重复的项目。 另一方面,如果你想获得包含的对象(元素)而没有重复并且你只关心元素是否在集合中,你可以使用 Set 或 SetHash。如果你想消除重复但仍保留顺序,请查看 List 的 unique 例程。 如果你想跟踪每个对象出现的次数,你可以使用 Bag 或 BagHash。在这些Baggy 容器中,每个元素都有一个权重(无符号整数),表示同一个对象已包含在集合中的次数。 类型 Mix 和 MixHash 类似于 Bag 和 BagHash,但它们也允许分数和负权重。 Set,Bag 和 Mix 是 immutable 类型。如果要在构造容器后添加或删除元素,请使用可变变体 SetHash, BagHash, 和 MixHash 。 六个集合类 Set,SetHash,Bag,BagHash,Mix,MixHash,都有相似的语义。 首先,就它们而言,相同的对象引用相同的元素 - 其中使用 WHICH 方法确定身份(即以相同的方式=== 运算符检查身份)。对于像 Str 这样的值类型,这意味着具有相同的值; 对于像“Array”这样的引用类型,它意味着引用相同的对象实例。 其次,它们提供了类似 Hash 的接口,其中集合的实际元素(可以是任何类型的对象)是“键”,关联的权重是“值”: type of $a value of $a{$b} if $b is an element value of $a{$b} if $b is not an element Set / SetHash True False Bag / BagHash a positive integer 0 Mix / MixHash a non-zero real number 0 Set/Bag operators 有几个中缀运算符致力于在 Set 上执行常见操作,例如并集和差集。其他操作包括布尔检查,例如对象是否是 Set 中的元素,或者一个 Set 是否是另一个 Set 的子集。

🎄 7/25. Raku 中 Unicode 的乐趣

欢迎来到 Raku One-Liner Advent Calendar 的第7天! 今天,我们将看看2019年Raku日历中的三月份: 这里的代码使用 ASCII 领地之外的三个字符。 我们甚至可以再添加一个 Unicode 字符: say π × $𝜌² 在 Raku 中,您可以在标识符中自由地使用 Unicode 字符,例如变量或函数名。 但最重要的是,有许多预定义的符号,如 π,它们有 ASCII 替代品。 检查文档页面 Unicode 与 ASCII 符号,以查看可在 Raku 中使用的全部 Unicode 字符集。 使用 ASCII,可以通过以下方式重写上述单行程序: say pi * $r ** 2 让我们回到第2天的附加题代码,看看 Unicode 字符可以用在什么位置: sub f($n) { ($n <<*>> (1...1000 / $n)).grep: * < 1000 } say (f(3) ∪ f(5)).keys.sum; 这里有一些因素。 首先,超运算符 <<*>> 可以用适当的法语引号替换:«*»,乘法字符可以是我们今天使用的交叉:«×»。 同样可以应用于除法:÷。 其次,序列运算符的三个点可以用一个 Unicode 字符替换:…(如果您在 Word 中编程,则在键入完整停止三次后自动获得此字符)。

第六天 - 用 Yancy 制作一个列表

在这些近代,世界上有数十亿人,圣诞老人需要一个现代化的系统来跟踪他的淘气又好看的名单。幸运的圣诞老人,现代 Perl 有一个现代的 web 框架,Mojolicious。 第1步:构建列表 首先,我们需要一个数据库模式。圣诞老人只需要知道某人是否顽皮或好,所以我们的架构非常简单。我们将通过使用 Mojo::SQLite 连接到 SQLite 数据库并使用 Mojo::SQLite 迁移从我们脚本的DATA部分加载我们的数据库模式来启动我们的 Mojolicious::Lite 应用程序: use v5.28; use Mojolicious::Lite; use Mojo::SQLite; # Connect to the SQLite database and load our schema from the # '@@ migrations' section, below my $db = Mojo::SQLite->new( 'sqlite:thelist.db' ); $db->auto_migrate(1)->migrations->from_data(); # Start the app. Must be the last code of the script. app->start; __DATA__ @@ migrations -- 1 up CREATE TABLE the_list ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR NOT NULL, address VARCHAR NOT NULL, is_nice BOOLEAN DEFAULT FALSE, is_delivered BOOLEAN DEFAULT FALSE ); 创建我们的架构后,我们可以添加 Yancy。 Yancy是一个简单的 CMS,用于编辑内容和简化数据驱动的网站开发。我们将告诉 Yancy 阅读我们的数据库架构以进行自我配置,但我们也会给它一些提示,以便更轻松地编辑内容。

例程

关于怎样定义例程和使用例程, 请参阅 Sub 。 控制例程 控制例程是能改变程序流的例程, 它可能会返回一个值。 sub exit 定义为: sub exit(Int() $status = 0) 使用返回码 $status 退出当前进程,如果未指定值,则返回码为 0。退出值 ($status),当不同于零时,必须从捕获它的进程(例如,shell)进行适当的计算; 它是从Main 返回不同于零的退出代码的唯一方法。 exit 防止 LEAVE phaser 被执行, 但是它会在 &*EXIT 变量中运行。 exit 应该作为最后的手段用于向父进程发出关于退出代码不等于零的信号,而不是异常地终止方法或 sub, 使用 exceptions 代替。 done 定义为: sub done(--> Nil) 如果用在任何 supply 或 react 块儿之外, 则抛出异常: done without supply or react。在 Supply 块儿里面, 它会指示该 supply 不会再发出任何东西. 还请参阅 documentation on method done. my $supply = supply { for 1 .. 3 { emit($_); } done; } $supply.

🎄 6/25. 在 Raku 中测试回文数字

欢迎来到 Raku One-Liner Advent Calendar 的第6天! 正如昨天所承诺的那样,今天我们将解决欧拉项目的问题4。 让我再次提醒您,您可以先自己暂停阅读并自行解决问题。 我的目的是展示Raku和Perl的美妙之处。 因此,任务是找到最大的回文数(从两端读取的数字,例如1551),这是两个三位数的乘积。 换句话说,我们必须扫描999×999以下的数字,并且可以优化解决方案,但实际上,我们只需要允许数字,即乘积,因此,我们不要跳过乘法部分。 这是我们今天的单行程序: (((999...100) X* (999...100)).grep: {$^a eq $^a.flip}).max.say 如果你正在阅读这个 advent 日历,那么你已经准备好了链式方法调用在Raku单行中使用非常方便的事实。 我们在第2天早些时候也看到了 grep 的冒号形式,但这次我们使用了一个带占位符变量的代码块。 目前还不清楚你是否可以在这里使用星号,因为我们需要在块中使用两次变量。 该行的第一部分使用十字运算符 X*,我们将在几天内回到本系列中。 它生成所有三位数字的乘积。 由于我们需要最大的数字,从右到左开始是有意义的,这就是为什么序列 999 ... 100,而不是 100 ... 999。 让我们看看 grepped 乘积序列中的前几个数字: 580085 514415 906609 119911 282282 141141 853358 650056 单行并不总是非常理想。 在我们的例子中,我们需要生成整个乘积序列以找到它们中的最大值。 答案位于第三个位置,因此将 max 替换为 first 将是错误的。 但好的部分是,如果你先使用,Raku将不会生成所有数字。 还有另一个有用的方法,head,它也可以防止生成超过必要结果的方法。 以下代码运行得更快,并提供正确的结果: (((999...100) X* (999...100)).grep: {$^a eq $^a.flip}).head(10).max.say 让我们今天就到此为止。 敬请关注!

第六天 - 懒惰精灵与勤劳精灵

对圣诞老人来说,圣诞节总是一年中最忙碌的时刻。 幸运的是,圣诞老人有很多帮手。 他们总是做一些小工作和家务,只是为了创造最好的假日季节体验! Object::Delayed 模块为圣诞老人的快乐精灵添加了两个非常有趣的精灵! 他们的名字是 slack 和 catchup! Lazy slack 那个懒散的(slack)精灵确实非常懒惰。 懒散(slack)精灵不会做任何事情,直到你真的需要他去做。 虽然人们可以认为这是精灵中非常糟糕的性格特征,但它也是一种非常生态的特征。 人们可以认为这个懒散的(slack)精灵是他们所有人中最环保的精灵! 你有多少次要求精灵为你做点事情,然后却没用过那个精灵辛苦工作的结果? 即使它只是到处移动的被回收的电子,但仍然需要能量来移动它们! 特别是, 如果那些电子被用来告诉其他精灵做一些遥远的事情,就像在外部数据库中一样! use Object::Delayed; my $dbh = slack { DBIish.connect(...) } 这就是你需要的 $dbh 变量,它只在实际需要时才与数据库建立连接。 当然,如果你想对该数据库进行查询,那么也可以使其懒惰! use Object::Delayed; my $dbh = slack { DBIish.connect(...) } my $sth = slack { $dbh.prepare(...) } 由于语句句柄也是懒惰的,因此在实际需要之前它实际上不会进行查询准备。 use Object::Delayed; my $dbh = slack { DBIish.connect(...) } my $sth = slack { $dbh.prepare(...) } # lotsa program if $needed { $sth.

第五天 - 复合选择器比正则表达式更容易

当人们告诉我我不能(他们的意思是不应该)用正则表达式解析 HTML 时,我说“拿酒来”。这不仅仅是技巧或态度,而是方便。以正确的方式做到并不总是那么容易(我记得 HTML 0.9 是一个大问题)。最近,我一直在使用 Mojo::DOM 为我做这件事。它比旧的,权宜之计更容易。 诀窍始终是隔离感兴趣的 HTML。我可以做到这一点切除有趣部分周围的所有数据: my $html = ...; $html =~ s/.*?<table class="foo".*?>//; $html =~ s/<\/table>.*//; 现在我不必解析所有的 HTML;我可以考虑一下表格。即使这是权宜之计,也不是那么好。在我用更好的东西替换它之前,我会快速绕道而行。 层叠样式表 您可能知道层叠样式表(CSS)使你的网页看起来很漂亮(但不是我的,真的)。您可以将元数据添加到标记上: <img id="bender" class="robot" src="..." /> <img id="fry" class="human" src="..." /> <img id="leela" class="mutant" src="..." /> CSS 规则可以通过其 ID 或类来处理这些项目以将样式应用于它们。这个寻址是一个“选择器”,拥有比我更好的技能的人使用这些来使演示非常漂亮: img#fry { border: 1px; } img.robot { margin: 20px; } HTML 可能有点复杂。也许那些有趣的标签在列表中。这包裹了数据的另一层 HTML 结构: <ul class="employees"> <li><img id="bender" class="robot" src="..." /></li> <li><img id="fry" class="human" src="..." /></li> <li><img id="leela" class="mutant" src=".

🎄 5/25. 在 Raku 中今天是什么日期?

欢迎来到这个可爱的 Raku One-Liner Advent Calendar 的第5天! 今天,我们将回答今天是什么日期的问题(如果你愿意的话,明天我们可以谈论回文)。 因此,要打印答案,您可以使用以下Raku代码行: DateTime.now.yyyy-mm-dd.say 它看起来显而易见,并以YYYY-MM-DD的格式打印日期。 好的部分是 DateTime 类可以直接使用,例如,您不需要像在Perl 5中那样包含模块。 $ raku -e'DateTime.now.yyyy-mm-dd.say' 2018-12-05 正如您在日历的前几天已经看到的那样,链式方法调用是典型的Raku惯用法。 另一种选择是使用say作为子例程并使用括号来标记方法调用: say(DateTime.now().yyyy-mm-dd()); 这段代码也有效; 它完全正确,但看起来很重。 您还应该注意并告诉您的朋友,在Raku中,您可以在标识符中使用短划线和撇号。 好吧,也许使用撇号不是一个好主意,但是在Raku的源代码中已经广泛使用了连字符。只需确保在表达式中的减法运算符周围放置空格以避免解析中的任何冲突。 浏览源代码或阅读文档,您将找到以相同方式命名的另一种方法: hh-mm-ss。 我打赌你明白它的作用。 > DateTime.now.hh-mm-ss.say 00:12:01 请注意,对于不同的输出格式,您将找不到类似的方法,例如 dd-mm-yy 或 hh-mm。 请改用 formatter 程序。 它不是方法,而是在 Datish 角色中定义的属性。 DateTime 类中有一个默认格式化程序,但您可以通过为构造函数提供自己的子例程来重新定义它,例如: DateTime.now(formatter => -> $dt { sprintf '%02d.%02d.%04d', $dt.day, $dt.month, $dt.year }).say 这里的格式化程序接收一个带有参数 $dt 的匿名子例程(由箭头引入)。 我希望这段代码与我们的初始单行打印相同的日期,因为您最有可能在一天内阅读整篇文章。 不过,明天见一下测试回文的代码!

正则表达式

正则表达式, 简称 regexes, 是描述文本模式的字符序列。模式匹配就是将这些模式和实际的文本进行匹配的过程。 词法约定 Raku 正则表达式有特殊的写法: m/abc/; # a regex that is immediately matched against $_ rx/abc/; # a Regex object /abc/; # a Regex object 对于前两个例子, 分隔符还能用除了斜线之外的其它字符: m{abc}; rx{abc}; 注意, 冒号和圆括号都不能用作分隔符; 禁止使用冒号作为正则表达式分割符是因为它和副词冲突, 例如 rx:i/abc/(忽略大小写的正则表达式), 而圆括号表明函数调用。 空白符在正则表达式中通常被忽略(带有 :s 或 :sigspace 副词的正则表达式除外)。 通常, 对于 Raku 来说, 正则表达式中的注释以 # 号开头, 直至行尾。 字面值 正则表达式最简单的情况是匹配字符串字面值。 if 'properly' ~~ m/ perl / { say "'properly' contains 'perl'"; } 字母数字和下划线 _ 按字面值匹配。所有其它字符要么使用反斜线转义(例如, \: 匹配一个冒号), 要么用引号引起来:

第四天 - 测试钩子和助手

Test::Mojo 是 Mojolicious 测试工具,有很多方法可以测试 Web 应用程序中的路由(甚至可以在其他 Web 框架中进行测试)。 但是,如果我需要测试的不是路由呢?如果它是一个钩子,一个插件或一个助手怎么办?我们也可以测试所有这些东西! 钩子 为了彻底测试钩子,我需要找到配置我的测试用例的方法。我可以指望我的应用程序执行它,并找到正确的路由来测试正确的行为。但是,这会产生更大的测试,集成不同的部分并使测试失败更难调试。我想要的是隔离我正在测试的东西。最好的方法是创建仅测试我想测试的路由。 如果我有一个钩子来记录特殊日志文件的异常,如下所示: #!/usr/bin/env perl use Mojolicious::Lite; # Log exceptions to a separate log file hook after_dispatch => sub { my ( $c ) = @_; return unless my $e = $c->stash( 'exception' ); state $path = $c->app->home->child("exception.log"); state $log = Mojo::Log->new( path => $path ); $log->error( $e ); }; app->start; 为了测试这个,一旦我加载了我的应用并创建了一个 Test::Mojo 对象,我就可以自由地为我的应用添加更多配置,包括新路由!这些路由可以为我的测试设置恰当的条件。 # test.pl use Test::More; use Test::Mojo; use Mojo::File qw( path ); my $t = Test::Mojo->new( path( 'myapp.

引用结构

The Q Lang 在 Raku 中, 字符串通常使用一些引号结构来表示. 这些引号结构中,最简单的就是 Q, 通过便捷方式 「…」 或 Q 后跟着由任意一对儿分隔符包围着的文本. 大多数时候, 你需要的只是 '…' 或 "…". Literal strings: Q Q[A literal string] 「More plainly.」 Q ^Almost any non-word character can be a delimiter!^ Q 「「Delimiters can be repeated/nested if they are adjacent.」」 分隔符能够嵌套, 但是在普通的 Q 形式中, 反斜线转义是不允许的. 换种说法就是, Q 字符串尽可能被作为字面量. 在 Q、q 或 qq 之后不允许立即使用一些分隔符。标识符中允许的任何字符都不允许使用,因为在这种情况下,引号结构和这些字符一起被解释为标识符。此外,( ) 是不允许的,因为它被解释为函数调用。如果你仍然希望使用这些字符作为分隔符,请用空格将它们与 Q、q 或 qq 分隔开。请注意,一些自然语言在字符串的右侧使用左分隔引号。Q 不支持这些,因为它依赖unicode 属性来区分左分隔符和右分隔符。 Q'this will not work!' Q(this won't work either!

🎄 4/25. 在 Raku 中使用大数

欢迎来到第4天的 Raku One-Liner Advent Calendar! 今天我们会看一看欧拉项目的第十三个问题。我先给你展示一张截图: 实际上,它看起来很大,任务是找到一百个整数之和的前十位数,每个整数由50位数组成。 听起来像是一项任务,可能需要一些优化和简化来摆脱对结果的前十位无贡献的一切。 但不是在Raku中。 在Raku中,您可以简单地将数字相加并取其前十位数字: < 37107287433902102798797998220837590246510135740250 # Other 98 numbers here 53503534526472524250874054075591789781264330331690 >.sum.substr(0, 10).say Raku默认使用任意长整数运行; 您不需要包含任何模块或以其他方式激活此行为。 您甚至可以快速计算幂并获得结果: $ raku -e'say 37107287433902102798797998220837590 ** 1000' 需要注意的另一件事是我们可以透明地将整数转换为数字,反之亦然。 在我们今天的程序中,数字列表显示为一对尖括号内的字符串的引用列表。 在列表中,您调用 sum 方法,该方法适用于数字。 获得总和后,再次将其视为字符串并提取其前十个字符。 整个代码看起来非常自然,易于阅读。 有了这种情绪,我们明天再见!

第三天 - 高阶 Promises

通过组合 Promises 来创建新的复杂 Promise Mojolicious 7.49 添加了自己的 Promises/A+ 规范实现。莫霍克在第14天写到了这些:你答应召唤! 在 2017 年的 Mojolicious Advent Calender 中,他向你展示了如何同时获取多个网页。这个 Advent 条目扩展了更多的 Promise 技巧。 Promise 是一种旨在消除嵌套回调(也称为“回调地狱”)的结构。正确编写的 Promises 链具有扁平结构,易于线性跟随。 高阶 Promise 是包含其他 Promise 并以其身份为基础的 Promise。 Mojo::Promise::Role::HigherOrder 发行版提供了三个角色,您可以将它们混合到 Mojo::Promise 中以创建包含 Promises 的更好看的 Promise。不过,在你看到它们之前,先看看 Mojo::Promise 已经提供的两个。 All 只有当 all Promise 都解决时,所有承诺才会解决。如果其中一个被拒绝,则拒绝所有承诺。这意味着如果一个被拒绝并且不需要知道任何其他人的状态,整个 Promise 知道该怎么做。 use Mojo::Promise; use Mojo::UserAgent; my $ua = Mojo::UserAgent->new; my @urls = ( ... ); my @all_sites = map { $ua->get_p( $_ ) } @urls; my $all_promise = Mojo::Promise ->all( @all_sites ) ->then( sub { say "They all worked!

编译指令

在 Raku 中,pragma 是用于识别要使用的 Raku 的特定版本或以某种方式修改编译器的正常行为的指令。use 关键字开启编译指示(类似于你怎么 use 一个模块)。要禁用 pragma,请使用 no 关键字: use v6.c; # use 6.c language version no worries; # don't issue compile time warnings 以下是一个编译指令列表,其中包含每个编译指令意图的简短描述或指向其使用的更多详细信息的链接。(注意:标记为“[NYI]”的编译指令尚未实现,标记为“[TBD]”的编号将在稍后定义。) v6.x 该编译指令 声明了将要使用的编译器的版本,如果它们是可选的,则开启它的功能。 use v6; # Load latest supported version (non-PREVIEW). # Also, useful for producing better errors when accidentally # executing the program with `perl` instead of `raku` use v6.c; # Use the "Christmas" version of Raku use v6.d; # Use the "Diwali" version of Raku use v6.

🎄 3/25. 在 Raku 中生成随机整数

欢迎来到 Raku One-Liner Advent Calendar 的第3天! 今天,我们将生成随机数。 您可能会问,与它有什么关系,调用一种 rand 函数不是常规任务吗? 好吧,从某种意义上说,是的,但在Raku中,您可能更喜欢调用方法。 我们来看看Raku日历中8月的代码: 2019.rand.Int.say 这就是整个程序! 它只使用方法调用,方法一个接一个地链接在一起。 如果您以前从未见过Raku,那么你首先注意到的是在一个数字上调用的方法。 在Raku中,这一点也不特别。 在数字上调用 rand 方法可能是对 Cool类 中定义的方法的调用,该类立即代理给数字的数字表示: method rand() { self.Num.rand } 稍后在 Num类 中,会发生对底层 NQP 引擎的调用: method rand(Num:D: ) { nqp::p6box_n(nqp::rand_n(nqp::unbox_n(self))); } 在我们的示例中,对象 2019 是 Int,因此 rand 被直接分派给 Num 类的方法。 rand 方法返回一个浮点数,因此在其上调用 Int 来获取整数。 运行代码几次以确认它生成随机数: $ raku -e'2019.rand.Int.say' 543 $ raku -e'2019.rand.Int.say' 1366 $ raku -e'2019.rand.Int.say' 1870 如果您想了解随机数生成器的质量,请深入挖掘 NQP 和 MoarVM,然后再深入了解虚拟机的后端引擎。 要使结果可重复(例如,对于测试),通过调用 srand 函数来设置种子:

第二天 - 自动重载以实现快速开发

使用 Mojolicious 开发 Web 应用程序非常有趣!使用 morbo 服务器进行开发,对我的 webapp 的每次更改都会导致重新启动以加载我的更改。这样我下一个请求就有了我的新代码! 所以,我改变了我的代码,重新启动了 webapp,然后我回到浏览器窗口。等等……我的新代码在哪里?为什么不修复错误?没有…我忘记重新加载我的浏览器窗口了吗?啊!当然! 这是否发生在你身上?可能不是。但是,在每次后端代码更改后重新加载浏览器窗口仍然很烦人。如果每次重新启动 Web 服务器时我的浏览器窗口都会自动重新加载,那就太好了! AutoReload 插件 像 Perl 中的每个问题一样,有一个 CPAN 模块:Mojolicious::Plugin::AutoReload。将此插件添加到我们的应用程序将自动重新加载连接到我们的应用程序的任何浏览器窗口,使开发 Mojolicious 应用程序更容易! 要使用该插件,我们使用 plugin 方法将其添加到我们的应用程序中。然后,我们将 auto_reload 帮助器添加到布局模板中: use Mojolicious::Lite; plugin 'AutoReload'; get '/' => 'index'; app->start; __DATA__ @@ layouts/default.html.ep %= auto_reload %= content @@ index.html.ep % layout 'default'; <h1>Hello, World!</h1> 在这里下载代码。现在,当我们在浏览器中打开应用程序时,如果服务器重新启动,我们的浏览器将重新加载页面以查看新应用程序! 工作原理 这个插件简单优雅:当浏览器加载页面时,它连接到位于 /auto_reload 的 WebSocket。服务器重新启动时,WebSocket 连接中断。客户端看到断开的连接,等待服务器再次开始侦听连接,然后重新加载页面。 在生产中禁用 一旦我们从 morbo 切换到 hypnotoad,我们就不再需要自动重新加载了。因此,插件不会向浏览器发送 JavaScript 来构建 websocket。这是使用 mode 属性控制的。当 mode 为 development 模式(morbo 的默认模式)时,将告知浏览器创建 WebSocket。当 mode 为其他任何模式时,不会创建 WebSocket。

Phasers

程序的生命周期(执行时间表)分为几个阶段。phaser是在特定执行阶段调用的代码块。 Phasers phaser 块只是包含它的闭包的 trait,并在适当的时刻自动调用。这些自动调用的块称为 phasers,因为它们通常标记从计算的一个阶段到另一个阶段的转换。例如,在编译编译单元结束时调用 CHECK 块。也可以安装其他类型的 phasers; 它们会在适当的时候自动调用,其中一些 phasers 响应各种控制异常和退出值。例如,如果块的退出成功或失败,则可能会调用某些 phasers,在这种情况下成功退出, 则在这时返回定义的值或列表,而不带任何 Failure 或异常。 以下是摘要: BEGIN {...} # * at compile time, as soon as possible, only ever runs once CHECK {...} # * at compile time, as late as possible, only ever runs once INIT {...} # * at runtime, as soon as possible, only ever runs once END {...} # at runtime, as late as possible, only ever runs once DOC [BEGIN|CHECK|INIT] {.

Raku 原生类型

Raku 提供了一组原生类型,在内存中具有固定且已知的表示。此页面显示了存在哪些原生类型以及如何使用它们。有关它们的更多信息,请查看有关原生数字 的页面。 Types with native representation Raku 中的一些简单类型具有原生表示,表示它们将使用编译器,操作系统和原生提供的 C 语言表示。这些是可用的四种原生类型: int Equivalent to Int (with limited range) uint Equivalent to Int (with limited range) with the unsigned trait num Equivalent to Num str Equivalent to Str 但是,这些类型不一定具有 NativeCall 接口所需的大小(例如,Raku 的 int 可以是 8 个字节,但 C 的 int 只有 4 个字节); 必须使用以下类型而不是上面列出的 int 或 num 类型。 通常,这些变量的行为与常规标量变量的行为方式相同,称为自动装箱; 然而,存在一些差异,因为您实际宣称的是如何表示它们,而不是它们的实际类型。第一个是它们的类型实际上是它们的等效类型,而不是它们的原生类型。 my int $intillo = 3; say $intillo.^name; # OUTPUT: «Int␤» 这显然意味着他们将智能匹配他们的等效(自动装箱)类型,而不是他们的原生类型: my str $strillo = "tres"; say $strillo ~~ str; # OUTPUT: «False␤» say $strillo ~~ Str; # OUTPUT: «True␤» 并且与非原生对应物不同,他们将始终具有默认值:

🎄 2/25. 在 Raku 中 Grepping 可整除的数字

欢迎来到 Raku One-Liner Advent Calendar 的第2天! 今天, 我们将从编号为 1 的Euler 项目开始解决一个很好的任务. 我要再次提醒你, 该文的其余部分包含一个答案, 所以欢迎你暂停一下, 先考虑自己的答案。 但我几乎可以肯定, 如果你一直在阅读 raku.online, 那么你很可能在过去就已经解决了欧拉项目的问题。 任务是找到 1000 以下, 既是3的倍数, 又是5的倍数的数字的总和。我们感兴趣的行的前几个元素是 3,5,9,15,20,21等。你已经可以看到它们中的一些数字, 例如 15, 是 3和5的倍数, 因此不可能将3和5的倍数分别相加。 答案在这里: say sum((1..999).grep: * %% (3 | 5)) 现在让我们解读它。 我们需要的是过滤3的倍数或5的倍数的数字。如果你重新阅读上一句话, 铃声应该为你响起:在 Raku 中, 这可以用 Junctions 来实现, 非正式地称为量子叠加。 要测试数字是否可以被3或5整除, 请这样写: $x %% 3 | 5 顺便说一下, %% 是 divisibility 运算符, 是一个非常好的东西, 有助于避免布尔测试中的否定, 如果你写成下面这样, 那么你只有一个百分号: !($x % (3 | 5)) 好的, 主要条件已准备就绪, 让我们扫描1和(包括)999之间的数字:

第一天 - 欢迎来到 Mojo 会议

欢迎来到 Mojolicious Advent Calendar 的另一年! 2018 年对 Mojolicious 非常好,我认为没有比在挪威奥斯陆举行的 2018 年北欧 Perl 研讨会和 MojoConf 的回顾更好的方式开始这个日历了。 到挪威西部的边路 就个人而言,我特别喜欢这次旅行,因为我可以多花几天时间游览挪威西部,那里是标志性的峡湾。在乘船和火车游览峡湾之前,我拜访了卑尔根及其几个 Perlers。如果我的行李还没决定要在冰岛停留额外的几天,我会有一个延迟的停留,那将是一次完美的旅程。 我仍然度过了一段美好的时光,一生只见过一次!感谢克里斯托弗和乔尼斯,所有人都是我旅行的同伴! Nordic Perl Workshop和 MojoConf Oslo Perl Mongers 知道如何抛出 Perl 事件!今年我实际上曾经两次参加 Perl 工具链峰会。 顾名思义,Nordic Perl Workshop 是 Perl 热情聚集和谈论 Perl 的区域研讨会。然而今年,它与 MojoConf 2018 联合品牌;之前的 MojoConf 也于 2014 年在奥斯陆举行。 在 Mojolicious 和 Perl 上都有很多很棒的演讲。您可以在 NPW Youtube 频道上看到它们。为简洁起见,我将在这里强调几点。 Keynote 今年的主题演讲由作家和 Perl 杰出人物 brian d foy 发表。他谈到了 Mojo 在他的咨询演出中如何迅速完成真正的工作。 Mojolicious 8.0 公告 Mojolicious 项目创始人兼首席开发商 Sebastian Riedel 颁发了“八点哦”。虽然它表面上是 8.

性能

该页面是关于在 Raku 上下文中 计算机性能 的。 首先,剖析你的代码 确保你没有在错误的代码上浪费时间: 通过剖析你的代码的性能以从识别你的 “临界 3%” 开始。本文档的其余部分将向您展示如何执行此操作。 Time with now - INIT now 对于 now - INIT now 形式的表达式, 其中 INIT 是一个 Raku 程序中运行的 phase, 为计时代码片段提供了一个很好的习惯用法。 使用 m: your code goes here raku 频道 evalbot 来写出这样的行: m: say now - INIT now rakudo-moar abc1234: OUTPUT«0.0018558␤» INIT 左边的 now 比 INIT 右边的 now 晚运行了 0.0018558 秒, 因为后者在INIT phase 期间出现。 本地剖析 当使用 MoarVM 后端时, Rakudo 编译器的 --profile 命令行选项将剖析数据写到一个 HTML 文件中。

🎄 1/25. 在 Raku 中生成随机密码

欢迎来到全新的 Raku.Online Advent Calendar 的第1天!今年,它的主题是 Raku One-Liners(来自 raku.onliners 的双关语)。因此,欢迎参加今年的 Raku One-Liner Advent Calendar。整个 raku.online 博客最初计划每天一篇,因此这是一个很好的机会,可以保持 25天每天一篇。 不要忘记关注其他人编写的其他与 Perl 相关的日历: Perl Advent Calendar Raku Advent Calendar Dancer Advent Calendar Mojolicious Advent Calendar 在接下来的几天里,我将发表简短的文章,要么解释 Raku Calendar 2019 的代码,要么解决 Project Euler 启发的一些问题,Raku 可以展示它的极致美感(如果你不想看到我的答案)在你之前,我邀请你先自己解决这些任务,然后再阅读这些文章。但是今天,让我们从其他东西开始,在 Raku 中生成一个随机密码。 这是完整的代码: ('0'..'z').pick(15).join.say 运行几次: Z4x72B8wkWHo0QD J;V?=CE84jIS^r9 2;m6>kdHRS04XEL O6wK=umZ]DqyHT5 3SP\tNkX5Zh@1C4 PX6QC?KdWYyzNOc bq1EfBZNdK9vHxz 每次运行时,我们的这行代码都会生成不同的字符串。如果您尚未安装 Raku,请自行尝试创建密码或使用上述密码之一。 前段时间,我们研究了pick方法的内部结构。它在Any类中定义,它实际上是将工作代理给List类的方法: proto method pick(|) is nodal {*} multi method pick() { self.list.pick } multi method pick($n) { self.

正则表达式和猜测(名称提取)

在我的上一篇文章中,我解释了我为生活所做的事情,并举例说明了我在工作中使用 Raku 的特殊内容。 这是另一个例子。 首先是一些背景信息:挪威是一个小国家,大多数人都讲同一种语言(当然,方言有所不同,但语言是相同的)。但听起来很疯狂……这种单一的口语有两种书面语言。是的,你读的是正确的:你说挪威语,但你写的是 Bokmål 或 Nynorsk - 松散地翻译成 “Book language” 和 “New Norwegian”。我不会深入了解其中的历史原因 - 如果你有兴趣我会推荐关于它的维基百科文章。我只想说这两个版本的挪威语非常相似,但差异足以让差异显而易见。 一些新闻媒体使用 Nynorsk,而大多数人使用 Bokmål。这样做的结果是,Bokmål 读者的内容远远多于 Nynorsk 读者。这就是为什么我的工作场所试图获得将 Bokmål 翻译成 Nynorsk 的软件的原因。 翻译工作得很好,但偶尔会有不幸。其中相当一部分是由译者误解的名字引起的。我们有字典的字典,所以它们应该很容易识别。但是,如果名称是较少使用的名称,则不容易识别。当字典失败时,我们可以尝试使用规则/正则表达式来识别它们。但即便如此,我们也无法总是决定一个单词是常规单词还是名称。 让我们来看看 Fuglesang 这个名字(即宇航员)。 Fuglesang 是一个名字,但它也是一个名词,意思是鸟的歌或唧唧。如上所述,如果使用正则表达式或使用名称字典在句子中间发生 Fuglesang 作为名称,则没有问题。正则表达式查找以大写字母开头的单词,并向译者发出该单词可能是名称的信号。但如果这个词出现在一个句子的开头 - 所有单词都被大写 - 那么 Fuglesang 就会变得模棱两可,即使这个名字出现在名字词典中。消除这种歧义很重要,因为如果没有,名称可以翻译为好像是任何其他单词。在我的情况下,由于不公正,Fuglesang 可能被翻译成 “fuglesong”,如果它是任何其他类型的单词而不是名称,这是正确的。 那么 Fuglesang 是什么?一首名字还是鸟鸣? 显然,这是翻译软件需要帮助的情况。我想了一会儿,并决定也许我们可以开发一个相当天真的算法来帮助我们。我编写了一个简单的脚本:它解析一个文本并找到句子中出现的每个大写单词。然后它提取任何句子的第一个单词并检查该单词是否出现在句子中间单词列表中。如果确实如此,那么我们假设 - 在该单个文本的特定上下文中 - 该单词可以被视为名称。 我必须照顾的唯一特殊情况,与所有格有关。在英语中,所有格相对容易处理,因为它们很容易被发现和移除。假设你有两句 “Peter owns a car” 和 “This is Peter’s car”。一个简单的 Str.subst("'s", "") 将删除后一句中的所有格,现在 Peter 可以在第一句中与 Peter 相比较。 但是在挪威,所有格只是在没有撇号的情况下拖尾s-es,因此答案并不那么简单(“Peter eier en bil” vs “Peters bil”)。但总而言之,使用 Raku 解决这个问题也相当简单。

Packages - Organizing and referencing namespaced program elements 包是指定程序元素的嵌套命名空间。 模块,类,Grammar是包类型。 像目录中的文件一样,通常可以使用其短名称(如果它们是本地的)或使用较长的名称来消除歧义的引用具名元素。 Names 名称是作为变量名称的合法部分的任何东西(不包括sigil符号)。 这包括: $foo # 简单标识符 $Foo::Bar::baz # 通过 :: 分割的组合标识符 $Foo::($bar)::baz # 执行插值的组合标识符 $42 # numeric names $! # 某些标点符号变量 :: 用于分割嵌套的包名。 包限定名 普通的包限定名像这样: $Foo::Bar::baz # 包 Foo::Bar 中的 $baz 变量 有时保持sigil与变量名很清晰,所以来写这个的一个替代方式是: Foo::Bar::<$baz> 这在编译时解决,因为变量名是一个常量。 如果 :: 之前的名称部分为 null,则意味着包未指定并且必须搜索。 一般来说,这意味着跟在主sigil后面的初始 :: 是对编译时已知的名字的无操作(no-op),但 ::() 也可以用来引入插值。 另外,在没有另一个sigil的情况下,:: 可以作为它自己的sigil,表明有意使用一个尚未声明的包名。 伪包 在名称前面保留以下伪包名称: MY # 当前词法作用域中的符号 (aka $?SCOPE) OUR # 当前包中的符号 (aka $?PACKAGE) CORE # 最外层词法作用域, 定义标准 Perl GLOBAL # Interpreter-wide package symbols, really UNIT::GLOBAL PROCESS # 进程相关的全局变量 (superglobals) COMPILING # 正在编译的作用域中的词法符号 以下相对名称也保留,但可以在名称中的任何位置使用:

在结构化流中使用复杂数据格式

在本系列结构化流博客文章的第1部分中,我们演示了使用结构化流编写端到端流式 ETL 管道是多么容易,它将 JSON CloudTrail 日志转换为 Parquet 表。 该博客强调,构建此类管道的主要挑战之一是从各种来源和复杂格式中读取和转换数据。 在这篇博文中,我们将更详细地研究这个问题,并展示如何使用 Apache Spark SQL 的内置函数来解决所有数据转换挑战。 具体来说,我们将讨论以下内容: 有哪些不同的数据格式及其权衡 如何使用Spark SQL轻松使用它们 如何为您的用例选择正确的最终格式 Data sources and formats 数据以各种不同的格式提供。 电子表格可以用 XML,CSV,TSV 表示; 应用程序指标可以用原始文本或 JSON 写出。 每个用例都有针对它定制的特定数据格式。 在大数据领域,我们通常会遇到 Parquet,ORC,Avro,JSON,CSV,SQL 和 NoSQL 数据源以及纯文本文件等格式。 我们可以将这些数据格式大致分为三类:结构化,半结构化和非结构化数据。 让我们试着了解每个类别的好处和缺点。 结构化数据 结构化数据源定义数据的模式。利用有关底层数据的额外信息,结构化数据源可提供有效的存储和性能。例如,Parquet和ORC等柱状格式使得从列的子集中提取值变得更加容易。首先逐行读取每个记录,然后从感兴趣的特定列中提取值可以读取比查询仅对一小部分列感兴趣时所需的更多数据。基于行的存储格式(如Avro)可有效地序列化和存储提供存储优势的数据。然而,这些优点通常以灵活性为代价。例如,由于结构的刚性,演变模式可能具有挑战性。 非结构化数据 相比之下,非结构化数据源通常是自由格式文本或二进制对象,其不包含标记或元数据(例如,CSV文件中的逗号),以定义数据的组织。报纸文章,医疗记录,图像blob,应用程序日志通常被视为非结构化数据。这些类型的源通常要求数据周围的上下文是可解析的。也就是说,您需要知道该文件是图像或是报纸文章。大多数数据源都是非结构化的。具有非结构化格式的成本是从这些数据源中提取价值变得麻烦,因为需要许多转换和特征提取技术来解释这些数据集。 半结构化数据 半结构化数据源是按记录构建的,但不一定具有跨越所有记录的明确定义的全局模式。结果,每个数据记录都使用其架构信息进行扩充。 JSON和XML是很受欢迎的例子。半结构化数据格式的好处是,它们在表达数据时提供了最大的灵活性,因为每条记录都是自我描述的。这些格式在许多应用程序中非常常见,因为存在许多用于处理这些记录的轻量级解析器,并且它们还具有人类可读的优点。但是,这些格式的主要缺点是它们会产生额外的解析开销,并且不是特别为ad-hoc查询而构建的。 与Spark SQL交换数据格式 在我们之前的博客文章中,我们讨论了如何将Cloudtrail Logs从JSON转换为Parquet,将我们的即席查询的运行时间缩短了10倍。 Spark SQL允许用户从批处理和流式查询中提取这些数据源类中的数据。 它本身支持以Parquet,ORC,JSON,CSV和文本格式读取和写入数据,并且Spark包中还存在大量其他连接器。 您还可以使用JDBC DataSource连接到SQL数据库。 Apache Spark可用于交换数据格式,如下所示: events = spark.readStream \ .format("json") \ # or parquet, kafka, orc... .option() \ # format specific options .

用于 Spark 结构化流的 Cassandra 接收器

Cassandra Sink for Spark Structured Streaming 我最近开始使用 Spark,并且必须将结构化流式 API 生成的结果存储在 Cassandra 数据库中。 在这篇文章中,我提供了一个如何在 Spark Structured Streaming 中创建和使用 Cassandra 接收器的简单示例。 我希望它对那些刚刚开始使用 Structured Streaming API 并想知道如何将它与数据库连接的人有用。 应用程序的想法非常简单。 它从 Kafka 读取消息,解析它们并将它们保存到 Cassandra 中。 Why Structured Streaming? 从文档中可以看出,Structured Streaming 是一个基于 Spark SQL 引擎的可扩展且容错的流处理引擎。 您可以使用 Dataset/DataFrame API 来表示流聚合,事件时间窗口,流到批处理连接等。在 Spark 中进行流式计算,并允许使用熟悉的 SQL 处理流数据。 What Is the Problem? Spark Structured Streaming 在 2017 年已经被标记为稳定。因此,它是一个相对较新的 API,并不是所有功能都在那里。例如,有几种类型的内置输出接收器:File,Kafka,Console 和内存接收器。但是,如果您想将流式计算的结果输出到数据库中,则需要使用 foreach 接收器并实现 ForeachWriter 接口。从 Spark 2.3.1 开始,这仅适用于 Scala 和 Java。 在这里,我假设您已经大致了解 Structured Streaming 如何工作,并且知道如何读取和处理流数据,现在可以将其输出到数据库中。如果上述某些步骤不清楚,可以使用一些很好的在线资源来帮助您开始使用结构化流。特别是,官方文档是一个很好的起点。在本文中,我想关注您需要将结果存储在数据库中的最后一步。

操作符

操作符 操作符优先级 在像 1 + 2 * 3 这样的表达式中, 2 * 3 被首先计算, 因为中缀操作符 * 的优先级比 + 的优先级高。下面的表中总结了 Perl 6 中 的优先级级别, 从最牢固到最松散: A Level Examples N Terms 42 3.14 "eek" qq["foo"] $x :!verbose @$array L 方法后缀 .meth .+ .? .* .() .[] .{} .<> .«» .:: .= .^ .: N 自增 ++ -- R 求幂 ** L Symbolic unary ! + - ~ ? | || +^ ~^ ?^ ^ L 乘法 * / % %% +& +< +> ~& ~< ~> ?

面向对象

Object Orientation in Raku Raku 有很多预先定义好的类型,这些类型可以归为 2 类:普通类型和原生类型。原生类型用于底层类型(例如 uint 64)。原生类型没有和对象同样的功能,尽管你可以在它们身上调用方法, 它们还是被包装成普通的对象。所有你能存储到变量中的东西要么是一个原生的 value, 要么是一个对象。这包括字面值、类型(类型对象)、code 和容器。 使用对象 方法可以有参数, 但是方法名和参数列表之间不可以有空格: say "abc".uc; # ^^^ 不带参数的方法调用 my @words = $string.comb(/\w+/); # ^^^^^^^^^^^^ 带一个参数的方法调用 另外一种方法调用的语法将方法名和参数列表用一个冒号分开(冒号紧跟方法名, 中间不能有空格): say @*INC.join: ':'; 方法能返回一个可变容器, 这种情况下 你可以赋值给方法调用的返回值. $*IN.input-line-separator = "\r\n"; 类型对象 Types本身就是对象 ,你可以使用类型的名字获取 type object : my $int-type-obj = Int; 你可以通过调用 WHAT 方法查看任何对象的 type object(它实际上是一个方法形式的macro): my $int-type-obj = 1.WHAT; 使用 === 操作符可以比较 类型对象的相等性: sub f(Int $x) { if $x.WHAT === Int { say 'you passed an Int'; } else { say 'you passed a subtype of Int'; } } 子类型可以使用 smart-matching来检查:

数值

Int Int 类型提供任意大小的整数。它们可以像计算机内存允许的那样大,虽然有些实现在被要求生成真正惊人大小的整数时会选择抛出数字溢出错误: say 10**600**600 # OUTPUT: «Numeric overflow» 与某些语言不同,当两个操作数都是 Int 类型时,使用/运算符执行除法将生成小数,而不执行任何舍入。 say 4/5; # OUTPUT: «0.8» 这种除法产生的类型是 Rat 或 Num 类型。换算后,如果分数的分母是小于64位,则产生 Rat, 否则产生 Num 类型。 如果你想落得 Int 的结果,那么 div 和 narrow 例程可能会有帮助,只要有可能。div运算符执行整除,丢弃余数,而narrow 会把数拟合到它适合的最窄类型: say 5 div 2; # OUTPUT: «2» # Result `2` is narrow enough to be an Int: say (4/2).narrow; # OUTPUT: «2» say (4/2).narrow.^name; # OUTPUT: «Int» # But 2.5 has fractional part, so it ends up being a Rat type: say (5/2).

Spark 结构化流之旅

http://vishnuviswanath.com/spark_structured_streaming.html Structured Streaming 是 Apache Spark 的流式引擎, 可用于进行近实时分析。 在这篇博客中,我们通过一个非常简单的用例来探索结构化流。 想象一下,你开始一段旅程,需要检查车辆是否超速。 我们将创建一个简单的近实时流应用程序来计算每几秒钟车辆的平均速度,同时谈论 SlidingWindow,TumblingWindow,EventTime,ProcessingTime,Watermarks 和 Kafka Source&Sink。 此博客中使用的所有代码都可以在我的 Github仓库中找到。 基于流的微批处理 在我们进入用例之前,让我们先看看 Apache Spark 中流式传输的工作原理。 Spark 中的结构化流,与其前身(DStream)类似,使用微批处理进行流式处理。 也就是说,spark 等待非常小的间隔,例如 1 秒(或甚至 0 秒 - 即,尽快),并将在该间隔期间接收的所有事件批量聚集成微批。 然后,Driver 程序将调度此微批处理在 Executors 中作为 Tasks 执行。 完成微批处理后,将收集下一批并再次调度。 频繁进行该调度以给出流式执行的印象。 Kafka Source 我们将读取 Kafka topic - cars 中的事件。 为此,我们需要将 format 设置为 “kafka”,用 boker 地址设置 kafka.bootstrap.server 并使用 “subscribe” 选项提供 topic 名。 val df: DataFrame = spark .readStream .format("kafka") .option("kafka.bootstrap.servers", "localhost:9092") .option("subscribe", "cars") //.

读取 Parquet 并写回 Kafka

读取 parquet 文件发到 Kafka 定义 schema 根据 val df = spark.read.parquet("xxxx/part-*") df.printSchema 得到的 schema 如下: root |-- alm_dm_list: array (nullable = true) | |-- element: integer (containsNull = true) |-- alm_esd_list: array (nullable = true) | |-- element: integer (containsNull = true) |-- alm_others_list: array (nullable = true) | |-- element: integer (containsNull = true) |-- data_batt_sc_volt_lowest: double (nullable = true) |-- veh_dcdcst: integer (nullable = true) |-- alm_common_dcdc_st: integer (nullable = true) |-- dm_cnt: integer (nullable = true) |-- alm_common_temp_diff: integer (nullable = true) |-- data_batt_temp_probe_highest_seq: integer (nullable = true) |-- alm_common_dm_temp: integer (nullable = true) |-- alm_common_soc_high: integer (nullable = true) |-- alm_eng_cnt: integer (nullable = true) |-- alm_common_esd_high: integer (nullable = true) |-- alm_common_esd_unmatch: integer (nullable = true) |-- alm_common_soc_low: integer (nullable = true) |-- alm_esd_cnt: integer (nullable = true) |-- veh_curr: double (nullable = true) |-- esd_temp_data: array (nullable = true) | |-- element: struct (containsNull = true) | | |-- esd_temp_probe_cnt: integer (nullable = true) | | |-- esd_temp_probe_list: array (nullable = true) | | | |-- element: integer (containsNull = true) | | |-- esd_temp_subsys_seq: integer (nullable = true) |-- esd_temp_subsys_cnt: integer (nullable = true) |-- veh_pedal_deep: integer (nullable = true) |-- esd_volt_data: array (nullable = true) | |-- element: struct (containsNull = true) | | |-- esd_curr: double (nullable = true) | | |-- esd_frame_sc_cnt: integer (nullable = true) | | |-- esd_frame_sc_list: array (nullable = true) | | | |-- element: double (containsNull = true) | | |-- esd_frame_start: integer (nullable = true) | | |-- esd_sc_cnt: integer (nullable = true) | | |-- esd_volt: double (nullable = true) | | |-- esd_volt_subsys_seq: integer (nullable = true) |-- alm_others_cnt: integer (nullable = true) |-- data_batt_sc_volt_highest: double (nullable = true) |-- veh_chargest: integer (nullable = true) |-- alm_common_esd_charge_over: integer (nullable = true) |-- ts: long (nullable = true) |-- alm_common_sc_consistency: integer (nullable = true) |-- eng_consumption: double (nullable = true) |-- veh_pedalst: integer (nullable = true) |-- data_batt_subsys_temp_lowest_seq: integer (nullable = true) |-- loc_lon84: double (nullable = true) |-- esd_volt_subsys_cnt: integer (nullable = true) |-- data_batt_temp_lowestest: integer (nullable = true) |-- data_batt_subsys_volt_lowest_seq: integer (nullable = true) |-- alm_common_brk: integer (nullable = true) |-- alm_common_esd_low: integer (nullable = true) |-- veh_insulation: integer (nullable = true) |-- veh_volt: double (nullable = true) |-- alm_common_temp_high: integer (nullable = true) |-- alm_dm_cnt: integer (nullable = true) |-- alm_lvl_higest: integer (nullable = true) |-- vin: string (nullable = true) |-- veh_odo: double (nullable = true) |-- data_batt_subsys_temp_highest_seq: integer (nullable = true) |-- data_batt_sc_volt_lowest_seq: integer (nullable = true) |-- loc_lat84: double (nullable = true) |-- eng_st: integer (nullable = true) |-- veh_soc: integer (nullable = true) |-- data_batt_subsys_volt_highest_seq: integer (nullable = true) |-- eng_spd: integer (nullable = true) |-- alm_common_insulation: integer (nullable = true) |-- alm_common_soc_hop: integer (nullable = true) |-- alm_common_flag: string (nullable = true) |-- alm_common_dcdc_temp: integer (nullable = true) |-- data_batt_temp_probe_lowest_seq: integer (nullable = true) |-- veh_runmode: integer (nullable = true) |-- veh_st: integer (nullable = true) |-- veh_spd: double (nullable = true) |-- data_batt_temp_highest: integer (nullable = true) |-- loc_st: integer (nullable = true) |-- alm_common_sc_low: integer (nullable = true) |-- veh_gear: string (nullable = true) |-- alm_common_dmc_temp: integer (nullable = true) |-- alm_common_sc_high: integer (nullable = true) |-- data_batt_sc_volt_highest_seq: integer (nullable = true) |-- alm_common_hvil_st: integer (nullable = true) |-- dm_data: array (nullable = true) | |-- element: struct (containsNull = true) | | |-- dm_ctl_dc_curr: double (nullable = true) | | |-- dm_ctl_temp: integer (nullable = true) | | |-- dm_ctl_volt: double (nullable = true) | | |-- dm_seq: integer (nullable = true) | | |-- dm_spd: integer (nullable = true) | | |-- dm_st: integer (nullable = true) | | |-- dm_temp: integer (nullable = true) | | |-- dm_torq: integer (nullable = true) 从而构造出我们在 JSON 转换时用到的 schema:

Raku 中的换行处理

不同的操作系统使用不同的字符或字符的组合来表示换行符。每种语言都有自己的一套规则来处理这个问题。 Raku 有以下几个规则: 字符串字面量中的 \n 表示 Unicode 代码点 10。 由 say 附加到字符串的默认 nl-out 也是 \n。 在输出时,当在 Windows 上时,编码器默认将 \n 转换为 \r\n,当它转到文件,进程或终端时(但它不会在套接字上执行此操作)。 在输入时,在任何平台上,解码器默认将 \r\n 标准化为 \n,以便从文件,进程或终端(同样不是套接字)输入。 以上两点一起意味着你可以 - 把套接字编程放在一边 - 期望永远不会在你的程序中看到 \r\n(这也是许多其他语言的工作原理)。 :$translate-nl 命名参数存在于控制此转换的各个位置,例如,在 Proc::Async.new 和 Proc::Async.Supply 中。 正则表达式语言中的 \n 是合乎逻辑的,并且匹配 \r\n。 您可以通过在创建该句柄时设置 :nl-out 属性来更改特定句柄的默认行为。 my $crlf-out = open(IO::Special.new('<STDOUT>'), :nl-out("\\\n\r")); $*OUT.say: 1; #OUTPUT: «1␤» $crlf-out.say: 1; #OUTPUT: «1\␤␍» 在这个例子中,我们通过使用 IO::Special 将标准输出复制到新句柄,我们在字符串的末尾附加一个 \,然后是换行符 ␤ 和回车符 ␍; 我们打印到该句柄的所有内容都会在行尾添加这些字符,如图所示。 在正则表达式中,\n 是根据逻辑换行符的Unicode定义定义的。它会匹配 ., 还有 \v,以及包含空格的任何类。

Nim

真的无聊, 我在学习 Nim。 Nim 的升级使用 choosenim update stable, choosenim 的升级使用 choosenim update self。

原生调用接口

原生调用接口 入门指南 能想象出的最简单的 NativeCall 用法应该类似于这样的东西: use NativeCall; sub some_argless_function() is native('something') { * } some_argless_function(); 第一行导入了各种 traits 和类型,接下来的一行看起来很像相对普通的 Raku 子例程声明 - 稍微有点变化。我们使用native这个 trait 是为了指定这个 sub 子例程实际上被定义在原生库中。Raku 会给你添加特定平台的扩展名(比如 .so 或者 .dll)还有任何惯常的前缀(例如: ’lib’)。 当你第一次调用 “some_argless_function” 时,“libsomething” 将会被加载,然后会在 libsomething 库中定位到 “some_argless_function” 函数,接下来将会进行一次调用。之后的调用将会更快,因为符号句柄会被保留。 当然,大部分的函数都会接受参数或者返回值 - 但是你可以做的其他事情只是增加了这个声明Raku sub的简单模式 但是一切你需要做的就是增加这个简单的模式,通过声明一个 Raku 的过程、在符号后面指出你想要调用的名字,并且使用 “native” trait。 改变名字 有时你想要 Raku 子例程的名字和加载库中使用的名字不同,可能这个名字很长, 或者有不同的大小写或者在你想要创建的模块的上下文中, 这个名字很繁琐。 NativeCall 为你提供了一个 symbol trait 以指定库中原生子例程的名字, 这个名字和你的 Raku 子例程名字不同。 module Foo; use NativeCall; our sub init() is native('foo') is symbol('FOO_INIT') { * } 在 libfoo 库里面有一个子例程叫 FOO_INIT,因为我们创建了一个模块叫做 Foo,我们更愿意使用 Foo::init 调用子例程,我们使用 symbol trait 来指定在 libfoo 库名字符号的名字,然后以任何我们想要的方式调用这个子例程(这里是 “init”)。

codewars

codewars Highest and Lowest In this little assignment you are given a string of space separated numbers, and have to return the highest and lowest number. Example: Kata.HighAndLow("1 2 3 4 5"); // return "5 1" Kata.HighAndLow("1 2 -3 4 5"); // return "5 -3" Kata.HighAndLow("1 9 3 4 -5"); // return "9 -5" Notes: All numbers are valid Int32, no need to validate them. There will always be at least one number in the input string.

roundrobin

roundrobin 在中文里意味着 「设置轮流捡取」。 roundrobin 用于构建 list of lists. say roundrobin <a b c>, <d e f>, <g h i>; # OUTPUT: «((a d g) (b e h) (c f i))␤» say .join(",") for roundrobin([1, 2], [2, 3], [3, 4]); # OUTPUT: «1,2,3␤ # 2,3,4␤» 一旦输入列表中的一个或多个列表中的元素用光了, roundrobin 不会终止, 而是继续直到所有列表的所有元素用光为止。 say roundrobin <a b c>, <d e f m n o p>, <g h i j>; # OUTPUT: «((a d g) (b e h) (c f i) (m j) (n) (o) (p))␤» say .

元对象协议

自省和 Raku 的对象系统 Raku 是构建在元对象层上面的。那意味着有些对象(元对象)控制着各种面向对象结构(例如类、roles、方法、属性、枚举,…)怎样去表现。 要感受类的元对象, 这儿有一个同样的例子出现2次: 一次一种 Raku中的普通声明, 一次通过元模型来表达: class A { method x() { say 42 } } A.x(); # 42 对应于: constant A := Metamodel::ClassHOW.new_type( name => 'A' ); # class A { A.^add_method('x', my method x(A:) { say 42 }); # method x() .. . A.^compose; # } A.x(); # 42 (除了声明形式的运行在编译时, 后面这种形式不是) 对象后面的元对象能使用 $obj.HOW获取, 这儿的 HOW 代表着 Higher Order Workings(或者 HOW the *%@$ does this work?

bettercap

bettercap 入门 bettercap -autostart net.probe on, set ticker.commands net.show, ticker on bettercap -eval "net.probe on; set ticker.commands net.show; ticker on" help wifi active 显示活动的模块 get wifi.ap.encryption # 获取参数值 get * # 获取所有参数 set wifi.ap.encryption false # 设置参数 help net.recon # 探测模块帮助 wifi.recon on # 开启 wifi 探测 wifi.show # 显示 wifi 列表 wifi.deauth 72:fe:a1:d2:fb:c2 # 攻击一次 全自动 wifi 攻击 help ticker set ticker.commands "wifi.deauth d4:ee:07:67:cc:16" set ticker.period 1 # 设置每秒攻击一次 ticker on ticker off 全自动探测 wifi 热点 set ticker.

列表、序列和数组

列表一直是计算机的核心部分,因为之前有计算机,在这段时间里,许多恶魔占据了他们的细节。 它们实际上是 Raku 设计中最难的部分之一,但是通过坚持和耐心,Raku 已经使用了一个优雅的系统来处理它们。 Literal Lists 字面上的列表用逗号和分号不是用圆括号创建,因此: 1, 2 # This is two-element list (1, 2) # This is also a List, in parentheses (1; 2) # same List (1) # This is not a List, just a 1 in parentheses (1,) # This is a one-element List 括号可用于标记列表的开头和结尾,因此: (1, 2), (1, 2) # This is a list of two lists. 多维字面上的列表是通过逗号和分号组合而成的。 它们可以在常规参数列表和下标中使用。 say so (1,2; 3,4) eqv ((1,2), (3,4)); # OUTPUT«True␤» say('foo';); # a list with one element and the empty list # OUTPUT«(foo)()␤» 单个元素可以使用下标从列表中拉出。 列表的第一个元素的索引号为零:

sub trip

Use the exa command line instead of ls to list tree of code: master ~/work/cihon/gac/socket-streaming> exa --tree socket-streaming/src socket-streaming/src └── main ├── resources │ ├── application.conf │ ├── application.prd.conf │ ├── application.test.conf │ ├── log4j.properties │ └── metrics.properties └── scala └── com └── gac └── xs6 └── bigdata ├── BigdataApplication.scala ├── conf │ └── SparkConfiguration.scala ├── core │ ├── Adapter.scala │ └── impl │ ├── AdapterImpl.scala │ └── SubTripImpl.scala ├── model │ ├── SourceData.

输入和输出专家指南

基础知识 绝大多数常见的 IO 工作都是由IO::Path类型完成的。如果您想以某种形式或形状读取或写入文件,这就是您想要的类。它抽象出文件句柄(或“文件描述符”)的细节,因此你甚至不必考虑它们。 在幕后,IO::Path 与 IO::Handle 一起使用 ; 一个你可以直接使用的类,如果你需要比 IO::Path 提供的更多控制。当与其他进程,例如通过 Proc 或 Proc::Async类型,您还可以处理IO::Handle 的子类:在IO::Pipe。 最后,你有 IO::CatHandle,以及 IO::Spec 及其子类,你很少直接使用它们。这些类为您提供了高级功能,例如将多个文件作为一个句柄进行操作,或者进行低级路径操作。 除了所有这些类之外,Raku 还提供了几个子程序,可以让您间接使用这些类。如果您喜欢函数式编程风格或 Raku 单行程序,这些就派上用场了。 虽然 IO::Socket 及其子类也与输入和输出有关,但本指南并未涵盖它们。 导航路径 What’s an IO::Path anyway? 要将路径表示为文件或目录,请使用 IO::Path 类型。获取该类型对象的最简单方法是通过在它身上调用 .IO 方法强制将 Str 类型转为路径类型: say 'my-file.txt'.IO; # OUTPUT: «"my-file.txt".IO␤» 看起来这里似乎缺少某些东西 - 没有卷或绝对路径 - 但该信息实际上存在于对象中。你可以通过使用 .perl 方法看到它: say 'my-file.txt'.IO.perl; # OUTPUT: «IO::Path.new("my-file.txt", :SPEC(IO::Spec::Unix), :CWD("/home/camelia"))␤» 这两个额外的属性 - SPEC 和 - CWD 指定路径应该使用的操作系统语义类型以及路径的“当前工作目录”,即如果它是相对路径,则它相对于该目录。 这意味着无论你如何制作一个路径,IO::Path 对象在技术上总是指一个绝对路径。这就是它的 .absolute 和 .relative 方法返回 Str 对象的原因,它们是字符串化路径的正确方法。

散列和映射

关联角色和关联类 关联角色是 Hash 和 Map 以及 MixHash 等其他类的基础。它定义了将在关联类中使用的两种类型; 默认情况下,您可以使用任何内容(字面意思,因为任何 Any 子类的类都可以使用)作为键和 keyof 方法。 默认情况下,使用 % sigil 声明的任何对象都将获得 Associative 角色,默认情况下将表现为散列,但此角色仅提供上述两种方法,以及默认的 Hash 行为。 say (%).^name ; # 输出 Hash 相反,如果未混入 Associative 角色,则不能使用 % sigil,但由于此角色没有任何关联属性,因此你必须重新定义散列下标操作符的行为。为此,你必须重写几个函数: class Logger does Associative[Cool,DateTime] { has %.store; method log( Cool $event ) { %.store{ DateTime.new( now ) } = $event; } multi method AT-KEY ( ::?CLASS:D: $key) { my @keys = %.store.keys.grep( /$key/ ); %.store{ @keys }; } multi method EXISTS-KEY (::?

使用 Spark Structured Streaming 落数据到 HBase 中

编译 SHC 截至目前, Structured Streaming 中的 Sink 还不支持 HBase! Stackoverflow 上有一个解决方案是用第三方的 shc自定义自己的 HBase Sink。有人按照第二个答案说搞不出来, 我也照着做了一遍, 发现是真的! 继续往下翻, 发现 github 上有个人做了一丢丢的修改, 于是我照抄了过来: package org.apache.spark.sql.execution.datasources.hbase import org.apache.spark.sql.{DataFrame, SQLContext, Row} import org.apache.spark.sql.execution.streaming.Sink import org.apache.spark.sql.sources.{DataSourceRegister, StreamSinkProvider} import org.apache.spark.sql.streaming.OutputMode import org.apache.spark.sql.catalyst.CatalystTypeConverters class HBaseSink(options: Map[String, String]) extends Sink with Logging { // String with HBaseTableCatalog.tableCatalog private val hBaseCatalog = options.get("hbasecat").map(_.toString).getOrElse("") override def addBatch(batchId: Long, data: DataFrame): Unit = synchronized { val schema = data.schema val res = data.

Grammars

Grammars Grammars - 一组具名 regexes 组成正式的 grammar Grammars 是一个很强大的工具用于析构文本并通常返回数据结构。 例如, Raku 是使用 Raku 风格 grammar 解析并执行的。 对普通 Raku 使用者更实用的一个例子是 JSON::Tiny模块, 它能反序列化任何合法的 JSON 文件, 而反序列代码只有不到 100 行, 还能扩展。 Grammars 允许你把 regexes 组织到一块儿, 就像类(class) 中组织方法那样。 具名正则 (Named Regexes) grammars 的主要组成部分是 regexes。 而 Raku 的 regexes语法不在该文档的讨论范围, 具名正则(named regexes) 有它自己的特殊语法, 这跟子例程(subroutine) 的定义很像: my regex number { \d+ [ \. \d+ ]? } # 普通 regex 中空格被忽略, [] 是非捕获组 上面的代码使用 my 关键字指定了本地作用域的 regex, 因为具名正则(named regexes) 通常用在 grammars 里面。

函数

Raku 中的函数 例程(Routines)是 Raku 中代码重用的最小手段。它们有几种形式,最明显的是属于类和角色并与对象相关联的方法,还有函数, 也叫做子例程或短子程序,它们独立于对象而存在。 子例程默认是词法(my)作用域的,对它们的调用通常在编译时解析。 子例程可以具有签名,也称为参数列表,其指定签名期望的参数(如果有的话)。 它可以指定(或保持打开)参数的数量和类型,以及返回值。 子例程的内省通过例程提供。 定义/创建/使用 函数 子例程 创建子例程的基本方法是使用 sub 声明符,后跟可选标识符 sub my-func { say "Look ma, no args!" } my-func; sub 声明符返回可以存储在任何容器中的 Sub 类型的值: my &c = sub { say "Look ma, no name!" } c; # OUTPUT: «Look ma, no name!␤» my Any:D $f = sub { say 'Still nameless...' } $f(); # OUTPUT: «Still nameless...␤» my Code \a = sub { say ‚raw containers don't implement postcircumfix:<( )>‘ }; a.

使用 Raku 发送数据到 Socket

While programming in Spark Streaming, I’d like to send some fake data to socket. although the command nc -lk 9999 is useful, but you have to type the data manually! With Raku’s IO::Socket::Async module, I can easily send data to socket continuously. In the server side, I listen a port on 3333, I will print the current time every second, every 5 seconds, skip the report of time: my $vin = 'LSJA0000000000091'; my $last_meter = 0; # 当前里程数 react { whenever IO::Socket::Async.

异常

Raku 中的异常是保存有关错误信息的对象。例如,错误可能是意外接收数据或网络连接不再可用,或者丢失文件。异常对象存储的信息是关于错误条件的人类可读消息,错误引发的回溯等等。 所有内置异常都继承自 Exception,它提供了一些基本行为,包括回溯的存储和回溯打印机的接口。 热异常 通过调用带有描述错误的 die 函数来使用热异常: die "oops, something went wrong"; # RESULT: «oops, something went wrong in block <unit> at my-script.p6:1␤» 值得注意的是,die 会将错误消息打印到标准错误 $*ERR。 类型化的异常 类型化异常提供有关异常对象中存储的错误的更多信息。 例如,如果在对象上执行 .zombie copy 时,所需的路径 foo/bar 变得不可用,则可以引发 X::IO::DoesNotExist异常: die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy")) # RESULT: «Failed to find 'foo/bar' while trying to do '.zombie copy' # in block <unit> at my-script.p6:1» 请注意对象如何为回溯提供有关出错的信息。代码的用户现在可以更轻松地找到并纠正问题。 捕获异常 通过提供 CATCH 块可以处理异常情况: die X::IO::DoesNotExist.new(:path("foo/bar"), :trying("zombie copy")); CATCH { when X::IO { $*ERR.

枚举

在 Raku 中,枚举(enum)类型比其他语言复杂得多,详细信息可在此处的类型描述中找到。 这个简短的文档将给出一个简单的使用示例,就像在 C 语言中一样。 假设我们有一个需要写入各种目录的程序; 我们想要一个函数,给定一个目录名,测试它(1)是否存在(2)它是否可以被该程序的用户写入; 这意味着从用户的角度来看有三种可能的状态:要么你可以写(CanWrite),要么没有目录(NoDir)或者目录存在,但你不能写(NoWrite)。 测试结果将决定程序接下来要采取的操作。 enum DirStat <CanWrite NoDir NoWrite>; sub check-dir-status($dir --> DirStat) { if $dir.IO.d { # dir exists, can the program user write to it? my $f = "$dir/.tmp"; spurt $f, "some text"; CATCH { # unable to write for some reason return NoWrite; } # if we get here we must have successfully written to the dir unlink $f; return CanWrite; } # if we get here the dir must not exist return NoDir; } # test each of three directories by a non-root user my $dirs = '/tmp', # normally writable by any user '/', # writable only by root '~/tmp'; # a non-existent dir in the user's home dir for $dirs -> $dir { my $stat = check-dir-status $dir; say "status of dir '$dir': $stat"; if $stat ~~ CanWrite { say " user can write to dir: $dir"; } } # output # status of dir '/tmp': CanWrite # user can write to dir: /tmp # status of dir '/': NoWrite # status of dir '~/tmp': NoDir

日期和时间函数

Raku 包括几个处理时态信息的类:Date,DateTime,Instant 和 Duration。前三个是 dateish,所以它们混合了 Dateish 角色,它定义了处理日期的类应该采用的所有方法和属性。它还包括以 X::Temporal 为根的异常的类层次结构。 我们将尝试在下一个(稍微扩展)的示例中说明这些类,这个示例可用于处理目录中的所有文件(默认情况下)。在目录中使用特定扩展名(默认为 .p6),根据他们的年龄对其进行排序,并计算每月创建的文件数量,以及在几个月的范围内表示的特定时期内修改的文件数量: use v6; sub MAIN( $path = ".", $extension = "p6" ) { my DateTime $right = DateTime.now; my %metadata; my %files-month; my %files-period; for dir($path).grep( / \.$extension $/ ) -> $file { CATCH { when X::Temporal { say "Date-related problem", .payload } when X::IO { say "File-related problem", .payload } default { .payload.say } } my Instant $modified = $file.

数据流

标量结构 某些类没有任何内部结构, 访问它们的一部分必须使用特定的方法。数字,字符串和其他一些整体类包含在该类中。他们使用 $ sigil,虽然复杂的数据结构也可以使用它。 my $just-a-number = 7; my $just-a-string = "8"; 有一个 Scalar 类,它在内部用于为使用 $ sigil 声明的变量赋值。 my $just-a-number = 333; say $just-a-number.VAR.^name; # OUTPUT: «Scalar␤» 任何复杂数据结构都可以通过使用 $ 在项上下文中标量化。 (1, 2, 3, $(4, 5))[3].VAR.^name.say; # OUTPUT: «Scalar␤» 但是,这意味着它将在它们的上下文中被视为标量。你仍然可以访问其内部结构。 (1, 2, 3, $(4, 5))[3][0].say; # OUTPUT: «4␤» 有一个有趣的副作用,或者可能是故意的特性,是标量化保留了复杂结构的同一性。 for ^2 { my @list = (1, 1); say @list.WHICH; } # OUTPUT: «Array|93947995146096␤Array|93947995700032␤» 每次 (1, 1) 被分配时,创建的变量在 === 上的意义上是不同的; 如它所示,打印了内部指针所表示的不同值。然而

控制流

语句 Raku 程序由一个或多个语句组成。简单语句由分号分隔。以下程序将打印 “Hello”,然后在下一行打印“World”。 say "Hello"; say "World"; 在语句中出现空白的大多数地方,且在分号之前,语句可能会分成许多行。此外,多个语句可能出现在同一行。这会很尴尬,但上面的内容也可以写成: say "Hello"; say "World"; 块儿 与许多语言一样,Raku 使用 { 和 } 将 blocks括起来以将多个语句转换为单个语句。可以省略块中最后一个语句和闭合 }之间的分号。 { say "Hello"; say "World" } When a block stands alone as a statement, it will be entered immediately after the previous statement finishes, and the statements inside it will be executed. 当块单独作为一个语句存在时,它将在前一个语句完成后立即进入,并且其中的语句将被执行。 say 1; # OUTPUT: «1» { say 2; say 3 }; # OUTPUT: «23» say 4; # OUTPUT: «4» 除非它作为一个语句单独存在,否则一个块只会创建一个闭包。内部的语句不会立即执行。闭包是另一个主题,如何使用它们在别处有解释。现在,了解块何时运行以及何时不运行是非常重要的:

上下文和上下文器

在许多情况下,需要上下文来解释容器的值。在 Raku 中,我们将使用 context 将容器的值强制转换为某种类型或类,或者决定如何处理它,就像接收器(sink)上下文的情况一样。 Sink 上下文 Sink 相当于 void 上下文,也就是说,我们抛出(在接收器下面)操作的结果或块的返回值的上下文。通常,当语句不知道如何处理该值时,将在警告和错误中调用此上下文。 my $sub = -> $a { return $a² }; $sub; # OUTPUT: «WARNINGS: Useless use of $sub in sink context (line 1)» 您可以使用 sink-all 方法在 Iterator 上强制使用该接收器上下文。Proc也可以通过 sink 方法沉没,迫使它们引发异常而不返回任何东西。 通常,如果在 sink 上下文中进行计算,则块将发出警告; 但是,在 sink 上下文中 gather/take 块是显式计算的,并使用 take 显式返回值。 在 sink 上下文中,对象将调用其 sink 方法(如果存在): sub foo { return [<a b c>] does role { method sink { say "sink called" } } } foo # OUTPUT: sink called Number 上下文 这个上下文,可能除了上面的所有内容之外,都是转换或解释上下文,因为它们接收无类型或类型化的变量,并将其类型化为执行操作所需的任何内容。在某些情况下,这意味着转换(例如从 Str 到 Numeric); 在其他情况下只是一种解释(IntStr 将被解释为 Int 或 Str)。

容器

本节介绍了处理变量和容器元素时所涉及的间接级别。解释了 Raku 中使用的容器的不同类型,以及适用于它们的操作,如赋值,绑定和展平。最后讨论了更多高级主题,如自引用数据,类型约束和自定义容器。 变量是什么? 有些人喜欢说“一切都是对象”,但实际上在 Raku 中变量不是对用户暴露的对象。 当编译器遇到类似 my $x 的变量声明时,它会将其注册到某个内部符号表中。此内部符号表用于检测未声明的变量,并将变量的代码生成与正确的作用域联系起来。 在运行时,变量显示为词法板中的条目,或简称为lexpad。这是一个每个作用域的数据结构,它存储每个变量的指针。 在 my $x 这种情况下,变量的 $x 的 lexpad 条目是指向 Scalar 类型对象的指针,通常称为容器。 标量容器 虽然 Scalar 类型的对象在 Raku 中无处不在,但您很少直接将它们视为对象,因为大多数操作都是去容器化的,这意味着它们会对 Scalar 容器的内容而不是容器本身起作用。 在这样的代码中: my $x = 42; say $x; 赋值 $x = 42 在标量容器中存储指向 Int 对象 42 的指针,lexpad 条目 $x 指向该标量容器。 赋值运算符要求左侧的容器将值存储在其右侧。究竟是什么意思取决于容器类型。因为 Scalar 它意味着“用新的值替换先前存储的值”。 请注意,子例程签名允许传递容器: sub f($a is rw) { $a = 23; } my $x = 42; f($x); say $x; # OUTPUT: «23» 在子例程内部,lexpad 条目 $a 指向 $x 指向子例程外部的同一容器。这就是为什么给 $a 赋值也修改了 $x 的内容。

要避免的陷阱

在学习一门编程语言时,可能有熟悉另一门编程语言的背景,总有一些事情会让您感到惊讶,并且可能会耗费宝贵的调试和发现时间。 本文件旨在展示常见的误解,以避免它们。 在编写 Raku 的过程中,我们付出了巨大的努力来消除语法中的瑕疵。然而,当你消灭一个瑕疵的时候,有时另一个会突然冒出来。所以我们花了很多时间去寻找最小数量的瑕疵或者试图把它们放在它们很少被看到的地方。正因为如此,Raku 的瑕疵出现在了不同的地方,而不是来自另一种语言时所期望的那样。 变量和常量 常量在编译时计算 常量是在编译时计算的,所以如果在模块中使用它们,请记住,由于模块本身的预编译,它们的值将被冻结: # WRONG (most likely): unit module Something::Or::Other; constant $config-file = "config.txt".IO.slurp; $config-file 将在预编译时一次性被读入。config.txt 文件的更改不会在你再次启动脚本时重新加载;只有当模块被重新编译时才会重新加载。 避免使用容器,而倾向于将值绑定到提供类似于常量行为的变量上,但允许更新值: # Good; file gets updated from 'config.txt' file on each script run: unit module Something::Or::Other; my $config-file := "config.txt".IO.slurp; 赋值为 Nil 产生不同的值, 通常是 Any 实际上,赋给 Nil 会将变量还原为其默认值。所以: my @a = 4, 8, 15, 16; @a[2] = Nil; say @a; # OUTPUT: «[4 8 (Any) 16]␤» 在本例中,Any 是 Array 元素的默认值。

测试

测试代码是软件开发不可或缺的一部分。测试提供代码行为的自动,可重复的验证,并确保您的代码按预期工作。 在 Raku 中,Test 模块提供了一个测试框架,也被 Raku 的官方 spectest 套件使用。 测试函数发出符合 Test Anything Protocol 的输出。通常,它们用于 sink 上下文中: ok check-name($meta, :$relaxed-name), "name has a hyphen rather than '::'" 但是不论测试成功与否,所有函数都会返回布尔值,如果测试失败,可以使用它来打印消息: ok check-name($meta, :$relaxed-name), "name has a hyphen rather than '::'" \ or diag "\nTo use hyphen in name, pass :relaxed-name to meta-ok\n"; 写测试 与任何 Perl 项目一样,测试位于项目基本目录的 t 目录下。 典型的测试文件看起来像这样: use v6.c; use Test; # a Standard module included with Rakudo use lib 'lib'; plan $num-tests; # .

Raku 中的大部分句法结构能归类为项和操作符. 这儿你能找到各种不同类型的项的概览. Literals Int 42 12_300_00 :16<DEAD_BEEF> #十六进制 Int 字面量由数字组成, 并且能在数字之间包含下划线. 使用 :radix<number> 冒号对儿形式能指定 10 进制外的其它进制. Rat 有理数 12.34 1_200.345_678 Rat(有理数)字面量由一个点号分割的两部分整数组成. 注意尾部的点号是不允许的, 所以你必须写成 1.0 而非 1. ( 这个规则很重要, 因为有一个以点号开头的中缀操作符, 例如 .. 范围操作符 ). Num 浮点数 12.3e-32 3e8 Num(浮点数)字面量由 Rat 或 Int 字面量后面再跟着一个字母 e 和 一个指数(可能为负)组成. 3e8 使用 值 3* 10**8 构建了一个 Num. Str 'a string''I\'m escaped!' "I don't need to be" "\"But I still can be,\" he said." q|Other delimiters can be used too!

Pod 6 表

Raku POD 表的官方规范位于文档规范中:表。虽然 Pod 6 的规格尚未完全妥善处理,但仍有几个项目正在进行纠正。一个这样的项目是确保正确处理 Pod 6 表。 作为该工作的一部分,本文档通过示例解释了 Pod 6 表的当前状态:有效表,无效表和丑陋表(即,由于草率构造,可能导致与用户期望的不同的有效表) 。 Restrictions 1.唯一有效的列分隔符要么是可见的(| 或 +)(注意在可见列分隔符之前和之后至少需要一个空格)或不可见[两个或多个连续的空格(WS)字符(例如, ’ ‘)]。在表格的左侧或右侧通常不会识别列分隔符,但是右侧的列分隔符可能会导致一个或多个空单元格,具体取决于其他行中单元格的数量。(请注意,作为单元格数据一部分的管道或加号字符将导致意外的额外列,除非使用反斜杠转义字符,例如 \| 或 \+。) 2.在同一个表中混合可见和不可见的列分隔符是非法的。 3.唯一有效的行分隔符字符是 _,-,+,' ',| 和 =。 4.连续的内部行分隔符是非法的。 5.前导和尾随行分隔符会生成警告。 6.当前忽略表格单元格中的格式,并将其视为纯文本。 提示:在开发过程中,使用环境变量 RAKUDO_POD6_TABLE_DEBUG 将向您展示 Rakudo 如何在将 pod 表传递给渲染器之前解释它们,例如 Pod::To::HTML,Pod::To::Text 和 Pod::To::Markdown。 最佳实践 提示:由于在表行上进行额外的循环,不遵循以下最佳实践可能需要更多的表处理。 1.对列分隔符使用 WS 很脆弱,它们只能用于简单表。以下 Ugly Tables 部分说明了这个问题。 2.仔细对齐表格列和行。请参阅后面的最佳实践中的示例。 3.不要在表上使用可见的边框。 4.对于具有标题和单行或多行内容的表,在标题后使用一个或多个连续的等号(’=’)作为行分隔符,并使用一个或多个连续的连字符(’-’)作为表的内容部分中的行分隔符。例如, 标题和单行或多行内容 =begin table hdr col 0 | hdr col 1 ====================== row 0 | row 0 col 0 | col 1 ---------------------- row 1 | row 1 col 0 | col 1 ---------------------- =end table 标题和单行内容 =begin table hdr col 0 | hdr col 1 ====================== row 0 col 0 | row 0 col 1 row 1 col 0 | row 1 col 1 =end table 5.

Raku pod

Raku Pod 是一种易于使用的标记语言。 Pod 可用于编写语言文档,用于文档化程序和模块,以及其他类型的文档组合。 每个 Pod 文档必须以 =begin pod 开头,以 =end pod 结束。这两个分隔符之间的所有内容都将被处理并用于生成文档。 =begin pod A very simple Raku Pod document =end pod 块结构 Pod 文档可能包含多个 Pod 块。有四种方法可以定义块(分隔符,段落,缩写和声明符); 前三个产生相同的结果,但第四个不同。你可以使用最方便你的特定文档任务的任何形式。 分割符块 分隔块由 =begin 和 =end 标记限定,两者都后跟有效的 Raku 标识符,后者是块的 typename。完全小写的类型名称(例如 =begin head1)或完全大写(例如:=begin SYNOPSIS)保留。 =begin head1 Top Level Heading =end head1 配置信息 在 typename 之后, =begin 标记行的其余部分被视为块的配置信息。此信息由不同类型的块以不同方式使用,但始终使用 Raku-ish 选项对指定。也就是说,任何: alue is… Specify with… Or with… Or with… List :key[$e1, $e2, …] :key($e1, $e2, …) Hash :key{$k1=>$v1, $k2=>$v2} Boolean (true) :key :key(True) :key[True] Boolean (false) :!

术语

匿名 子例程、方法或子方法,当它们不能通过名字调用时,就被称为匿名的 # named subroutine sub double($x) { 2 * $x }; # 匿名子例程,存储在一个具名的标量里 my $double = sub ($x) { 2 * $x }; 注意,匿名子例程仍然可以有名字 # 使用 anon 关键字使子例程匿名 my $s = anon sub triple($x) { 3 * $x } say $s.name; # triple 副词 通常, 副词是函数的命名参数. 也有一些其它特殊语法形式允许副词出现在某些合适的地方: q:w"foo bar" # ":w" is a Quotelike form modifier adverb m:g/a|b|c/ # ":g" is also 4 +> 5 :rotate # ":rotate" is an operator adverb @h{3}:exists # ":exists" is also, but is known as a subscript adverb 副词通常使用冒号对儿标记来表示, 因为这个原因, 冒号对儿标记法也以副词对儿形式著称:

FAQ

源文件可以在 github 或 raku.org上找到. General Rakudo 和 Raku 的区别是什么? Rakudo 是 Raku 的一个实现。目前它是完成度最好的但是过去也有其它的实现, 将来也可能会有其它实现。Raku 是语言的定义。很多场合 这两个名字可以宽松地使用并互相替换。 会有 Raku 版本 6.0.0 吗? 第一个稳定语言版本的版本称为 v6.c,而不是 6.0.0。 不同的命名方案使得不太可能发布具有精确版本 6.0.0 的语言。 您可以使用下面的代码检查您的 Rakudo 编译器是当前至少是什么版本(注意这可能不是真正的供应商二进制文件): raku -e 'say q[too old] if $*PERL.version before Version.new(q[6.c])' 它首先由 Rakudo Raku 编译器版本的 2015.12 实现,并且可能通过使用 ‘use 6.c’ 指令在可预见的未来支持后续版本。 下一个语言版本(无发布日期)为 v6.d. 作为一个 Raku 初学者我应该安装什么? 如果你是一个 Linux 或 Mac 用户, 你可能需要下载 Rakudo Star 并通过编译 MoarVM 版本安装(一个简单的处理) 如果你是一个 Windows 32 或 64 位用户, 那么 Rakudo Star 二进制版本在 rakudo 网站也能获得。你需要 Windows Git 来使用 panda。

社区

概览 “Perl 5是我对 Perl 的重写。我希望 Raku 能够成为社区重写的 Perl, 并且 Raku 是社区的 Perl。” - 拉里沃尔 Raku 社区 freenode.net 上的 #raku 频道有很多人,他们很乐意提供支持和回答问题。可以在 raku.org 社区页面 中找到更多资源。 Camelia 是她身上带有 P 6 的多色蝴蝶,她是这个多元化和热情的社区的象征。我们广泛使用 #raku IRC 频道进行沟通,提问和简单地闲逛。查看此IRC术语资源,了解那里经常使用的缩写。 StackOverflow 也是一个很好的资源,用于提出问题并帮助其他人解决他们的 Raku 问题和挑战。 Raku 周刊 Elizabeth Mattijsen 通常在 “Raku Weekly” 博客中发帖,这是有关 Raku 的帖子,推文,评论和其他有趣花絮的摘要。是知道 Perl 社区正在发生什么的最佳单个资源。 Raku 降临节日历 Raku 社区每年 12 月都会发布一个 Advent Calendar,每天都有 Raku 教程,直到圣诞节。通过不同的 Raku 频道和 Raku/mu 存储库完成组织和日期分配。如果您想参与,它将在10月底开始组织,因此请查看上面的频道。

关于文档

本文档集代表了正在努力记录 Raku 编程语言的目标是:全面; 使用方便; 易于导航; 对新手和经验丰富的 Raku 程序员都很有用。 该文档的 HTML 版本位于线上的 https://docs.raku.org。 该文档的官方来源位于 GitHub上的raku/doc。 本特定文档快速概述了在GitHub上的贡献中更详细描述的过程。本文档还简要介绍了编写 Raku Pod 文件,这些文件可以渲染为 HTML 和其他格式。 结构 所有文档都是用 Raku Pod 编写的,并保存在 doc/ 目录 doc/Language/ 和 doc/Type/ 子目录中。这些文件作为定义集合或“文档”处理,然后进行后处理并链接在一起。 从 Pod 生成 HTML 要从 Pod 文件生成 HTML,你需要: 最新版本的 Rakudo Raku 编译器 Raku 模块Pod::To::HTML,Pod::To::BigPage 和 URI::Escape(可以通过zef安装)。 可选:GraphViz,用于创建 Raku 类型之间关系的图形 可选:Atom Highlights 和 language-raku,用于语法高亮显示 要在文件html/文件夹中生成文档,请运行: raku htmlify.p6 要从 Web 服务器托管文档,请安装 Perl 5 和 Mojolicious::Lite,然后运行: perl app.pl daemon 贡献 文档是用 Raku Pod 编写的。

正则表达式最佳实践

为了提供强大的正则表达式和 grammar,这里有一些代码布局和可读性的最佳实践,实际匹配的内容,以及避免常见的陷阱。 代码布局 如果没有 :sigspace 副词,在 Raku 正则表达式中空格并不重要。 使用它自己的优势,并插入空格,增加可读性。 此外,必要时插入注释。 比较非常紧凑的写法 my regex float { <[+-]>?\d*'.'\d+[e<[+-]>?\d+]? } 和这种可读性更好的写法: my regex float { <[+-]>? # optional sign \d* # leading digits, optional '.' \d+ [ # optional exponent e <[+-]>? \d+ ]? } 根据经验, 在原子周围和组的内部使用空白; 将量词直接放在原子之后; 并垂直对齐开口和闭合关方括号和括号。 在括号或方括号内使用替换列表时,请对齐竖线: my regex example { <preamble> [ || <choice_1> || <choice_2> || <choice_3> ]+ <postamble> } 保持短小 正则代码通常比常规代码更紧凑。 因为他们用这么少的字符就做得那么多,所以保持了正则表达式的简短。 当你可以给正则表达式的一部分命名时,通常最好将它放入一个单独的,命名的正则表达式中。 例如,您可以以前面获取浮点正则表达式为例: my regex float { <[+-]>?

创建操作符

通过使用 sub 关键字后跟 prefix, infix, postfix, circumfix, 或 postcircumfix; 声明运算符; 然后是冒号结构中的冒号和运算符名称。对于(后)环缀操作符,用空格分隔这两部分。 sub hello { say "Hello, world!"; } say &hello.^name; # OUTPUT: «Sub␤» hello; # OUTPUT: «Hello, world!␤» my $s = sub ($a, $b) { $a + $b }; say $s.^name; # OUTPUT: «Sub␤» say $s(2, 5); # OUTPUT: «7␤» # Alternatively we could create a more # general operator to sum n numbers sub prefix:<Σ>( *@number-list ) { [+] @number-list } say Σ (13, 16, 1); # OUTPUT: «30␤» sub infix:<:=:>( $a is rw, $b is rw ) { ($a, $b) = ($b, $a) } my ($num, $letter) = ('A', 3); say $num; # OUTPUT: «A␤» say $letter; # OUTPUT: «3␤» # Swap two variables' values $num :=: $letter; say $num; # OUTPUT: «3␤» say $letter; # OUTPUT: «A␤» sub postfix:<!

模块

模块 导出和选择性导出 is export packages(包), subroutines(子例程), variables(变量), constants(常量) 和 enums(枚举) , 通过在它们的名字后面添加 is export 特性来导出。 unit module MyModule; our $var is export = 3; sub foo is export { ... }; constant $FOO is export = "foobar"; enum FooBar is export <one two three>; # Packages like classes can be exported too class MyClass is export {}; # If a subpackage is in the namespace of the current package # it doesn't need to be explicitly exported class MyModule::MyClass {}; 就像所有的 traits 一样, 如果应用到子例程(routine)上, “is export” 应该出现在参数列表的后面:

模块包

N.B. “Module” is an overloaded term in Raku; this document focuses on use of the module declarator. 注意 “模块”是 Raku 中的重载术语; 本文档重点介绍 module 声明符的使用。 什么是模块? 模块,如类和 grammars,是一种包。模块对象是 ModuleHOW 元类的实例; 这提供了某些功能,可用于创建命名空间,版本控制,代理和数据封装(另请参见类和角色)。 要创建模块,请使用 module 声明符: module M {} say M.HOW; # OUTPUT: «Raku::Metamodel::ModuleHOW.new» 这里我们定义一个名为 M 的新模块; 内省 HOW 确认了底层的元类 M 是 Raku::Metamodel::ModuleHOW。 何时使用模块 模块主要用于封装不属于类或角色定义的代码和数据。模块内容(类,子程序,变量等)可以从具有 is export trait 的模块中导出; 一旦import 或 use 了模块,这些内容在调用者的命名空间中就可用了。模块还可以选择性地在其命名空间中通过 our 暴露符号以进行限定引用。 使用模块 为了说明模块作用域和导出规则,我们首先定义一个简单的模块 M: module M { sub greeting ($name = 'Camelia') { "Greetings, $name!

模块开发工具

以下是您可以在 Raku 生态系统中找到的模块列表,旨在使开发 Raku 模块的体验更加有趣。 模块构建器和创作工具 一些模块和工具可帮助您生成属于模块分发的文件。 App::Assixt 模块开发者的助手 App::Mi6 Raku 的最小创作工具 META6 用 Raku META 文件做事 Module::Skeleton 生成骨架模块 p6doc 生成文档最终产品 Tests 一些模块质量测试。 Test::META 测试您的 META6.json 文件 Test::Output 测试程序生成的 STDOUT 和 STDERR 的输出 Test::Screen 使用GNU screen测试全屏VT应用程序 Test::When 控制测试运行时间(作者测试,在线测试等) NativeCall 这里有一些模块可以帮助您使用 NativeCall。 NativeHelpers::Array 提供处理 CArray 的例程 App::GPTrixie 从 C 头文件生成 NativeCall 代码 NativeCall::TypeDiag 提供测试 CStruct 的例程 Sample modules 仅作为极简主义示例,安装程序测试或骨架的模块。 Foo 具有两个不同版本分布的模块

迭代

Iterator 和 Iterable 角色 Raku 是一种函数式语言,但在处理复杂的数据结构时,函数需要保持住。特别是,他们需要一个可以应用于所有这些界面的统一接口。此接口由 Iterator 和 Iterable 角色提供。 Iterable 角色相对简单。它为迭代器方法提供了一个存根,该方法实际上是由诸如 for 之类的语句使用的。 for 会在它前面的变量上调用 .iterator,然后为每个项目运行一次块。其他方法(如数组赋值)将使 Iterable 类以相同的方式运行。 class DNA does Iterable { has $.chain; method new ($chain where { $chain ~~ /^^ <[ACGT]>+ $$ / and $chain.chars %% 3 } ) { self.bless( :$chain ); } method iterator(DNA:D:){ $.chain.comb.rotor(3).iterator } }; my @longer-chain = DNA.new('ACGTACGTT'); say @longer-chain.perl; # OUTPUT: «[("A", "C", "G"), ("T", "A", "C"), ("G", "T", "T")]␤» say @longer-chain».

进程间通信

运行程序 许多程序需要能够运行其他程序,我们需要将信息传递给它们并接收它们的输出和退出状态。在 Raku 中运行程序非常简单: run 'git', 'status'; 这一行运行名为 “git” 的程序,并将 “git” 和 “status” 传递给它的命令行。它将使用 %*ENV<PATH> 设置找到该 git 程序。 如果您想通过向 shell 发送命令行来运行程序,那么也有一个工具。所有 shell 元字符都由 shell 解释,包括管道,重定向,环境变量替换等。 shell 'ls -lR | gzip -9 > ls-lR.gz'; 使用 shell 用户输入时应小心。 Proc对象 run 和 shell 都返回一个PROC对象,它可以使用具有更详细的进程进行通信。请注意,除非您关闭所有输出管道,否则程序通常不会终止。 my $git = run 'git', 'log', '--oneline', :out; for $git.out.lines -> $line { my ($sha, $subject) = $line.split: ' ', 2; say "$subject [$sha]"; } $git.out.close(); 如果程序失败(以非零退出码退出),它将在返回的Proc对象沉没时抛出异常。您可以将其保存为变量,甚至是匿名变量,以防止下沉: $ = run '/bin/false'; # does not sink the Proc and so does not throw 您可以通过传递 :out 和 :err 标志来告诉 Proc 对象将输出捕获为文件句柄。您也可以通过 :in 标记传递输入。

输入和输出

在这里,我们简要概述了与文件相关的输入/输出操作。详细信息可以在 IO 角色的文档中找到,也可以在 IO::Handle 和 IO::Path 类型中找到。 读取文件 读取文件内容的一种方法是通过带有 :r(读取)文件模式选项的 open 函数打开文件,并吞噬内容: my $fh = open "testfile", :r; my $contents = $fh.slurp; $fh.close; 这里我们使用 IO::Handle 对象上的 close 方法显式地关闭文件句柄。这是一种非常传统的读取文件内容的方法。但是,同样的事情可像这样更容易和更清楚地完成: my $contents = "testfile".IO.slurp; # or in procedural form: $contents = slurp "testfile" 通过将 IO 角色添加到文件名字符串中,我们实际上能够将字符串作为文件对象本身引用,从而直接吞噬其内容中。请注意,slurp 负责为你打开和关闭文件。 逐行读取 当然,我们也可以选择逐行读取文件。将排除新行分隔符(即 $*IN.nl-in)。 for 'huge-csv'.IO.lines -> $line { # Do something with $line } # or if you'll be processing later my @lines = 'huge-csv'.IO.lines; 写文件 要将数据写入文件,我们再次选择调用 open 函数的传统方法 - 这次使用 :w(write)选项 - 并将数据打印到文件中:

Grammar 指南

开始之前 为什么是 grammars? Grammars 解析字符串并从这些字符串返回数据结构。Grammars 可用于编写执行程序以确定程序是否可以运行(如果它是一个有效的程序),将网页分解成组成部分,或在其它的东西中识别句子的不同部分。 我什么时候该使用 grammars? 如果你有驯服或解释的字符串,grammar 提供工具来完成这项工作。 该字符串可能是一个文件, 您想把它拆分成多个章节; 也许是一个协议,比如 SMTP,你需要指定哪些“命令”来自用户提供的数据;也许你正在设计自己的领域特定语言。Grammars 可以提供帮助。 grammars 的广义概念 正则表达式(Regexes)适用于查找字符串中的模式。然而,对于一些任务来说,如同时查找多个模式,或者组合模式,或者单独测试可能围绕字符串正则表达式的模式是不够的。 在使用 HTML 时,您可以定义一个 grammar 来识别 HTML 标记,包括开始和结束元素以及它们之间的文本。然后,您可以将这些元素组织到数据结构中,例如数组或散列。 Grammar 指南 你总是会遇到令人头疼的字符串解析。举个例子, 据说 HTML 不能被有效地分解和解析,只需使用正则表达式来排序元素。另一个例子是定义单词和符号可能构成语言并提供含义的顺序。这正 和 Perl 的 Gramamr 系统完美契合。 Grammar 非常适合接受字符串,试图理解它们,然后将它们保存到一个你实际可以使用的数据结构中。如果你有某种带顺序或解释类型的字符串,Grammar 给你一些很强大的工具,使解析字符串更容易。 你的字符串可能是整个文件,你需要分成几个部分。也或许是一行一行的。也许你有一个正在使用的 SMTP 那样的协议,想要一个方便有条理的方式来定义哪些“命令”需要在用户数据的后面,使协议工作。也许你想创建自己的基于字符串的协议。也许你正在设计自己的语言。 正则表达式(regex)很好地在字符串中查找模式并操作它们。然而,当你需要同时找到多个模式,或者需要组合模式,或者测试可能围绕字符串的模式或其他模式 - 单单用正则表达式是不够的。 Grammar 提供了一种方式来定义如何使用正则表达式来检查字符串,并且可以将这些正则表达式组合在一起以提供更多的意义。 例如,在HTML的情况下,您可以定义一个语法,它可以识别HTML标记(开始和结束元素以及它们之间的文本),并通过将这些元素填充到数据结构中来对这些元素进行操作,例如数组或散列,然后可以轻松使用。实质上,Grammar 提供了一种定义可用于解析任意大小和复杂度的字符串的完整语言或规范的手段。 更多 Grammar 技术 概念描述 Gramamr 被定义为对象, 就像 Perl 中的其它东西。从技术上讲, Gramamr 是普通的类加上一点额外的魔法, 我们稍后就说到它 – 还有一点限制。你像类那样命名和定义一个 Grammar, 除了使用「grammar」关键字代替「class」。 grammar My::Gram { ..methods 'n stuff.

输入 Unicode 字符

输入 Unicode 字符 Raku 允许把 unicode 字符用作变量名. 很多操作符使用 unicode 符号(特别是在 set/bag 操作符中)还有一些引号结构. 因此, 知道如何把这些符号输入编辑器, Raku shell 和 命令行中是极好的, 特别是现实键盘中不存在那个符号的时候. 在各种操作系统和环境下关于输入 unicode 字符的通用信息可以在 Wikipedia unicode 输入页 中找到. 编辑器和 shell Vim 在 Vim 中, unicode 字符是通过先按 Ctrl-V(也表示为 ^V), 然后按下 u 和 要输入的 unicode 字符的十六进制值来输入的(在插入模式). 例如, 希腊字母 λ (lambda) 是通过组合键来输入的: ^Vu03BB 更多关于在 Vim 中输入特殊字符的信息可以在 Vim Wikia 页 键入特殊字符 中找到. Emacs 在 Emacs 中, unicode 字符的输入是首先输入和弦 Ctrl-x 8 Enter , 然后再输入 unicode 代码点的十六进制数字, 然后回车. 因此, 要输入希腊字母 λ (lambda) 使用下面的组合键(命令之间添加了空格以使清晰):

用 Raku 做数学

Sets Raku 包括 Set 数据类型,以及对大多数 set 操作的支持。并集和交集不仅是原生操作,它们使用自然符号 ∩ 和 ∪。例如,此代码将检查有限数量集的集算术的基本定律: my @arbitrary-numbers = ^100; my \U = @arbitrary-numbers.Set; my @sets; @sets.push: Set.new( @arbitrary-numbers.pick( @arbitrary-numbers.elems.rand)) for @arbitrary-numbers; my (@union, @intersection); for @sets -> $set { @union.push: $set ∩ $set === $set; @intersection.push: $set ∪ $set === $set; } say "Idempotent union is ", so @union.all; # OUTPUT: «Idempotent union is True» say "Idempotent intersection is ", so @intersection.all; # OUTPUT: «Idempotent intersection is True» my (@universe, @empty-set, @id-universe, @id-empty); for @sets -> \A { @universe.

核心模块

Rakudo 实现包含一些您可能想要使用的模块。以下是它们的列表,以及它们的源代码的链接。 CompUnit::* 模块和角色 这些模块主要由分发构建工具使用,并不打算由最终用户使用(至少在版本6.c之前)。 CompUnit::Repository::Staging. CompUnit::Repository::(FileSystem|Installation|AbsolutePath|Unknown|NQP|Raku|RepositoryRegistry). NativeCall 模块 NativeCall 原生调用接口 (docs) NativeCall::Types 由 NativeCall 使用 NativeCall::Compiler::GNU 由 NativeCall 使用 NativeCall::Compiler::MSVC 由 NativeCall 使用 Other modules Pod::To::Text 由多个外部模块使用 Test Test 子例程 (docs) experimental newline

并发

Concurrency 与大多数现代编程语言一样,Raku 被设计为支持并发(允许多个事件同时发生)和异步编程(有时称为事件驱动或反应式编程 - 即程序某些部分的事件或变化可能会导致程序流异步地改变程序的其它部分)。 Perl 的并发设计的目的是提供一个高层级的,可组合的,一致的接口,而不管如下所述的虚拟机通过工具层怎样为特定操作的系统来实现它。 此外,某些 Perl 的特性可以隐式地以异步的方式操作,所以为了确保这些特性可预测的互通,用户代码应在可能情况下,避免较低层级的并发的 API(即线程和调度器),并使用高层级接口。 High-level APIs Promises Promise(在其他编程环境中也被称为 future)封装了在获得 promise 时可能尚未完成或甚至未开始的计算结果。Promise 从 Planned 状态开始, 结果要么是 Kept 状态, 这意味着该 promise 已成功完成, 要么是 Broken 状态, 意味着该 promise 已失败。 通常这就是用户代码需要以并行或异步方式操作的使用最多的功能。 my $p1 = Promise.new; say $p1.status; # OUTPUT: «Planned␤» $p1.keep('Result'); say $p1.status; # OUTPUT: «Kept␤» say $p1.result; # OUTPUT: «Result␤» # (since it has been kept, a result is available!) my $p2 = Promise.new; $p2.break('oh no'); say $p2.

命令行接口

命令行接口 - 概述 Raku 脚本的默认命令行界面由三部分组成: 将命令行参数解析为捕获 这将查看 @*ARGS 中的值,根据某些策略解释这些值,并创建一个 Capture 对象。解析器的替代方式可以由开发者提供或使用模块安装。 使用该捕获调用提供的MAIN子例程 标准多分重分派用于使用生成的 Capture 对象调用 MAIN 子例程。这意味着您的 MAIN子 例程可能是一个 multi sub,其中每个候选程序负责处理给定命令行参数的某些部分。 如果调用 MAIN 失败,则创建/显示使用信息 如果多重分派失败,则应尽可能通知脚本的用户失败的原因。默认情况下,这是通过检查每个 MAIN 候选 sub 的签名以及任何关联的 pod 信息来完成的。然后在 STDERR 上向用户显示结果(如果指定了 --help,则在 STDOUT 上显示)。生成使用信息的替代方式可以由开发者提供或使用模块安装。 sub MAIN 在运行所有相关的输入phasers(BEGIN,CHECK,INIT,PRE,ENTER)并执行脚本的主线之后,将执行具有特殊名称 MAIN 的子程序。如果没有 MAIN sub,则不会发生错误:您的脚本只需要在脚本的主线中执行工作,例如参数解析。 从 MAIN sub 的任何正常退出将导致退出代码为 0,表示成功。 MAIN 子的任何返回值都将被忽略。如果抛出未在 MAIN 子内部处理的异常,则退出代码将为 1。如果调度到 MAIN 失败,则在 STDERR 上将显示一条用法消息,退出代码将为 2。 命令行参数存在于 @*ARGS 动态变量中,并且可以在调用 MAIN 单元之前在脚本的主线中进行更改。 (多个子 MAIN 的候选者)的签名确定使用标准多重分派语义实际调用哪个候选者。 一个简单的例子: # inside file 'hello.

类和对象

Raku 有一个丰富的内置语法来定义和使用类。 默认构造函数允许为创建的对象设置属性: class Point { has Int $.x; has Int $.y; } class Rectangle { has Point $.lower; has Point $.upper; method area() returns Int { ($!upper.x - $!lower.x) * ( $!upper.y - $!lower.y); } } # Create a new Rectangle from two Points my $r = Rectangle.new(lower => Point.new(x => 0, y => 0), upper => Point.new(x => 10, y => 10)); say $r.area(); # OUTPUT: «100␤» 您也可以提供自己的构建和构建实现。下面更详细的例子展示了 Raku 中依赖处理器的外观。它展示了自定义构造函数,私有属性和公共属性,方法以及签名的各个方面。它代码不多,但结果是有趣和有用的。

Ruby 到 Raku

Raku from Ruby - Nutshell 基本语法 语句结束分号 Ruby 使用换行(有几个例外)来探测大部分语句的结束, 只要表达式已经完成。通过把运算符挂在行的末尾以保证解析会继续而打断一个长的表达式的做法很常见: foo + # 在 Ruby 中结尾的运算符意味着解析会继续 bar + baz 在 Raku 中你必须显式地使用 ; 来结束语句, 这允许更好的反馈和更灵活的断行。有两个例外不需要显式的 ;, 块儿中的最后一条语句, 在块自身的闭合花括号之后(如果那一行上没有任何其它东西): if 5 < $x < 10 { say "Yep!"; $x = 17 # 在闭合花括号 } 之前不需要分号 ; } # 因为换行, 在闭合花括号 } 之后不需要分号 ; say "Done!"; # 如果后面什么也没有, 那么这儿的分号也不需要 空白 Ruby 中允许使用大量令人吃惊的灵活的空白, 即使在开启了严格模式和警告的情况下: # 不符合习惯但是在 Ruby 中是合法的 puts"Hello "+ (people [ i] .

Python 到 Raku - 简而言之

此页面试图为来自 Python 背景的人们提供学习 Raku 的方法。我们在 Raku 中讨论了许多 Python 构造和惯用法的等价语法。 基本语法 Hello, world 让我们从打印 “Hello, world!” 开始吧。 Raku 中的 put 关键字相当于 Python 中的 print。与 Python 2 一样,括号是可选的。换行符添加到行尾。 Python 2 print "Hello, world!" Python 3 print("Hello, world!") Raku put "Hello, world!" 还有 say 关键字,其行为类似,但会调用其参数的 gist 方法。 Raku my $hello = "Hello, world!"; say $hello; # also prints "Hello, world!" # same as: put $hello.gist 在 Python 中 ' 和 " 是可互换的。在 Raku 中两者都可用于引用, 但双引号(")表示应该进行插值。例如, 以 $ 开头的变量和包含在花括号中的表达式会被插值。

Haskell 到 Raku - 简而言之

Haskell 和 Raku 是非常不同的语言。这很明显。 但是,这并不意味着没有相似之处或共同的想法! 此页面尝试让一个 Haskell 用户启动并运行 Raku。Haskell 用户可能会发现,在用 Raku 编写脚本时,他们不需要放弃所有 Haskelly 的想法。 请注意,这不应该被误认为是初学者教程或 Raku 概述; 它旨在作为具有强大 Haskell 背景的 Raku 学习者的技术参考。 类型 类型 vs 值 在 Haskell 中, 您有类型级编程, 然后进行值级编程。 plusTwo :: Integer -> Integer -- Types plusTwo x = x + 2 -- Values 您不要像下面那样在 Haskell 中混合类型和值。 plusTwo 2 -- This is valid plusTwo Integer -- This is not valid 在 Raku 中, 类型(亦称为类型对象)和值处于同样的级别 sub plus-two(Int $x --> Int) { $x + 2 } plus-two(2); # This is valid plus-two(Int); # This is valid 我将再用一个例子来说明 Raku 这个独特之处:

Javascript(Node.js) 到 Raku - 简而言之

大西瓜啊,忘记翻译了! This page attempts to provide a way for users experienced in Node.js to learn Raku. Features shared between the two languages will be explained here, as well as major differences in syntax and features. This is not a tutorial for learning Raku; this is a reference for users who are already at an intermediate to advanced skill level with Node.js. 此页面试图为在Node.js中有经验的用户提供学习Raku的方法。这里将解释两种语言之间共享的功能,以及语法和功能的主要差异。 这不是学习Raku的教程; 对于已经使用Node.js处于中级到高级技能级别的用户,这是一个参考。 基础语法 “Hello, world!” Let’s start with the typical first program when learning new languages.

Perl 5 到 Raku 指南 - 特殊变量

描述 一个(希望)全面的 Perl 5 特殊变量列表及其 Raku 等价物,并在必要时记录它们之间的变化。 注意 本文档试图引导读者从 Perl 5 中的特殊变量到 Raku 中的等效变量。有关 Raku 特殊变量的完整文档,请参阅每个变量的 Raku 文档。 特殊变量 通用变量 $ARG $_ 值得庆幸的是, $_ 是 Perl 5 中的常规默认变量。Raku 的主要区别在于现在你可以在它身上调用方法。 例如,Perl 5 的 say $_ 可以在 Raku 中以 $_.say 表示。 此外,因为它是默认变量,您甚至不需要使用变量名称。 前面的例子也可以 通过使用 .say 实现。 @ARG @_ 由于 Raku 现在具有函数签名,您的参数可以去那里,而不是依赖于 @_。 事实上,如果你使用函数签名,使用 @_ 会吐出你告诉它不能覆盖一个现有签名。 但是,如果您不使用函数签名,则 @_ 将包含您传递给函数的参数, 就像它在Perl 5中那样。再次,与 $_ 一样 ,您可以在其上调用方法。 与 $_ 不同,你不能假设 @_ 为 这些方法的默认变量(即 @_.shift works, .shift 不 work)。

Perl 5 到 Raku 指南 - 语法

描述 关于 Perl 5 和 Raku 之间差异的全面(希望)描述。 注意 我不会详细解释 Raku 语法。本文档旨在指导你从 Perl 5 中的工作原理过渡到 Raku 中的等效工具。有关 Raku 语法的完整文档,请参阅 Raku 文档。 自由形式 Raku 仍然主要是自由形式。但是,有一些情况下,空白的存在或缺失现在很重要。例如,在 Perl 5 中,你可以省略关键字后面的空格(例如 while($x < 5) 或 my($x, $y))。在 Raku 中,这个空白是必需的,因此 while ($x < 5) 或 my ($x, $y)。但是,在 Raku 中,你可以完全省略括号:while $x < 5 。这适用于 if,for 等等。 奇怪的是,在 Perl 5 中,你可以在数组或散列与其下标之间以及后缀运算符之间留出空格。所以 $seen {$_} ++ 是有效的。Raku 再不这样了。Raku 中现在必须是 %seen{$_}++。 如果能让你感觉更好,你可以使用反斜杠来 “unspace” 空格,这样你就可以使用空格,否则它将被禁止。 有关详细信息,请参阅空白。 声明 正如函数 指南中所述,Raku 中没有 undef 。声明但未初始化的标量变量将计算其类型。换句话说,my $x;say $x; 会给你"(Any)"。my Int $y;say $y; 会给你"(Int)"。

Perl 5 到 Raku 指南 - 运算符

描述 一个(希望)全面的 Perl 5 运算符列表及其 Raku 等价物,并在必要时记录它们之间的差异。 注意 本文档没有详细解释运算符。本文档旨在指导您从 Perl 5 perlop 文档中的操作符过渡到 Raku 中的等效文档。有关 Raku 等效文档的完整文档,请参阅Raku文档。 运算符优先级和关联性 运算符优先级表在 Raku 中与在 Perl 5 中有所不同,因此这里不再详述。如果您需要知道 Raku 中给定运算符的优先级和关联性,请参阅运算符优先级。 项和列表运算符 Perl 5 perlop 文档中列出的作为一元运算符和列表运算符的内容在这个章节里往往可以被视为函数,例如 print 和 chdir。因此,您可以在函数指南中找到有关它们的信息。括号仍用于分组。有一点需要注意:在 Raku 中,是,(逗号)创建列表而不是圆括号。所以: my @foo = 1,2,3,4,5; # no parentheses needed .say for 1,2,3,4,5; # also no parentheses my $scalar = (1); # *not* a list, as there is no comma my $list = (1,); # a List in a scalar container 箭头运算符 由于您通常不会在 Raku 中使用引用,因此箭头作为解除引用运算符可能不太有用。但是,如果您确实需要解引用某些内容,则箭头就是点号。它也是方法调用的中的点号。因此,Perl 5 中的 $arrayref->[7] 在 Raku 中变成 $arrayref.

Perl 5 到 Raku 指南 - 概览

这些文档不应该被误认为是初学者教程或 Raku 的宣传概述; 它旨在作为具有很强 Perl 5 背景的人学习 Raku 的技术参考,以及任何将 Perl 5 代码移植到 Raku 的人。 Raku in a nutshell 果壳中的 Raku提供了语法,运算符,复合语句,正则表达式,命令行标志以及各种其他零碎内容的快速概述。 句法差异 语法章节提供的 Perl 5 和 Raku 之间的语法区别的一个概述:它是如何保持大部分形式自由的,写注释的其他方法,以及 switch 如何是一个很 Raku 的东西。 Raku 中的运算符 运算符章节将引导您从Perl 5 的 perlop运算符在Raku 中的等价物。 Raku 中的函数 该函数章节描述了所有的 Perl 5 函数和它们的 Raku 等价物和任何行为差异。它还提供了对提供 Perl 5 函数行为的生态系统模块的引用,这些函数存在于 Raku 中,具有稍微不同的语义(例如 shift),或者在 Raku 中不存在(例如 tie)。 Raku 中的特殊变量 特殊变量章节描述很多 Perl 5 中的特殊(标点符号)变量是否以及如何在 Raku 中的支持。

Perl 5 到 Raku 指南 - 简而言之

这个页面试图提供从 Perl 5 到 Raku 的语法和语义变化的快速路径。无论在 Perl 5 中有什么用,必须在 Raku 中以不同的方式编写,这里应该列出(而许多新的 Raku 特性和惯用法)不需要)。 因此,这不应该被误认为初学者教程或 Raku 的宣传概述;它旨在作为 Raku 学习者的技术参考,具有强大的 Perl 5 背景,以及任何将 Perl 5 代码移植到 Raku 的人(尽管注意到自动翻译可能更方便)。 关于语义的注释;当我们在本文档中说“现在”时,我们大多只是说“现在你正在试用 Raku”。我们并不是要暗示 Perl 5 现在突然过时了。恰恰相反,我们大多数人都喜欢 Perl 5,我们期望 Perl 5 能够继续使用多年。实际上,我们更重要的目标之一是使 Perl 5 和 Raku 之间的交互顺利进行。然而,我们也喜欢 Raku 中的设计决策,它们比 Perl 5 中的许多历史设计决策更新,可以说是更好的集成。我们很多人都希望在接下来的十年或两年内,Raku 将成为更主要的语言。如果你想在未来的意义上采取“现在”,那也没关系。但是我们根本不会对导致战斗的任何/或者思考感兴趣。 CPAN 参考 https://modules.raku.org/ 如果您使用的模块尚未转换为 Raku,并且本文档中未列出任何替代方案,则可能尚未解决其在 Raku 下的使用问题。 Inline::Perl5 项目通过使用 Perl 解释器的嵌入式实例来运行 Perl 5 代码,可以直接从 Raku 代码中使用 Perl 5 模块。 这很简单: # the :from<Perl5> makes Raku load Inline::Perl5 first (if installed) # and then load the Scalar::Util module from Perl 5 use Scalar::Util:from<Perl5> <looks_like_number>; say looks_like_number "foo"; # 0 say looks_like_number "42"; # 1 许多 Perl 5 模块已经移植到 Raku,试图尽可能多地维护这些模块的 API,作为 CPAN Butterfly Plan 的一部分。 这些可以在 https://modules.

Raku 101 例

假设您举办乒乓球锦标赛。裁判员以格式告诉你每场比赛的结果 Player1 Player2 | 3:2,这意味着 Player1 赢 Player2 了3到2局。你需要一个脚本来总结每个玩家赢得的比赛和数量,以确定总冠军。 输入数据(存储在一个名为的文件中scores.txt)如下所示: Beth Ana Charlie Dave Ana Dave | 3:0 Charlie Beth | 3:1 Ana Beth | 2:3 Dave Charlie | 3:0 Ana Charlie | 3:1 Beth Dave | 0:3 第一行是球员名单。每个后续行记录匹配的结果。 这是在Raku中解决该问题的一种方法: use v6; my $file = open 'scores.txt'; my @names = $file.get.words; my %matches; my %sets; for $file.lines -> $line { next unless $line; # ignore any empty lines my ($pairing, $result) = $line.

简介

记录像 Raku 这样的大型语言必须平衡几个相互矛盾的目标,例如简洁而全面,迎合具有丰富经验的专业开发人员,同时也可以迎合接触到该语言的新手。 有关快速实践的介绍,有一个简短的注释编程示例。 对于具有其他语言经验的程序员,有许多迁移指南可以将 Raku 的功能与其他语言进行比较和对比。 许多教程涵盖了 Raku 特别具有创新性的几个领域。节标题应该有助于导航剩余的文档。 raku.org 网站上的其他地方列出了许多有用的资源。这些包括文章,书籍,幻灯片演示和视频。 已经发现,Raku 的新手经常会提出问题,这些问题表明了其他编程范例带来的假设。建议首先审查基本主题部分中的以下部分。 签名 - 每个例程(包括子例程和方法)都有签名。理解子或方法的签名中给出的信息提供了一种快速掌握例程的操作和效果的方法。 容器 - 变量,就像计算机语言的名词一样,是存储信息的容器。容器正式名称中的第一个字母,例如 $my-variable 的 '$',或 @an-array-of-things 的 '@',或 %the-score-in-the 的 '%' - 携带有关容器的信息。但是,Raku 比其他语言更抽象,可以存储在容器中。因此,例如,$scalar 容器可以包含实际上是数组的对象。 类和角色 - Raku 基本上基于对象,它们根据类和角色进行描述。与某些语言不同,Raku 并没有强制使用面向对象的编程实践,并且可以编写有用的程序,就好像 Raku 纯粹是程序性的。然而,复杂的软件,例如 Raku 的 Rakudo 编译器,通过编写面向对象的惯用法变得更加简单,这就是为什么通过查看类是什么以及角色是什么来更容易理解 Raku 文档。如果不了解类和角色,就很难理解类型,文档的整个部分都是专门用的。 要避免的陷阱 - 一些常见的假设导致代码无法像程序员想要的那样工作。本节标识了一些。当某些事情没有成功时,值得回顾一下。

mapGroupsWithState

/** * ::Experimental:: * (Scala-specific) * Applies the given function to each group of data, while maintaining a user-defined per-group * state. The result Dataset will represent the objects returned by the function. * For a static batch Dataset, the function will be invoked once per group. For a streaming * Dataset, the function will be invoked for each group repeatedly in every trigger, and * updates to each group's state will be saved across invocations.

Raku 中的命令行参数

Sub MAIN 在 Raku 中,命令行参数的解析是通过 MAIN 子例程完成的,MAIN 子例程是一种特殊的子例程,它根据 MAIN 子例程的签名解析命令行参数。与其他子例程一样,MAIN 子例程可以具有命名参数和位置参数、可选(和必需)参数、多重分派等等。 有了 MAIN 子例程的定义,USAGE 子例程将由编译器自动生成。可以修改此子例程以返回定制的使用消息。所有命令行参数也可以在特殊变量 @*ARGS 中使用,它可以在 MAIN 处理之前发生转变。 命名参数和位置参数 命名参数 让我们从一个简单的程序开始(保存为 prog.p6): use v6; sub MAIN( Str :$name = 'John', Str :$last-name = 'Doe', ) { my $formatted-name = "$name.tc() $last-name.tc()"; say $formatted-name; } 在这个 MAIN 子句中,我们通过前置 : 到子例程签名中的每个变量上,创建了两个带有类型约束(Str)的命名参数,$name 和 $last-name。这些参数也有默认值,这是通过给参数赋值来实现的。在本例中,我们将 $name 设置为默认值 “John”,将 $last-name 设置为 “Doe”。如果执行 prog.p6 时命令行参数与 MAIN 签名匹配,则会打印出一个格式化的全名: $ raku prog.p6 John Doe $ raku prog.p6 --name='carl' --last-name='sagan' Carl Sagan $ raku prog.

Raku 中的命令行参数

Sub MAIN 在 Raku 中,命令行参数的解析是通过 MAIN 子例程完成的,MAIN 子例程是一种特殊的子例程,它根据 MAIN 子例程的签名解析命令行参数。与其他子例程一样,MAIN 子例程可以具有命名参数和位置参数、可选(和必需)参数、多重分派等等。 有了 MAIN 子例程的定义,USAGE 子例程将由编译器自动生成。可以修改此子例程以返回定制的使用消息。所有命令行参数也可以在特殊变量 @*ARGS 中使用,它可以在 MAIN 处理之前发生转变。 命名参数和位置参数 命名参数 让我们从一个简单的程序开始(保存为 prog.p6): use v6; sub MAIN( Str :$name = 'John', Str :$last-name = 'Doe', ) { my $formatted-name = "$name.tc() $last-name.tc()"; say $formatted-name; } 在这个 MAIN 子句中,我们通过前置 : 到子例程签名中的每个变量上,创建了两个带有类型约束(Str)的命名参数,$name 和 $last-name。这些参数也有默认值,这是通过给参数赋值来实现的。在本例中,我们将 $name 设置为默认值 “John”,将 $last-name 设置为 “Doe”。如果执行 prog.p6 时命令行参数与 MAIN 签名匹配,则会打印出一个格式化的全名: $ raku prog.p6 John Doe $ raku prog.p6 --name='carl' --last-name='sagan' Carl Sagan $ raku prog.

Rmarkdown 中的 Raku 代码

安装 首先,首先安装 R 编程语言。在此之后,运行 R 并执行以下命令来安装 rmarkdown: install.packages("rmarkdown")。在安装 rmarkdown 时,我收到以下错误消息: Error: .onLoad failed in loadNamespace() for 'tcltk', details: call: dyn.load(file, DLLpath = DLLpath, ...) error: unable to load shared object '/usr/lib/R/library/tcltk/libs/tcltk.so': libtk8.6.so: cannot open shared object file: No such file or directory 这可以通过安装包 tk 来解决,然后继续安装 rmarkdown。 Rmarkdown 的代码块 你可以用 rmarkdown 做的几乎所有都可以用“常规” markdown 来做的事情。然而,rmarkdown 的一个突出特性是它能够执行代码块并返回结果。为此,rmarkdown 使用了 knitr 包,这是一个使用 R 生成动态报告的引擎,除了 R 之外,它还支持其他语言引擎,您可以使用这些引擎来计算来自其他语言的代码。要列出可用引擎的名称,请在 R REPL 中执行 names(knitr::knit_engines$get()) 命令。就像“常规” markdown 一样,代码块可以用三个反勾号创建,然后是代码,最后是另外三个反勾号。如果希望计算代码块,请在花括号 {} 中指定语言,该语言位于前三个反勾号之后。例如,要执行 Perl 5 代码,可以在 {} 中指定 perl:

Raku 面向对象简单入门

序言 介绍 本教程最多只关注 Raku 中的面向对象编程(OOP)的基本知识。因此,对语句/表达式、变量、条件、循环、子例程(函数)等有一个基本的了解是很重要的,如果不在 Raku 中,至少在另一种编程语言中是这样的。此外,您至少应该对类、属性和方法有一般的了解。作为对 Raku 的介绍,我强烈推荐 Raku introduction。下一步是 Raku 文档。 确保你已经设置好了 Raku 编译器。如果你还没有设置好,请看这里。 从这里开始,你可能会厌倦代词“我们”,但它的使用是经过深思熟虑的。这是一个教程,希望你能跟上。所以,是的,我们在一起工作,你应该做好准备。顺便说一下,本教程是冗长的,这是故意的,但也是手把手教程的副产品。 问题陈述 我们将从现实生活中的问题开始,并尝试以面向对象的方式对其进行建模。问题陈述如下: 在她的数学101课程中,一位教授记录了三个作业(2个作业和1个考试)的分数,按照学生交作业的顺序: Bill Jones:1:35 Sara Tims:2:45 Sara Tims:1:39 Bill Jones:1:42 Bill Jones:E1:72 在一个名为 MATH-101 的简单文本文件中。您可以假设有更多的学生,而这只是数据文件的一个代表性块。在这个文件中,每行记录学生的姓名、作业编号(作业编号为1,2,第一次考试为E1)和学生获得的原始分数。 教授使用另一个扩展名为 .std 的文件存储她课程的学生名单: Bill Jones Ana Smith Sara Tims Frank Horza 除了 MATH-101,这位教授还教其他课程,并设计了一个扩展名为 .cfg 的配置文件来存储给定课程的配置格式。她这样做的目的是在她的其他课程中也使用它。配置文件格式指定了作业的类型、作业编号、作业的总分以及作业对最终课程成绩的贡献。她的数学101课程的 .cfg 文件如下: Homework:1:50:25 Homework:2:50:25 Exam:1:75:50 您的任务是创建一个名为 report.p6 的程序。该程序生成一个报告,其中列出了班级中每个学生的姓名、每次作业的分数和最终成绩。该程序应该假设具有扩展名 .cgf 和 .std 的文件在执行该程序的目录中可用。另一方面,包含学生成绩的文件必须通过命令行传递给程序。为了简单起见,您可以假设每个文件都是根据课程命名的。对于她的数学101课程,教授会有以下的文件: MATH-101, MATH-101.std 和 MATH-101.cfg,还有脚本 report.p6。 分析 如果我们看问题陈述,我们可以把所有的东西分成三类:课程,学生和作业。就目前而言,每个类别都可以被视为具有状态和行为的类。我们将从最简单的类别,作业类别,到最一般的类别,课程类别。为了做到这一点,我们首先学习 Raku 中类的定义。 Raku 类 类定义 在 Raku 中,类是用 class 关键字定义的,通常后面跟着类名(通常以首字母大写形式)。

第二十章. 高级话题

声明 本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版。 第二十章. 高级话题 In such a short book I don’t have enough pages to show you everything that you can do. This chapter is a brief survey of some of the features I would have liked to explain in more detail. You now know these exist and you can investigate them further on your own. 在这么短的书中,我没有足够的页面向你展示你可以做的一切。本章简要介绍了一些我希望更详细解释的功能。你现在知道这些存在,你可以自己进一步研究它们。 单行 You can run raku one-liners. These are programs that you compose completely on the command line.

第十七章. Grammars

声明 本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版。 第十七章. Grammars Grammars are patterns on a higher plane of existence. They integrate and reuse pattern fragments to parse and react to complicated formats. This feature is at the core of Raku in a very literal sense; the language itself is implemented as a grammar. Once you start using it you’ll probably prefer it to regexes for all but the most simple problems. Grammars 是存在于更高层面上的模式。它们集成并重用模式片段来解析复杂的格式并做出反应。从字面意义上讲,这个功能是Raku的核心;语言本身是作为语法实现的。一旦你开始使用它,你可能更喜欢它除了最简单的问题之外的所有正则表达式。

第二章. 猜数字

声明 本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版。 第二章. 猜数字 You’re about to be thrown in the deep end. There are some basic things you need to know to write useful programs, and you’ll meet a lot of them in this chapter so you can write a number-guessing program by the end. It’s quite a bit to take in all at once but it should make the rest of the chapters more interesting.

Raku 中的容器

在本系列的第一篇文章中,将 Perl 5 与 Raku 进行了比较,我们研究了将代码迁移到 Raku 时可能遇到的一些问题。在第二篇文章中,我们研究了垃圾收集在 Raku 中的工作原理。第三篇文章,我们将重点介绍 Perl 5 的引用以及如何在 Raku 中处理它们,并介绍绑定和容器的概念。 引用 Raku 中没有引用,这对许多习惯于 Perl 5 语义的人来说都是令人惊讶的。但不要担心:因为没有引用,所以您不必担心是否应该解引用某些内容。 # Perl 5 my $foo = \@bar; # must add reference \ to make $foo a reference to @bar say @bar[1]; # no dereference needed say $foo->[1]; # must add dereference -> # Raku my $foo = @bar; # $foo now contains @bar say @bar[1]; # no dereference needed, note: sigil does not change say $foo[1]; # no dereference needed either 有人可能会说 Raku 中的所有东西都是引用。来自 Perl 5(其中一个对象是一个受祝福的引用),这将是关于 Raku 的逻辑结论,其中所有的东西都是对象(或者可以被认为是一个对象)。但这并不能完全符合 Raku 中的情况,并且会妨碍你理解 Raku 的工作原理。谨防虚假的朋友!

Use Spark to read and write HBase data

Use Spark to read and write HBase data 启动 hbase start-hbase.sh 在 HBase 中准备 sample 数据 1、运行 HBase shell hbase shell 2、创建一个含有 Personal 和 Office 列簇的 Contacts 表 create 'Contacts', 'Personal', 'Office' 3、加载一些样例数据 put 'Contacts', '1000', 'Personal:Name', 'John Dole' put 'Contacts', '1000', 'Personal:Phone', '1-425-000-0001' put 'Contacts', '1000', 'Office:Phone', '1-425-000-0002' put 'Contacts', '1000', 'Office:Address', '1111 San Gabriel Dr.' put 'Contacts', '8396', 'Personal:Name', 'Calvin Raji' put 'Contacts', '8396', 'Personal:Phone', '230-555-0191' put 'Contacts', '8396', 'Office:Phone', '230-555-0191' put 'Contacts', '8396', 'Office:Address', '5415 San Gabriel Dr.

使用 Raku 连接 Kafka

有这样一个场景, 数据发送方将压缩文件读成字节数组后发往 Kafka, 然后第三方的 Kafka Client 从中读取字节数组解压缩, 每条 message 对应一个压缩文件, 每个压缩文件中包含 _log.txt 和 _result.txt。 Raku 可以从 Kafka 中读取消息并完成解析。 首先安装相关模块: Pkafka 用于和 Kafka 交互; Archive::Libarchive 用于解压缩字节数组。 Cro 用于 HTTP 请求,DBiish 用于数据库读写。 zef install Pkafka zef install Archive::Libarchive zef install Cro zef install DBIish 代码片段如下: use PKafka::Consumer; use PKafka::Message; use PKafka::Producer; use Archive::Libarchive; use Archive::Libarchive::Constants; use Cro::HTTP::Client; use JSON::Fast; use JSON::Path; use DBIish; sub MAIN () { my $brokers = "127.0.0.1"; my $test = PKafka::Consumer.

Cro Http Test

Cro::HTTP::Test 原则上可以通过使用 Cro::HTTP::Server托管应用程序, 使用 Cro::HTTP::Client向其发出请求, 并使用标准 Test 库检查结果来编写 Cro HTTP 服务的测试。该库使编写此类测试更容易, 并通过以下方式更快地执行它们: 为发出测试请求和检查结果提供更方便的 API 跳过网络并将 Cro::TCP 对象从客户端管道传递到服务器管道, 反之亦然 基本示例 给定模块 MyService::Routes, 如下所示: sub routes() is export { route { get -> { content 'text/plain', 'Nothing to see here'; } post -> 'add' { request-body 'application-json' => -> (:$x!, :$y!) { content 'application/json', { :result($x + $y) }; } } } } 我们可以像这样编写测试: use Cro::HTTP::Test; use MyService::Routes; test-service routes(), { test get('/'), status => 200, content-type => 'text/plain', body => /nothing/; test-given '/add', { test post(json => { :x(37), :y(5) }), status => 200, json => { :result(42) }; test post(json => { :x(37) }), status => 400; test get(json => { :x(37) }), status => 405; } } done-testing; 设置要测试的服务 test-service 函数有两个候选者。

Deconstructing Simple Grammars

去年我写了一个鸡蛋定时器,它的解析命令行参数类似于 GNU sleep。我对这个解析器的严格形式很满意,如下。 my Seconds $to-wait = @timicles»\ .split(/<number>/, :v)\ .map(-> [$,Rat(Any) $count, Str(Any) $unit] --> Seconds { %unit-multipliers{$unit} * $count })\ .sum; 它只做了几件简单的事情,而且是一件接着一件做。带有动作类的 grammar 就显得矫枉过正了。我不满意用 split 的能力来返回带有零件的针。它肯定不会提高可读性。 经过相当多的迭代(并踩到了一个 bug),我想出了一个使用 Str.match 代替的方法。如果我把每个 Match 对象转换成 Hash,我就可以在一个尖号块的签名中使用解构。 my Seconds $to-wait = @timicles»\ .match(/<number> <suffix>+/)».hash\ # the +-quatifier is a workaround .map(-> % ( Rat(Any) :$number, Str(Any) :$suffix ) { %unit-multipliers{$suffix} * $number })\ .sum; 我可以不使用位置参数,而是使用命名的参数,这些参数对应于匹配参数中命名正则表达式。 即使是在这样一小段代码中,事情也会变得有条不紊。超方法调用摆脱了简单的循环。精心设计的内置类型允许在没有临时变量负载的情况下进行签名解构。这几乎就像某些语言设计者的目标是制造一种最优雅的语言一样。 by gfldex.

Raku 中的列表解析

Raku 中的列表解析 看一看 Python 中关于列表推导的页面。 S = {x² : x in {0 ... 9}} V = (1, 2, 4, 8, ..., 2¹²) M = {x | x in S and x even} Python 列表解析: S = [x**2 for x in range(10)] V = [2**i for i in range(13)] M = [x for x in S if x % 2 == 0] 在原始定义中我没有看到 10 或 13 , Raku 与原始语言最接近的语法是: my \S = ($_² for 0 .

在 Raku 中设置超时

在 Raku 中设置超时 在 Perl 5 中,我曾经使用信号设置超时(至少,这是一种简单且可预测的方式)。在 Raku 中,您可以使用 promise。让我们看看如何做到这一点。 要模仿长时间运行的任务,请创建一个无限循环,然后打印其状态。开始吧: for 1 .. * { .say if $_ %% 100_000; } 只要循环得到控制,它将永远不会退出。我们的任务是在几秒钟内停止程序,因此计时器应在循环之前设置: Promise.in(2).then({ exit; }); for 1 .. * { .say if $_ %% 100_000; } 在这里,Promise.in 方法创建一个 promise,在给定秒数后自动 kept。在 promise 的基础上,使用 then,我们添加了另一个 promise,其代码将在超时后运行。这里唯一的语句就是退出,停止主程序。 运行该程序以查看它的工作原理: $ time raku timeout.pl 100000 200000 300000 . . . 3700000 3800000 3900000 real 0m2.196s user 0m2.120s sys 0m0.068s 该程序在我的计算机上计数达四百万,并在两秒内退出。这正是我们需要的行为。 为了比较,下面是实现同样功能的 Perl 5 程序:

添加第三方 pom 仓库

Apache HBase Connector 这个 Hbase 连接器可以使用 Spark 读写 Hbase。但是 github 上没有说明怎么把它添加到 pom 文件中。 Google 了一下, 在 Maven 仓库 有这个依赖: <!-- https://mvnrepository.com/artifact/com.hortonworks/shc-core --> <dependency> <groupId>com.hortonworks</groupId> <artifactId>shc-core</artifactId> <version>1.1.1-2.1-s_2.11</version> </dependency> 添加到 pom 文件里面发现还不行, 报红。说明找不到依赖!注意到下面还有行小字: Note: this artifact it located at Hortonworks repository 点击 hortonworks 看下右边的 Indexed Repositories , 其中每一行对应着仓库配置中的一个 id: Hortonworks, 这个 ID 在下面的仓库配置中就对应 <id>。 这样的 jar 包在标准仓库里面没有,需要你在 pom 文件里面添加上仓库地址: <repository> <id>hortonworks</id> <name>Hortonworks Repository</name> <url>http://repo.hortonworks.com/content/repositories/releases/</url> </repository>

Raku 中的数据类型 Bag

数据类型 Bag 是一种 Perl 5 中没有的新的数据类型。 它可以被认为是一个容器,它一方面知道它里面有多少个单独的元素,另一方面可以说有多少种不同类型的商品。您可以用不同的方式描述此类型:Bag 是一个哈希,默认情况下,您添加的键的值为1。我们来看看例子。 把一个 1 放进 bag 里,看看 perl 的输出: my $b1 = bag(1); say $b1.perl; 该程序打印以下输出: (1=>1).Bag 也就是说,我们有一个 1。 如果你把另一个数字也放进 bag 里面: my $b2 = bag(1, 2); say $b2.perl; 现在有一个 1 和一个 2: (1=>1,2=>1).Bag 好的,如果你添加另一个 1 呢? my $b3 = bag(1, 2, 1); say $b3.perl; 现在有两个 1: (1=>2,2=>1).Bag 让我们稍微离题一下:所有显示的例子都可以把括号去掉: my $b1 = bag 1; my $b2 = bag 1, 2; my $b3 = bag 1, 2, 1; 比较典型的是,bag 不止能存储数字,还可以存储字符串,例如:

在 Python 中使用 XPath

根据字节点中的属性值提取父节点 今天使用 Python 的 lxml 模块来提取网页中的内容, 有一个 XPath 的用法不明白, 问题是, table 子节点下有一系列 tr 子节点, 每个 tr 子节点里有 3 列 (td), 如果 td 中 style 属性的值为 color: 0, 那么就不提取它所属的这一 tr 子节点。 查了很久 stackoverflow 才解决, 方法一: from lxml import etree html = ''' <html> <table> <tr id="id_l107" class="new nick nick_cxreg"> <td class="time" ><a href="/raku/2016-09-21>05:07</a></td> <td style="color: #8d8100" class="nick">cxreg</td> <td class="msg">probably useful</a></td> </tr> <tr id="id_l108" class="new nick nick_mask"> <td class="time" ><a href="/raku/2016-09-21>05:09</a></td> <td style="color: #8d8100" class="nick">mask</td> <td class="msg">maybe better directed at <a href="/moarvm/today">#moarvm</a></td> </tr> <tr id="id_l74" class="cont special dark"> <td class="time" ><a href="/raku/2016-09-21>02:37</a></td> <td style="color: 0" class="nick"></td> <td class="msg">mcmillhj joined <a href="/raku/today">#raku</a></td> </tr> <table> </html> ''' selector = etree.

使用 Scrapy 爬饭否帖子

新建工程 scrapy startproject fanfou 该工程的目录结构如下图所示: main.py 文件的内容如下: # -*- coding:utf-8 -*- from scrapy import cmdline cmdline.execute("scrapy crawl fanfou".split()) 这个文件与 scrapy.cfg 存放在同一目录下, 用于在 Pycharm 中调试和运行爬虫时使用, 如果 Pycharm 项目中没有这个文件的话, 会提示找不到模块 fanfou。 在 items.py 中设置要抓取的字段: # -*- coding: utf-8 -*- from scrapy.item import Item, Field class FanfouItem(Item): # define the fields for your item here like: home_url = Field() title = Field() avatar = Field() content = Field() location = Field() Spiders #-*- coding:utf-8 -*- import scrapy from scrapy.

使用 Python 解压缩 gzip 数据流

有一个使用场景:将 tar.gz 读成字节数组(byte array) 然后发往 Kafka。我要查看 Kafka 里面的消息, 直接查看的话就是二进制的乱码,首先想到的是把 Kafka 里面保存的字节数组存储到本地,每一行存成一个 .tar.gz 文件: # -*- coding: utf-8 -*- import sys,os from confluent_kafka import Consumer, KafkaError import gzip def run(args): c = Consumer({ "bootstrap.servers" : args[1], # broker 'group.id': args[2], # group id, 每次不一样 'default.topic.config': { 'auto.offset.reset': 'earliest' } }) c.subscribe([ args[3] ]); # topic i=0 while True: msg = c.poll(1.0) if msg is None: continue if msg.error(): if msg.error().code() == KafkaError.

Playing with the code of Rakudo Raku

昨天,我们查看了返回字符串的 Bool 类的两个方法。函数产生的字符串表示在源代码中被硬编码。 让我们使用这个观察并尝试改变文本。 所以,这里是我们要修改的片段: Bool.^add_multi_method('gist', my multi method gist(Bool:D:) { self ?? 'True' !! 'False' }); 该 gist 方法用于对已定义的变量进行字符串化。 要做到这一点,你需要在计算机上安装 Rakudo 的源代码,以便编译它们。首先从 GitHub 克隆项目: $ git clone https://github.com/rakudo/rakudo.git 编译 MoarVM: $ cd rakudo $ perl Configure.pl --gen-moar --gen-nqp --backends=moar $ make 完成之后,你会在 rakudo 目录下获得 raku 可执行文件。 现在,打开 src/core/Bool.pm 文件,并将 gist 方法的字符串更改为使用 Unicode 大拇指代替纯文本: Bool.^add_multi_method('gist', my multi method gist(Bool:D:) { self ?? '👍' !! '👎' }); 保存文件后,您需要重新编译 Rakudo。 Bool.pm 位于要在 Makefile 中编译的文件列表中:

MongoDB 的使用

安装 mongodb 到 mongodb 的官网下载后安装。以 Windows 下的使用为例。 在安装路径 C:\Program Files\MongoDB\Server\3.2\bin 目录下, 新建一个名为 data 的文件夹, 同时新建一个 start.bat 批处理文件用于启动 mongodb: @echo off mongod --dbpath ./data 安装 pymongo: pip install pymongo 安装 mongodb 可视化工具 这里测试使用的是 Robomongo 0.9.0-RC10, 体积小, 方便下载。安装完成后打开该软件: 设置好之后保存, 然后点击连接按钮。剩下的就交给代码了。 测试 MongoDB 数据库 新建一个爬虫工程, 在与 scrapy.cfg 同级的目录下新建一个 controlDB.py 文件: import pymongo connecton = pymongo.MongoClient() # 建立连接 test_db = connecton.Daomubiji # 连接上名为 Daomubiji 的数据库 post_info = test_db.test # 获取名为 test 的文档(表) sanshu = {'name': u'盗墓', 'age': 27, 'Skill': 'Python'} mengzhu = {'name': u'张无忌', 'age': 23, 'Skill': '乾坤大挪移', 'girlfriend':'赵敏'} post_info.

redis 的使用

setting.py # -*- coding: utf-8 -*- import scrapy_redis BOT_NAME = 'CrawlWithRedis' SPIDER_MODULES = ['CrawlWithRedis.spiders'] NEWSPIDER_MODULE = 'CrawlWithRedis.spiders' ITEM_PIPELINES = {'CrawlWithRedis.pipelines.CrawlWithRedisPipeline':300} # Obey robots.txt rules ROBOTSTXT_OBEY = True # Redis 数据库设置 SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_PERSIST = True SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue" REDIS_URL = None REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379 # MongonDB 设置 MONGODB_HOST = '127.0.0.1' MONGODB_PORT = 27017 MONGODB_DBNAME = 'XiaoYunKeji' MONGODB_DOCNAME = 'daomubiji' items.py # -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # http://doc.

pip install 权限

Mac 升级到 10.13 之后, sudo -H pip install xxxx 某些模块会出现 Operation not permitted 权限问题: 解决办法是: pip install ipython --user -U 燃鹅儿,安装完后 command not found - WTF! 反复几次之后,准备放弃之际,又搜索了一下: sudo pip install airflow --upgrade --ignore-installed 竟然解决了!

MongoDB之存储盗墓笔记

启动 MongoDB 和 Robomongo 可视化界面。 新建爬虫项目 scrapy startproject mudao 在 scrapy.cfg 文件同级目录中新建一个 main.py, 内容如下: #-*- coding:utf-8 -*- from scrapy import cmdline cmdline.execute("scrapy crawl mudao".split()) settings.py 中增加的额外配置如下: ITEM_PIPELINES = { 'mudao.pipelines.MudaoPipeline': 300, } MONGODB_HOST = '127.0.0.1' MONGODB_PORT = 27017 MONGODB_DBNAME = 'Daomubiji' MONGODB_DOCNAME = 'Book' 这样当 spiders.py 中用到 mongodb 的一些配置时, 导入这个配置文件, 以 seetings['key'] 的形式访问那些配置。 items.py 设置如下: from scrapy import Item, Field class MudaoItem(Item): bookName = Field() bookTitle = Field() chapterNum = Field() chapterName = Field() chapterURL = Field() 总共要提取网页中的 5 个字段, 所以需要 5 个类变量。

Python 终端录屏工具

Python 3 有一个很好的终端命令录制工具,叫做 asciinema, 使用 pip 安装就好了。它不支持 windows。 pip install asciinema 安装完,开始录制 asciinema rec demo 录制结束后按 Ctrl + D 结束。 查看录制的东西使用 asciinema play demo 分享

第五天 - 你应用程序的内置命令

我在本系列文章的开头提到,Mojolicious 应用程序不仅仅是 Web 服务器。然后,我展示了如何使用 daemon 守护程序或 prefork 命令启动 Web 服务器。在上一篇文章中,我提到了一个 inflate命令,它可以帮助您将应用程序从 Lite 增加到 Full。 但是,还有其他命令,内置在您的应用程序中,可以帮助您立即提高工作效率! 命令基础 在开始之前,我想简要讨论与 Mojolicious 发行版捆绑在一起的 mojo 应用程序/脚本。这个命令是一个很小的Mojolicious应用程序(实际上是另一个“hello world”),可以被认为是“空应用程序”。内置命令适用于您的应用程序和此null应用程序,因此请使用更合适的选项。当哪个应用程序运行命令无关紧要时,您可以使用 mojo。 每个命令都带有一行描述和一个(可能是多行)用法语句。要查看可用命令,请运行 mojo help,您将看到所有命令及其说明。你应该看到这样的东西: $ mojo help Usage: APPLICATION COMMAND [OPTIONS] mojo version mojo generate lite_app ./myapp.pl daemon -m production -l http://*:8080 ./myapp.pl get /foo ./myapp.pl routes -v Tip: CGI and PSGI environments can be automatically detected very often and work without commands. Options (for all commands): -h, --help Get more information on a specific command --home <path> Path to home directory of your application, defaults to the value of MOJO_HOME or auto-detection -m, --mode <name> Operating mode for your application, defaults to the value of MOJO_MODE/PLACK_ENV or "development" Commands: cgi Start application with CGI cpanify Upload distribution to CPAN daemon Start application with HTTP and WebSocket server eval Run code against application generate Generate files and directories from templates get Perform HTTP request inflate Inflate embedded files to real files prefork Start application with pre-forking HTTP and WebSocket server psgi Start application with PSGI routes Show available routes test Run tests version Show versions of available modules See 'APPLICATION help COMMAND' for more information on a specific command.

第四天 - 不要害怕完整的应用程序

关于Mojolicious最常见的误解之一是,我们目前看到的声明式“Lite”应用程序与大型结构化“完整”应用程序之间存在很大差异。没有东西会离事实很远。 Mojolicious::Lite 是一个非常小的包装器,围绕着所谓的“完整”应用程序架构,赋予它平易近人的关键字语法。 因为文档的简洁单文件示例要好得多,所以 Mojolicious 的大多数文档大多数时候都使用Lite语法。可以理解的是,即使一旦他们的应用程序受益于面向对象的结构,人们也会担心迁移(或者我们称之为“成长”);毕竟所有的文档似乎都面向Lite应用程序。但是,让这些担忧消失,过渡很容易。一旦你理解了它,文档化的例子就很难翻译了。 此外,Mojolicious 在转换时提供两种形式的帮助。第一个是成长指南,涵盖了这篇文章的所有内容,但是从移植现有应用程序的角度来看(我不会在这里复制)。第二个是 inflate 命令,它甚至可以通过将模板从数据部分移动到自己的文件中来启动您的过程。 也就是说,为了进一步揭开神秘面纱的神秘面纱,我将介绍一些差异,并对Lite语法本身拉开帷幕。 让我说服你 在反复试图说服人们两者之间几乎没有什么区别后,我发现有一种非常好的方式来转变对话。我告诉他们代码。没有真正看一看。在撰写本文时,Mojolicious::Lite 只有37行代码(由David A. Wheeler的 SLOCCount计算的)! 37 行代码有多大差异? 好了,现在你相信我,让我们谈谈这些差异。 脚本和类 在 Lite 脚本中,您的应用程序逻辑就位于脚本中。如果是一个完整的应用程序,你的逻辑进入一个单独的类,主要是在 startup 方法中,但删除 app->start 这一行。虽然方法(调用者)的第一个参数通常被称为 $self,你会看到,为了在本系列中保持清晰,我将始终使用 $app。所以我们有: sub startup { my $app = shift; ... # the rest of what was your script } 与此同时,运行的脚本只是启动应用程序的几行。该脚本始终是相同的,与您的应用程序无关,而是要调用的类的名称。我只是使用成长指南末尾的那个。 关键词 现在代码生活在正确的位置,它需要被转换为面向对象。第一步是将逻辑放入一个名为 startup 的方法中,该方法将应用程序对象作为其第一个参数。 实际上有三种类型的关键字,即应用程序对象或应用程序上的方法,路由上的方法和 group。 app 关键字就是之前的调用,因此 app 成为 $app。关键字 helper,hook 和 plugin 只是 app 上的方法,所以 plugin ... 变成 $app->plugin(...) 等。

第三天 - 使用命名路由

我们从多年的编程中知道的一件事是,如果你不需要,你就不应该对任何东西进行硬编码。然而,太多的网络应用程序硬编码他们的网址,特别是内部网站。但是,如果你不需要怎么办? 每个 Mojolicious 路由都有自己的名字,可用于生成网址。如果您没有指定一个,则会生成一个,但您不应该依赖该名称,给它一个有意义且与您的目的相关的名称。在lite应用程序中,名称是任何默认值或回调之后的最后一个参数。 (在完整的应用程序中,它是一个属性,但我们将在另一篇文章中讨论这些内容)。 然后,当您需要一个URL而不是硬编码时,使用 url_for 或相关功能按名称生成 URL,您甚至可以根据需要传递占位符值。让我们看看它是如何工作的! 北极主页 圣诞老人从一个简单的 webapp 开始。它只有一个主页和一个页面供他和他的员工使用。由于它如此简单,他没有任何更深的路径,只有 / 用于主页和 /:name 用于员工页面。当然,我要告诉你的是一个简化,我无法向你展示圣诞老人的完整网站,原因很明显。 use Mojolicious::Lite; get '/:name' => {template => 'staff'} => 'staff'; get '/' => {template => 'home'} => 'home'; app->start; __DATA__ @@ staff.html.ep <p>This is <%= ucfirst $name %>.</p> @@ home.html.ep <p>Welcome to The North Pole!</p> <p> Say hi to <%= link_to 'Santa' => staff => {name => 'santa'} %> and <%= link_to 'Rudolph' => staff => {name => 'rudolph'} %>.

第二天 - The Stash

在 Mojolicious 中,当处理请求并准备响应时,最重要的概念之一是“藏匿”。由于它是一个非阻塞框架,因此您的代码在处理期间不能使用全局变量来存储任何状态。如果您运行了一些其他代码,那么很容易在请求之间进行串扰。 存储是您在处理信息时可以存储信息的地方。它只是一个简单的哈希引用,它附加到处理请求的控制器对象上。它与这一笔交易一起生存和死亡。 虽然你可以而且应该将它用作暂存器,但实际上还是要多得多。存储控制几乎控制了您生成的响应的每个方面。让我们仔细看看它是如何工作的 使用 Stash 渲染文本 在上一篇文章中,我们讨论了最简单的“Hello world”应用程序。 use Mojolicious::Lite; get '/' => {text => 'Hello 🌍 World!'}; app->start; 虽然这是一个非常简单的工作案例,但更常见的例子就是这样 use Mojolicious::Lite; get '/' => sub { my $c = shift; $c->render(text => 'Hello 🌍 World!'); }; app->start; 在此示例中,GET /请求由“动作回调”处理。回调是函数引用,打算将来调用;在这种情况下,当客户端请求进入与该类型的请求匹配时,将调用回调。 使用一个参数调用操作,称为控制器。控制器是一个对象,表示我们的应用程序与当前事务的交互。它包含一个表示事务的对象,该对象又保存请求和响应的对象。它有一些方法可用于生成响应,其中一个是渲染,您可以在上面看到。在这里,您会看到我们将呈现一些文本。 事实上,渲染的大多数参数实际上只是合并到了藏匿处。实际上,上面的例子是相同的 use Mojolicious::Lite; get '/' => sub { my $c = shift; $c->stash(text => 'Hello 🌍 World!'); $c->render; }; app->start; 你现在看到的是,Mojolicious 看着藏匿处看看如何呈现回应。事实上,如果你不调用 render,但它有足够的信息来在 stash 中呈现响应,它就会这样做。 use Mojolicious::Lite; get '/' => sub { my $c = shift; $c->stash(text => 'Hello 🌍 World!

第一天 - 入门

从头开始 在这个出现日历系列中,一些帖子将是介绍性的,一些将是高级的,一些将是新功能。谁知道接下来会发生什么?但是现在让我们通过研究如何开始来确保公平竞争。 什么是 Mojolicious? 嗯,Mojolicious 真的是两件事。首先,它是一个强大的以 Web 为重点的工具包,名为 Mojo。其次,它是一个名为 Mojolicious 的强大的 Web 框架。 Mojolicious 框架是使用 Mojo 工具包构建的。 这并不意味着你不能在其他地方使用 Mojo 工具。如果您看到一些您喜欢的工具但想要与其他框架一起使用,请继续,我不会告诉您!在任何你想要的 Perl 代码中使用它! 拍我不会提到 Perl!是的,Mojolicious 是用 Perl 编写的。不要让那吓到你。它有很多方法可以保证您和您的代码安全!从内置对象系统到具有可链接方法的一致 API,Mojolicious 旨在使您的代码保持清洁和可读。希望你甚至可以使用它玩得开心! 安装 安装简单快捷。实际上,如果您将测试工具设置为并行运行,则应在几秒钟内完成安装!您可以通过在您的环境中设置 HARNESS_OPTIONS = j9(其中 9 比您的核心数多一个)来实现。 最简单的安装方法是运行 curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org -n Mojolicious 或者,您可以使用任何cpan客户端(我们喜欢 cpanm)或使用系统的包管理器进行安装。 你的第一个应用 在编程的传统中,我们需要做的第一件事就是运行一个hello world应用程序。 将以下内容保存为 hello.pl use Mojolicious::Lite; get '/' => {text => 'Hello 🌍 World!'}; app->start; 这个脚本很简单 导入 Mojolicious(精简版) 定义一个 GET 处理程序来响应具有 unicode 版本的 hello world 的请求 启动应用程序 但在此之前,我们必须启动一个 Web 服务器。

Hugo 配置

替换文件名 使用 replaceRE title: "{{ .TranslationBaseName | replaceRE "^[0-9]{4}-[0-9]{2}-[0-9]{2}-?(.*)" "$1" | title }}" 设置分页 Hugo 不能禁用分页,所以设置一个很大的数(达到 9999 篇文章后分页): vi config.toml: paginate = 9999

使用 mapWithState计算单词个数

package allinone import org.apache.spark.streaming._ import org.apache.log4j.{Level, Logger} import org.apache.spark.sql.SparkSession /** * Counts words cumulatively in UTF8 encoded, '\n' delimited text received from the network every * second starting with initial value of word count. * Usage: StatefulNetworkWordCount <hostname> <port> * <hostname> and <port> describe the TCP server that Spark Streaming would connect to receive * data. * * To run this on your local machine, you need to first run a Netcat server * `$ nc -lk 9999` * and then run the example * `spark-submit.

发送字节数组到 Kafka

测试 Kafka 连通性 往 Kafka 里面写数据时, 默认在不存在那个topic 时,会自动创建 测试能否连上某台机器的 kafka yum install telnet yum install telnet-server telnet 10.10.10.35 9092 如果 Trying 10.10.10.35... Connected to 10.10.10.35. Escape character is '^]'. 说明可以连通。 同样适用于 zoomkeeper: telnet 10.0.201.83 2181 Trying 10.0.201.83... Connected to 10.0.201.83. Escape character is '^]'. 从指定位置消费 Kafka pip install kafka-python import gzip from kafka import KafkaConsumer from kafka import TopicPartition consumer = KafkaConsumer(bootstrap_servers='10.30.10.15:9092') partition = TopicPartition('dc-diagnostic-report', 0) start = 8833 end = 8835 consumer.

JavaScript 编程全解

函数JavaScript 的函数是一种对象。 对象 Javascript 中没有类这样的语言结构, Javascript 中的对象是一个 名称与值配对的集合。这样一对儿名称和值的配对被称为属性。例如一个人的属性有: 身高: 178cm 体重 65kg 年龄 28 所以, Javascript 对象可以定义为属性 的集合。Javascript 的对象字面量: // 对象字面量表达式的语法 { 属性名 : 属性值, 属性名 : 属性值, ...... } 属性名可以是标识符。字符串和数值: // 对象字面量表达式的例子 { x: 2, y:1 } // 属性名是标识符 { "x":2, "y":1 } // 属性名是字符串值 { 'x':2, 'y':1 } // 属性名是字符串值 { 1:2, 2:1 } // 属性名是数值 { x:2, y:1, enable:true, color:{ r:255, g:255, b:255 } } // 各种类型的属性值 对对象字面量表达式求值所得到的结果,是所生成对象的一个引用。

Raku from Haskell - Nutshell

Haskell 和 Raku 是非常不同的语言。这很明显。 但是,这并不意味着没有相似之处或共同的想法! 此页面尝试让一个 Haskell 用户启动并运行 Raku。Haskell 用户可能会发现,在用 Raku 编写脚本时,他们不需要放弃所有 Haskelly 的想法。 请注意,这不应该被误认为是初学者教程或 Raku 概述; 它旨在作为具有强大 Haskell 背景的 Raku 学习者的技术参考。 类型 类型 vs 值 在 Haskell 中, 您有类型级编程, 然后进行值级编程。 plusTwo :: Integer -> Integer -- Types plusTwo x = x + 2 -- Values 您不要像下面那样在 Haskell 中混合类型和值。 plusTwo 2 -- This is valid plusTwo Integer -- This is not valid 在 Raku 中, 类型(亦称为类型对象)和值处于同样的级别 sub plus-two(Int $x --> Int) { $x + 2 } plus-two(2); # This is valid plus-two(Int); # This is valid 我将再用一个例子来说明 Raku 这个独特之处:

用 parquet 数据模拟实时数据流

用 parquet 数据模拟实时数据流 import ohmysummer.conf.{KafkaConfiguration, UriConfiguration} import ohmysummer.pipeline.schema.{EnterpriseSchema, NationSchema} import org.apache.spark.sql._ import org.apache.spark.sql.streaming.Trigger import org.apache.spark.sql.functions.{col, struct, to_json} /** * 读取 parquet 文件转为 JSON 后写到 HDFS, 在用命令行将 JSON 数据逐行发到 Kakfa 模拟实时流 */ object WriteEnterprise2Kafka { def main(args: Array[String]): Unit = { val spark: SparkSession = SparkSession .builder .master("local[2]") .appName("Write Enterprise Parquet to Kafka") .getOrCreate() val parquetSchema = (new EnterpriseSchema).schema val parqurtUri = (new UriConfiguration).xs6enterprise val topics = (new KafkaConfiguration).topics val bootstrap_servers = (new KafkaConfiguration).

本地读取 HBase 的 pom 文件

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>mileageanxiety</groupId> <artifactId>wmanxiety</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- Languages --> <java.version>1.8</java.version> <scala.version>2.11.8</scala.version> <scala.binary.version>2.11</scala.binary.version> <!-- Apache Spark --> <spark.version>2.1.0</spark.version> <hadoop.version>2.6.0-cdh5.13.2</hadoop.version> <!-- Thirty Party --> <scopt.version>3.3.0</scopt.version> <typesafe-config.version>1.3.0</typesafe-config.version> <hbase.version>1.1.2</hbase.version> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/com.hortonworks/shc-core --> <dependency> <groupId>com.hortonworks</groupId> <artifactId>shc-core</artifactId> <version>1.1.1-2.1-s_2.11</version> </dependency> <!--<dependency>--> <!--<groupId>org.apache.spark</groupId>--> <!--<artifactId>spark-core_2.11</artifactId>--> <!--<version>2.1.0</version>--> <!--</dependency>--> <!--<dependency>--> <!--<groupId>org.apache.spark</groupId>--> <!--<artifactId>spark-sql_2.10</artifactId>--> <!--<version>2.1.0</version>--> <!--</dependency>--> <!--<dependency>--> <!--<groupId>org.scala-lang</groupId>--> <!--<artifactId>scala-library</artifactId>--> <!--<version>2.11.8</version>--> <!--</dependency>--> <!-- https://mvnrepository.com/artifact/com.typesafe/config --> <dependency> <groupId>com.typesafe</groupId> <artifactId>config</artifactId> <version>1.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.phoenix/phoenix-spark --> <!

在 IDEA 中配置 Pyspark

在 IDEA 中配置 Pyspark 参考 https://had00ping.wordpress.com/2017/11/14/setting-intellij-for-pyspark/ 最后添加根目录: 结果如下: 在 IDEA 中添加 java 目录 删除 pom 文件中的如下配置: <sourceDirectory>src/main/scala</sourceDirectory> <sourceDirectory>src/main/java</sourceDirectory>

在命令行中导入 Maven 包

mill 是一个新的 build 工具, 可以在命令行中导入 jar 包: import $ivy.`com.alibaba:fastjson:1.2.12` import com.alibaba.fastjson.{JSON, JSONObject} val j = JSON.parseObject("{'a':123}") println(j.get("a")) // 123

使用 mvn 单独下载某个依赖

例如,某个依赖在 pom 文件中是这样的: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency> 那么使用 mvn 单独下载这个依赖: mvn org.apache.maven.plugins:maven-dependency-plugin:2.1:get \ -DrepoUrl=https://mvnrepository.com/artifact/ -Dartifact=redis.clients:jedis:2.6.2

使用 scopt 解析命令行参数

https://github.com/scopt/scopt 简单的 scala 命令行选项解析 scopt 是一个小小的命令行选项解析库。 Sonatype libraryDependencies += "com.github.scopt" %% "scopt" % "X.Y.Z" 查看上面的 Maven Central badge 使用方法 scopt 提供了两种解析方式:immutable 和 mutable。无论哪种情况,首先您需要一个表示配置的 case class: import java.io.File case class Config(foo: Int = -1, out: File = new File("."), xyz: Boolean = false, libName: String = "", maxCount: Int = -1, verbose: Boolean = false, debug: Boolean = false, mode: String = "", files: Seq[File] = Seq(), keepalive: Boolean = false, jars: Seq[File] = Seq(), kwargs: Map[String,String] = Map()) 在不可变的解析样式中,config 配置对象作为参数传递给 action 回调。另一方面,在可变解析样式中,你需要修改配置对象。

trait

大多数情况下, Scala 中的 trait 相当于 Java 中的借口, 或者 Raku 中的 Role。Scala 可以继承多个 trait。 trait 作为接口 trait BaseSoundPlayer { def play def close def pause def stop def resume } 和 OC 中的接口类似, 如果方法带有参数,则声明时加上参数即可: trait Dog { def speak(whatToSay: String) def wagTail(enabled: Boolean) } 类继承 trait 时需要使用 extends 和 with 关键字, 如果类只继承一个 trait, 则只使用 extends 就够了: class Mp3SoundPlayer extends BaseSoundPlayer { ... 继承一个类和一个或多个 trait 时,对类使用 extends, 对 trait 使用 with: class Foo extends BaseClass with Trait1 with Traits { .

Spark Structured Streaming 官方例子

package ohmysummer import java.sql.Timestamp import org.apache.spark.sql.SparkSession import org.apache.spark.sql.streaming._ /** * Counts words in UTF8 encoded, '\n' delimited text received from the network. * * Usage: MapGroupsWithState <hostname> <port> * <hostname> and <port> describe the TCP server that Structured Streaming * would connect to receive data. * * To run this on your local machine, you need to first run a Netcat server * `$ nc -lk 9999` * and then run the example * `$ bin/run-example sql.

Spark Structured Streaming 之 explode 多列

package ohmysummer import ohmysummer.model.SourceCan import ohmysummer.pipeline.kafka.WmKafkaDeserializer import ohmysummer.pipeline.schema.{CanSignalSchema, DcSaleData} import org.apache.spark.sql.{DataFrame, Dataset, SparkSession} import org.apache.spark.sql.streaming.OutputMode import org.apache.spark.sql.functions._ /** * 从 Kafka 读取 JSON 数据 * https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html * https://stackoverflow.com/questions/43297973/how-to-read-records-in-json-format-from-kafka-using-structured-streaming * https://stackoverflow.com/questions/48361177/spark-structured-streaming-kafka-convert-json-without-schema-infer-schema * https://databricks.com/blog/2017/04/26/processing-data-in-apache-kafka-with-structured-streaming-in-apache-spark-2-2.html * https://databricks.com/blog/2017/02/23/working-complex-data-formats-structured-streaming-apache-spark-2-1.html */ object WmDcSaleApplication { def main(args: Array[String]) { val spark = SparkSession .builder .appName("ReadFromKafka") .master("local[2]") .getOrCreate() object KafkaDeserializerWrapper { val deser = new WmKafkaDeserializer } spark.udf.register("deserialize", (topic: String, bytes: Array[Byte]) => KafkaDeserializerWrapper.deser.deserialize(topic, bytes) ) val df = spark.

Spark 集群出现 standby 的问题

重启 hdfs 集群后,出现的standy 错误的原因: 是因为没有启动zookeeper, zookeeper不会自动重启, zook的启动命令是zkServer.sh. 以后启动集群时,先启动那slave上安装了 zookeeper 的 zookeeper , 然后再启动 hdfs。 Master 上 的 zoomkeeper, vim /opt/zookeeper-3.4.9/conf/zoom.cfg: # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes.

Standalone 模式下 Spark 集群的启动和停止

以下所有操作都是在 Master 上。 停掉所有的 spark crontab 任务 stop crontab sudo rm /etc/cron.d/crontab_ald_spark 杀掉所有的正在运行的 spark 进程 kill spark progress ps aux | grep 'spark' | awk '{print $2}' | xargs kill -9 杀掉 Master(10.0.0.247) stop-master.sh spark/sbin/stop-master.sh -h 10.0.0.247 -p 7077 停掉所有的 slaves stop-slaves.sh spark/sbin/stop-slaves.sh -h 10.0.0.247 -p 7077 停掉 slaves, 注意是 s, 需要先做 Master 到各 slave 的免密码登陆, 即把 Master 下的 ~/.ssh/id_rsa.pub 中的公钥复制到各 slave 下(用sudo su切换到root)的 ~/.ssh/authorized_keys 中。 在 Master 下的 /etc/hosts 文件中, 加上各 slave 的主机名:

Spark 集群配置

salve 上, root@slave2:/opt/spark-2.1.0-bin-hadoop2.6/conf# vim spark-env.sh: export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64 export SCALA_HOME=/opt/scala-2.11.8 export SPARK_MASTER_IP=namenode export HADOOP_HOME=/opt/hadoop-2.6.5 export SPARK_WORKER_CORES=3 export SPARK_WORKER_MEMORY=12g export HADOOP_CONF_DIR=/opt/hadoop-2.6.5/etc/hadoop export HADOOP_CONF_LIB_NATIVE_DIR=/opt/hadoop-2.6.5/lib/native export HADOOP_MAPRED_HOME=/opt/hadoop-2.6.5 export HADOOP_COMMON_HOME=/opt/hadoop-2.6.5 export HADOOP_HDFS_HOME=/opt/hadoop-2.6.5 export YARN_HOME=/opt/hadoop-2.6.5 export HADOOP_INSTALL=/opt/hadoop-2.6.5 export YARN_CONF_DIR=/opt/hadoop-2.6.5/etc/hadoop export SPARK_HOME=/opt/spark-2.1.0-bin-hadoop2.6 export SPARK_CLASSPATH=/opt/hadoop-2.6.5/etc/hadoop:/opt/hadoop-2.6.5/share/hadoop/common/lib/*:/opt/hadoop-2.6.5/share/hadoop/common/*:/opt/hadoop-2.6.5/share/hadoop/hdfs:/opt/hadoop-2.6.5/share/hadoop/hdfs/lib/*:/opt/hadoop-2.6.5/share/hadoop/hdfs/*:/opt/hadoop-2.6.5/share/hadoop/yarn/lib/*:/opt/hadoop-2.6.5/share/hadoop/yarn/*:/opt/hadoop-2.6.5/share/hadoop/mapreduce/lib/*:/opt/hadoop-2.6.5/share/hadoop/mapreduce/*:/opt/hadoop-2.6.5/contrib/capacity-scheduler/*.jar slave2上 yarn-site.xml <?xml version="1.0"?> <!-- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.

Spark 中的 --files 参数与 ConfigFactory 工厂方法

Spark 中的 –files 参数与 ConfigFactory 工厂方法 scala 对象 以前有个大数据项目做小程序统计,读取 HDFS 上的 Parquet 文件,统计完毕后,将结果写入到 MySQL 数据库。首先想到的是将 MySQL 的配置写在代码里面: val jdbcUrl = "jdbc:mysql://127.0.0.1:6606/test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=false" val user = "root" val password = "averyloooooongword" val driver = "com.mysql.jdbc.Driver" properties 文件 如果是测试,生产环境各有一套,那上面的代码就要分别复制俩份,不便于维护!后来知道了可以把配置放在 resources 目录下, 针对本地,测试和生产环境,分别创建不同的 properties 文件: conf.properties conf_product.properties env.properties local.properties 例如其中的 conf.properties 内容如下: # 测试环境配置 ## 数据库配置 jdbc.url=jdbc:mysql://10.0.0.11:3306/ald_xinen_test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false jdbc.user=aldwx jdbc.pwd=123456 jdbc.driver=com.mysql.jdbc.Driver # parquet 文件目录 tongji.parquet=hdfs://10.0.0.212:9000/ald_log_parquet 然后在代码里面读取 resource 文件中的配置: /** * 根据 key 获取 properties 文件中的 value * @param key properties 文件中等号左边的键 * @return 返回 properties 文件中等号右边的值 */ public static String getProperty(String key) { Properties properties = new Properties(); InputStream in = ConfigurationUtil.

Spark Streaming 配置

看一下提交命令 offline.sh 中的一个有趣的配置: spark2-submit \ --class $1 \ --master yarn \ --deploy-mode cluster \ --driver-memory 4g \ --driver-cores 2 \ --executor-memory 6g \ --executor-cores 3 \ --num-executors 12 \ --conf spark.yarn.submit.waitAppCompletion=false \ --files /etc/hbase/conf/hbase-site.xml \ /tmp/xxxx.jar spark.yarn.submit.waitAppCompletion 这个配置,看字面意思,提交任务,直到程序结束运行的意思。如果设置为 false, 那么提交完就可以去干别的事情了,不用一直等着看结果;如果设置为 true(默认的), 顾名思义,提交完程序后会一直在终端中打印信息,直到程序运行结束。所以为了方便,还是设置成 false 比较合适。因为等一会你可以通过 yarn logs applicationId application_1534402443030_0345 查看运行的日志/运行结果。 ./offline.sh wmstat.Completion 12 7 它提交完就完事了,任务还在后台运行,所以打印出来的日志很短: SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/opt/cloudera/parcels/SPARK2-2.3.0.cloudera2-1.cdh5.13.3.p0.316101/lib/spark2/jars/phoenix-4.14.0-cdh5.13.2-client.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/opt/cloudera/parcels/CDH-5.

使用 Spark Structured Streaming 解析字段不固定的 JSON

数据样例 有两个文件,一个是 json: a.json { "createTime": 1532598069, "event": { "info": { "AAA": "one", "BBB": "two", "DDD": "opps" } } } 另一个也是 json: b.json { "createTime": "1532598069", "event": { "info": { "AAA": "three", "BBB": "four", "CCC": "haha" } } } Kafka Producer info 里面的字段个数是不固定的。用下面的代码先将 a.json 发送到 Kafka: from confluent_kafka import Producer p = Producer({'bootstrap.servers': 'localhost:9092'}) def delivery_report(err, msg): """ Called once for each message produced to indicate delivery result. Triggered by poll() or flush().

使用 Spark Structured Streaming 解析 JSON

Producer 发送 JSON 数据到 Kafka: from confluent_kafka import Producer p = Producer({'bootstrap.servers': 'localhost:9092'}) def delivery_report(err, msg): """ Called once for each message produced to indicate delivery result. Triggered by poll() or flush(). """ if err is not None: print('Message delivery failed: {}'.format(err)) else: print('Message delivered to {} [{}]'.format(msg.topic(), msg.partition())) with open("/Users/ohmycloud/bigdata/Spark/camelia.json") as f: data = f.read() p.poll(0) p.produce('net-logs', data.encode('utf-8'), callback=delivery_report) p.flush() JSON 数据内容如下: { "metadata":{ "access_token":"c.FmDPkzyzaQe...", "client_version":1 }, "devices":{ "thermostats":{ "peyiJNo0IldT2YlIVtYaGQ":{ "device_id":"peyiJNo0IldT2YlIVtYaGQ", "locale":"en-US", "software_version":"4.

StackOverflowError

在写一个大数据项目的时候,用的 json schema 嵌套了很多层,使用了很多字段。编译的时候就爆出了这个堆栈溢出: scalac: Error: org.jetbrains.jps.incremental.scala.remote.ServerException java.lang.StackOverflowError 解决方法一: 在 pom 文件里面添加 jvmArg, 将 -Xss 设置为 4096K: <configuration> <args> <!--<arg>-make:transitive</arg>--> <arg>-dependencyfile</arg> <arg>${project.build.directory}/.scala_dependencies</arg> </args> <jvmArgs> <jvmArg>-Xss4096K</jvmArg> </jvmArgs> </configuration> 这样 submit 的时候就可以跑了: spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.11:2.3.0 --class ohmysummer.SparkStructuredStreaming --master local[2] --driver-memory 2g --driver-cores 2 --executor-memory 2g --executor-cores 2 --num-executors 2 target/wordcount-1.0-SNAPSHOT.jar 解决方法二: 点击 run 按钮的时候仍旧报堆栈溢出, 在 IDEA 中设置:

Scala 基础

在浏览器中运行 Scala scalafiddle.io 表达式 表达式是可计算的语句。 你可以使用 println 输出表达式的值。 println(1) // 1 println(1+1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! 值 使用 val 关键字来命名表达式的值。 val x = 1 + 1 println(x) // 2 这里的命名结果, 例如 x, 叫做值。引用一个值不会重新计算它。 值一旦确定就不能被重新赋值。 val x = 1+1 x = 3 // This does not compile. 值的类型可以被推断出来, 但是你也可以显式的声明它的类型, 像这样: val x: Int = 1 + 1 变量 变量像值一样, 但是你可以给它重新赋值。用 var 关键字来定义一个变量。 var x = 1 + 1 x = 3 // This compiles because "x" is declared with the "var" keyword.

NoClassDefFound

升级了一下 IDEA, 运行 spark 程序的时候给我报错了, NoClassDefFound : Scala/xml/metadata 解决方法, 在pom 文件里面添加如下依赖: <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>2.11.8</version> </dependency> <!-- https://mvnrepository.com/artifact/org.scala-lang.modules/scala-xml --> <dependency> <groupId>org.scala-lang.modules</groupId> <artifactId>scala-xml_2.11</artifactId> <version>1.1.0</version> </dependency> CDH 集群,Spark 升级到 2.3 后,运行任务报错: Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/Logger at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.privateGetMethodRecursive(Class.java:3048) at java.lang.Class.getMethod0(Class.java:3018) at java.lang.Class.getMethod(Class.java:1784) at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526) Caused by: java.lang.ClassNotFoundException: org.slf4j.Logger at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.

maven 依赖树

使用 IDEA 打包的时候,报这个 plugins 空指针异常,导致打包失败: <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <configuration> 解决方法: mvn dependency:tree 查看依赖树: com.google.inject:guice:jar:3.0:compile com.google.inject 是 groupId, guice 是 artifactId, 3.0 是 version

Raku 中的笑脸

+++ 在 Raku 中在调用者的类型身上使用 :D 或 :U 类型笑脸来制造 type/instance 方法: class Foo { multi method foo (Foo:D:) { say "instance" } multi method foo (Foo:U:) { say "type object" } } Foo .foo; # 输出 type object Foo.new.foo; # 输出 instance # Can use compile time vars to aovid re-typing the actual name everywhere: class Bar { multi method foo (::?CLASS:D:) { say "instance" } multi method foo (::?CLASS:U:) { say "type object" } } Bar .

使用 IDEA 创建 maven 父子工程

参考 使用IDEA创建maven父子工程 如果手动创建的 , 子项目的 pom 文件总是图标显示不正常。 package 标签替换为 jar,它的值有 jar、war、pom 等, 如果要生成 jar 包,则: <packaging>jar</packaging>

Introducing Pandas UDF for PySpark

Introducing Pandas UDF for PySpark 更新:此博客于 2018 年 2 月 22 日更新,以包含一些更改。 这篇博文在即将发布的 Apache Spark 2.3 版本中引入了 Pandas UDFs(即 Vectorized UDFs) 特性,这大大提高了 Python 中用户定义函数(UDF)的性能和可用性。 在过去的几年中,Python 已经成为数据科学家的默认语言。像 pandas,numpy,statsmodel 和 scikit-learn 这样的软件包已经获得了广泛的采用并成为主流工具包。同时,Apache Spark 已成为处理大数据的事实标准。为了使数据科学家能够利用大数据的价值,Spark 在 0.7 版中添加了 Python API,并支持user-defined functions。这些用户定义的函数一次只能操作一行,因此会遭遇高序列化和调用开销。因此,许多数据管道在 Java 和 Scala 中定义 UDF,然后从 Python 中调用它们。 基于 Apache Arrow 构建的 Pandas UDF 为您提供了两全其美的功能 - 完全用 Python 定义低开销,高性能 UDF的能力。 在 Spark 2.3 中,将会有两种类型的 Pandas UDF: 标量(scalar)和分组映射(grouped map)。接下来,我们使用四个示例程序来说明它们的用法:Plus One,累积概率,减去平均值,普通最小二乘线性回归。 Scalar Pandas UDFs Scalar Pandas UDFs 用于向量化标量运算。要定义一个标量 Pandas UDF,只需使用 @pandas_udf 来注释一个 Python 函数,该函数接受 pandas.

dr.who

提交了 Spark Streaming 程序后,看到 Spark Streaming 的 UI 界面, Executors 下面只有一个 driver 在运行,Streaming 界面一直在排队,像卡住了一样!点进去 driver 的 logs -> stdout 界面,看到很多这样的日志: 18/09/04 19:10:45 org.apache.spark.internal.Logging$class.logWarning(Logging.scala:66) WARN YarnClusterScheduler: Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources 18/09/04 19:10:51 org.apache.spark.internal.Logging$class.logWarning(Logging.scala:66) WARN YarnAllocator: Container marked as failed: container_e13_1534402443030_0354_02_000125 on host: WMBigdata6. Exit status: 1. Diagnostics: Exception from container-launch. Container id: container_e13_1534402443030_0354_02_000125 Exit code: 1 Stack trace: ExitCodeException exitCode=1: at org.

依赖未知

在创建 Flink 的 SocketWindowWordCount 例子的时候: import java.sql.Time import org.apache.flink.api.java.utils.ParameterTool import org.apache.flink.streaming.api.scala._ import org.apache.flink.streaming.api.windowing.time.Time /** * Implements a streaming windowed version of the "WordCount" program. * * This program connects to a server socket and reads strings from the socket. * The easiest way to try this out is to open a text sever (at port 12345) * using the ''netcat'' tool via * {{{ * nc -l 12345 * }}} * and run this example with the hostname and the port as arguments.

Pyspark 不支持 cluster 模式

/app/spark/bin/spark-submit --master spark://10.0.0.46:7077 --deploy-mode cluster --driver-memory 2g --executor-memory 4g --executor-cores 2 /home/spark/aldstat_page_view.py 1 Error: Cluster deploy mode is currently not supported for python applications on standalone clusters. 改为本地模式: /app/spark/bin/spark-submit --master spark://10.0.0.46:7077 --deploy-mode client --driver-memory 2g --executor-memory 4g --executor-cores 2 /home/spark/aldstat_page_view.py 1

case class

Case Classes Scala 支持 case classes 记法。Case Class 就是普通的类, 除了: 默认不可变 可以通过模式匹配拆分 通过结构相等比较而非通过引用比较 易于实例化和操作 下面是一个 Notification 类型等级的例子, 它由一个抽象的超类 Notification 和三个具体的用 case classes Email, SMS, VoiceRecording 实现的 Notification 类型。 abstract class Notification case class Email(sourceEmail: String, title: String, body: String) extends Notification case class SMS(sourceNumber: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification 实例化一个 case class 很容易:(注意我们不需要使用 new 关键字) val emailFromJohn = Email("john.doe@mail.com", "Greetings From John!", "Hello World!

AirFlow 教程

安装 AirFlow sudo pip install airflow --upgrade --ignore-installed mkdir -p /Users/ohmycloud/airflow/dags dags 目录中放入该文件: # -*- coding:utf-8 -*- # airflowPysparkDagTest.py from airflow import DAG from airflow.operators.bash_operator import BashOperator from datetime import datetime, timedelta import os sparkSubmit = '/Users/ohmycloud/opt/spark-2.1.0-bin-hadoop2.6/bin/spark-submit' ## Define the DAG object default_args = { 'owner': '焉知非鱼', 'depends_on_past': False, 'start_date': datetime(2017, 12, 27), 'retries': 5, 'retry_delay': timedelta(minutes=1), } dag = DAG('PysparkTest', default_args=default_args, schedule_interval=timedelta(1)) numUniqueAuthors = BashOperator( task_id='unique-event', bash_command=sparkSubmit + ' ' + '/Users/ohmycloud/scripts/Python/spark-json/test.

使用 Raku 进行文本处理

文本处理 1)对于字符串:A_B_C_D_E.mp4,如何匹配倒数第二个“_”? 2)问题1: 每行行首都是下列形式数字章节号开头 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.3 以上章节号每个占据一行,章节号后面均有一个空格字符,然后是数量不定的英文或中文字符或数字,最后两个字符是tab空格和位数不限的数字。 章节号里有1个小数点的,行首加1个tab空格;2个小数点的,行首加2个tab空格,以此类推。 问题2: 每行行首大多数是以问题1的数字开头,但有部分是以英文或汉字开头,怎么吧以英文或汉字开始的行合并到上一行去? 问题1原始数据: 1.1 甲 1 1.1.1 yi 2 1.1.1.1 4 3 1.2 乙 14 1.2.1 yi 20 1.2.1.1 4 32 问题1期望数据: 1.1 甲 1 1.1.1 yi 2 1.1.1.1 4 3 1.2 乙 14 1.2.1 yi 20 1.2.1.1 4 32 问题2原始数据: 1.1 甲 1 1.1.1 yi 哈 2 1.1.1.1 4 3 1.2 乙 AV 4 1.2.1 yi 20 1.

使用 Docker 创建 MySQL 服务

安装 mysql 的 docker 镜像: docker search mysql # 搜索 mysql 镜像 docker pull daocloud.io/library/mysql:5.7 # 使用国内镜像 新建一个 docker-compose.yml 文件, docker-compose.yml 文件的内容如下: version: '3' services: mysql: image: daocloud.io/library/mysql:5.7 ports: - 0.0.0.0:6606:3306 container_name: mysqldb environment: MYSQL_ROOT_PASSWORD: 000608 volumes: - ./data:/var/lib/mysql 启动 mysql 服务: cd ~/Desktop # docker-compose.yml 放在桌面了 sudo docker-compose up 连接 mysql: sudo mysql -h127.0.0.1 -P6606 -uroot -p000608

SCP 的一个坑

scp SPARK2-2.3.0.cloudera2-1.cdh5.13.3.p0.316101-el7.parcel root@bigdata3:/opt/cloudera/parcel-repo 会把 parcel-repo 目录给覆盖了!复制整个目录: scp -r /opt/cloudera/parcel-repo root@bigdata3:/opt/cloudera/

CentOS 7.4 升级 openssl

CentOS 7.4 升级 openssl 去 openssl官网 下载最新的稳定版 tar.gz 包: wget https://www.openssl.org/source/openssl-1.1.0h.tar.gz 编译安装 tar -zxvf /tmp/openssl-1.1.0h.tar.gz cd /tmp/openssl-1.1.0h ./config --prefix=/usr/local/openssl # 如果此步骤报错,需要安装perl以及gcc包 make make install mv /usr/bin/openssl /usr/bin/openssl.bak ln -sf /usr/local/openssl/bin/openssl /usr/bin/openssl echo "/usr/local/openssl/lib" >> /etc/ld.so.conf ldconfig -v # 设置生效 openssl version # OpenSSL 1.1.0h 27 Mar 2018 .so 文件 但是这样生的 lib 里面是含有 .a 文件, 没有 .so 文件! 看一下 INSTALL 文件,里面: ./config shared --prefix=/usr/local/openssl 这样才会生成 .so 文件。

2017 StackOverFlow-sort,deepmap,flat

raku What is the best way to match any of a group of words? 我想匹配任意一组单词,但是失败了,请问怎样才能正确地匹配到? my @a=<a b c d e f>; my $x="a1234567"; say $x ~~ m/ @a.any /; Answer my @a = <a b c d e f>; my $x = "a1234567"; say $x ~~ /@a/ /@a/ 和 /| @a/ 相同,它是最长的备选分支。对于备选分支,你可以使用 /|| @a/。 How to build lazy lists with defined generators and is there a “takeWhile” alternative? sub foo($x) { $x**2 } my $alist = (1,2, &foo .

给 openresty 的 luajit 安装 luarocks

# .bashrc export OPENRESTY=/usr/local/Cellar/openresty/1.13.6.1 export LUAJIT_LIB=$OPENRESTY/luajit/lib export LUAJIT_INC=$OPENRESTY/luajit/include/luajit-2.1 下载 luarocks 解压: ./configure --prefix=/usr/local/luarocks-2.4.3 --with-lua=/usr/local/Cellar/openresty/1.13.6.1/luajit --lua-suffix="jit" --with-lua-include=/usr/local/Cellar/openresty/1.13.6.1/luajit/include/luajit-2.1 make sudo make install sudo ln -s /usr/local/Cellar/openresty/1.13.6.1/luajit/bin/luajit /usr/local/bin/luajit sudo nginx -p `pwd`/ -c conf/nginx.conf

使用 termux 运行 Linux 系统

ssh -p 8022 u0_a159@192.168.1.173 vim ../usr/etc/apt/sources.list deb [arch=all,aarch64] http://mirrors.tuna.tsinghua.edu.cn/termux stable main 我的 Xperia XZ1 的 cpu 架构是 aarch64

Raku 时间戳转换器命令行版

Raku By Example: Datetime Conversion for the Command Line 我偶尔会在数据库中存储 UNIX 时间戳, 即从 1970-01-01 开始的秒数。我在按照日期查询数据库中的数据时, 需要将 UNIX 时间戳转换为人类可读的时间, 所以我写了个很小的工具来帮助我在 UNIX 时间戳和日期/时间之间来回转换: $ autotime 2015-12-24 1450915200 $ autotime 2015-12-24 11:23:00 1450956180 $ autotime 1450915200 2015-12-24 $ autotime 1450956180 2015-12-24 11:23:00 使用库 Raku 的 DateTime 和 Date 模块会做实际的转换。 DateTime.new 构造函数有一个接收单个整数作为 UNIX 时间戳的变体: $ raku -e "say DateTime.new(1480915200)" 2016-12-05T05:20:00Z 看起来我们已经完成了一个方向的转换,对吗? #!/usr/bin/env raku sub MAIN (Int $timestamp) { say DateTime.new($timestamp) } 我们来运行它: $ autotime 1450915200 Invalid DateTime string '1450915200'; use an ISO 8601 timestamp (yyyy-mm-ddThh:mm:ssZ or yyyy-mm-ddThh:mm:ss+01:00) instead in sub MAIN at autotime line 2 in block <unit> at autotime line 2 发生了什么?看起来 DateTime 构造函数把参数当作了字符串, 尽管 sub MAIN 的参数被声明为 Int。怎么会变成那样呢?

Raku Grammar 之分割结构化文本

如何使用 Grammar 分割一个有规律的文本文件? 首先这个文本有规律, 但是却是多行的。 我想将这样的文档分为独立的. 比如下面这个例子, 我想将他们分成3个独立的文本, 每个文本包含: [时间] Title 以及下面的 content lines. 实际的文件会有上千个, 最终输出的文本的名字是按照括号里面的时间来。 sample.txt [28/04/2015 12:32] Title1 content line 1 content line 2 content line 3 content line 4 content line 5 balabala balabala [28/04/2015 12:16] Title2 content line 6 balabala content line 7 [27/04/2015 17:30] ​Title3 content line 8 content line 9 content line 10 下面是解析: use Grammar::Tracer; # 开启 Grammar 调试有助于排错 grammar StructedText { token TOP { ^ <entry>+ $ } token entry { <head> \s* # 每一项有一个标题 <line>+ \s* # 每个标题下面有很多行 } token head { '[' <datetime> ']' \s+ <title> } token datetime { <filedate> \s+ <filetime> } token filedate { [\d+]+ % '/' } token filetime { [\d+]+ % ':' } token title { \N+ } token line { [ <!

第 10000 个素数

到目前为止(2016.10.26), Raku 的速度相比 Perl 5/Python 的差距还是很大的。以打印从 1 开始计数的第 10000 个素数为例, 使用如下版本的 Rakudo: This is Rakudo version 2016.07.1 built on MoarVM version 2016.07 implementing Raku.c. 从程序执行耗费的时间来看, Raku 实在慢的如蜗牛。 并发打印第10000个素数 sub find-prime($count) { my $channel = Channel.new; my $promise = start { for ^$count { $channel.send($_) if .is-prime; } LEAVE $channel.close unless $channel.closed; } return $channel.list but role :: { method channel { $channel } };; } my @primes = find-prime(110000); #for @primes { # @primes.

Raku 中的 with

with orwith without with 语句就像 if 但是是为了测试是否定义而非真假。此外, 它主题化了条件, 这很像 given: with "abc".index("a") { .say } # print 0 代替 elsif, orwith 用于把是否定义的测试链接起来: # The below code says "Found a at 0" my $s = "abc"; with $s.index("a") { say "Found a at $_" } orwith $s.index("b") { say "Found b at $_" } orwith $s.index("c") { say "Found c at $_" } else { say "Didn't find a, b or c" } 你可以混合基于 if 的从句和基于 with 的从句:

These keys are LTA

在折腾枚举作为子例程布尔选项的时候, 我发现默认的错误信息不够酷。 Constraint type check failed for parameter '@options' 让错误信息变得更具体有点困难。我们来创建几个 exceptions 来告诉我们当东西出错时究竟发生了什么。 class X::Paramenter::Exclusive is Exception { has $.type; method message { "Parameters of {$.type.perl} are mutual exclusive" } } 现在我们能检查 Find::Type 的选项是否是独占的从而抛出异常。 && ( exclusive-argument(@options, Find::Type) or fail X::Paramenter::Exclusive.new(type => Find::Type) ) class X::Parameter::UnrecognisedOption is Exception { has $.type; has $.unrecognised; method message { "Option { $.unrecognised } not any of { $.type.map({ (.^name ~ '::') xx * Z~ .

Whatever Star

Whatever 是什么? Placeholder for unspecified value/parameter - 未指定的值/参数的占位符。 * 字面量在 「term」 位置上创建 「Whatever」 对象。 * 的大部分魔法来自于 「Whatever 柯里化」. 当 * 作为 item 与很多操作符组合使用时, 编译器会把表达式转换为 「WhateverCode」 类型的闭包. my $c = * + 2; # same as -> $x { $x + 2 }; say $c(4); # 6 如果一个表达式中有 N 个 *, 则会产生一个含有 N 个参数的闭包: my $c = * + *; # same as -> $x, $y { $x + $y } 在复杂的表达式中使用 * 也会产生闭包:

智能匹配

智能匹配 智能匹配通常作用在当前”主题”(topic)上, 即作用在 $_ 变量上. 在下面的表格中, $_ 代表 ~~ 操作符的左侧, 或者作为 given 的参数, 或者作为其它主题化的参数. X 代表 ~~ 操作符右侧要匹配的模式, 或者在 when 后面的模式.(实际上, ~~ 操作符充当着一个小型的主题; 即, 为了右侧的计算, 它把 $_ 绑定到左侧的值上. 使用底层的 .ACCEPTS 形式来避免这种主题化.) 第一节包含了含有享有特权的语法; 如果匹配能通过那些条目之一完成, 那它就会那样做. 这些特别的语法是通过它们的形式而非它们的类型进行分派的. 否则就使用表格中的剩余部分,并且匹配会根据普通的方法分派规则进行分派. 允许优化器(optimizer)假定编译时之后没有定义额外的匹配操作符, 所以, 如果在编译时模式类型就显而易见的, 那么跳转表(jump table)可以被优化. 然而, 这部分表格的语法仍然有点特权的, 跟 ~~ 操作符一样, 是 Perl 中少有的几个不使用多重分派的操作符之一. 相反, 基于类型的智能匹配会被单个地分派给属于 X 模式对象的底层方法. 换句话说, 智能匹配首先根据模式(pattern)的形式或类型(下面的X)进行分派(dispatch), 然后那个模式自身决定是否和怎样注意主题($_)的类型. 所以, 下面表格中的第二列就是初始列. 第一列中的 Any 条目标示了模式要么不关心主题的类型, 要么把那个条目作为默认值, 因为上面列出的更具体的类型不匹配. $_ X Type of Match Implied Match if (given $_) ====== ===== ===================== =================== Any True ~~ True (parsewarn on literal token) Any False ~~ False match (parsewarn on literal token) Any Match ~~ Successful match (parsewarn on literal token) Any Nil ~~ Benign failure (parsewarn on literal token) Any Failure Failure type check (okay, matches against type) Any * block signature match block successfully binds to |$_ Any Callable:($) item sub truth X($_) S03-smartmatch/any-callable.

Raku Types--成人之美

Raku Types: 成人之美 在我的第一次大学编程语言课中, 我被告知 Pascal 语言在其它类型之外还拥有 Integer、Boolen 和 Stirng 类型。我知道了类型本来就该存在因为计算机很笨。当我在 C语言中涉猎的时候,我学到了更多有关 int、 char 和其它像在暖和的地方里地寄生虫, 还有我课桌底下嗡嗡的金属盒的声音。 Perl 5 没有类型,它给我的感觉就像骑着自行车无拘无束的追风少年,沿着斜坡而下。不久之后我一门心思钻到计算机硬件的缝隙中。我拥有数据并且我能用它做任何我想做的事, 只要我得到的不是错误的数据。当我搞定的时候,我从自行车上掉了下来并刮破了我的膝盖。 有了 Raku,鱼和熊掌可以兼得。你可以使用类型来避免它们。你可以拥有一个广域的类型来接收很多种类的值或窄类型。并且你可以享受代表机器智力的类型的速度, 或者你可以享受你自定义的代表你自己意志的类型的精度,类型为人类而生。 渐进类型 my $a = "whatever"; my Str $b = "strings only"; my Str:D $c = "defined strings only"; my int $d = 14; # native int sub foo ($x) { $x + 2 } sub bar (Int:D $x) returns Int { $x + 2 } Raku 拥有渐进类型, 这意味着你要么可以使用它们,要么避免使用它们。所以究竟为什么要打扰它们呢?

学习Raku的一些网站

Blog rakuintro raku.party Raku - Reddit Raku Tablet Rosetta Rosetta GitBook mfollett perlgeek Raku advent calendars RakuMaven Strangely Consistent Death by Raku jnthn 道 官方网站 raku.org Raku Documentation Raku Design Documents modules.raku.org pl6anet examples.raku.org Raku Features Raku 测试套件 Raku IRC 国内 sunnavy songzan 扶凯

roundrobin

定义为 multi roundrobin(List:D: --> Seq) 用法 roundrobin LISTS Round-Robin Merge Two Lists of Different Length roundrobin很像 zip。不同之处是, roundrobin不会在用光元素的列表上停止而是仅仅跳过任何未定义的值: my @a = 1; my @b = 1..2; my @c = 1..3; for flat roundrobin(@a, @b, @c) -> $x { $x.say } # 1,1,1,2,2,3 它只是跳过了未定的值, 直到最长的那个列表的元素用完。 my @list1 = 'a' .. 'h'; my @list2 = <x y>; say flat roundrobin @list1, @list2; # a x b y c d e f g h roundrobin 返回的是一列 Seq, 所以使用 flat 进行展开。

Raku 中的 Subscripts

Subscripts 通过索引或键访问数据结构中的元素。 通常,人们需要引用集合或数据结构中的一个特定的元素(或特定的元素切片)。从数学标记法中偷学到的,向量 v 的组成部分用 v₁, v₂, v₃ 来引用,在 Raku 中这个概念叫做 “下标” (或“索引”)。 Basics Raku 提供了两个通用的下标接口: elements are identified by interface name supported by [ ] zero-based indices Positional Array, List, Buf, Match, ... { } string or object keys Associative Hash, Bag, Mix, Match, ... Positional 下标 (通过 postcircumfix [ ] 通过元素在有序集合中的位置来寻址元素。)索引 0 引用第一个元素, 索引 1 引用第二个元素, 以此类推: my @chores = "buy groceries", "feed dog", "wash car"; say @chores[0]; #-> buy groceries say @chores[1]; #-> feed dog say @chores[2]; #-> wash car Associative 下标 (通过 postcircumfix { }), 不要求集合以任何特定的顺序保存元素 - 相反,它使用一个唯一的键来寻址每个值。键的种类取决于使用的集合: 举个例子, 一个标准的散列 使用字符串作为键, 而一个 Mix 能使用任意的对象作为键, 等等:

正则替换

想把 Desgin Raku 中的 pod/html 转为 Markdown 格式, Raku 的 pod2markdown 不能用, 只能下载 html 格式的了, 然后用 pandoc test.html -o result.markdown 转换了, 但是也不理想, 里面还有很多 html 标签, 写个脚本批量替换下吧。 token 中的空白要显式地使用 \s、\h、\t 等表示, rule 中 :sigspace 是开启的。程序很丑, 仅仅是记录一下。 use v6; my rule r1 {'<'span id\='"'line_\\d+'"''>''<''/'span'>' '<'span id\='"'line_\\d+'"''>''<''/'span'>'( '<'span)?$} my rule r2 {id\='"'line_\d+'"''>''<''/'span'>' '<'span id\='"'line_\d+'"''>''<''/'span'>'( '<'span)?$} my rule r3 {^id\='"'line_\d+'"''>''<''/'span'>'$} my rule r4 {'<'div class\='"'smartlink'"''>'} my rule r5 {'<''/'div'>'} my rule r6 {'<'div class\='"'indexgroup'"''>'} my rule r7 {'<'span id\='"'__top'"''>''<''/'span'>'} my token r8 {^ \s* '<'span$} my rule r9 {^id\='"'line_\d+'"''>''<''/'span'>' '<'span id\='"'line_\d+'"''>''<''/'span'>'$} my rule r10 {id\='"'line_\d+'"''>''<''/'span'>' '<'span id\='"'line_\d+'"''>''<''/'span'>'(\s'<'span)?

匿名状态变量的工作原理

Anonymous State Variables And How They Work 当调试代码的时候, 我经常添加一个计数变量以用于循环, 所以我能跟踪发生了什么, 或我能在代码片段中处理正迭代的部分数据集: my $event-no = 0; for get_events() -> $event { $event-no++; process-event($event); last if $event-no >= 5; } 如果你正在调试, 或者你正尝试在单行中节省空间, Perl 6 实际上有一个匿名状态变量(anonymous state variables)标记, 用不含名字的 $符号来标示(你还可以在很多可迭代对象身上使用 kv 方法来完成类似的东西, 但是匿名的 $ 更普遍。) for get_events() -> $event { process-event($event); last if ++$ >= 5; } 然而, 注意; 下面这样的用法是没有效果的: for get_events() -> $event { process-event($event); $++; last if $ >= 5; } 好了, 为什么是那样的?

Raku 中的动态变量

Raku 中的动态变量 $*ARGFILES $*ARGFILES Magic command-line input handle. argfiles.pl6 use v6; $*ARGFILES.perl.say; #=> IO::Handle.new(:path(Any),:chomp) # 按行读取 for $*ARGFILES.lines -> $line { say "$line"; } # 一次性读取 # say $*ARGFILES.slurp; USAGE $ raku argfiles.pl6 file1 file2 file3 ... class IO::Handle - Raku Documentation Input/Output - Raku Documentation Raku文件操作 - Qiita @*ARGS @*ARGS - Arguments from the command line. 命令行中的参数。 agrs.pl6 use v6; say @*ARGS.WAHT; #=> (Array) say @*ARGS; #=> [a b c d e] say @*ARGS.

S07-Lists

push 和 append 的表现不同, push 一次只添加单个参数到列表末端, append 一次可以添加多个参数。 use v6; my @d = ( [ 1 .. 3 ] ); @d.push( [ 4 .. 6 ] ); @d.push( [ 7 .. 9 ] ); for @d -> $r { say "$r[]"; } # 1 # 2 # 3 # 4 5 6 # 7 8 9 for @d -> $r { say $r.WHAT() } # (Int) # (Int) # (Int) # (Array) 整个数组作为单个参数 # (Array) say @d.

IO 操作

文件存在、文件的时间戳、文件的修改时间等等 批量插入文本 use v6; my @filenames = dir '.', test => any(/\.md$/, /\.markdown/); for @filenames -> $filePath { my $path = $filePath.path(); $path ~~ s/.md//; $path ~~ s/.markdown//; my $date = DateTime.new(now); my $head = qq:heredoc 'EOT'; title: $path.IO.basename() date: $date tags: Raku categories: Raku --- <blockquote class="blockquote-center">这城市有太多风景都在提醒那过去!</blockquote> [TOC] EOT my @content = slurp $filePath; spurt($filePath.path, "$head\n@content[]"); } 在当前目录中查找所有以 .md (.markdown)结尾的文件(即markdown文件), 并在文件最前面插入一段文本, 形如: title: Raku date: 2015-08-20T23:19:13Z tags: Raku categories: Raku --- <blockquote class="blockquote-center">我站在天桥上念你, 有点狼狈</blockquote> 类 IO::Path 提供了 basename, path, parts, 等方法供使用, 具体用法请看文档:

使用代理进行页面传值

建立一个空项目, 删除 ViewController.swift, 新建 AViewControllor.swift 和 BViewController.swift, 并在 AViewController 中嵌入导航控制器。分别在这两个控制器中拖入按钮和 Label, 并进行联线和设置 Outlet。 选中 AViewControllor 和 BViewController 之间的联线, 设置其 identifier 为 “AtoB”。 // // AViewController.swift // 控制器间反向传值 // // Created by chenyf on 16/3/17. // Copyright © 2016年 chenyf. All rights reserved. // import UIKit class AViewController: UIViewController, UITextFieldDelegate, SendMessageDelegate { @IBOutlet var aTextField: UITextField! @IBOutlet var aTextLabel: UILabel! @IBAction func passValueToB(sender: UIButton) { } override func viewDidLoad() { // 设置控制器为 UITextField 的代理 aTextField.

Swift 值类型和引用类型

值和引用类型 值语义 var str = "Hello, playground" var playgroundGreeting = str playgroundGreeting += "How are you today?" print(str) # "Hello, playground" 所以 str 的值并没有改变。因为字符串是一个结构体, 而结构体是值类型。那什么是值类型呢? 值类型在赋值给实例或作为参数传递给函数时总是被复制一份。 Swift 的基本类型 - Array, Dictionary, Int, String 等等都是用结构体来实现的, 它们都是值类型。所以在 Swift 中值类型是何等重要。 你应该在模型化你的数据时首先考虑结构体, 然后再考虑类。 引用语义 class GreekGod { var name: String init(name: String) { self.name = name } } let hecate = GreekGod(name: "Hecate") // 现在有了一个新的名为 "Hecate" 的 GreekGod 实例 let anotherHecate = hecate // 两个常量指向了同一个 GreekGod 类的实例 // 想想在 Perl 语言中的按值传递和按引用传递, 就知道引用能产生副作用 anotherHecate.

Swift 构造函数

初始化(Initilization)是设置类型的实例的操作。它为每个存储属性给定了一个初始值, 并且可能会牵涉其它准备工作。这个处理之后, 这个实例就准备好了并且可用了。 初始化时, 属性的值要么是给定的默认存储值, 要么是根据需要计算得到的值。 初始化函数的语法 结构体和类要求在初始化完成后, 它们的存储属性拥有初始值。这个要求解释了为什么你一直给你所有的存储属性设置默认值。如果你还没给你的这些存储属性默认值, 那么编译器就会给你报错并告诉你该类型的属性还没有被准备好使用。在类型上定义一个初始化函数是另外一种保证在实例被创建之后属性有值的方式。 初始化函数的定义和你看过的函数有点不一样, 初始化函数不是以 func关键字开头, 尽管它也是类型中的方法。初始化函数的语法看起来像这样: struct CustomType { init(someValue: SomeType) { // 这儿是初始化代码 } } 这个通用的语法在结构体、枚举和类之间没什么不同。在上面的例子中, 初始化函数有一个叫做 someValue类型为 SomeType 的参数。而初始化函数通常有一个或多个参数, 它们也可以含有 0 个 参数。(这时 init关键字后面有一组空括号) 不像其它方法, 初始化函数不返回值。相反, 初始化函数的任务是给类型的每个存储属性设定上值。 结构体初始化 结构体即可以有默认初始化函数又可以有自定义初始化函数。当你使用结构体的时候, 你通常会利用提供好的默认初始化函数, 但是也有其它你会自定义初始化处理的情况。 结构体的默认初始化函数 还记得你是怎么获得你的Town类型的实例的吗? 你给Town类型的存储属性设置了默认值。你不知道的是你使用了一个由 Swift 自动提供的空的初始化函数。(一个不含参数的初始化函数)。 当你键入像 var myTown = Town() , 那么这个语法就会调用空的初始化函数并给新的实例的属性设置上你指定的默认值。 另外一种形式的默认初始化函数就是逐个成员初始化函数(memberwise initializer)。逐个成员初始化函数中类型的每个存储属性都有一个参数。 这时, 你不会让编译器根据你指定的默认值来填充新的实例的属性的值。相反, 免费的逐个成员初始化函数会包含所有需要值的存储属性。(我们称之为免费的, 是因为它是 Swift 编译器自动提供的 — 你不需要定义它)。 记住, 初始化的一个准则就是给新的实例的所有存储属性设置上值以准备使用。编译器会强制要求你的新的实例中的所有存储属性都有值。如果你没有为你的自定义的结构体提供初始化函数, 你必须通过默认值或逐个成员初始化函数提供必要的值。 // 使用逐个成员初始化函数 struct Town { var population = 5422 var numberOfStoplights = 4 // 实例方法 func printTownDescription() { print("Population: \(population); number of stoplights: \(numberOfStoplights)") } } var myTown() = Town(population: 10000, numberOfStoplights: 6) myTown.

Swift 扩展协议

协议的命名遵循 Swift 的标准库, 即协议名以 “Type”, “-able”, “-ible” 结尾 例如 SequenceType, GeneratorType, CustomStringConvertible, -type 定义行为, -able 定义元素怎样做某事。 protocol ExerciseType: CustomStringConvertible { var name: String { get } var caloriesBurned: Double { get } var minutes: Double { get } } // 结构体遵守 ErerciseType 协议, 但是也可以有自己的属性和方法, 这也是一种继承和封装 struct EllipticalTrainer: ExerciseType { let name = "Elliptical Machine" let caloriesBurned: Double let minutes: Double } struct Treadmill: ExerciseType { let name = "Treadmill" let caloriesBurned: Double let minutes: Double let distancesInMiles: Double } extension Treadmill { var description: String { return "Treadmill(\(caloriesBurned) calories and \(distancesInMiles) miles in \(minutes) minutes)" } } let ellipticalWorkout = EllipticalTrainer(caloriesBurned: 335, minutes: 30) let runningWorkout = Treadmill(caloriesBurned: 350, minutes: 25, distancesInMiles: 4.

面向协议编程

协议是一组方法和属性的集合。如果某个类型想要得到协议中的方法和属性, 那么这个类型就必须遵守(conform) 这个协议。 类(Classes)、结构体(Struct)和枚举(Enum)都可以遵守协议, 并可选地实现协议中的方法和功能。 协议的声明 protocol SomeProtocol { // 一组方法和属性 } 要使某个类型遵守某个协议, 需要在类型名字后面加上协议名称, 中间用逗号分割。若遵守多个协议, 则各协议之间用逗号隔开。 struct SomeStructure: FirstProtocol, AnotherProtocol { // 结构体内容 } 如果类在遵守协议的同时继承了父类, 那么应该把父类的名字放在协议名之前, 以逗号分割。 class SomeClass: SuperClass, SomeProtocol { // 类的内容 } 协议中的属性 协议中的属性只需声明而不需赋初始值, 不用指定是存储型属性或计算型属性。协议中的属性必须指定是只读还是可读可写的。 protocol FirstProtocol { var marks: Int { get set } var result: Bool { get } func attendance() -> String func markssecured() -> String } protocol AnotherProtocol: FirstProtocol { var present: Bool { get set } var subject: String { get set } var stname: String { get set } } class SwiftConference: AnotherProtocol { var marks = 96 let result = true var present = false var subject = "Swift协议" var stname = "Protocols in Swift" func attendance() -> String { return "99% 的人都参加了 \(stname) 大会。" } func markssecured() -> String { return "\(stname) 的票数为 \(marks)。" } } let std = SwiftConference() std.

Swift 扩展

假设你想给 Swift 标准库 — 比如说Double类型添加额外的方法, 但是你又不知道Double的实现, 所以你不能直接给它添加功能。 你改怎么办呢? Swift 设计了一个叫做 Extensions 的功能来处理这样的情况。 Extensions 允许你为已经存在的类型添加功能。你可以扩展结构体、枚举和类。 你可以使用如下结构来扩展类型: 计算型属性 新的初始化方法 协议的遵守 新的方法 嵌入类型 扩展一个已经存在的类型 Swift 的扩展不允许你给已存在的类型添加存储属性, 但可以添加计算型属性。 typealias Velocity = Double extension Velocity { var kph: Velocity { return self * 1.60934 } var mph: Velocity { return self } } // 为已存在的类型添加要遵守的协议 protocol VehicleType { var topSpeed: Velocity { get } var numberOfDoors: Int { get } var hasFlatbed: Bool { get } } VehicleType声明了三个属性, topSpeed, numberOfDoors 和 hasFlatbed。每个属性都要求遵守该协议的类型只需为该属性实现 getter 方法。遵守该协议的类型需要提供协议中声明的这些属性。

文本输入和代理

键盘属性 键盘的外观由一系列叫做 UITextInputTraits 的 UITextField 属性决定。其中一个属性就是展示的键盘的类型。这个程序中, 你需要使用数字键盘。 选中文本框, 在属性指示器面板里面找到 Keyboard Type, 选择 Number Pad, 并把 Correction 和 Spell Checking 修改为 NO。 响应文本框的更改 下一步是当文本被键入到文本框中时, 更新 Celsius Label。你需要写点代码来完成这个任务。 目前, 它会响应在 ViewController.swift 中定义的 ViewController 类。然而, 对于管理华氏温度和摄氏温度转换的视图控制器来说, ViewController 不是一个很好的描述性的名字。拥有一个描述性的类型名允许你在工程变得更大的时候更容易地管理它。你将删除这个文件并使用一个更具描述性的类来代替它。 删除 ViewController.swift 并新建一个 ConversionViewController 类。在 ConversionViewController.swift 中: import UIKit class ConversionViewController: UIViewController { } 即声明一个名为 ConversionViewController 的类继承自 UIViewController。现在你需要在 Main.storyboard 中把你创建的界面和这个你定义的新的视图控制器关联在一块儿。 打开 Main.storyboard 并选择 ConversionViewController 这个视图控制器, 要么在左侧的文档大纲中, 要么点击视图控制器上面的黄色圆圈。 打开身份检查器, 即工具视图中的第三个 tab(Command-Option-3)。 在最上面, 找到 Custom Class 一栏, 并把 Class 修改为 ConversionViewController。

视图控制器

iOS Programming - 第五章 视图控制器 视图控制器是 UIViewController 的子类的一个实例。 视图控制器管理着视图层级。它负责创建组成视图层级的视图对象并在视图层级中处理跟视图对象关联的事件。 视图控制器中的 View 作为 UIViewController 的子类, 所有的视图控制器都继承了一个重要的属性: var view: UIView! 这个属性指向一个 UIView 实例, 这个 view 是视图控制器的视图层级中的根视图。当视图控制器的 view 被作为 window 的子视图添加时, 该视图控制器的整个视图层级就被添加上了。 视图控制器的 view 直到它需要出现在屏幕上时才被创建。这种优化叫做懒加载(lazy loading), 这能减少内存使用并提升性能。 视图控制器有两种方式创建它的视图层级: 编写程序, 通过重写 UIViewController 的 loadView 方法 在 Interface Builder 中, 通过使用诸如 storyboard 的界面文件 在第三章中你已经使用了这两种方法, 在第六章中你将使用 loadView() 创建程序上的视图。 设置初始的视图控制器 每个 storyboard 都可以有很多视图控制器, 但是每个 storyboard 文件只有一个初始视图控制器(initial view controller)。 初始视图控制器是 storyboard 的入口点。你将在画布中添加并配置另外一个视图控制器并把该控制器设置为 storyboard 的初始视图控制器。 打开 Main.storyboard, 从对象库中, 拖拽一个 ViewController 到画布中, 如果你用完了可用的空间, 你可以按住 Ctrl 并单击背景视图来选择不同的缩放比例。

协议

Swift 支持另外一种形式的封装, 协议, 它允许你不必知道类型本身就能指定和使用类型的接口。接口就是类型提供的一组方法和属性的集合, 跟 Raku 的 role 很像。 要看协议是如何工作的, 你将创建一个函数来把数据格式化为一个像 Excel 那样的电子数据表格。下一步, 你将使用协议来使那个函数更加灵活以处理不同的数据源。Mac 和 iOS apps 通常把数据的展现和提供的数据源分开。这种分离是一种极其有用的模式, 它允许 Apple 提供一个类来处理数据的展示, 而把数据该如何存储留给你自己来决定。 格式化数据表格 定义一个函数, 函数的参数是一个数组, 数据中的元素是一个 Int 型的数组: func printTable(data: [[Int]]) { for row in data { var out = "" for item in row { out += " \(item) |" } print(out) } } let data = [ [30, 6], [40, 18], [50, 20], ] printTable(data) // 输出 30 | 6 | 40 | 18 | 50 | 20 | 然后你想给每一行加上标题, 并对齐每一行:

视图和视图层级

视图和视图层级 视图基础 视图是 UIView 的一个实例, 或它的一个子类 视图知道怎么绘制自己 能处理事件, 例如触摸(touches) 视图存在于视图层级中, 它的根是程序的窗口 视图层级 每个应用程序都有一个 UIWindow 的单个实例用作程序中所有视图的容器。UIWindow 是 UIView 的子类, 所以窗口自己也是一个视图。窗口在程序启动时被创建。一旦窗口创建完成, 其它视图就会被添加到窗口上。 当其它视图被添加到窗口中时, 它就是窗口的子视图。窗口的子视图还可以有子视图, 结果就是视图对象的层级, 而 window 窗口是它们的根(root)。 一旦视图层级创建完成, 它会被画到屏幕上。这个过程可以被分为2步: 视图层级中的每个视图, 包括窗口, 绘制自己。它们把自己渲染到它的图层上(layers), 你可以把 layers 看作一张位图。(layer 是 CALayer 的一个实例) 所有视图的 layers 被组合到屏幕上 视图和 Frames 当你用程序初始化一个视图时, 使用 init(frame:) 指定初始化函数。(designated initializer) 这个函数接收一个参数, 即 CGRect , 它会变成视图的 frame, 即UIView 的一个属性。 var frame: CGRect 视图的frame 指定了视图的大小和它相对于父视图的位置。因为视图的大小总是由它的 frame 指定, 视图的形状总是矩形。 CGRect 包含成员 origin 和 size。origin 是类型为 CGPoint 的结构体, 它包含两个 CGFloat 属性: x 和 y。 size是类型为 CGSize 的结构体, 它包含两个 CGFloat 属性: width 和 height。

Introspection

Raku 支持"泛型, roles 和 多重分发", 它们都是很好的特点, 并且已经在其它 advent calendar 中发布过了。 但是今天我们要看的是 MOP。 “MOP"代表着元对象协议(“Meta-Object Protocol”)。那意味着, 它们实际上是你能从用户那边改变的一部分, 而不是对象、类等定义语言的东西。 实际上, 在 Raku中, 你可以为类型添加方法, 移除某个方法, 包裹方法, 使用更多能力增强类(OO::Actors 和 OO::Monitors 就是两个这样的例子), 或者你可以完全重定义它(并且, 例如, 使用 Ruby-like 的对象系统。这儿有个例子)。 但是今天, 我们首先看一下第一部分: 自省。在类型创建完之后查看它的类型, 了解它, 并使用这些信息。 我们将要创建的模块是基于 Sixcheck 模块(一个 QuickCheck-like 模块)的需求: 为某个类型生成一些随机数据, 然后把数据喂给我们正测试的函数, 并检查某些后置条件(post-condition)。 所以, 我们先写出第一个版本: my %special-cases{Mu} = (Int) => -> { (1..50).pick }, (Str) => -> { ('a'..'z').pick(50).join('') }, ; sub generate-data(Mu:U \t) { %special-cases{t} ?? %special-cases{t}() !! t.

散列也是容器

散列也是容器 假设我们想计算某个东西的出现次数, 我们通常的做法是弄一个 “seen-hash” 散列。有时候我们有一组待查询的键, 其中有些键可能不在我们所扫描的数据中。那是一种特殊情况, 但是 Raku 能够完美地解决, 因为散列也是容器, 因此我们能够拥有默认值。 my $words = <Hashes are containers too>.lc; constant alphabet = 'a' .. 'z'; my %seen of Int is default(0); %seen{$_}++ for $words.comb; put "$_: %seen{$_}" for alphabet; 输出结果: a: 3 b: 0 c: 1 d: 0 e: 3 f: 0 g: 0 h: 2 i: 1 j: 0 k: 0 l: 0 m: 0 n: 2 o: 3 p: 0 q: 0 r: 2 s: 3 t: 2 u: 0 v: 0 w: 0 x: 0 y: 0 z: 0 $words 中没有出现的特殊字符由 is default(0) 处理了。 默认值可以被精心设计。我们来弄一个在数值上下文中为默认值为 0 但是在字符串上下文中为默认值为 NULL 并且总是被定义的一个散列。

面向对象的 Raku

https://docs.raku.org/language/objects Raku 为面向对象编程(OOP)提供强大支持。尽管 Raku 允许程序员以多种范式进行编程,但面向对象编程是该语言的核心。 Raku 带有丰富的预定义类型,可分为两类:常规类型和原生类型。所有你能存储到变量中的东西要么是一个原生的 value, 要么是一个对象。这包括字面值、类型(类型对象)、code 和容器。 原生类型用于底层类型(例如 uint64)。尽管原生类型没有和对象同样的功能,如果在其上调用方法,它们也会自动装入普通对象。 一切不是原生值的东西都是一个对象。对象确实允许继承和封装。 使用对象 要在对象上调用方法,请在对象名上添加一个点,然后添加方法名称: say "abc".uc; # OUTPUT: «ABC␤» 这将在 “abc” 上调用 uc 方法, 这是一个 Str 类型的对象。要为方法提供参数, 请在方法后面的括号内添加参数。 my $formatted-text = "Fourscore and seven years ago...".indent(8); say $formatted-text; # OUTPUT: « Fourscore and seven years ago... » $formatted-text 现在包含上面的文本,但缩进了8个空格。 多个参数由逗号分隔: my @words = "Abe", "Lincoln"; @words.push("said", $formatted-text.comb(/\w+/)); say @words; # OUTPUT: «[Abe Lincoln said (Fourscore and seven years ago)]␤» 类似地,可以通过在方法后放置冒号并使用逗号分隔参数列表来指定多个参数:

正则表达式一例

以指定音量随机播放音频文件: my %v; # hash to hold data my token filename { .+? \.\S\S\S }; # filenames end in .??? my token volume { \d+ }; # any digits for volume my regex extra { .+ \S }; # anything following that my $mixer = 'mixer'; my $player = 'mplayer -vf dsize=600:-2 -geometry +200-10 '; my $lockfile = '/tmp/myplayer'; $lockfile.IO.spurt( $*PID ); # store the process ID so other process can kill this one END { $lockfile.

怎么在 Raku 中自定义存取器

How does one write custom accessor methods in Raku? 我有一个类: class Wizard { has Int $.mana is rw; } 我可以这样做: my Wizard $gandalf .= new; $gandalf.mana = 150; 我想在不放弃使用$gandalf.mana = 150; 的情况下, 在 setter里面做一些检查。换句话说, 我不想这样写: $gandalf.setMana(150)。 如果程序尝试设置一个负值的话, 就退出。 class Wizard { has Int $!mana; method mana() is rw { return Proxy.new: FETCH => sub ($) { return $!mana }, STORE => sub ($, $mana) { die "It's over 9000!" if ($mana // 0) > 9000; $!

Raku 中的正则表达式(五)

从匹配中返回值 Match 对象 成功的匹配总是返回一个 Match 对象, 这个对象通常也被放进 $/ 中, (具名 regex, token, 或 rule 是一个子例程, 因此会声明它们自己的本地 $/ 变量, 它通常指 rule 中最近一次的 submatch, 如果有的话)。当前的匹配状态被保存到 regex 的 $¢ 变量中, 当匹配结束时它最终会被绑定到用户的 $/变量中 不成功的匹配会返回 Nil (并把 $/ 设置为 Nil, 如果匹配已经设置了 $/的话) 名义上, Match 对象包含一个布尔的成功值, 一个有序的子匹配对象(submatch objects)数组, 一个具名的子匹配对象(submatch objects)散列.(它也可选地包含一个用于创建抽象语法树(AST)的抽象对象) 为了提供访问这些各种各样值的便捷方法, Match 对象在不同上下文中求值也不同: 在布尔上下文中 Match 对象被求值为真或假 if /pattern/ {...} # 或: /pattern/; if $/ {...} 如果模式使用 :global 或 :overlap 或 :exhaustive 修饰符, 会在第一个匹配处返回布尔真值. 如果在列表上下文中求值, Match 对象会根据需要(lazily)产生剩下的结果.

When and Where

When 可以用在主题化($_)的语句中 Perl 里面有个特殊的变量叫 $_, 即主题化变量, the variable in question. > for ('Swift', 'PHP', 'Python', 'Perl') -> $item { say $item when $item ~~ /^P/ } PHP Python Perl > for (12, 24, 56, 42) {.say when *>40 } 56 42 而 where 用于对类型进行约束. > for ('Swift', 'PHP', 'Python', 'Perl', 42) -> $item where $item ~~ Str {say $item} Swift PHP Python Perl Constraint type check failed for parameter '$item' 未完待续.

Perl 自动发送邮件

#!/usr/bin/perl use strict; use warnings; sub getTime { my $time = shift || time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); $year += 1900; $mon ++; $min = '0'.$min if length($min) < 2; $sec = '0'.$sec if length($sec) < 2; $mon = '0'.$mon if length($mon) < 2; $mday = '0'.$mday if length($mday) < 2; $hour = '0'.$hour if length($hour) < 2; my $weekday = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$wday]; return { 'second' => $sec, 'minute' => $min, 'hour' => $hour, 'day' => $mday, 'month' => $mon, 'year' => $year, 'weekNo' => $wday, 'wday' => $weekday, 'yday' => $yday, 'date' => "$year-$mon-$mday" }; } my $date = getTime(); my $today = $date->{date}; # 获取xxxx-xx-xx这样的日期 #my $month = $date->{month}; # 获取月 #my $day = $date->{day}; # 获取日 #my $year = $date->{year}; # 获取年 #my $weekday = $date->{wday}; # 获取星期 my $yesterday = getTime(time() - 86400)->{date}; # 获取昨天的日期,也可以用 86400*N,获取N天前的日期 # or using the eval{ $obj->Method()->Method()->.

用字符画美化一下命令提示符窗口

用字符画美化一下命令提示符窗口 一直以为prompt就只能显示单行字符串,但$_代表换行, 所以可以使用【字符画】来美化一下单调的命令提示符窗口: #!usr/bin/perl use strict; use warnings; my $prompt=""; while(<DATA>){ s/&/\$A/g; # &符号替换为$A s/\|/\$B/g; # 管道符号替换为$B s/\(/\$C/g; # 左括号替换为$C s/\)/\$F/g; # 右括号替换为$F s/>/\$G/g; # 大于号替换为$G s/</\$L/g; # 小于号替换为$L s/=/\$Q/g; # 等号替换为$Q s/ /\$S/g; # 空格替换为$S s/ /\$S\$S/g; # 中文空格替换为两个英文空格 s/\n/\$_/g; # 换行符替换为$_ s/\^/^^/g; # 单个^替换为2个^^ $prompt .= $_; } system("cmd /k prompt $prompt"); # 直接用system(prompt $prompt)不行 __DATA__ \. - - . ' \ 情 _ , -`. ' \ _,' _,' ' ,-' _/ ' 爱 ,-' \ _/ ' ,' \ _' ' ' _\ ' , _,-' \ \,_,--' \ ——————————————————————————————————————————

使用IE浏览器进行Web应用自动化

Win32::IEAutomation –使用IE浏览器进行Web应用自动化 翻译:sxw Email:sxw2k@sian.com Date:2012-3-23 概要 use Win32::IEAutomation; #创建新的Internet Explorer实例 my $ie = Win32::IEAutomation->new( visible => 1, maximize => 1); # 网站导航 $ie->gotoURL('http://www.google.com'); # 查找超链接并点击 # 使用'linktext:'选项 (网页上显示的链接的文字) $ie->getLink('linktext:', "About Google")->Click; # 或使用'linktext:' 带有正则表达式匹配的选项 $ie->getLink('linktext:', qr/About Google/)->Click; #或使用 'id:' 选项 ( <a id=1a class=q href=......>) $ie->getLink('id:', "1a")->Click; #查找复选框并选择 #使用 'name:'选项 ( <input type = "checkbox" name = "checkme" value = "1"> ) $ie->getCheckbox('name:', "checkme")->Select; # 或使用'aftertext:' option (网页上在某些文字后的复选框for checkbox after some text on the web page) $ie->getCheckbox('aftertext:', "some text here")->Select; #查找文本域并输入数据 #使用 'name:'选项 ( <input type="text" name="username" .

Perl 逐行显示动画

使用 Perl 来完成一则文字逐字显示的动画。 use Win32::Console; use Encode; use 5.010; use AnyEvent; use Win32::Console::ANSI; use Term::ANSIColor; my @color = qw( red green yellow blue magenta cyan white bright_black bright_red bright_green bright_yellow bright_blue bright_magenta bright_cyan bright_white ansi0); #my @color= map {'ansi'.$_} (0..15); $|=1; #必须开启这个 system("mode con cols=135 lines=25"); my $Out = new Win32::Console(STD_OUTPUT_HANDLE) || die; my $cv = AnyEvent->condvar; my $count=0; my $w; $w = AnyEvent->timer( after => 2, interval => 1, cb => sub { $count++; my ( $x, $y ) = $Out->Cursor(); $Out->Cursor( $x+125, $y + 5,0,0); while (<DATA>) { s/ / /g; chomp; $a=decode('gb2312',$_); @words=$a=~m/(.

Excel::Writer::XLSX 翻译

---------------------------------------------------------------------------- 翻译: 小Perl Email:sxw2k@sina.com ---------------------------------------------------------------------------- NAME 名称 Excel::Writer::XLSX - 以Excel2007+XLSX格式创建一个新文件. VERSION 版本 该文档是在2013年11月发布的Excel::Writer::XLSX 0.75版本。 SYNOPSIS 概要 在perl.xlsx的第一个工作表中写入字符串、格式化的字符串、数字和公式: use Excel::Writer::XLSX; # 新建excel工作簿 my $workbook = Excel::Writer::XLSX->new( 'perl.xlsx' ); # 增加一个工作表 $worksheet = $workbook->add_worksheet(); # 添加并定义一个格式 $format = $workbook->add_format();#增加一种格式 $format->set_bold(); #设置粗体 $format->set_color( 'red' ); #设置颜色 $format->set_align( 'center' ); #设置对齐方式(此处为居中) #写入一个格式化和非格式化的字符串,使用行列表示法。 $col = $row = 0; #设置行和列的位置 $worksheet->write( $row, $col, 'Hi Excel!', $format ); $worksheet->write( 1, $col, 'Hi Excel!' ); #使用A1表示法写入一个数字和公式 $worksheet->write( 'A3', 1.

Excel::Writer::XLSX 用法一例

use 5.010; use strict; # use warnings; use Spreadsheet::XLSX; use Excel::Writer::XLSX; use MyExcelFormatter; use Encode; use HTML::TokeParser; use Data::Dumper; use Mojo::UserAgent; use Mojo::UserAgent::CookieJar; use Mojo::UserAgent::Proxy; use YAML 'Dump'; use Win32::API; #获取当天的日期,作为后面 Excel 的行首 sub getTime { my $time = shift || time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); $year += 1900; $mon ++; $min = '0'.$min if length($min) < 2; $sec = '0'.$sec if length($sec) < 2; $mon = '0'.$mon if length($mon) < 2; $mday = '0'.

AnyEvent 的用法一例

use strict; use warnings; use 5.010; use Mojo::UserAgent; use Mojo::UserAgent::CookieJar; use Mojo::UserAgent::Proxy; use Win32::API; use MyExcelFormatter; use Encode; use AnyEvent; use Excel::Writer::XLSX; sub H{ my $text = shift; return decode('utf8',$text); # 进行转码 } sub getTime { my $time = shift || time(); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($time); $year += 1900; $mon ++; $min = '0'.$min if length($min) < 2; $sec = '0'.$sec if length($sec) < 2; $mon = '0'.$mon if length($mon) < 2; $mday = '0'.

ImageMagick - 在批处理中使用 ImageMagick

在批处理中,如果需要把一个命令的处理结果赋值给一个变量,可以使用如下方法: for /f "delims=" %%t in ('命令字符串') do set str=%%t echo %str% 当命令字符串中含有%时,需要转义。使用imagemagick中的identify命令获取图片的宽和高: identify -format %wx%h demo.jpg 直接在命令行中使用上述命令可以看到输出了图片的高和宽,但是用它放在批处理脚本中来获取命令的输出结果,则失败,原因是命令字符串中含有%符号,它在批处理有特殊含义,所有需要转义,转义的方法是使用两个%%,正确的做法是: for /f "delims=" %%t in ('identify -format %%wx%%h demo.jpg') set str=%%t 为什么用2个%号? 因为这是在批处理脚本中而非命令行下!

ImageMagick - 给图片瘦身

使用ImageMagick给图片瘦身 影响图片大小(占用空间)主要取决于图片的profile和quality。 quality:图片的品质,品质越高,占用的空间越大。适当降低品质能很大程度的减少图片的尺寸。一般来说,从品质100降到85,基本上肉眼很难区别其差别,但尺寸上减少很大。imagemagick通过通过 -quality 来设置。 profile:记录图片一些描述信息。例如相机信息(光圈,相机型号)、photoshop元数据,颜色表等信息。它占用的空间可以从几KB到几百KB,甚至可能更大。ImageMagicK可以通过两种方式来去掉这些信息。+profile “*” 或 -strip 下述图片中第一张原始图片为56KB,第二张图片执行了 convert +profile “*” -strip src.jpg src-profile.jpg 后变成了26.3KB, 第三张设置图片品质为85,convert -quality 85 src.jpg src-quality85.jpg,图片大小变成了19.5KB,第四张是同时使用去掉profile和设置品质为85, convert -quality 85 -strip src.jpg src-p-q85.jpg,图片只有18.7KB。经过一个简单的命令处理,就可以把原始图片体积减小到原来的的三分之一。一般来说jpg格式的图片有比较大的操作空间,而png、gif有时候处理了反而变大。所以具体问题需要具体分析。 在linux下可以很方便把某个目录下的所有jpg文件来一次瘦身运动,例如命令 find /tmp/images -iname “*.jpg” -exec convert -strip +profile “*” -quality 85 {} {} \; 可以把 /tmp/images目录下所有jpg图片进行压缩

ImageMagick - 批量去水印

安装完ImageMagick后记得更新Path环境变量 # 用ImageMagick批量去水印 @echo off mogrify -path g:\ -sigmoidal-contrast 10,20% *.jpg pause 除去水印后的图片被放置在G盘根目录了,如果不指定路径,就会在当前文件夹进行操作,并且直接修改原图像,所以要记得备份。

ImageMagick - Vintage 效果

@echo off set layer1Color="#3066FF" ::#C0FFFF "#000699" set layer2Color="#000699" set layer1Alpha=180 set layer2Alpha=180 convert %1 -fill %layer1Color% -colorize 100% layer1.png convert layer1.png -alpha on -channel A -evaluate Set %layer1Alpha% layer1.png convert %1 -fill %layer2Color% -colorize 100% layer2.png convert layer2.png -alpha on -channel A -evaluate Set %layer2Alpha% layer2.png convert %1 layer1.png -compose softlight -composite temp.png convert temp.png layer2.png -compose exclusion -composite result.png convert result.png -background black -vignette 0x65000 result.png del layer1.png del layer2.

ImageMagick - The Definitive Guide To ImageMagick

ImageMagick学习之 The Definitive Guide To ImageMagick convert -contrast -contrast -contrast -contrast in.jpg out.png # 增强对比,contrast选项可以有多个 convert -append zx.jpg modulate.jpg modulate145.jpg append.jpg # 垂直排列 convert -append zx.jpg modulate.jpg modulate145.jpg -border 5 append-border.jpg # 垂直排列后加上边框 The Definitive Guide To ImageMagick之添加边框 给图像添加边框 A commonly requested operation that ImageMagick can help you with is creating borders on images. To make simple, single-colored borders, you just use the border command-line option. This option takes two arguments—the horizontal width of the border and the vertical height of the border.

ImageMagick - nconvert 命令

Usage : nconvert [options] file … Options : -quiet : 不显示该软件的信息 -info : 只显示信息 -fullinfo : 显示全部信息包括该软件的信息 -v[.] : 冗长模式 -in format : 输入图片格式如jpg,bmp -page num : 页数/页码 -xall : 提取所有图像 -multi : 创建多页(只适用tiff,dcx,ldf) -npcd num : PCD 0:192x128, 1:384x256, 2:768x512 (default) pcd格式 -ngrb npic : HP-48 number of grey : 1, 2 or 4 (default : 1) 灰度:数值 -no# : # not used for numeric operator -clipboard : 从剪切板导入 -ctype type : 通道类型 (Raw) grey : 灰度(默认) rgb : 红,绿,蓝 bgr : 蓝,绿,红 rgba : 红,绿,蓝,透明 abgr : 透明,蓝,绿,红 cmy : 青紫,洋红,黄色 cmyk : 青紫,洋红,黄色,黑色 -corder order : 通道排列(Raw) inter : 交错的(default) seq : 连续的 sep : 独立的 -size geometry : 宽和高(Raw/YUV) 几何是高*宽+偏移 -i file : 使用文件作为文件列表 => nconvert -i test.

ImageMagick - 合并图像

ImageMagick操作之合并图像 本帖介绍了ImageMagick软件合并图像的常用操作,所有命令均在Windows命令行环境下进行测试通过。 背景图片background.png 顶层图片overlay.png 背景图片background.png 顶层图片overlay.png 1、图像合并基本操作(覆盖) 将一张图片覆盖到另一张图片的指定位置。 指定操作方法(over)、顶层图片尺寸及坐标位置(100x100+0+0)。 convert background.png -compose over overlay.png -geometry 100x100+0+0 -composite new.png 2、图像合并基本操作(异或) 两图相交处变为无色,不相交处保持不变。 指定操作方法(xor)、顶层图片尺寸及坐标位置(100x100+0+0)。 convert background.png -compose xor overlay.png -geometry 100x100+0+0 -composite new.png 3、图像合并基本操作(in) 顶层图片与背景图片交汇处不变,未交汇处变为无色,背景图片不显示。 指定操作方法(in)、顶层图片尺寸及坐标位置(100x100+0+0)。 convert background.png -compose in overlay.png -geometry 100x100+0+0 -composite new.png 4、图像合并基本操作(out) 顶层图片与背景图片交汇处变为无色,未交汇处不变,背景图片不显示。 指定操作方法(out)、顶层图片尺寸及坐标位置(100x100+0+0)。 convert background.png -compose out overlay.png -geometry 100x100+0+0 -composite new.png 5、图像合并基本操作(atop) 顶层图片与背景图片交汇处不变,未交汇处变为无色,背景图片显示。 指定操作方法(atop)、顶层图片尺寸及坐标位置(100x100+0+0)。 convert background.png -compose atop overlay.png -geometry 100x100+0+0 -composite new.png 6、图像合并基本操作(dstover) 顶层图片与背景图片交汇处变为无色,未交汇处不变,背景图片显示。 指定操作方法(dstover)、顶层图片尺寸及坐标位置(100x100+0+0)。 convert background.

ImageMagick - lomo 效果

@echo off set layer1Color="#3066FF" ::#C0FFFF "#000699" set layer2Color="#000699" set layer1Alpha=180 set layer2Alpha=180 convert %1 -fill %layer1Color% -colorize 100% layer1.png convert layer1.png -alpha on -channel A -evaluate Set %layer1Alpha% layer1.png convert %1 -fill %layer2Color% -colorize 100% layer2.png convert layer2.png -alpha on -channel A -evaluate Set %layer2Alpha% layer2.png convert %1 layer1.png -compose softlight -composite temp.png convert temp.png layer2.png -compose exclusion -composite result.png convert result.png -background black -vignette 0x65000 result.png del layer1.png del layer2.

ImageMagick - 动态图像生成

ImageMagick操作之动态图像生成 1、简单gif图片制作(10_1.gif) 图片依次出现,不消失。 指定每帧图片间隔时间(100)、处理方法(none)、画布尺寸(150x50)、画布颜色(rgb(202,225,255))、在指定坐标位置添加图片(例+0+10 1.gif)、循环次数(0表示无限循环)。 convert -delay 100 -dispose none -size 150x50 xc:"rgb(202,225,255)" -page +0+10 1.gif -page +40+10 2.gif -page +80+10 3.gif -page +120+10 4.gif -loop 0 10_1.gif 2、简单gif图片制作(10_2.gif) 图片依次出现,并消失。 指定画布处理方法(none)、画布尺寸(150x50)、画布颜色(rgb(202,225,255))、每帧图片间隔时间(100)、图片处理方法(previous)、在指定坐标位置添加图片(例+0+10 1.gif)、循环次数(0表示无限循环)。 convert -dispose none -size 150x50 xc:"rgb(202,225,255)" -delay 100 -dispose previous -page +0+10 1.gif -page +40+10 2.gif -page +80+10 3.gif -page +120+10 4.gif -loop 0 10_2.gif 3、简单gif图片制作(10_3.gif) 图片依次出现,并消失,并且清除当前图片占据位置的所有颜色。 指定画布处理方法(none)、画布尺寸(150x50)、画布颜色(rgb(202,225,255))、每帧图片间隔时间(100)、图片处理方法(background)、在指定坐标位置添加图片(例+0+10 1.gif)、循环次数(0表示无限循环)。 convert -dispose none -size 150x50 xc:"rgb(202,225,255)" -delay 100 -dispose background -page +0+10 1.

ImageMagick - draw

ImageMagick的convert参数draw笔记 convert 通过 draw 可以在已有的图片上绘制一些文字、线条、形状等。可能会用 text、line、rectangle、roundRectangle、circle、ellipse 等,下面逐一来说明。 text 把一段文本绘制到已有图片上。 基本命令格式: convert 图片 -draw "text x坐标,y坐标 '文本信息'" 结果图片 另外还可有一些参数是可以设置的,比如字体,字体大小,字体颜色已经重新定义坐标原点。 convert -size 512x50 xc:gray -gravity southeast -fill red -pointsize 24 -draw "text 20,5 'hello netingcn.com!'" txt.png 上述命令中使用gravity来重新定义坐标,关于gravity的更多信息,请参考ImageMagicK之gravity参数详解,-fill 设置文字的颜色,颜色能够使用类似 #ff00ff 的方式,但是需要用双引号引起来,-pointsize 24 设置文字的大小, -draw 后面引号中第一个单词是 text,表明是在图片上绘制一个设置的文本,20是 x 坐标,5 则是 y 坐标,hello netingcn.com! 是绘制到图片上的文本信息,如果文本信息中含有空格,需要用单引号引起来,没有空格也可以用单引号,所以建议总是使用单引号。不过遗憾的是不能把中文通过这种方式绘制到图片,要把中文加在图片是,需要另外的方法。结果如下图: line 在图片上绘制一条直线,基本命令格式: convert 图片 -draw "line 起点x坐标,起点y坐标 终点x坐标,终点y坐标" 结果图片 可以使用的参数有-stroke 来指定线条的颜色,-strokewidth 指定线的宽度。 convert -size 512x50 xc:gray -stroke white -strokewidth 4 -draw "line 10,25 500,25" line.

ImageMagick - 学习笔记

欢迎共同研究ImageMagick Email:sxw2k@sina.com sxw 2011年暑假 ImageMagick学习笔记 注意:ImageMagick对中文支持不好,文件(夹)一定不要含中文,否则会报错!切记! -开启开关 +关闭开关,恢复从前。 convert convert顾名思义就是对图像进行转化,它主要用来对图像进行格式的转化,同时还可以做缩放、剪切、模糊、反转等操作。 格式转化 比如把 foo.jpg 转化为 foo.png:转换不会破坏原图像。 convert foo.jpg foo.png Mogrify mogrify -format png *.jpg 将所有jpg文件转换成png格式,不破坏源文件(相当于批量转换了) 注意: mogrify -format png *.gif 会把gif文件的每一帧都转化成png文件,会生成很多文件 convert还可以把多张照片转化成pdf格式: convert *.jpg foo.pdf convert test.gif test.jpg 会生成很多以test开头的图片文件 =>大小缩放 为一个普通大小的图片做一个缩略图 convert -resize 100x100 foo.jpg thumbnail.jpg 也可以用百分比,更为直观: convert -resize 50%x50% foo.jpg thumbnail.jpg (50%x50%是字母x) convert会自动地考虑在缩放图像大小时图像的高宽的比例,也就是说新的图像的高宽比与原图相同。 批量生成缩略图: mogrify -sample 80x60 *.jpg 注意,这个命令会覆盖原来的图片,不过你可以在操作前,先把你的图片备份一下。 convert -sample 25%x25% input.jpg output.jpg 等比列缩放 =>加边框 在一张照片的四周加上边框,可以用 -mattecolor 参数, convert -mattecolor "#000000" -frame 60x60 yourname.

ImageMagick - 图片部分负片处理

图片部分负片处理 通过指定-region 选项,可以对指定的区域进行反色处理: @echo off convert -region 300x400+60+50 -negate %1 negate-part.png 部分区域反色 注意:regoin参数需要在negate前,否则无法对指定的区域进行反色处理。 > negate.bat 1.jpg @echo off convert -gravity center -region 300x400+10+10 -negate %1 center-negate_%1.png

ImageMagick - 图片高级操作

ImageMagick操作之图片高级操作 1、图片指定区域变色 指定区域尺寸和起始坐标(50x60+20+10)、区域颜色(rgb(255,0,0))、着色程度(20%)。 convert old.png -region 50x60+20+10 -fill "rgb(255,0,0)" -colorize 20% new.png convert 2.jpg -region 150x100+20+10 -fill "rgb(255,0,0)" -colorize 20% new.png 2、图片指定区域变色(放大或缩小区域) 指定区域尺寸和起始坐标(50x60+20+10)、尺寸(120%)、区域颜色(rgb(255,0,0))、着色程度(20%)。 convert old.png -region 50x60+20+10 -resize 120% -fill "rgb(255,0,0)" -colorize 20% new.png 3、替换相同颜色的区域(指定颜色) 指定颜色差异程度(10%),替换颜色(黑色),被替换颜色(白色)。将整张图片中的指定颜色全部替换。 convert 2.jpg -alpha set -channel RGBA -fuzz 10% -fill "rgb(0,0,0)" -opaque "rgb(255,255,255)" new.png 4、替换不同颜色的区域(指定颜色) 指定颜色差异程度(50%),替换颜色(白色),指定颜色(蓝色)。将整张图片中除指定颜色外的颜色全部替换。 convert 2.jpg -alpha set -channel RGBA -fuzz 50% -fill "rgb(255,255,255)" +opaque "rgb(0,0,255)" new.png 5、替换相同颜色的区域(指定坐标) 指定颜色差异程度(10%),替换颜色(红色),指定坐标(180,150)。将整张图片中与指定位置颜色相同的颜色全部替换。 convert 2.jpg -alpha set -channel RGBA -fuzz 10% -fill "rgb(255,0,0)" -draw "color 180,150 replace" new.

ImageMagick - 合并图像

ImageMagick之合并图像 ImageMagick操作–图片连接操作 1、图片连接 指定待连接图片(按顺序)、单张图片旋转角度(0)、画布颜色(rgb(0,0,255))、较小图片的对齐方式(center)、连接后图片旋转角度(0)。 convert bee.png 2.png -rotate 0 -background "rgb(0,0,255)" -gravity center -append -rotate -0 new.png 若需横向连接,通常单张图片旋转角度(90)、连接后图片旋转角度(90)。 convert 1.jpg 2.jpg -rotate 90 -background "rgb(0,0,255)" -gravity center -append -rotate -90 new.png 2、多张图片连接 指定待连接图片(按顺序)、列数和行数(3x2)、单张小图片尺寸(20x15!)、边框宽度(+2+4)、画布颜色(rgb(0,0,255))。注:!表示强制调整为指定尺寸,如果不加则不会改变原图比例。 montage 1.jpg 2.jpg 3.jpg 4.jpg -tile 3x2 -geometry 240x320!+2+4 -background "rgb(0,0,255)" new.gif 3、多张图片连接(添加说明文字) 指定文字尺寸(15)、字体(Forte)、文字颜色(rgb(0,0,0))、每张图片的文字内容、待连接图片(按顺序)、边框宽度(5)、列数和行数(3x2)、单张小图片尺寸(20x15!)、边框宽度(+2+4)、画布颜色(rgb(0,0,255))。 montage -pointsize 15 -fill "rgb(0,0,0)" -label "1.jpg" 1.jpg -label " " 2.jpg -label "" 3.jpg -label "I'm 4.jpg" 4.jpg -frame 5 -tile 3x2 -geometry 150x240!

ImageMagick - 合成图片

ImageMagicK之合成图片 原文地址:http://www.netingcn.com/imagemagick-composite.html ImageMagicK能方便的把多张小图片合成一张大图片。合成的方式大致有三种, 使用convert命令加 +append或-append参数 使用convert命令加 -composite参数 直接使用composite命令来完成 其中方式1处理图片只能左右或上下来拼接图片,方式2最为灵活,可以一次性把多张图片合成在一起,方式3处理多张图片时需要一张一张来处理。下面的例子是把google地图中的4个256×256块合成一张512×512的大图,原始图片如下: d0 d1 u0 u1 使用方式1 convert +append u0.png u1.png u.png convert +append d0.png d1.png d.png convert -append u.png d.png dest.png 说明:其中 +append 横向把多张图片拼接在一起,可以多于2张,图片按上边缘对齐,最后一个参数是目标图片,而-append是纵向拼接图片,图片按左边缘对齐。 使用方式2 convert -size 512x512 -strip -colors 8 -depth 8 xc:none u0.png -geometry +0+0 -composite u1.png -geometry +256+0 -composite d0.png -geometry +0+256 -composite d1.png -geometry +256+256 -composite dest4.png 说明:convert -size 512×512 xc:none 创建一张空白图片,然后把小图片合成到其上面。合成的命令大致为:convert 背景图片 图片 定义坐标原点 图片的位置 -composite 目标图片,其中“图片 定义坐标原点 图片的位置 -composite” 可以重复,从而把多张图片一次性合成到背景图片上。使用 -gravity 定义坐标原点,默认是左上角,可以用east,north,northwest等来重新定义原点,-geometry相对于原点的位置

ImageMagick - 制作 gif 图片

ImageMagick之制作gif图片 gif动画由一系列图片按照一定的时间间隔来播放的,每张单独的图片作为gif动画的一帧。使用ImageMagicK的convert命令很容易获取gif动画中的每桢图片,例如命令 convert exam.gif p.png 就会把生产 p-0.png,p-1.png 等一系列图片。gif动画的原理就是把一些列动画合成在一起。所以通过convert很容易做到。例如命令 convert *.jpg dest.gif 是把当前目录下的所有 jpg格式的图片生成一个名为dest.gif的文件,有两个参数比较重要,分别是-delay和-loop,其中-delay是控制每桢的切换时间,-loop是控制gif动画的播放次数,默认是0,0表示无尽的循环播放。 如果想控制每桢的间隔时间不一致,可以使用类似 convert -delay 50 0.jpg 1.jpg -delay 100 2.jpg 3.jpg 4.jpg dest.gif 这样的操作来完成,甚至可以先制作两个临时的gif,然后把临时的gif合成自己想要的,例如 convert -delay 50 0.jpg 1.jpg t1.gif convert -delay 100 2.jpg 3.jpg 4.jpg t2.gif convert t1.gif t2.gif dest.gif 通过上述的素材加一个logo图片制作一个如下的gif动画 convert -size 84x200 xc:"#f396eb" bg.gif convert bg.gif logo.gif -geometry +2+2 -composite bg.gif convert bg.gif 0.jpg -geometry +2+55 -composite 0.png convert bg.gif 1.jpg -geometry +2+55 -composite 1.png convert bg.

ImageMagick - HDR 效果

@echo off convert %1 ( %1 -blur 3x3 ) -compose overlay -composite output.png REM 上述命令完成了如下工作: REM 1:将输入图像进行模糊。 REM 2:将模糊之后的图片以overlay的方式,叠加到输入图像上去。

ImageMagick - gravity 参数详解

ImageMagick之gravity参数详解 使用gravity重新定义坐标后,可以很容易让子元素与父元素的对齐方式达到想要的效果,让一切变得非常简单。比如把一张小图片叠加到背景图片的正中位置,按照默认的坐标系统,那必须要先知道背景图片和小图片的宽度以及高度,然后才能计算出起始点的坐标,再通过-geometry来设置坐标点。 如果使用gravity,把其设置center,即把中心作为坐标的原点,那么根本不需要计算起始坐标点,ImageMagicK会自动把小图片放置在背景的正中央位置,-geometry 默认是+0+0。gravity不仅影响父元素的坐标系统,而且子元素的重心点(或者叫参照点)也随之改变。 举例来说,当gravity值为southeast,父元素的坐标原点变为右下角了,x轴方向是从右到左,y轴方向从下到上;子元素重心点也是右下角,所以geometry设置的坐标点就是子元素的右下角相对父元素右下角的位置。 gravity会影响通过geometry、annotate、region等来定义的坐标点。 gravity可用值有九个,分别是: NorthWest:左上角为坐标原点,x轴从左到右,y轴从上到下,也是默认值。 convert -size 400x120 xc:gray -size 100x50 xc:blue -gravity northwest -geometry +10+10 -composite -size 100x50 xc:yellow -gravity northwest -geometry +110+60 -composite -gravity northwest -fill red -pointsize 24 -draw "text 110,60 'hello netingcn.com!'" nw.png 说明:创建一个灰色的400×120的背景,分别把两个100×50的小图片放置在背景上的(10,10)和(110,60)的位置,同时通过draw在图片输入一段文本,小图片和文本的参照点是左上角,效果如下图。 North:上部中间位置为坐标原点,x轴从左到右,y轴从上到下。 convert -size 400x120 xc:gray -size 100x50 xc:blue -gravity north -geometry +10+10 -composite -size 100x50 xc:yellow -gravity north -geometry +110+60 -composite -fill red -pointsize 24 -gravity north -draw "text 0,60 'hello netingcn.

ImageMagick - 入门教程

ImageMagick的功能简介: 原文地址 ,由BBDD完成。 1、批量旋转、分割,并顺序编号,一步到位 2、自动批量切除白边(auto-crop) 3、自动倾斜校正(deskew) 4、批量加标注 5、批量去标注 6、批量加水印 7、批量去水印 1、将 a.gif 转为 png 格式 convert a.gif a.png 请注意,convert 命令的基本格式为 convert 源文件 [参数] 目标文件 在上面的命令中,源文件是 a.gif,目标文件是 a.png。由于这是最简单的格式转换,所以不需要中间的参数。 convert 常用于单个文件的转换。上面的命令是它最基本的用法。 前面说过 IM 支持超过 100 种的文件格式。 下面的命令,可以列出 IM 所支持的所有格式: identify -list format 2、批量文件的格式转换 mogrify -path newdir -format png *.gif 这个命令的作用,是将当前目录下的所有 gif 文件,转换为 png 格式,并将其存放在 newdir 目录下。 mogrify 是用于批量处理文件的命令。它的基本格式是这样的: mogrify 参数 源文件 mogrify 支持基本的通配符,例如你可以用 a*.png 指代所有以 a 打头的 png 文件,诸如此类。 再回到刚才的命令: mogrify -path newdir -format png *.

ImageMagick - 命令行处理

ImageMagick 命令行处理 ImageMagic命令行能像这样简单: convert image.jpg image.png 或者它很复杂,就像下面的: convert label.gif +matte \ \( +clone -shade 110x90 -normalize -negate +clone -compose Plus -composite \) \ \( -clone 0 -shade 110x50 -normalize -channel BG -fx 0 +channel -matte \) \ -delete 0 +swap -compose Multiply -composite button.gif 不用知道太多ImageMagick命令行,你可能认为上面的第一个命令是转换一个JPEG格式的图像为Png格式。然而,很少有人知道第二个命令,它更加复杂,用丰富的质感和模拟的深度使一个色彩单调的二位符号变成三维 [命令格式快速浏览:上面第二个命令那个太长了,以至于跨越了好几行,所以我们为了清晰,插入反斜线()来格式化命令行。反斜线在unix中是续行符。在windows shell中,使用^符号作为续行符。在这些网页中,我们使用Unix风格,就像上面的。然而,有时,如果你的浏览器窗口太小了,命令行被浏览器隐藏了,但是在空白处的命令行,依然会被作为一行打印。续行符不需要被输入了。上面使用反斜线转义的的圆括号在Windows中不转义。在Unix和Windows之间有一些其他的不同之处(包括引号标记,比如),但是我们将在它们出现时再讨论。] 此处我们以一个带有阴影的圆柱展示一个任务的完成百分比: 考虑到示意图的复杂性,你可能为它能用单一的命令行完成感到吃惊: convert -size 320x90 canvas:none -stroke snow4 -size 1x90 -tile gradient:white-snow4 \ -draw 'roundrectangle 16, 5, 304, 85 20,40' +tile -fill snow \ -draw 'roundrectangle 264, 5, 304, 85 20,40' -tile gradient:chartreuse-green \ -draw 'roundrectangle 16, 5, 180, 85 20,40' -tile gradient:chartreuse1-chartreuse3 \ -draw 'roundrectangle 140, 5, 180, 85 20,40' +tile -fill none \ -draw 'roundrectangle 264, 5, 304, 85 20,40' -strokewidth 2 \ -draw 'roundrectangle 16, 5, 304, 85 20,40' \( +clone -background snow4 \ -shadow 80x3+3+3 \) +swap -background none -layers merge \( +size -font Helvetica \ -pointsize 90 -strokewidth 1 -fill red label:'50 %' -trim +repage \( +clone \ -background firebrick3 -shadow 80x3+3+3 \) +swap -background none -layers merge \) \ -insert 0 -gravity center -append -background white -gravity center -extent 320x200 \ cylinder_shaded.

ImageMagick - 图片缩放

ImageMagick之图片缩放 利用ImageMagicK的convert命令,能很方便的实现图片的放大缩小,可以进行等比例缩放,也能缩放到指定的固定大小。缩放的参数resize,由它来指定缩放后图片的宽高,比如“200×100”。 等比缩放 例如把图片a.jpg缩放到200×100的尺寸,可以用命令: convert -resize 200x100 src.jpg dest.jpg 注意:虽然明确指定了图片大小为200×100,但dest.jpg的不一定就是200×100,因为是等比缩放的,dest.jpg大小取决原始图片比例。假设src.jpg的大小是500×200,那么缩放后dest.jpg的真实大小为200×80,再比如src.jpg的大小是300×200,缩放后的尺寸为150×100。原则是缩放后的尺寸最少有一个是符合宽或高,且另外一个不能大于指定的参数中对应的宽或高。另外可以通过只指定宽或高的方式来进行缩放。例如: convert -resize 200 src.jpg dest.jpg 得到图片宽为200,高根据原始图片比例计算而来 convert -resize x100 src.jpg dest.jpg 得到的图片高为100,宽根据原始图片比例计算而来 固定宽高缩放 即不考虑原是图宽高的比例,把图片缩放到指定大小。例如命令: convert -resize 200x100! src.jpg dest.jpg 说明:区别是宽高后面多了一个叹号,此时不管原图片比例如何,缩放后的图片大小都是200×100,这样就可能导致图片变形。注意:在linux环境对参数需要用单引号引起来,而windows下又不能使用单引号。 有条件缩放 可以通过>或<符号来控制原始图片是否进行缩放,例如在处理一批尺寸大小各异的图片,只想在尺寸大于给定值的情况下,图片才进行缩放,如果没有指定条件,可能会把那些小的图片进行了放大处理。 convert -resize "200x100>" src.jpg dest.jpg 注解:只有当src.jpg的宽大于200或高大于100时候,才进行缩小处理,否则生成的dest.jpg和src.jpg具有一样的尺寸。注意在linux下要用单引号替换双引号,即'200x100>’。 convert -resize "200x100<" src.jpg dest.jpg 注解:只有当src.jpg的宽小于200或高小于100时候,才进行放大处理,否则生成的dest.jpg和src.jpg具有一样的尺寸。注意在linux下要用单引号替换双引号,即'200x100<' 上述两种有条件缩放是按原始图等比例缩放的,也就是对符合条件的图片进行等比缩放。同时有条件缩放也可以与固定大小缩放联合起来用。例如如下命令: convert -resize "800x100>!" src.jpg dest.jpg 注解:假设src.jpg尺寸是300x200。很显然src.jpg的宽(200)是大于指定值宽(100),符合缩小的条件,由于执行的不是等比缩放,所以dest.jpg的尺寸理论上是800x100,但是800是超过原始图片宽的,故dest.jpg的宽只能是300 convert -resize "10x1000<!" src.jpg dest.jpg 注解:假设src.jpg尺寸是300x200,src.jpg的宽(200)小于指定值宽(1000),因此该命令将执行放大图片操作,dest.jpg的高将放到到1000,由于宽比原始图片还小,就用原始图片的宽,所以得到的dest.jpg的尺寸是300x1000。

ImageMagick - 图片裁剪详解

ImageMagick之图片裁剪详解 imagemagick 的 convert 命令通过 crop 参数,可以把一幅大图片分成若干块大小一样的图片,同时也可以在大图上截取一块图片来。命令格式为 convert 原始图片 -crop widthxheight+x+y 目标图片 其中 widthxheight 是目标图片的尺寸,+x+y 是原始图片的坐标,这两组值至少要出现一组,也可以同时存在。另外该命令也可使用 gravity来重新定义坐标系统。关于更多 gravity 的信息,请参考:ImageMagicK 之 gravity 参数详解。下面介绍几种常用的命令。 把原始图片分割成多张小图 (可能是同样大小) convert src.jpg -crop 100x100 dest.jpg 假设src.jpg的大小是300x200,执行命令后将得到名为dest-0.jpg、dest-1.jpg…dest-5.jpg的6张大小为100x100的小图片。注意如果尺寸不是目标图片的整数倍,那么右边缘和下边缘的一部分图片就用实际尺寸 在原始图片上剪裁一张指定尺寸的小图 convert src.jpg -crop 100x80+50+30 dest.jpg 在原始图片的上,距离顶部30像素、距左侧50像素为起点的位置,分别向左向下截取一块大小为100x80的图片。如果x相对于坐标,宽度不够100,那就取实际值。 convert src.jpg -gravity center -crop 100x80+0+0 dest.jpg 在原始图上截取中心部分一块100x80的图片 convert src.jpg -gravity southeast -crop 100x80+10+5 dest.jpg 在原始图上截取右下角距离下边缘10个像素,右边缘5个像素一块100x80大小的图片

ImageMagick - 创建缩略图和帧

ImageMagick v6 Examples –创建缩略图和帧 几种存储方式: 第一种方式可以使用mogrify,不会破坏源图像,需指定-format选项 第二种方式,指定-path选项也可以使用mogrify进行批量处理 eg:mkdir thumbs mogrify -format gif -path thumbs -thumbnail 100x100 *.jpg 选择输出格式 JPEG 对于大文件很好,对于缩略图效果不好,有失真。非要使用这,建议加上"-quality 百分比"参数。 使用 “-sampling-factor 2x1"也会产生小的图像尺寸。 GIF 对于小图片效果好。256种颜色。只有布尔(on / off)透明度。 PNG 对于缩略图很理想,无损,能显示所有的颜色。更重要的是该格式理解半透明的颜色,让阴影和边缘更强烈和清晰。对于缩略图可以通过减少颜色的深度和数量来减少最终图像的尺寸,就像设置了一个高的"bzip"压缩质量,例如 -strip -quality 95 PNG8:thumbnail.png 重要提示:不要使用JPEG,PNG8,或GIF作为中间的图像格式!最好使用PNG或MIFF。 因为png会保留图像更多的颜色信息。 Profiles, Stripping, and JPEG Handling 缩略图没必要保留图像的配置信息,可以移除: convert input.jpg -strip output.jpg 或 mogrify -strip *.jpg 也可以使用”-profile ‘*’“选项移除配置信息。 It is however recommended you only strip profiles when you modify an image, especially if reducing it in size for web displays, or thumbnail images.

ImageMagick - 老照片效果

convert %1 -sepia-tone 75%% ( %1 -fill #FFFFFF -colorize 100%% +noise Random -colorspace gray -alpha on -channel A -evaluate Set 100 ) -compose overlay -composite %2 ::使用方法:在命令提示符下输入 imagemagick-老照片效果.bat input.jpg output.jpg REM 上述命令完成如下工作: REM 1:将输入图像使用sepia-tone滤镜处理 REM 2:生成一个白色蒙版,填充随机噪声,转化为灰度,并加上alpha通道 REM 3:步骤1和步骤2的结果使用overlay的方式compose

ImageMagick - Convert 命令中文帮助

Usage: convert [options …] file [ [options …] file …] [options …] file 图像设置 -adjoin 附加,连接图像成为一个单一的多图像文件 -affine matrix 仿射矩阵, 仿射变换矩阵 -antialias 反锯齿, 移除像素走样 -authenticate value decrypt image with this password 验证 值 用此密码破译图像 -background color background color 背景 颜色 背景色 -bias value add bias when convolving an image 偏斜 值 回旋图像时增加偏斜度 -black-point-compensation 黑点补偿 use black point compensation 使用黑点补偿 -blue-primary point chromaticity blue primary point 主蓝色 染为主蓝色点 -bordercolor color border color 边框颜色 颜色 边框颜色 -caption string assign a caption to an image 标题 字符串 给图像添加一个标题 -channel type apply option to select image channels 通道 类型 应用选项选择图像通道 -colors value preferred number of colors in the image 色彩 值 图像的色彩数 -colorspace type alternate image colorspace 色彩空间 类型 交替图像色彩空间 -comment string annotate image with comment 注释 字符串 为图像添加注释 -compose operator set image composite operator -compress type type of pixel compression when writing the image 压缩 类型 写入图像时的像素压缩类型 -debug events display copious debugging information 排错 事件 显示丰富的调试信息 -define format:option define one or more image format options -delay value display the next image after pausing 延时 值 暂停一定时间后显示下张图像 -density geometry horizontal and vertical density of the image 密度 几何 图像的水平和垂直密度 -depth value image depth 深度 值 图像深度 -display server get image or font from this X server 显示 服务器 从此服务器获取图像或字体 -dispose method GIF disposal method 处理 方法 gif处理方法 -dither apply Floyd/Steinberg error diffusion to image -encoding type text encoding type 编码 类型 正文编码类型 -endian type endianness (MSB or LSB) of the image -family name render text with this font family -fill color color to use when filling a graphic primitive 填充 颜色 填充简单图表时使用的颜色 -filter type use this filter when resizing an image 滤镜 类型 调整图像大小时使用的滤镜 -font name render text with this font 字体 字体名 用此字体描绘正文 -format "string" output formatted image characteristics 样式 字符串 输出格式化的图像属性 -fuzz distance colors within this distance are considered equal 柔化 距离 在此距离之内的颜色被认为是相同的 -gravity type horizontal and vertical text placement 引力 类型 正文的水平和垂直位置 -green-primary point chromaticity green primary point -intent type type of rendering intent when managing the image color -interlace type type of image interlacing scheme -interpolate method pixel color interpolation method -label string assign a label to an image 标签 字符串 为图像分配一个标签 -limit type value pixel cache resource limit 限额 类型 值 像素缓存资源限制 -log format format of debugging information 日志格式 调试信息的格式 -loop iterations add Netscape loop extension to your GIF animation 循环 反复 -mask filename associate a mask with the image 遮罩 文件名 连结遮罩和图像 -matte store matte channel if the image has one 不光滑的 存储不光滑的通道如果图像有一个的话 -mattecolor color frame color 帧色彩 -monitor monitor progress 监测 监测进展 -orient type image orientation -origin geometry image origin -page geometry size and location of an image canvas (setting) 页 几何 图像画布大小和位置 -ping efficiently determine image attributes 高效测定图像属性 -pointsize value font point size 像素大小 值 字体像素大小 -preview type image preview type 预览 类型 图像预览类型 -quality value JPEG/MIFF/PNG compression level 质量 值 JPEG/MIFF/PNG压缩水平 -quiet suppress all error or warning messages 安静模式 不显示错误和警告信息 -red-primary point chromaticity red primary point -regard-warnings pay attention to warning messages 注意警告 注意警告信息 -sampling-factor geometry 抽样因子 几何 horizontal and vertical sampling factor 水平和垂直抽样因子 -scene value image scene number 场景 值 图像场景数 -seed value pseudo-random number generator seed value 种子 值 伪随机数 -size geometry width and height of image 大小 几何数 图像宽和高 -stretch type render text with this font stretch 伸展度 类型 用此字体伸展度描绘正文 -stroke color graphic primitive stroke color 笔调 颜色 简单图表笔调颜色 -strokewidth value graphic primitive stroke width -style type render text with this font style 风格 类型 用此字体风格描绘正文 -support factor resize support: > 1.