由于經常記不住stream的一些API每次要復制來復制去并且又長又臭,想要更加語意化的api,于是想到了以前寫大數據Spark pandnas 等DataFrame模型時的API, 然后發現其實也存在java的JVM層的DataFrame模型比如 tablesaw,joinery
但是他們得硬編碼去指定字段名,這對于有代碼潔癖的人實在難以忍受,而且我只是簡單統計下數據,我想在一些場景下能不能使用匿名函數去指定的字段處理去處理,于是便有了這個
一個jvm層級的仿DataFrame工具,語意化和簡化java8的stream流式處理工具
1.1、引入依賴
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>jdframe</artifactId>
<version>0.0.4</version>
</dependency>
1.2、案例
統計每個學校的里學生年齡不為空并且年齡在9到16歲間的合計分數,并且獲取合計分前2名的學校
static List<Student> studentList=new ArrayList<>();
static {
studentList.add(new Student(1,"a","一中","一年級",11, new BigDecimal(1)));
studentList.add(new Student(2,"a","一中","一年級",11, new BigDecimal(1)));
studentList.add(new Student(3,"b","一中","三年級",12, new BigDecimal(2)));
studentList.add(new Student(4,"c","二中","一年級",13, new BigDecimal(3)));
studentList.add(new Student(5,"d","二中","一年級",14, new BigDecimal(4)));
studentList.add(new Student(6,"e","三中","二年級",14, new BigDecimal(5)));
studentList.add(new Student(7,"e","三中","二年級",15, new BigDecimal(5)));
}
// 等價于SQL:
// select school,sum(score)
// from students
// where age is not null and age >=9 and age <=16
// group by school
// order by sum(score) desc
// limit 2
SDFrame<FI2<String, BigDecimal>> sdf2=SDFrame.read(studentList)
.whereNotNull(Student::getAge)
.whereBetween(Student::getAge,9,16)
.groupBySum(Student::getSchool, Student::getScore)
.sortDesc(FI2::getC2)
.cutFirst(2);
sdf2.show();
輸出信息:
c1 c2
三中 10
二中 7
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private String school;
private String level;
private Integer age;
private BigDecimal score;
private Integer rank;
public Student(String level, BigDecimal score) {
this.level=level;
this.score=score;
}
public Student(int id, String name, String school, String level, Integer age, BigDecimal score) {
this.id=id;
this.name=name;
this.school=school;
this.level=level;
this.age=age;
this.score=score;
}
}
2.1、矩陣查看相關
void show(int n); // 打印矩陣信息到控制臺
List<String> columns(); // 獲取矩陣的表頭字段名
List<R> col(Function<T, R> function); // 獲取矩陣某一列值
T head(); // 獲取第一個元素
List<T> head(int n); // 獲取前n個元素
T tail(); // 獲取最后一個元素
List<T> tail(int n); // 獲取后n個元素
List<T> page(int page,int pageSize) // 獲取分頁數據
2.2、篩選相關
SDFrame.read(studentList)
.whereBetween(Student::getAge,3,6) // 過濾年齡在[3,6]歲的
.whereBetweenR(Student::getAge,3,6) // 過濾年齡在(3,6]歲的, 不含3歲
.whereBetweenL(Student::getAge,3,6) // 過濾年齡在[3,6)歲的, 不含6歲
.whereNotNull(Student::getName) // 過濾名字不為空的數據, 兼容了空字符串''的判斷
.whereGt(Student::getAge,3) // 過濾年齡大于3歲
.whereGe(Student::getAge,3) // 過濾年齡大于等于3歲
.whereLt(Student::getAge,3) // 過濾年齡小于3歲的
.whereIn(Student::getAge, Arrays.asList(3,7,8)) // 過濾年齡為3歲 或者7歲 或者 8歲的數據
.whereNotIn(Student::getAge, Arrays.asList(3,7,8)) // 過濾年齡不為為3歲 或者7歲 或者 8歲的數據
.whereEq(Student::getAge,3) // 過濾年齡等于3歲的數據
.whereNotEq(Student::getAge,3) // 過濾年齡不等于3歲的數據
.whereLike(Student::getName,"jay") // 模糊查詢,等價于 like "%jay%"
.whereLikeLeft(Student::getName,"jay") // 模糊查詢,等價于 like "jay%"
.whereLikeRight(Student::getName,"jay"); // 模糊查詢,等價于 like "%jay"
2.3、匯總相關
JDFrame<Student> frame=JDFrame.read(studentList);
Student s1=frame.max(Student::getAge);// 獲取年齡最大的學生
Integer s2=frame.maxValue(Student::getAge); // 獲取學生里最大的年齡
Student s3=frame.min(Student::getAge);// 獲取年齡最小的學生
Integer s4=frame.minValue(Student::getAge); // 獲取學生里最小的年齡
BigDecimal s5=frame.avg(Student::getAge); // 獲取所有學生的年齡的平均值
BigDecimal s6=frame.sum(Student::getAge); // 獲取所有學生的年齡合計
MaxMin<Student> s7=frame.maxMin(Student::getAge); // 同時獲取年齡最大和最小的學生
MaxMin<Integer> s8=frame.maxMinValue(Student::getAge); // 同時獲取學生里最大和最小的年齡
2.4、去重相關
原生steam只支持對象去重,不支持按特定字段去重
List<Student> std=null;
std=SDFrame.read(studentList).distinct().toLists(); // 根據對象hashCode去重
std=SDFrame.read(studentList).distinct(Student::getSchool).toLists(); // 根據學校名去重
std=SDFrame.read(studentList).distinct(e -> e.getSchool() + e.getLevel()).toLists(); // 根據學校名拼接級別去重復
std=SDFrame.read(studentList).distinct(Student::getSchool).distinct(Student::getLevel).toLists(); // 先根據學校名去除重復再根據級別去除重復
2.5、分組聚合相關
類似sql的group by語義 簡化處理分組和聚合的邏輯, 如果用原生stream需要寫可能一大串邏輯。
JDFrame<Student> frame=JDFrame.from(studentList);
// 等價于 select school,sum(age) ... group by school
List<FI2<String, BigDecimal>> a=frame.groupBySum(Student::getSchool, Student::getAge).toLists();
// 等價于 select school,max(age) ... group by school
List<FI2<String, Integer>> a2=frame.groupByMaxValue(Student::getSchool, Student::getAge).toLists();
// 與 groupByMaxValue 含義一致,只是返回的是最大的值對象
List<FI2<String, Student>> a3=frame.groupByMax(Student::getSchool, Student::getAge).toLists();
// 等價于 select school,min(age) ... group by school
List<FI2<String, Integer>> a4=frame.groupByMinValue(Student::getSchool, Student::getAge).toLists();
// 等價于 select school,count(*) ... group by school
List<FI2<String, Long>> a5=frame.groupByCount(Student::getSchool).toLists();
// 等價于 select school,avg(age) ... group by school
List<FI2<String, BigDecimal>> a6=frame.groupByAvg(Student::getSchool, Student::getAge).toLists();
// 等價于 select school,sum(age),count(age) group by school
List<FI3<String, BigDecimal, Long>> a7=frame.groupBySumCount(Student::getSchool, Student::getAge).toLists();
// (二級分組)等價于 select school,level,sum(age),count(age) group by school,level
List<FI3<String, String, BigDecimal>> a8=frame.groupBySum(Student::getSchool, Student::getLevel, Student::getAge).toLists();
// (三級分組)等價于 select school,level,name,sum(age),count(age) group by school,level,name
List<FI4<String, String, String, BigDecimal>> a9=frame.groupBySum(Student::getSchool, Student::getLevel, Student::getName, Student::getAge).toLists();
2.6、排序相關
簡化原生stream的排序方式,直接指定字段即可,不用使用Comparator還要去關注升序還是降序。
如果是多級排序使用Compartor或者Sorter去指定多級排序的邏輯。Sorter也是Compartor的一種實現,只是提供了更加語義化的多級排序指定邏輯, 相當于內置了Compartor的thenComparing
// 等價于 order by age desc
SDFrame.read(studentList).sortDesc(Student::getAge);
// (多級排序) 等價于 order by age desc, level asc.
SDFrame.read(studentList).sortAsc(Sorter.sortDescBy(Student::getAge).sortAsc(Student::getLevel));
// 等價于 order by age asc
SDFrame.read(studentList).sortAsc(Student::getAge);
// 使用Comparator 排序
SDFrame.read(studentList).sortAsc(Comparator.comparing(e -> e.getLevel() + e.getId()));
2.7、連接矩陣相關
API列表
append(T t); // 等價于集合 add
union(IFrame<T> other); // 等價于集合 addAll
join(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join); // 等價于 sql內連接
leftJoin(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join); // 等價于sql左連接,如果左連接失敗,K值為null,需手動判斷
rightJoin(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join); // 等價于sql右連接,如果右連接失敗,T值為null,需手動判斷
內連接例子:
System.out.println("========矩陣1=======");
SDFrame<Student> sdf=SDFrame.read(studentList);
sdf.show(20);
// 獲取學生年齡在9到16歲的學學校合計分數最高的前10名
SDFrame<FI2<String, BigDecimal>> sdf2=SDFrame.read(studentList)
.whereNotNull(Student::getAge)
.whereBetween(Student::getAge,9,16)
.groupBySum(Student::getSchool, Student::getScore)
.sortDesc(FI2::getC2)
.cutFirst(10);
System.out.println("========矩陣2=======");
sdf2.show();
SDFrame<UserInfo> frame=sdf.join(sdf2, (a, b) -> a.getSchool().equals(b.getC1()), (a, b) -> {
UserInfo userInfo=new UserInfo();
userInfo.setKey1(a.getSchool());
userInfo.setKey2(b.getC2().intValue());
userInfo.setKey3(String.valueOf(a.getId()));
return userInfo;
});
System.out.println("========連接后結果=======");
frame.show(5);
打印信息:
========矩陣1=======id name school level age score rank
1 a 一中 一年級 11 1
2 a 一中 一年級 11 1
3 b 一中 一年級 12 2
4 c 二中 一年級 13 3
5 d 二中 一年級 14 4
6 e 三中 二年級 14 5
7 e 三中 二年級 15 5 ========矩陣2=======c1 c2
三中 10
二中 7
一中 4========連接后結果=======key1 key2 key3 key4
一中 4 1
一中 4 2
一中 4 3
二中 7 4
二中 7 5
類似于
select a.*,b.* from sdf a inner join sdf2 b on a.school=b.c1
2.8、截取相關
cutFirst(int n); // 截取前N個
cutLast(int n); // 截取后N個
cut(Integer startIndex,Integer endIndex) // 按照索引范圍截取 [startIndex,endIndex). 等價于 List.subList
cutPage(int page,int pageSize) // 按分頁截取
cutFirstRank(Sorter<T> sorter, int n); // 截取前N排名的數據
2.9、Frame參數設置相關
defaultScale(int scale, RoundingMode roundingMode); // 設置計算結果的默認小數精度
2.10、其他
// 等價于 select round(score*100,2) from student
SDFrame<Student> map2=SDFrame.read(studentList).mapPercent(Student::getScore, Student::setScore,2);
將每個5個元素分成一個小集合,用于將大任務拆成小任務
List<List<Student>> t=SDFrame.read(studentList).partition(5).toLists();
按照age排序,然后根據當前順序生成排序號到rank字段 (序號從1開始)
SDFrame.read(studentList)
.sortDesc(Student::getAge)
.addRowNumberCol(Student::setRank)
.show(30);
輸出信息:
id name school level age score rank
7 e 三中 二年級 15 5 1
5 d 二中 一年級 14 4 2
6 e 三中 二年級 14 5 3
4 c 二中 一年級 13 3 4
3 b 一中 三年級 12 2 5
1 a 一中 一年級 11 1 6
2 a 一中 一年級 11 1 7
補充條目
1、補充缺失的學校條目
// 所有需要的學校條目
List<String> allDim=Arrays.asList("一中","二中","三中","四中");
// 根據學校字段和allDim比較去補充缺失的條目, 缺失的學校按照ReplenishFunction生成補充條目作為結果一起返回
SDFrame.read(studentList).replenish(Student::getSchool,allDim,(school) -> new Student(school)).show();
輸出
id name school level age score rank
1 a 一中 一年級 11 1
2 a 一中 一年級 11 1
3 b 一中 一年級 12 2
4 c 二中 一年級 13 3
5 d 二中 一年級 14 4
6 e 三中 二年級 14 5
7 e 三中 二年級 15 5
0 四中
2、分組補充組內缺失的條目
按照學校進行分組, 匯總所有年級allDim,然后與allDim比較補充每個分組內缺失的年級,缺失的年級按照ReplenishFunction生成補充條目
SDFrame.read(studentList).replenish(Student::getSchool,Student::getLevel,(school,level) -> new Student(school,level)).show(30);
輸出
id name school level age score rank
1 a 一中 一年級 11 1
2 a 一中 一年級 11 1
3 b 一中 三年級 12 2
0 一中 二年級
4 c 二中 一年級 13 3
5 d 二中 一年級 14 4
0 二中 三年級
0 二中 二年級
6 e 三中 二年級 14 5
7 e 三中 二年級 15 5
0 三中 一年級
0 三中 三年級
應用場景舉例:要求計算近兩年每個月的數據,但是數據的年月可能不全,這時就補充缺失的年月數據作為結果一起返回
JDFrame還支持編程式的窗口函數的使用, 具體使用教程見
https://juejin.cn/post/7367306429054959631
代碼地址
https://github.com/burukeYou/JDFrame
Maven依賴地址
https://central.sonatype.com/artifact/io.github.burukeyou/jdframe
提供了兩種Frame,SDFrame和JDFrame 在API層面一模一樣, 區別是JDFrame的所有操作實時生效,無需要重新read生成,而SDFrame與stream流一致,只有執行終止操作才會生效,并且需要重新read生成流, 而且在同一個流之間的操作是互相影響的。
如果只是需要流式操作一條流執行完就用SDFrame,如果需要“中間站點”數據,然后從“中間站點數據“開始計算就用JDFrame,這個在含義層面與DataFrame模型類似。
這個在語法層面能實現的矩陣還是比較有限的因為行列是通過枚舉的幾個FI去描述,但是不同的邏輯導致的矩陣變換的變化可能是非常大的,除非JDK能語法層面支持到吧或者放棄強類型全部硬編碼才能實現各種矩陣的表示和變換。期待JDK一個JVM層面的“pandans” 出現。
還有一些api沒有列舉出來使用的比較少
主要是對邏輯的封裝和語意化,如果還有哪些邏輯和api可以擴展可以在評論區留下你的想法。
來源:juejin.cn/post/7356652717392740404
要用java寫貪吃蛇呢,你要掌握這幾個知識點
1、集合的應用,Arraylist。
用來存儲蛇塊對象的。
2、swing編程
你需要繪制圖形界面
3、事件監聽
鍵盤監聽和按鈕點擊事件監聽
4、線程或定時器的應用(這里我們就用線程)
5、貪吃蛇的核心算法
(面向對象作為java的基本功在這里就不寫出來了)
掌握了這些知識點之后,我們就可以來制作貪吃蛇了
1、創建蛇塊對象
蛇塊屬性有位置位置用x,y表示,
package snakeeat;
public class Snakeact {
private int x;
private int y;
public int getX() {
return x;
}
public void setX(int x) {
this.x=x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y=y;
}
}
2、創建窗口
package snakeeat;
import javax.swing.*;
public class Snake extends JFrame{
public Snake (){
Snakewin win=new Snakewin();
add(win);
setTitle("貪吃蛇");
setSize (435,390);
setLocation(200,200);
setVisible(true);
}
public static void main(String[] args) {
new Snake();
}
}
3、繪制面板(很多邏輯方法都在這個類里)
package snakeeat;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
public class Snakewin extends JPanel implements ActionListener, KeyListener, Runnable {
JButton newGame, stopGame;//開始和結束的按鈕
int score=0;//分數
int speed=1;//蛇運動的速度
int rx=0;//食物的X坐標
int ry=0;
int num1=0, num2=0;
int temp=0;//判斷方向
boolean start=false;//判斷游戲是否開始
JLabel lable;
List<Snakeact> list=new ArrayList<Snakeact>();
Random r=new Random();
JDialog die=new JDialog();
JButton ok;
//構造方法,初始化各種值
public Snakewin() {
newGame=new JButton("開始");
stopGame=new JButton("結束");
this.setLayout(new FlowLayout(FlowLayout.LEFT));
newGame.addActionListener(this);
stopGame.addActionListener(this);
this.addKeyListener(this);
this.add(newGame);
this.add(stopGame);
die.setLayout(new GridLayout(2, 1));
ok=new JButton("繼續");
ok.addActionListener(this);
die.setSize(200, 200);
die.setLocation(400, 300);
die.add(ok);
die.setVisible(false);
if (start) {
lable=new JLabel("您的分數是:" + score);
die.add(lable);
}
}
//繪制圖形界面和蛇
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(10, 40, 400, 300);
g.drawString("分數:" + score, 150, 20);
g.drawString("速度:" + speed, 150, 35);
g.setColor(new Color(0, 255, 0));
if (start) {
g.fillRect(10 + rx * 10, 40 + ry * 10, 10, 10);
g.setColor(new Color(255, 0, 0));
for (int i=0; i < list.size(); i++) {
g.fillRect(10 + list.get(i).getX() * 10, 40 + list.get(i).getY() * 10, 10, 10);
}
eat();
}
}
//吃食物的方法
public void eat() {
if (list.size() !=0) {
if (list.get(0).getX()==rx && list.get(0).getY()==ry) {
score++;
if (score % 10==0) {
speed++;
}
rx=r.nextInt(40);
ry=r.nextInt(30);
Snakeact tempAct=new Snakeact();
tempAct.setX(list.get(list.size() - 1).getX());
tempAct.setY(list.get(list.size() - 1).getY());
list.add(tempAct);
}
}
}
//蛇身移動方法
public void sonMove() {
Snakeact tempAct=new Snakeact();
for (int i=0; i < list.size(); i++) {
if (i==1) {
list.get(i).setX(list.get(0).getX());
list.get(i).setY(list.get(0).getY());
} else if (i > 1) {
tempAct=list.get(i - 1);
list.set(i - 1, list.get(i));
list.set(i, tempAct);
}
}
}
//蛇頭移動方法
public void move(int x, int y) {
if (list.size() !=0) {
if (minYes(x, y)) {
sonMove();
list.get(0).setX(list.get(0).getX() + x);
list.get(0).setY(list.get(0).getY() + y);
} else {
die.setVisible(true);
}
}
}
//按鈕事件監聽
public void actionPerformed(ActionEvent e) {
if (e.getSource()==newGame) {
newGame.setEnabled(false);
start=true;
rx=r.nextInt(40);
ry=r.nextInt(30);
Snakeact tempAct=new Snakeact();
tempAct.setX(20);
tempAct.setY(15);
list.add(tempAct);
requestFocus(true);
Thread mThread=new Thread(this);
mThread.start();
repaint();
}
if (e.getSource()==stopGame) {
System.exit(0);
}
if (e.getSource()==ok) {
list.clear();
newGame.setEnabled(true);
die.setVisible(false);
score=0;
speed=1;
}
}
public boolean minYes(int x, int y) {
if (list.size() !=0) {
if (!maxYes(list.get(0).getX() + x, list.get(0).getY() + y)) {
return false;
}
}
return true;
}
//判斷是否出界
public boolean maxYes(int x, int y) {
if (x < 0 || x >=40 || y < 0 || y >=30) {
return false;
}
for (int i=0; i < list.size(); i++) {
if (i > 1 && list.get(i).getX()==list.get(0).getX() && list.get(i).getY()==list.get(0).getY()) {
return false;
}
}
return true;
}
//鍵盤敲擊事件監聽
public void keyPressed(KeyEvent e) {
if (start) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
if (temp !=2) {
move(0, -1);
temp=1;
}
repaint();
break;
case KeyEvent.VK_DOWN:
if (temp !=1) {
move(0, 1);
temp=2;
}
repaint();
break;
case KeyEvent.VK_LEFT:
if (temp !=4) {
move(-1, 0);
temp=3;
}
repaint();
break;
case KeyEvent.VK_RIGHT:
if (temp !=3) {
move(1, 0);
temp=4;
}
repaint();
break;
default:
break;
}
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
}
//線程的run方法
public void run() {
while (start) {
switch (temp) {
case 1:
move(0, -1);
break;
case 2:
move(0, 1);
break;
case 3:
move(-1, 0);
break;
case 4:
move(1, 0);
break;
default:
move(1, 0);
break;
}
repaint();
try {
Thread.sleep(600 / speed);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
每一點都是一個類哦
效果圖
有問題可以加qq群708653884交流哦