JAVA开发中经常会遇到不方便使用数据库,但又要进行结构化数据计算的场景。JAVA早期没有提供相关类库,即使排序、分组这种基本计算也要硬写代码,开发效率很低。后来JAVA8推出了Stream库,凭借Lambda表达式、链式编程风格、集合函数,才终于解决了结构化数据计算类库从无到有的问题。
比如排序:
Stream<Order> result=Orders
.sorted((sAmount1,sAmount2)->Double.compare(sAmount1.Amount,sAmount2.Amount))
.sorted((sClient1,sClient2)->CharSequence.compare(sClient2.Client,sClient1.Client));
上面代码中的sorted是集合函数,可方便地进行排序。"(参数)->函数体"的写法即Lambda表达式,可以简化匿名函数的定义。两个sorted函数连在一起用属于链式编程风格,可以使多步骤计算变得直观。
仍然以上面的排序为例,sorted函数只需要知道排序字段和顺序/逆序就够了,参考SQL的写法"…from Orders order by Client desc, Amount",但实际上还要额外输入排序字段的数据类型。顺序/逆序用asc/desc(或+/-)等符号就可以简单表示了,但这里却要用compare函数。另外,实际要排序的字段顺序和代码写出来的顺序是相反的,有些反直觉。
再比如分组汇总:
Calendar cal=Calendar.getInstance();
Map<Object, DoubleSummaryStatistics> c=Orders.collect(Collectors.groupingBy(
r->{
cal.setTime(r.OrderDate);
return cal.get(Calendar.YEAR)+"_"+r.SellerId;
},
Collectors.summarizingDouble(r->{
return r.Amount;
})
)
);
for(Object sellerid:c.keySet()){
DoubleSummaryStatistics r =c.get(sellerid);
String year_sellerid[]=((String)sellerid).split("_");
System.out.println("group is (year):"+year_sellerid[0]+"\t (sellerid):"+year_sellerid[1]+"\t sum is:"+r.getSum()+"\t count is:"+r.getCount());
}
上面代码中,所有出现字段名的地方,都要先写上表名,即"表名.字段名",而不能像SQL那样省略表名。匿名函数语法复杂,随着代码量的增加,复杂度迅速增长。两个匿名函数形成嵌套,代码更难解读。实现一个分组汇总功能要用多个函数和类,包括groupingBy、collect、Collectors、summarizingDouble、DoubleSummaryStatistics等,学习成本不低。分组汇总的结果是Map,而不是结构化数据类型,如果要继续计算,通常要定义新的结构化数据类型,并进行转换类型,处理过程很繁琐。两个分组字段在结构化数据计算中很常见,但函数grouping只支持一个分组变量,为了让一个变量代表两个字段,就要采取一些变通技巧,比如新建一个两字段的结构化数据类型,或者把两个字段用下划线拼起来,这让代码变得更加繁琐。
Stream计算能力不足,原因在于其基础语言JAVA是编译型语言,无法提供专业的结构化数据对象,缺少来自底层的有力支持。
JAVA是编译型语言,返回值的结构必须事先定义,遇到较多的中间步骤时,就要定义多个数据结构,这不仅让代码变得繁琐,还导致参数处理不灵活,要用一套复杂的规则来实现匿名语法。解释性语言则天然支持动态结构,还可以方便地将参数表达式指定为值参数或函数参数,提供更简单的匿名函数。
在这种情况下,Kotlin应运而生。Kotlin是基于JAVA的现代开发语言,所谓现代,重点体现在对JAVA语法尤其是Stream的改进上,即Lambda表达式更加简洁,集合函数更加丰富。
比如排序:
var resutl=Orders.sortedBy{it.Amount}.sortedByDescending{it.Client}
上面代码无须指明排序字段的数据类型,无须用函数表达顺序/逆序,直接引用it作为匿名函数的默认参数,而不是刻意定义,整体比Stream简短不少。
仍然以排序为例,Kotlin虽然提供了it这个默认参数,但理论上只要知道字段名就够了,没必要带上表名(it)。排序函数只能对一个字段进行排序,不能动态接收多个字段。
再比如分组汇总:
data class Grp(var OrderYear:Int,var SellerId:Int)
data class Agg(var sumAmount: Double,var rowCount:Int)
var result=Orders.groupingBy{Grp(it.OrderDate.year+1900,it.SellerId)}
.fold(Agg(0.0,0),{
acc, elem -> Agg(acc.sumAmount + elem.Amount,acc.rowCount+1)
})
.toSortedMap(compareBy<Grp> { it. OrderYear}.thenBy { it. SellerId})
result.forEach{println("group fields:${it.key.OrderYear}\t${it.key.SellerId}\t aggregate fields:${it.value.sumAmount}\t${it.value.rowCount}") }
上面代码中,一个分组汇总的动作,需要用到多个函数,包括复杂的嵌套函数。用到字段的地方要带上表名。分组汇总的结果不是结构化数据类型。要事先定义中间结果的数据结构。
如果继续考察集合、关联等更多的计算,就会发现同样的规律:Kotlin代码的确比Stream短一些,但大都是无关紧要的量变,并未发生深刻的质变,该有的步骤一个不少。
Kotlin也不支持动态数据结构,无法提供专业的结构化数据对象,难以真正简化Lambda语法,无法脱离表名直接引用字段,无法直接支持动态的多字段计算(比如多字段排序)。
esProc SPL的出现,将会彻底改观JAVA生态下结构化数据处理的困境。
esProc SPL是JVM下的开源结构化数据计算语言,提供了专业的结构化数据对象,内置丰富的计算函数,灵活简洁的语法,易于集成的JDBC接口,擅长简化复杂计算。
比如排序:=Orders.sort(-Client, Amount)
SPL无须指明排序字段的数据类型,无须用函数指明方向/逆序,使用字段时无须附带表名,一个函数就可以动态地对多个字段进行排序。
分组汇总:=Orders.groups(year(OrderDate),Client; sum(Amount),count(1))
上面的计算结果仍然是结构化数据对象,可以直接参与下一步计算。对双字段进行分组或汇总时,也不需要事先定义数据结构。整体代码没有多余的函数,sum和count用法简洁易懂,甚至很难觉察这是嵌套的匿名函数。
更多计算也同样简单:
去重:=Orders.id(Client)
模糊查询:=Orders.select(Amount*Quantity>3000 && like(Client,“S”))
关联:=join(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))
Class.forName("com.esproc.jdbc.InternalDriver");
Connection connection =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = connection.createStatement();
String str="=T(\"D:/Orders.xls\"). Orders.groups(year(OrderDate),Client; sum(Amount))";
ResultSet result = statement.executeQuery(str);
SPL可简化分步计算、有序计算、分组后计算等逻辑较复杂的计算,很多SQL/存储过程难以实现的计算,用SPL解决起来就很轻松。比如,找出销售额累计占到一半的前n个大客户,并按销售额从大到小排序:
| A | B | |
| 1 | … | /取数据 |
| 2 | =A1.sort(amount:-1) | /销售额逆序排序 |
| 3 | =A2.cumulate(amount) | /计算累计序列 |
| 4 | =A3.m(-1)/2 | /最后的累计即总额 |
| 5 | =A3.pselect(~>=A4) | /超过一半的位置 |
| 6 | =A2(to(A5)) | /按位置取值 |
除了计算能力,SPL在系统架构、数据源、中间数据存储、计算性能上也有一些特有的优势,这些优势有助于SPL进行库外结构化数据计算。
比如,将上面的SPL代码存为脚本文件,再在JAVA中以存储过程的形式调用文件名:
Class.forName("com.esproc.jdbc.InternalDriver");
Connection connection =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery("call getClient()");
SPL是解释型语言,修改后可直接运行,无须编译,不必重启JAVA服务。SPL代码外置于JAVA,通过文件名被调用,不依赖JAVA代码,耦合性低。
SPL支持各类数据库,txt\csv\xls等文件,MongoDB、Hadoop、redis、ElasticSearch、Kafka、Cassandra等NoSQL,特别地,还支持WebService XML、Restful Json等多层数据:
| A | |
| 1 | =json(file("d:/Orders.json").read()) |
| 2 | =json(A1).conj() |
| 3 | =A2.select(Amount>p_start && Amount<=p_end) |
| A | |
| 1 | =T("Employees.csv") |
| 2 | =mysql1.cursor("select SellerId, Amount from Orders order by SellerId") |
| 3 | =joinx(A2:O,SellerId; A1:E,EId) |
| 4 | =A3.groups(E.Dept;sum(O.Amount)) |
SPL支持btx存储格式,适合暂存来自于低速数据源的数据,比如CSV:
| A | B | |
| 1 | =[T("d:/orders1.csv"), T("d:/orders2.csv")].merge@u() | /对记录做并集 |
| 2 | file("d:/fast.btx").export@b(A1) | /写入集文件 |
=T(“D:/fast.btx”).sort(Client,- Amount)
如果对btx进行有序存储,还能获得高计算性能,比如并行计算、二分查找。SPL还支持更高性能的ctx存储格式,支持压缩、列存、行存、分布式计算、大并发计算,适合持久存储大量数据,并进行高性能计算。
在数据库外的结构化数据计算方面,Stream做出了突破性的贡献;Kotlin加强了这种能力,但编译性语言的特性使它无法走得更远;要想彻底解决库外计算的难题,还需要SPL这种专业的结构化数据计算语言。
欢迎对SPL有兴趣的加小助手(VX号:SPL-helper),进SPL技术交流群
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
状态:我正在构建一个应用程序,其中需要一个可供用户选择颜色的字段,该字段将包含RGB颜色代码字符串。我已经测试了一个看起来很漂亮但效果不佳的。它是“挑剔的颜色”,并托管在此存储库中:https://github.com/Astorsoft/picky-color.在这里我打开一个关于它的一些问题的问题。问题:请建议我在Rails3应用程序中使用一些颜色选择器。 最佳答案 也许页面上的列表jQueryUIDevelopment:ColorPicker为您提供开箱即用的产品。原因是jQuery现在包含在Rails3应用程序中,因此使用基
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri