2019年7月17日

iOS: Share Extension

Share Extension介紹

Share Extension介紹

Extension介紹

Extension?

為iOS提供的功能,讓APP之間能夠資料分享

Extension有哪些類型?

常見的類型如下:

  • Today Extension
  • Share Extension
  • Action Extension etc...

Extension在App的角色定位?

  • Extension是一個App的附加功能,因此,想要有extension功能,必須依附在一個主要的project App底下,而這個被依附的Project App稱之為Containing app
  • Extension的存在會隨著App的安裝或移除決定

Share Extension在XCode工作環境

APPLE提供的extension架構圖

  • Host app:user透過哪個app(可以是本機照片或本機檔案)分享檔案到我的app,而透過的這個app稱之
  • App extension:由extension負責接收從Host app傳進來的檔案
  • Containing app:主要開發的project app,負責將資料如何對外呈現
  • Shared resources:extension與Containing app透過蘋果提供的一個group space,透過這個space來讓Containing app存取extension所接收到的

Share Extension介紹

使用情景

一般使用的情況會是使用者想要分享網頁文章或照片到FB、IG上,而開發者就是透過自己定義Share Extension功能,讓使用者也能過分享到自己定義的APP上

決定Share Extension出現在分享清單中的關鍵

圖中就是share extension功能所呈現的畫面,而ShareExtension則是我開發share extension取的名字,當然你也可以自行取名。如果是以最上面第一張圖來看的話,這邊呈現的名稱就會是hostShareExt

要讓share extension出現在分享畫面的話,就要看APP允許接受什麼類型的內容,可選擇支援類型如下:

  • 可經由share extension自己目錄底下plist檔,調整欄位NSExtensionNSExtensionAttributes 改為Dictionary Type ➔ 新增下列欲選取的key
    • NSExtensionActivationSupportsAttachmentsWithMaxCount
    • NSExtensionActivationSupportsAttachmentsWithMinCount
    • NSExtensionActivationSupportsFileWithMaxCount
    • NSExtensionActivationSupportsImageWithMaxCount
    • NSExtensionActivationSupportsMovieWithMaxCount
    • NSExtensionActivationSupportsText
    • NSExtensionActivationSupportsWebURLWithMaxCount
    • NSExtensionActivationSupportsWebPageWithMaxCount

  • 例如:若我只想允許接收分享網頁連結的類型,就只新增NSExtensionActivationSupportsWebURLWithMaxCount屬性並設定其數量
  • NSExtensionActivationSupportsAttachmentsWithMaxCount:附件最多數量限制。附件類型包括File、Image、movie,可以單一、可以複選,但總數量不得超過指定數量

提供的預設介面

會有一個預設畫面、一個Cancel按鈕、一個Post按鈕

提供的實作方式

一開始主要提供基本的三個函式讓我們實作:

  • isContentValid
  • didSelectPost
  • configurationItems

extension收到檔案後,如何傳給Containing App?

蘋果提供一項叫App Groups的服務,允許開發者能夠在自己的App之間傳遞資料,可透過下列三種方式

  • UserDefaults
  • FileManager
  • CoreData

因此,開發者使用哪個指令操作,取資料就需要鍵值或路徑,而這個相對於在APP Group服務底下稱為group name,這也是開發者可以自行定義的

建立與啟用App Group

1.新建專案

2.建立Target

3.選擇Share Extension

4.建立完後,到Project的左側欄位,選擇Capabilities,開啟App Group功能,Container App(EX: ShareExtensionTest)與extension端(EX: ShareExtension)都要啟用。

5.點選+號,新增自己的group name

☝︎取名建議:結尾名稱跟ContainerApp名稱一致,如:group.XXX.XXX.yourContainerAppName,否則可能會有未知的錯誤

6.完成,可以看到XCode工作環境會多一層extension端的目錄。

實作時會遇到的問題

發生情況:沒有權限

extension端想要對接收到的檔案的url做操作(如:try Data(contentsOf: url)),都會發生

Code = 257 "The file "XXX.JPG" couldn’t be opened because you don’t have 
permission to view it.
這邊我列出覺得可能比較有幫助的解決方法:

解決方案 1

這個問題跟我的一模一樣,可惜他的解法對我無效

https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E6%95%99%E5%AE%A4/share-extension-couldnt-be-opened-because-you-don-t-have-permission-to-view-it-error-handling-88f7c2aea466

解決方案 2

這個問題跟我遇到的狀況不一樣,但也有點相似,留言串很多人提供不同的方法,逐一試之也都無效

https://stackoverflow.com/questions/24924809/the-file-myapp-app-couldnt-be-opened-because-you-dont-have-permission-to-vi/25365372

解決方案 3

最後是這個方法能解決我的問題,在對URL操作的前後加上兩段code

fileURL.startAccessingSecurityScopedResource()
...
fileURL.stopAccessingSecurityScopedResource()

https://stackoverflow.com/questions/36355105/the-file-couldn-t-be-opened-because-you-don-t-have-permission-to-view-it?rq=1

解決的案例

名詞解釋,以開發者角度而言:

  • Extension:接收從其他App分享過來的檔案,以下簡稱Ext
  • Containing App:將接收到的檔案做怎樣的呈現,以下簡稱CA

Case 1

  • 操作端:Ext
  • 沒有權限的操作:try? Data(contentsOf: data as! URL)
  • 檔案類型:圖片

Case 2

  • 操作端:Ext
  • 沒有權限的操作:

let tmpData = try? Data(contentsOf: data as! URL)
let data = try? propertyListEncoder().encode(tmpData!)
  • 檔案類型:圖片、Zip檔

Case 3

  • 操作端:CA
  • 沒有權限的操作:

if let shareUrl = FileManager.default.containerURL(forSecurityApplication
GroupIdentifier: yourSuiteName) {
  let imagePath = shareUrl.appendingPathComponent("test.JPG")
  let image = UIImage(contentsOfFile: imagePath.path)
}
  • 檔案類型:圖片

2019年7月15日

Rust Programming Language

今年的 Stack Overflow 調查報告中,發現 Rust 是目前最受歡迎的程式語言。因此我們了解一下Rust 程式語言的定位是:一種撰寫可靠且有效率的軟體的程式語言。 A language empowering everyone to build reliable and efficient software. Rust 是 Mozilla 開發的程式語言,2010 年誕生,目前是 1.34.2 版,其設計目的是開發大型 server 端軟體,強調安全性、記憶體處理及並行處理。雖然效能比 C++ 稍差一點點,但提供了安全的保障。

調查9萬名程序員後,我們發現了一堆不為人知的秘密 Stack Overflow 的年度開發者調查是面向全球開發者的規模最大、最全面的調查,每年的調查內容會涵蓋開發人員最喜歡的技術以及工作偏好等內容。今年是 Stack Overflow 連續第九年進行開發者調查,吸引了將近 9w 名開發人員參加。今年的調查報告結果:Rust 是最受喜愛的編程語言,Python 則是增長最快的。今年 Python 超過 Java 在開發者最喜愛的編程語言榜中排名第二。

一般在討論 Rust 的時候,會跟 C++ 一起比較,通常會使用 C++ 是因為要開發貼近硬體,高速且穩定的系統程式,因為要接近 real time,就不能使用高階語言的 GC 機制,C/C++ 可以開發出高效率的軟體,但常會遇到有關記憶體的問題,也經常發生在底層 library 發生安全漏洞時,導致使用這些 library 的軟體一夕崩壞的問題。

C++ 的發展初期,為了跟 1972年誕生的 C 語言相容,保留了很多設計上的相容性,卻也留下很多問題。而 Rust 是一個沒有歷史包袱的程式語言,當然能吸收新的設計理念,解決安全性的問題,沒有 GC 機制,可直接編譯為machine code,可以直接跟 C 語言互通。

另一種討論,是將 Rust 跟 Golang 比較,Go 有 GC 機制,但 Rust 沒有。Rust 的語法比 Go 複雜,但 Go 更常發生執行期的 crash,Rust 支援泛型。Golang 的目標,應該是取代 Java, Python 在後端運算的地位,但 Rust 的目標,是 C/C++ 的環境,基本上,Rust 跟 Golang 的使用情境應該沒有衝突。

另外,Rust 最常被討論的,是學習曲線陡峭的問題,雖然 Rust 受到開發者的推崇,但實際上,使用 Rust 的開發者並不多,主要原因是 Rust 的用意是取代 C++,而 C++ 的學習曲線比 Rust 更陡峭,而 Rust 本身的困難點在於它接近作業系統,常常會遇到要跟 C/C++ 互通的狀況,還有 Rust 有著其他程式語言不存在的語言特性。

Installation

在 mac/linux 安裝測試環境

curl https://sh.rustup.rs -sSf | sh

他會將 rust 安裝在 $HOME/.rustup,將 cargo 安裝在 $HOME/.cargo,cargo是 rust 的套件管理工具。

另外會在 $HOME/.profile 增加 PATH 環境變數,裡面有常用的指令:rustc, cargo, and rustup

export PATH="$HOME/.cargo/bin:$PATH"

確認是否有安裝完成

$ rustc --version
rustc 1.34.2 (6c2484dc3 2019-05-13)

如果要移除就要

rustup self uninstall

官方網站提供的書本有兩本

The Rust Programming Language

Rust by example

Hello World

hello.rs

fn main() {
    println!("Hello, World!");
}

編譯後,就會產生執行檔

$ rustc hello.rs
$./hello
Hello, World!

另一種開發方式,是利用 cargo 產生專案

cargo new --bin hello
cd hello/

然後修改 main.rs 內容跟剛剛的 hello.rs 一樣,再利用 cargo 編譯 hello project

$ cargo run
   Compiling hello v0.1.0 (/Users/charley/Downloads/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 1.44s
     Running `target/debug/hello`
Hello, world!

如果要產生 release 版,沒有 debug message 的 執行檔,就要加上 build --release 參數

cargo build --release

另外類似 Go 內建程式碼 format 工具,rust 提供 rustfmt 工具,可用 cargo 安裝這個套件

cargo install rustfmt

在 project 中,可用以下指令重排專案

cargo fmt

如果執行時發生下錯誤

$ cargo fmt
error: 'cargo-fmt' is not installed for the toolchain 'stable-x86_64-apple-darwin'
To install, run `rustup component add rustfmt --toolchain stable-x86_64-apple-darwin`

依照說明,再安裝 rustfmt component 即可

rustup component add rustfmt --toolchain stable-x86_64-apple-darwin

Multi Thread

use std::thread;

// 產生 10 個 concurrent thread
fn main() {
    // 因 greeting 是不可變的,可以安全地同時被多個線程使用
    let greeting = "Hello";

    let mut threads = Vec::new();
    // for 可處理任何實作 iterator 特型的類別
    for num in 0..10 {
        threads.push(thread::spawn(move || {
            println!("{} from thread number {}", greeting, num);
        }));
    }

    // 等待所有 thread 結束
    for thread in threads {
        thread.join().unwrap();
    }
}

執行結果

Hello from thread number 0
Hello from thread number 2
Hello from thread number 3
Hello from thread number 1
Hello from thread number 4
Hello from thread number 5
Hello from thread number 6
Hello from thread number 7
Hello from thread number 8
Hello from thread number 9

thead之間是透過 channel 傳遞訊息

use std::thread;
use std::sync::mpsc::channel;

fn main() {
    // 產生 channel,channel 有 tx, rx 兩端
    let (tx, rx) = channel();

    // spawn a new thread,在 thread 中持續使用 channel 的 rx 接收訊息
    let join_handle = thread::spawn(move || {
        // 在 loop 中持續接收訊息,直到 tx 被 dropped
        // recv() 是 blocking method
        while let Ok(n) = rx.recv() {
            println!("Received {}", n);
        }
    });

    // 因為 rx 已經被 thread 使用了,不能在這邊使用到 rx,如果用到就會產生 compile error
    // 透過 tx 發送 10 個訊息
    // 如果接收端 被 dropped,那麼呼叫 unwrap() 就會發生 crash
    for i in 0..10 {
        tx.send(i).unwrap(); // send() 不是 blocking call
    }

    // drop tx 時,會讓 rx.recv() 收到 Err(_)
    drop(tx);

    // 等待 thread 結束
    join_handle.join().unwrap();
}

執行結果

Received 0
Received 1
Received 2
Received 3
Received 4
Received 5
Received 6
Received 7
Received 8
Received 9

Web Framework

如果要開發 web project,參考這兩個網頁的資訊

What are the best web frameworks for Rust?

Rust web framework comparison

可選用Actix 或是 Rocket,其中 Actix 的支援範圍最廣,支援 https, http client, WebSocket, asynchronous

另外因為 Rust 可直接編譯為 WebAssembly,故可以在網頁上運作。

使用 Actix 開發的 web project,雖然看起來不適合開發大型動態網頁的資料,但應該會適合開發 microservice,提供網頁微服務。

References

Rust wiki

「Rust」可進行安全的系統程式設計

如何看待 Rust 的應用前景?

[Rust] 程式設計教學:基礎概念

【譯】Tokio 內部機制:從頭理解 Rust 非同步 I/O 框架

我們為什麼要選擇小眾語言 Rust 來實現 TiKV?

【譯】Rust vs. Go

明明很好很強大,Rust 卻還是那麼小眾

「RustConAsia 2019」如何高效學習Rust

2019年7月1日

WebSocket Support in Jetty

WebSocket 是在 http protocol 進行雙向通訊傳輸的協定,可以用 UTF-8 Text 或 Binary format。message 沒有長度限制,但 framing 有限制長度。可發送無限個訊息。訊息必須依照順序傳送,無法支援 interleaved messages。

WebSocket connection state

有四種

State Description
Connecting 當 HTTP upgrade 到 Websocket
Open socket is open, ready to read/write
Closing 啟動 WebSocket Close Handshake
Closed websocket is closed

WebSocket Events

Event Description
on Connect 成功連線,會收到 org.eclipse.jetty.websocket.api.Session object reference,這是該 socket 的 session
on Close 會有 Status Code
on Error websocket 發生 error
on Message 代表收到完整的 message,可以是 UTF-8 Text 或是 raw BINARY message

Jetty 提供的 WebSocket Spec

  • RFC-6455
    目前支援 WebSocket Protocol version 13

  • JSR-356
    Java WebScoket API (javax.webscoket),這是處理 websocket 的標準 java API

目前還不穩定的功能

  • perframe-compression
    Per Frame Compression Extension

    這是 Google/Chromium team 提供的 frame compression,但還在 early draft,Jetty 支援 draft-04 spec,目前已經被 permessage-compression 取代

  • permessage-compression
    Per Message Compression Extension

    將壓縮改為整個 message,而不是每一個 frame

WebSocket Session

websocket Session 物件有以下的使用方式

  1. 檢查 connection state (opened or not)

    if(session.isOpen()) {
    }
  2. 檢查 secure

    if(session.isSecure()) {
      // connection is using 'wss://'
    }
  3. 有哪些在 Upgrade Request and Response

    UpgradeRequest req = session.getUpgradeRequest();
    String channelName = req.getParameterMap().get("channelName");
    
    UpgradeResponse resp = session.getUpgradeResponse();
    String subprotocol = resp.getAcceptedSubProtocol();
  4. 取得 Local and Remote Address

    InetSocketAddress remoteAddr = session.getRemoteAddress();
  5. 存取 idle timeout

    session.setIdleTimeout(2000); // 2 second timeout

Jetty WebSocket API

同時支援 server 及 client

要開發 Jetty Websocket 程式,首先要在 Maven POM 加上 library,因測試同時要支援 RFC-6455 及 JSR-356,故同時加上了兩種 library

<?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>tw.com.maxkit</groupId>
    <artifactId>test</artifactId>
    <version>0.1</version>

    <properties>
        <jetty.version>9.4.12.v20180830</jetty.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>${jetty.version}</version>
                <configuration>
                    <scanIntervalSeconds>2</scanIntervalSeconds>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!--Jetty dependencies start here -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>${jetty.version}</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <!--Jetty dependencies end here -->

        <!--Jetty Websocket server side dependencies start here -->
        <!--Jetty JSR-356 Websocket server side dependency -->
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>javax-websocket-server-impl</artifactId>
            <version>${jetty.version}</version>
        </dependency>

        <!--Jetty Websocket API server side dependency -->
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-server</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <!--Jetty Websocket server dependencies end here -->


        <!--Jetty Websocket client side dependencies start here -->
        <!--JSR-356 Websocket client side depencency  -->
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>javax-websocket-client-impl</artifactId>
            <version>${jetty.version}</version>
        </dependency>

        <!--Jetty Websocket API client side dependency -->
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-client</artifactId>
            <version>${jetty.version}</version>
        </dependency>
        <!--Jetty Websocket client side  dependencies end here -->

    </dependencies>

</project>
RFC-6455 websocket Server

首先要將 Jetty path 透過 WebSocketServlet 跟 WebSocket class 綁定。

以下是 ToUpperWebSocketServlet 的 servlet,會處理 /toUpper 這個 url,因為在 IDE 裡面,通常會將 webapp 對應到某個 context,假設是 test,那麼 websocket 服務的 url,應該是 ws://localhost:8080/test/toUpper

ToUpperWebSocketServlet.java

package tw.com.maxkit.jetty.server;

import javax.servlet.annotation.WebServlet;

import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

@WebServlet(name = "ToUpper WebSocket Servlet", urlPatterns="/toUpper")
public class ToUpperWebSocketServlet  extends WebSocketServlet{

    @Override
    public void configure(WebSocketServletFactory factory) {
        // set a 10 second timeout
        factory.getPolicy().setIdleTimeout(10000);

//      factory.register(ToUpperWebSocket.class);
//      factory.register(ToUpperWebSocketListener.class);
        factory.register(ToUpperWebSocketAdapter.class);
    }

}

程式裡面設定了 ide timeout 的時間為 10s,另外有三種真正實作 websocket 訊息的方式,如果要使用某一種實作方式,只要調整 register 的 implementation class 即可。

//      factory.register(ToUpperWebSocket.class);
//      factory.register(ToUpperWebSocketListener.class);
        factory.register(ToUpperWebSocketAdapter.class);
  • WebSocket annotation
annotation description
@WebSocket 將這個 POJO 標記為 WebSocket,class 不能是 abstract and public
@OnWebSocketClose (optional) 收到 onClose event
@OnWebSocketMessage (optional) 有兩個 method,分別是 TEXT 與 BINARY message
@OnWebSocketError (optional) 收到 error event
@OnWebSocketFrame (optional) 收到 frame event

ToUppderWebSocket.java

package tw.com.maxkit.jetty.server;

import java.io.IOException;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

@WebSocket
public class ToUpperWebSocket {

    @OnWebSocketMessage
    public void onText(Session session, String message) throws IOException {
        System.out.println("ToUpperWebSocket received:" + message);
        if (session.isOpen()) {
            String response = message.toUpperCase();
            session.getRemote().sendString(response);
        }
    }

    @OnWebSocketConnect
    public void onConnect(Session session) throws IOException {
        System.out.println( session.getRemoteAddress().getHostName() + " connected!");
    }

    @OnWebSocketClose
    public void onClose(Session session, int status, String reason) {
        System.out.println(session.getRemoteAddress().getHostName() + " closed!");
    }

}
  • WebSocketListener

ToUpperWebSocketListener.java

package tw.com.maxkit.jetty.server;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;

public class ToUpperWebSocketListener implements WebSocketListener {
    private Session outbound;

    public void onWebSocketBinary(byte[] payload, int offset, int len) {
        /* only interested in text messages */
    }

    public void onWebSocketClose(int statusCode, String reason) {
        this.outbound = null;
    }

    public void onWebSocketConnect(Session session) {
        this.outbound = session;
    }

    public void onWebSocketError(Throwable cause) {
        cause.printStackTrace(System.err);
    }

    public void onWebSocketText(String message) {
        if ((outbound != null) && (outbound.isOpen())) {
            System.out.printf("ToUpperWebSocketListener [%s]%n", message);
            // echo the message back
            outbound.getRemote().sendString(message.toUpperCase(), null);
        }
    }
}
  • WebSocketAdpapter

比 listener 簡單,提供檢查 session state 的 methods

ToUpperWebSocketAdapter.java

package tw.com.maxkit.jetty.server;


import org.eclipse.jetty.websocket.api.WebSocketAdapter;

import java.io.IOException;

public class ToUpperWebSocketAdapter extends WebSocketAdapter
{
    @Override
    public void onWebSocketText(String message)
    {
        if (isConnected())
        {
            try
            {
                System.out.printf("ToUpperWebSocketAdapter received: [%s]%n",message);
                // echo the message back
                getRemote().sendString(message.toUpperCase());
            }
            catch (IOException e)
            {
                e.printStackTrace(System.err);
            }
        }
    }
}
JSR-356 websocket Server

在網址 ws://localhost:8008/test/jsr356toUpper 提供服務

ToUpper356Socket.java

package tw.com.maxkit.jsr356.server;

import java.io.IOException;

import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/jsr356toUpper")
public class ToUpper356Socket {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket opened: " + session.getId());
    }
    @OnMessage
    public void onMessage(String txt, Session session) throws IOException {
        System.out.println("Message received: " + txt);
        session.getBasicRemote().sendText(txt.toUpperCase());
    }

    @OnClose
    public void onClose(CloseReason reason, Session session) {
        System.out.println("Closing a WebSocket due to " + reason.getReasonPhrase());

    }
}

測試網頁

websocketecho.html

<html>
<body>
    <div>
        <input type="text" id="input" />
    </div>
    <div>
        <input type="button" id="connectBtn" value="CONNECT"
            onclick="connect()" /> <input type="button" id="sendBtn"
            value="SEND" onclick="send()" disabled="true" />
    </div>
    <div id="output">
        <p>Output</p>
    </div>
</body>

<script type="text/javascript">
    var webSocket;
    var output = document.getElementById("output");
    var connectBtn = document.getElementById("connectBtn");
    var sendBtn = document.getElementById("sendBtn");

    function connect() {
        // oprn the connection if one does not exist
        if (webSocket !== undefined
                && webSocket.readyState !== WebSocket.CLOSED) {
            return;
        }
        // Create a websocket
        webSocket = new WebSocket("ws://localhost:8080/test/toUpper");

        webSocket.onopen = function(event) {
            updateOutput("Connected!");
            connectBtn.disabled = true;
            sendBtn.disabled = false;

        };

        webSocket.onmessage = function(event) {
            updateOutput(event.data);
        };

        webSocket.onclose = function(event) {
            updateOutput("Connection Closed");
            connectBtn.disabled = false;
            sendBtn.disabled = true;
        };
    }

    function send() {
        var text = document.getElementById("input").value;
        webSocket.send(text);
    }

    function closeSocket() {
        webSocket.close();
    }

    function updateOutput(text) {
        output.innerHTML += "<br/>" + text;
    }
</script>
</html>

WebSocket Client

client 同樣分 RFC-6455 與 JSR-356 兩種

RFC-6455

WebSocketClientMain.java

package tw.com.maxkit.jetty.client;

import java.net.URI;

import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;

public class WebSocketClientMain {

    public static void main(String[] args) {
        String dest = "ws://localhost:8080/test/toUpper";
        WebSocketClient client = new WebSocketClient();
        try {
            
            ToUpperClientSocket socket = new ToUpperClientSocket();
            client.start();
            URI echoUri = new URI(dest);
            ClientUpgradeRequest request = new ClientUpgradeRequest();
            client.connect(socket, echoUri, request);
            socket.getLatch().await();
            socket.sendMessage("echo");
            socket.sendMessage("test");
            Thread.sleep(10000l);

        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            try {
                client.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

ToUpperClientSocket.java

package tw.com.maxkit.jetty.client;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

@WebSocket
public class ToUpperClientSocket {

    private Session session;
    
    CountDownLatch latch= new CountDownLatch(1);

    @OnWebSocketMessage
    public void onText(Session session, String message) throws IOException {
        System.out.println("Message received from server:" + message);
    }

    @OnWebSocketConnect
    public void onConnect(Session session) {
        System.out.println("Connected to server");
        this.session=session;
        latch.countDown();
    }
    
    public void sendMessage(String str) {
        try {
            session.getRemote().sendString(str);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public CountDownLatch getLatch() {
        return latch;
    }

}
JSR-356 Client

WebSocket356ClientMain.java

package tw.com.maxkit.jsr356.client;

import java.net.URI;

import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;

public class WebSocket356ClientMain {

    public static void main(String[] args) {
    
        try {

            String dest = "ws://localhost:8080/test/jsr356toUpper";
            ToUpper356ClientSocket socket = new ToUpper356ClientSocket();
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(socket, new URI(dest));

            socket.getLatch().await();
            socket.sendMessage("echo356");
            socket.sendMessage("test356");
            Thread.sleep(10000l);

        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

ToUpper356ClientSocket.java

package tw.com.maxkit.jsr356.client;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;

@ClientEndpoint
public class ToUpper356ClientSocket {

    CountDownLatch latch = new CountDownLatch(1);
    private Session session;

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("Connected to server");
        this.session = session;
        latch.countDown();
    }

    @OnMessage
    public void onText(String message, Session session) {
        System.out.println("Message received from server:" + message);
    }

    @OnClose
    public void onClose(CloseReason reason, Session session) {
        System.out.println("Closing a WebSocket due to " + reason.getReasonPhrase());
    }

    public CountDownLatch getLatch() {
        return latch;
    }

    public void sendMessage(String str) {
        try {
            session.getBasicRemote().sendText(str);
        } catch (IOException e) {

            e.printStackTrace();
        }
    }
}

Sending Message to Remote Endpoint

發送訊息有幾種方式

Blocking Send Message

在完成訊息發送後,該 method 才會 return

這是發送 binary message

RemoteEndpoint remote = session.getRemote();

// Blocking Send of a BINARY message to remote endpoint
ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 });
try
{
    remote.sendBytes(buf);
}
catch (IOException e)
{
    e.printStackTrace(System.err);
}

這是發送 text message

RemoteEndpoint remote = session.getRemote();

// Blocking Send of a TEXT message to remote endpoint
try
{
    remote.sendString("Hello World");
}
catch (IOException e)
{
    e.printStackTrace(System.err);
}
發送 Partial Message

如果有個大訊息,希望切割成多個部分,可利用 partial message sending methods,最後一個的 isLast == true

binary message

RemoteEndpoint remote = session.getRemote();

// Blocking Send of a BINARY message to remote endpoint
// Part 1
ByteBuffer buf1 = ByteBuffer.wrap(new byte[] { 0x11, 0x22 });
// Part 2 (last part)
ByteBuffer buf2 = ByteBuffer.wrap(new byte[] { 0x33, 0x44 });
try
{
    remote.sendPartialBytes(buf1,false);
    remote.sendPartialBytes(buf2,true); // isLast is true
}
catch (IOException e)
{
    e.printStackTrace(System.err);
}

text message

RemoteEndpoint remote = session.getRemote();

// Blocking Send of a TEXT message to remote endpoint
String part1 = "Hello";
String part2 = " World";
try
{
    remote.sendPartialString(part1,false);
    remote.sendPartialString(part2,true); // last part
}
catch (IOException e)
{
    e.printStackTrace(System.err);
}
發送 Ping / Pong Control Frame

PING

RemoteEndpoint remote = session.getRemote();

// Blocking Send of a PING to remote endpoint
String data = "You There?";
ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
try
{
    remote.sendPing(payload);
}
catch (IOException e)
{
    e.printStackTrace(System.err);
}

PONG

RemoteEndpoint remote = session.getRemote();

// Blocking Send of a PONG to remote endpoint
String data = "Yup, I'm here";
ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
try
{
    remote.sendPong(payload);
}
catch (IOException e)
{
    e.printStackTrace(System.err);
}
發非同步訊息發送

有兩個 async send message methods

  • RemoteEndpoint.sendBytesByFuture(ByteBuffer message)
  • RemoteEndpoint.sendStringByFuture(String message)

會回傳 java.util.concurrent.Future,可用來測試是否有發送成功

binary

RemoteEndpoint remote = session.getRemote();

// Async Send of a BINARY message to remote endpoint
ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 });
remote.sendBytesByFuture(buf);

可利用 get 等待發送是否完成

RemoteEndpoint remote = session.getRemote();

// Async Send of a BINARY message to remote endpoint
ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 });
try
{
    Future<Void> fut = remote.sendBytesByFuture(buf);
    // wait for completion (forever)
    fut.get();
}
catch (ExecutionException | InterruptedException e)
{
    // Send failed
    e.printStackTrace();
}

可在 get 加上 timeout 時間

RemoteEndpoint remote = session.getRemote();

// Async Send of a BINARY message to remote endpoint
ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 });
Future<Void> fut = null;
try
{
    fut = remote.sendBytesByFuture(buf);
    // wait for completion (timeout)
    fut.get(2,TimeUnit.SECONDS);
}
catch (ExecutionException | InterruptedException e)
{
    // Send failed
    e.printStackTrace();
}
catch (TimeoutException e)
{
    // timeout
    e.printStackTrace();
    if (fut != null)
    {
        // cancel the message
        fut.cancel(true);
    }
}

text 訊息跟 binary 類似,只是將 sendBytesByFuture 換成 sendStringByFuture

References

Jetty WebSocket Example

Chapter 27. Jetty Websocket API

2019年6月24日

Jetty

Jetty 跟 Tomcat 一樣,都支援標準 Servlet 及 JavaEE 規範,但 Jetty 架構比 Tomcat 簡單,可以獨立運作,也可以用 embedded 的方式嵌入 project 中。目前 Jetty 支援 Servlet Spec 3.1 及 JSP 2.3,有部分規範是以模組的方式支援,可藉由設定的方式 enable/disable。

在專案配置 Jetty 只需要以 Maven POM 引用 jetty,就可以用嵌入的方式啟動 Jetty。

    <dependencies>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>9.4.12.v20180830</version>
        </dependency>
    </dependencies>

也可以直接下載 Jetty,以 standalone 方式啟動,下載 Jetty 有兩種,第一種是標準的完整的 Jetty,裡面包含了 modules 以及一個 demo server 配置。另一種是 jetty-home,是最小的 package,提供給進階使用者使用。

要啟動 Jetty 可執行下載的 $JETTY_HOME/bin 目錄中的 ./jetty.sh start,只要用 ./jetty.sh stop 停止 Jetty。

如果要以 console 模式啟動 jetty,可切換到 $JETTY_HOME 目錄,然後直接以 java 執行

java -jar start.jar

預設都是提供 http://localhost:8080 的服務,因為預設沒有部署任何 webapp,所以連接該網址後會得到 404 Error。

Jetty 裡面有個 Demo Base 配置範例,可以切換到該目錄,啟動範例

cd demo-base/
java -jar ../start.jar

可用以下指令,檢查 demo base 裡面的 modules 及 config

cd demo-base/
java -jar ../start.jar --list-modules

java -jar ../start.jar --list-config

Configuration

  • jetty.home
    定義 Jetty 主程式、lib、default modules與 default XML files 的目錄,這是不會修改異動的部分
  • jetty.base
    定義了 Jetty Server、configurtion、logs與 webapps 目錄

這兩個 properties 可透過 command line 設定,或是由環境變數 $JETTY_HOME, $JETTY_BASE 決定

以下是建立一個新的 jetty.base 目錄,然後 enable HTTP connector 與 web app deployer modules,並複製並部署 demo webapp

# JETTY_HOME 設定為下載 Jetty Distribution 解壓縮後的目錄
JETTY_HOME=~/java/jetty
JETTY_BASE=/tmp/mybase
mkdir $JETTY_BASE
cd $JETTY_BASE

直接啟動,會得到 error

$ java -jar $JETTY_HOME/start.jar
WARNING: Nothing to start, exiting ...
$ java -jar $JETTY_HOME/start.jar --create-startd
MKDIR : ${jetty.base}/start.d
INFO  : Base directory was modified

$ java -jar $JETTY_HOME/start.jar --add-to-start=http,deploy
INFO  : webapp          transitively enabled, ini template available with --add-to-start=webapp
INFO  : server          transitively enabled, ini template available with --add-to-start=server
INFO  : security        transitively enabled
INFO  : servlet         transitively enabled
INFO  : http            initialized in ${jetty.base}/start.d/http.ini
INFO  : threadpool      transitively enabled, ini template available with --add-to-start=threadpool
INFO  : deploy          initialized in ${jetty.base}/start.d/deploy.ini
MKDIR : ${jetty.base}/webapps
INFO  : Base directory was modified


$ cp $JETTY_HOME/demo-base/webapps/async-rest.war webapps/ROOT.war
$ java -jar $JETTY_HOME/start.jar

在 command line 可直接修改 http port

java -jar $JETTY_HOME/start.jar jetty.http.port=8081

但這只是暫時的設定,實際上設定值會用以下順序決定

  1. start.d/http.ini 該檔案是 http module 的設定參數
  2. modules/httpd.mod 定義料 http module 並指定使用 etc/jetty-http.xml 設定檔
  3. jetty.http.port 屬性,是由 /etc/jetty-http.xml 指定使用該屬性

後續再增加 SSL 及 http2

$ java -jar $JETTY_HOME/start.jar --add-to-start=https,http2

$ java -jar $JETTY_HOME/start.jar

啟動後,可使用 https://localhost:8443/


可用 --help 查詢所有 command line 指令

java -jar $JETTY_HOME/start.jar --help

deployment

部署 Jetty Webapp 有幾種方式,而且都可以在不停止 Jetty 的條件下,直接部署該 webapp

  1. 一個目錄 (ex: example/) 裡面有 WEB-INF 目錄,以及 web.xml,將目錄放到 $JETTY_BASE 的 webapps 目錄中,就可以部署該 webapp
  2. example.war
  3. 一個獨立的 XML 檔案,裡面定義 webapp 的相關資訊。可參考 demo-base/test.xml 為範例

在剛剛的 /tmp/mybase 新的 $JETTY_BASE 目錄中,如果要部署一個 example webapp,裡面有 JSP 網頁,必須先增加 jsp module support

$ java -jar $JETTY_HOME/start.jar --add-to-start=jsp

example 目錄結構包含一個目錄,兩個檔案

example/
    index.jsp
    WEB-INF/
        web.xml

web.xml 就是很單純的標準 webapp

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
</web-app>

啟動 jetty 後,將 example 複製到 /tmp/mybase/webapps 裡面,可看到 example hot deploy 的 log

$ java -jar $JETTY_HOME/start.jar
.....

oejsh.ContextHandler:Scanner-0: Started o.e.j.w.WebAppContext@7a5904a3{ex,/ex,file:///private/tmp/mybase/webapps/example/,AVAILABLE}{/example}

另外要注意的是,在 webapps 裡面如果有 ROOT 目錄,或是 ROOT.war,這個是 jetty server 的 default web context application。也就是 http://localhost:8080/ 連線取得的網頁。

Configuration

如何設定 jetty

POJO configuration
直些撰寫 java code 設定 jetty。或是使用 etc/jetty.xml,這是 main jetty XML config。或是利用 IoC framework (spring) 初始化 jetty objects as Spring beans
Jetty Start Configuration Files

jetty distribution 使用以下設定檔,透過 start.jar 初始化 jetty

  • ini files

    jetty 利用 command line 讀取 $JETTY_BASE/start.ini and/or $JETTY_BASE/start.d/*.ini files,產生 command line arguments

    • --module=name 啟用 module
    • name=value 用在參數化的 Jetty IoC XML
    • XML files: Jetty IoC(Spring) XML 格式
    • 標準 property file,包含附加的 start properties
    • 其他 start.jar options (java -jar start.jar --help)
    • 一些 JVM options ,以 --exec 整合,例如 -Xbootclasspath
  • mod files

    $JETTY_HOME/modules/*.mod files,以 --module=name 啟用,每個 mod 都定義了

    • module dependencies for ordering and activation
    • 需增加的 libraries
    • module 增加的 command line 參數
    • 啟用 module 需要的檔案
    • template ini,可用 --add-to-start=name 啟用
  • XML files

    Jetty IoC XML format 或是 Spring IoC format,通常放在 $JETTY_HOME/etc/ ,附加的 XML 放在 $JETTY_BASE/etc/


以下是這些設定檔的關係圖


在 Jetty 需要設定什麼?

  • Server

    核心設定檔為 /etc/jetty.xml,可加上其他 server configurations:

    • ThreadPool
      server instance 提供了一個 ThreadPool instance,這是 jetty server components 使用的預設 Executor service,可在 start.ini 或 start.d/server.ini 調整 max/min size

    • Handlers
      jetty 只能有一個 Handler instance 處理 incomping HTTP request,預設 handler tree 設定在 etc/jetty.xml,包含了 a context Handler Collection 及 Default Handler。

      Context Handler Collection 由 context path 選擇 handler,也就是 deployed Context Handler 及 Web Application Contexts

      Default Handler 無法處理的 request 會產生 404 page,可增加其他 handlers(ex: jetty-rewrite.xml, jetty-requestlog.xml) 或增加 hot deploy handlers (ex: jetty-deploy.xml)

    • Server Attributes
      server 會儲存 attribute map of strings,給 components 使用,如果 value objects 實作了 LifeCycle interface,就會 started/stopped with the server,通常 server attributes 是儲存 server-wide default values

    • Server fields
      可設定在 start.ini 或 start.d/server.init,控制 HTTP responses 的 dates, versions

    • Connectors
      接收 HTTP 或其他 protocol 的 connections

    • Services 儲存 service objects,通常會以 LifeCycle beans 存在,例如 Login Services 及 DataSources。以 server level 設定,在 webapp 使用。

  • Connector

    network endpoint,接收某個 protocol 的 connection,標準 protocol 為 http.ini, https.ini, jetty-http2.ini

    • Port
      jetty.http.port (jetty.ssl.port),預設為 8080 (8443)
    • Host
      jetty.host 預設為 0.0.0.0

    • Idle Timeout
      在 connector 動作前,可 idle 多少 ms,否則就 close connection

    • HTTP Configuration
      包含 http, https, http2

    • SSL Context Factory
      TLS connector type (https, http2) 設定 keystore 及 truststore

    jetty 9 是以單一 ServerConnector type,他是 NIO based,並以 Connection Factories 處理多個 protocols

  • Contexts

    • contextPath
      URL prefix,例如 /foo 可處理 /foo, /foo/index.html ... 這些 URL。 / 稱為 root context

    • virtualHost
      context 可設定多個 virtual hosts,virtual host 不需要設定 network parameters。virtual host 代表 IP 的 name service alias

    • classPath
      context 可有自訂的 classpath,該 context 內執行的 handler,有增加該 classpath 的 thread context classloader。標準 webapp 會增加 WEB-INF/lib 及 WEB-INF/classes 這兩個目錄到 classpath

    • attributes
      ex: javax.servlet.context.tempdir 用在 webapp 使用 File instance 的 temp dir

    • resourceBase
      包含 static resource for the context,圖片或 html


  • Context Configuration by API

    在 embedded server,是呼叫 ContextHandler API 進行 context 設定

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;

public class OneContext
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server( 8080 );

        // Add a single handler on context "/hello"
        ContextHandler context = new ContextHandler();
        context.setContextPath( "/hello" );
        context.setHandler( new HelloHandler() );

        // Can be accessed using http://localhost:8080/hello

        server.setHandler( context );

        // Start the server
        server.start();
        server.join();
    }
}
  • Context Configuration by IoC XML

    以下 XML 設定,提供 jetty distribution 的 javadoc context

<?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC
    "-//Mort Bay Consulting//DTD Configure//EN"
    "http://www.eclipse.org/jetty/configure_9_3.dtd">

<!--
  Configure a custom context for serving javadoc as static resources
-->

<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
  <Set name="contextPath">/javadoc</Set>
  <Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/javadoc/</Set>
  <Set name="handler">
    <New class="org.eclipse.jetty.server.handler.ResourceHandler">
      <Set name="welcomeFiles">
        <Array type="String">
          <Item>index.html</Item>
        </Array>
      </Set>
      <Set name="cacheControl">max-age=3600,public</Set>
    </New>
  </Set>
</Configure>
  • Configuring Web Applications

    jetty 用以下方式處理 WAR application

    • classpath 包含 WEB-INF/lib, WEB-INF/classes
    • WEB-INF/web.xml 定義 init parameters, filters, servlets, listeners, security constraints, welcome files, resources
    • annotations 處理 WEB-INF/lib 是定義的 filters, servlets, listeners
    • (optional) WEB-INF/jetty-web.xml 定義 Jetty IoC config
  • Setting the Context Path

    在 WEB-INF/jetty-web.xml 可設定 context path

    <?xml version="1.0"  encoding="UTF-8"?>
    <!DOCTYPE Configure PUBLIC
    "-//Mort Bay Consulting//DTD Configure//EN"
    "http://www.eclipse.org/jetty/configure_9_3.dtd">
    
    <Configure class="org.eclipse.jetty.webapp.WebAppContext">
        <Set name="contextPath">/contextpath</Set>
    </Configure>

    也可以直接在 $JETTY_HOME/webapps/test.xml,就定義了一個 webapp,test.xml 包含 war 的位置,及 contextPath

    <?xml version="1.0"  encoding="UTF-8"?>
    <!DOCTYPE Configure PUBLIC
        "-//Mort Bay Consulting//DTD Configure//EN"
        "http://www.eclipse.org/jetty/configure_9_3.dtd">
    
    <Configure class="org.eclipse.jetty.webapp.WebAppContext">
      <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</Set>
      <Set name="contextPath">/test</Set>
    </Configure>
  • Setting an Authentication Realm

    標準 realm name 設定在 web.xml,例如以下是宣告使用 BASIC authentication,並使用 "Test Realm"

    <login-config>
      <auth-method>BASIC</auth-method>
      <realm-name>Test Realm</realm-name>
    </login-config>

    "Test Realm" 設定在 $JETTY_BASE/etc/test-realm.xml,並傳入 start.ini 及 start.d/server.ini

    以下設定 LoginService

    <?xml version="1.0"?>
    <!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_3.dtd">
    <Configure id="Server" class="org.eclipse.jetty.server.Server">
        <!-- =========================================================== -->
        <!-- Configure Authentication Login Service                      -->
        <!-- Realms may be configured for the entire server here, or     -->
        <!-- they can be configured for a specific web app in a context  -->
        <!-- configuration (see $(jetty.home)/webapps/test.xml for an    -->
        <!-- example).                                                   -->
        <!-- =========================================================== -->
        <Call name="addBean">
          <Arg>
            <New class="org.eclipse.jetty.security.HashLoginService">
              <Set name="name">Test Realm</Set>
              <Set name="config"><Property name="jetty.demo.realm" default="etc/realm.properties"/></Set>
              <Set name="hotReload">false</Set>
            </New>
          </Arg>
        </Call>
    
        <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger">
          <Call name="warn"><Arg>demo test-realm is deployed. DO NOT USE IN PRODUCTION!</Arg></Call>
        </Get>
    </Configure>

隱藏 Server 資訊

在 start.ini 將 server module 裡面的 jetty.httpConfig.sendServerVersion 設定改為 false

--module=server
## Whether to send the Server: header
jetty.httpConfig.sendServerVersion=false

References

The Definitive Reference

CentOS 7 安装jetty

Deploying Web Applications in Jetty

system service

Setup SSL in Jetty

2019年6月16日

julia Data Visulization

分成三個部分: Basic Plots, Vega, Gadfly,討論如何在 julia 進行 data visulization

Basic plots

使用 PyPlot,這是以 python matplotlib.pyplot module 提供的功能

使用前要先安裝 matplotlib

python -m pip install matplotlib

在 julia 安裝 PyPlot

 Pkg.add("PyPlot")

用以下指令在 julia 測試 PyPlot

using PyPlot
x = 1:100
y = rand(100)
p = PyPlot.plot(x,y)
xlabel("x")
ylabel("y")
title("basic plot")
grid("true")

會得到這樣的圖形結果

另一個例子

using PyPlot

x = range(0, stop=4pi, length=1000)
y = cos.(pi .+ sin.(x))

xlabel("x-axis")
ylabel("y-axis")
title("using sin and cos functions")

plot(x, y, color="red")

XKCD 是一種 casual-style, handwritten graph mode

using PyPlot

x = [1:1:10;]
y = ones(10)

for i = 1:1:10
    y[i] = pi + i*i
end

xkcd()
xlabel("x-axis")
ylabel("y-axis")
title("XKCD")
plot(x,y)

bar chart

using PyPlot

x = [10,20,30,40,50]
y = [2,4,6,8,10]
xlabel("x-axis")
ylabel("y-axis")
title("Vertical bar graph")
bar(x, y, color="red")

horizontal bar chart

clf()
x = [10,20,30,40,50]
y = [2,4,6,8,10]
title("Horizontal bar graph")
xlabel("x-axis")
ylabel("y-axis")
barh(x,y,color="red")

2D histogram

clf()
x = rand(1000)
y = rand(1000)
xlabel("x-axis")
ylabel("y-axis")
title("2D Histograph")
hist2D(x, y, bins=50)

pie chart

clf()
labels = ["Fruits";"Vegetables";"Wheat"]
colors = ["Orange";"Blue";"Red"]
sizes = [25;40;35]
explode = zeros(length(sizes))
fig = figure("piechart", figsize=(10,10))
p = pie(sizes, labels=labels, shadow=true, startangle=90, colors = colors)
title("Pie charts")

Scatter chart

clf()
fig = figure("scatterplot", figsize = (10,10))
x = rand(50)
y = rand(50)
areas = 1000*rand(50);
scatter(x, y, s=areas, alpha=0.5)
xlabel("x-axis")
ylabel("y-axis")
title("Scatter Plot")

PyPlot 的 3D plot 是使用 surf(x, y, z, facecolors=colors)

參數 說明
X,Y,Z Data values as 2D arrays
rstride Array row stride (step size)
cstride Array column stride (step size)
rcount Use at most this many rows, defaults to 50
ccount Use at most this many columns, defaults to 50
color Color of the surface patches
cmap A colormap for the surface patches.
facecolors Face colors for the individual patches
norm An instance of Normalize to map values to colors
vmin Minimum value to map
vmax Maximum value to map
shade Whether to shade the facecolors
using PyPlot

clf()
a = range(0.0, stop=2pi, length=500)
b = range(0.0, stop=2pi, length=500)

len_a = length(a)
len_b = length(b)

x = ones(len_a, len_b)
y = ones(len_a, len_b)
z = ones(len_a, len_b)

for i=1:len_a
    for j=1:len_b
        x[i,j] = sin(a[i])
        y[i,j] = cos(a[i])
        z[i,j] = sin(b[j])
    end
end

colors = rand(len_a, len_b, 3)
fig = figure()
surf(x, y, z, facecolors=colors)
fig[:canvas][:draw]()

Gadfly

這是一個圖形的 library,可以輸出圖片為 SVG, PNG, PostScript, PDF,也可用 IJulia 運作,跟 DataFrames 緊密整合,提供 pan, zoom, toggle 的功能。執行 Gadfly.plot 後,browser 會打開一個 html 檔案,裡面是 svg 圖片。

Pkg.add("Gadfly")
using Gadfly
Gadfly.plot(x = rand(10), y=rand(10))

# 折線圖
Gadfly.plot(x = rand(10),y=rand(10), Geom.point, Geom.line)
Gadfly.plot(x=1:10, y=[10^n for n in rand(10)], Scale.y_sqrt, Geom.point, Geom.smooth, Guide.xlabel("x"), Guide.ylabel("y"), Guide.title("Graph with labels"))


Plotting DataFrames with Gadfly

使用 RDatasets (有一些範例資料) 產生 DataFrame for the plot function

折線圖

using RDatasets
Gadfly.plot(dataset("datasets", "iris"),
        x="SepalLength",
        y="SepalWidth",
        Geom.line)

Point Plot

Gadfly.plot(dataset("datasets", "iris"),
        x="SepalLength",
        y="SepalWidth",
        Geom.point)

plot a graph between SepalLength and SepalWidth

histogram

Gadfly.plot(x=randn(4000), Geom.histogram(bincount=100))

preceding showcased histogram

Gadfly.plot(dataset("mlmRev", "Gcsemv"),
        x = "Course", color="Gender", Geom.histogram)

References

Learning Julia

2019年6月15日

Swift 5 語言新特性簡述

Swift 5 在2019年3月25日正式release,主要最廣為人討論的就是ABI Stability,也就是Swift runtime將被放在作業系統裡而不是包含在App裡,更進一步的說明可以參考ABI Stability and MoreEvolving Swift On Apple Platforms After ABI Stability

此外,Swift 5語言本身也增加了一些新的特性,包含新的語法及標準函式庫的更新。本篇文章將概略說明部分較常被提出的新特性。關於完整的Swift 5更新內容請參考Swift部落格的這篇文章:Swift 5 Released!

Raw Strings SE-0200

在字串前後加上#符號,即可使用raw string
在raw string中,反斜線雙引號都可以直接使用,不再需要額外的跳脫字元

let rawStr = #"Hello, \ My "dear" friend!"#
print(rawStr) 

印出結果:

Hello, \ My "dear" friend!

若依然要使用跳脫字元,使用反斜線加上#符號:

let rawStr = #"Hello,\#nMy friend!"#
print(rawStr)

如此才可印出有換行的結果:

Hello,
My friend!

你可以改在字串前後加上兩個或更多個#符號,使用raw string。如此一來,即可改在字串中直接輸入"#;此外,跳脫字元變也為\##

let rawStr = ##"Hello,\##n Now yout can type "# directly!"##
print(rawStr) 

印出結果:

Hello,
Now yout can type "# directly!

Customizing string Interpolation SE-0228

String Interpolation是swift在字串中放入變數的方式之一,如下:

let name = "myitem"
let weight = 123
print("The item is \(name). Its weight is \(weight).") 
//印出The item is myitem. Its weight is 123.

然而,若是自訂的類別物件,在以往,需要令該類別實作CustomStringConvertibledescription property,才能印出自訂的內容;類似Java的toString。

而現在,在Swift 5中,可以使用extension替String.StringInterpolation增加新的appendInterpolation方法,以處理自訂的類別。

class MyItem {
    let name = "item name"
    let weight = 123 
    init(name:String, weight:Int) {
        self.name = name
        self.weight = weight
    }
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: MyItem) {
        appendInterpolation("The item is \(value.name). Its weight is \(value.weight).")
    }
}

let myitem = MyItem(name:"item name", weight:123)
print("myitem:\(myitem)")
//印出myitem:The item is myitem. Its weight is 123.

此外,在增加新的appendInterpolation方法時,
你也可以使用多個參數,以及使用argument label,就像自訂函數一樣,進一步客製想要的String Interpolation內容:

extension String.StringInterpolation {
    mutating func appendInterpolation(mystring: String, replace oldString:String, with newString:String) {
        let newstr = mystring.replacingOccurrences(of: oldString, with: newString)
        appendInterpolation(newstr)
    }
}

print("test:\(mystring:"aaabbbccc", replace:"bbb", with:"ddd")")
//印出test:aaadddccc

Standard Result type SE-0235

Swift 5 標準函式庫中加入了Result型別,讓程式開發者可以更一致地處理錯誤。

Result是一個enum,有successfailure兩個case,並分別各有一個associated value,分別代表執行成功時欲回傳的資料,以及執行失敗時發生的錯誤。

public enum Result<Success, Failure: Error> {
    case success(Success), failure(Failure)
}

假設我們使用要定義一個取得新聞資料的API,並使用Result表示結果,可以定義如下:

func fetchNews(from urlString: String, completionHandler: @escaping (Result<MyNews, MyNewsError>) -> Void) {
    guard let url = URL(string: urlString) else {
        //發生錯誤時回傳.failure及錯誤內容
        completionHandler(.failure(.networkError))
        return
    }

    var mynews = MyNews()
    // ...
    
    //正確執行時,回傳.success及資料
    completionHandler(.success(mynews))
}

MyNews是某個自訂的類別;MyNewsError則是自訂的enum,繼承自標準函式庫的Error。

class MyNews {
    //...
}

enum MyNewsError: Error {
    case networkError
}

如此一來,在呼叫fetchNews取新聞資料時,可以用switch case以更直覺簡單地分別處理成功與失敗的結果:

fetchNews(from: "https://www.xxx.com") { result in
    switch result {
    case .success(let mynews):
        // ...
    case .failure(let myerror):
        // ...
    }
}

Dynamically callable types SE-0216

dynamicCallable可以讓你定義一個型別,其宣告出來的變數可以如同函式般直接使用,如下:

@dynamicCallable
struct MyMultiplier {
    private num:Int
    init(num:Int) {
        self.num = num;
    }
    func dynamicallyCall(withArguments args: [Int]) -> Int {
        //將陣列中的所有元素乘以10後相加。
        return args.map { $0 * num }.reduce(0,+);
    }
}

建立MyMultiplier型別的物件,並令其當作函式來呼叫:

let m10 = MyMultiplier(num:10)
m10(1,2,3,4,5) //150

如上,該型別必須實作dynamicallyCall方法,而參數可以是withArguments任意型別的陣列,如上例為Int陣列。

此外,也可以改用withKeywordArguments,並把參數改為KeyValuePairs<String, Any>,讓函式可以取得傳入的參數名稱。

@dynamicCallable
struct MyFunc {
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Any>) {
    //...        
    }
}

let myfunc = MyFunc()
myfunc(name:"test name", count:123)

dynamicCallable可以用於struct, class, enum

Handling Future Enumeration Cases SE-0192

當switch述句中的條件判斷若其值來自非自定義的enum,如:C enums或是來自standard library的enum,則意味著此switch述句的條件判斷值,在未來可能需要處理非預期的內容;而通常在這種情況下,我們一定會用到default來處理。

以下以來自standard library的enum: AVAudioSession.InterruptionType為例。

Standard library中AVAudioSession.InterruptionType的定義如下:

    public enum InterruptionType : UInt {

        case began

        case ended
    }

假設我們有一個switch述句需要針對AVAudioSession.InterruptionType進行處理:

//判斷AudioInterruption的狀態並印出log...
func logAudioInterruptionType(inttType:AVAudioSession.InterruptionType) {
    switch inttType {
    case .began:
        print("began:\(inttType)")
    case .ended:
        print("ended:\(inttType)")
}

由於AVAudioSession.InterruptionType是來自standard library的enum,未來有可能會增加新的值;所以編譯器提出警告:

Switch covers known cases, but 'AVAudioSession.InterruptionType' may have additional unknown values, possibly added in future versions

Handle unknown values using "@unknown default"

使用 @unknown 標註,暗示未來隨著標準函式庫的更新,此switch case有可能會遇到其他未知的enum類型:

func logAudioInterruptionType(inttType:AVAudioSession.InterruptionType) {
    switch inttType {
    case .began:
        print("began:\(inttType)")
    case .ended:
        print("ended:\(inttType)")
        
    @unknown default:
        print("unknown type:\(inttType)")
    }
}

為何不直接寫上default?

如果直接寫個default,雖然也不會出現編譯錯誤,

func logAudioInterruptionType(inttType:AVAudioSession.InterruptionType) {
    switch inttType {
    case .began:
        print("began:\(inttType)")
    case .ended:
        print("ended:\(inttType)")
        
    default:  //不太合理,因為目前看來不可能...
        print("unknown type:\(inttType)")
    }
}

//備註:在Swift語言中,switch裡的每個case執行完就會自動跳出,不需要像C語言一樣使用break來防止繼續往下執行到其他case

但就邏輯上來說,這是令人困惑的,
因為就現況而言,AVAudioSession.InterruptionType確實只有 .began.ended 兩種值;
若是自定義的enum,而你的 switch case 也已明確地處理好所有enum值的話,再加上default,編譯器甚至還會回報Warning,請你將這個累贅的 default陳述句 移除:

Default will never be executed

一般的default應該用於如下情境,switch case中尚有值仍未處理的情況;

public enum MyGreetings {

    case .goodmorning

    case .goodnight
}

func printMyGreetings(greetingType:MyGreetings) {
    switch greetingType {
    case .goodmorning:
        print("Good Morning")
    default:  //合理,因為還有goodnight沒有處理到...
        print("Good night...I think...")
}

而加上 @unknown 則意味著,此處的 default 可能目前不會遇到,但是在往後有可能會遇到的,所以通常會是來自 非自定義的enum 或是C enums

Proposal SE-0192 就是希望能在程式中刻意區分這一點而提出,也才有了用 @unknown 來處理future enumeration的這個變動。

This proposal aims to distinguish between enums that are frozen (meaning they will never get any new cases) and those that are non-frozen, and to ensure that clients handle any future cases when dealing with the latter.

Flatten nested optionals resulting from ‘try?’ SE-0230

因爲try而造成的nested optionals,意即如:String?? 這樣有兩個 ?? 的情況,在swift 5之後也會變為普通的optional。如:

class MyItem {
    init?(itemid:String) {
        //假設init可能會因為不適合的itemid而建立失敗
    }
    
    //回傳String,但可能拋出錯誤
    func getItemInfo() throws -> String {
        ///...
    }
    
    //回傳String?
    func getItemDesc() -> String? {
        ///...
    }
}

//getItemInfo可能拋出錯誤,需使用try處理
let myItemInfo = try? MyItem(itemid:"aaa")?.getItemInfo()

在swift 5 之前,myItemInfo為String??

在swift 5 之後,myItemInfo為String?

這個改變是因為,原本多數的 optional 使用情境,都會避免發生 nested optionals,如下:

//getItemDesc方法不會拋出錯誤,而是回傳Optional String
let myItemDesc = MyItem(itemid:"aaa")?.getItemDesc()

上面程式中的myItemDesc為String?

不會因為 initializer 回傳 optional 型態,而getItemDesc方法也回傳 optional 型態,
進而造成有兩個 ??nested optionals

因此,於Swift 5中,為了使 try? 與其他 optional 的使用情境更一致,而做了此更動。

Reference

https://swift.org/blog/swift-5-released/

What’s new in Swift 5.0 – Hacking with Swift

What’s New in Swift 5? | raywenderlich.com

https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_release_notes/swift_5_release_notes_for_xcode_10_2

2019年6月10日

Numerical and Scientific Computation in Julia

julia 提供處理大量資料,以及進行統計運算的 library

Working with data

首先是讀取資料的方式,如果是 binary 資料,可直接用 read(), write()

julia> for val in [:Bool, :Char, :Int8]
         @eval println(@which read(stdin, $val))
       end
read(s::IO, ::Type{Bool}) in Base at io.jl:593
read(io::IO, ::Type{Char}) in Base at io.jl:613
read(s::IO, ::Type{Int8}) in Base at io.jl:588

使用

julia> read(stdin, Char)
j
'j': ASCII/Unicode U+006a (category Ll: Letter, lowercase)

julia> read(stdin, Bool)
true
true

julia> read(stdin, Int8)
12
49

readline

julia> methods(readline)
# 4 methods for generic function "readline":
[1] readline(s::IOStream; keep) in Base at iostream.jl:433
[2] readline() in Base at io.jl:370
[3] readline(filename::AbstractString; keep) in Base at io.jl:364
[4] readline(s::IO; keep) in Base at io.jl:370

使用

julia> number = parse(Int64, readline())
12
12

julia> println(number)
12

working with text files

open 參數除了 filename,後面還有存取該檔案的 mode

julia> methods(open)
# 8 methods for generic function "open":
[1] open(fname::AbstractString; read, write, create, truncate, append) in Base at iostream.jl:275
[2] open(fname::AbstractString, mode::AbstractString) in Base at iostream.jl:339
[3] open(f::Function, cmds::Base.AbstractCmd, args...) in Base at process.jl:615
[4] open(f::Function, args...; kwargs...) in Base at iostream.jl:367
[5] open(cmds::Base.AbstractCmd) in Base at process.jl:584
[6] open(cmds::Base.AbstractCmd, mode::AbstractString) in Base at process.jl:562
[7] open(cmds::Base.AbstractCmd, mode::AbstractString, other::Union{RawFD, FileRedirect, IO}) in Base at process.jl:562
[8] open(cmds::Base.AbstractCmd, other::Union{RawFD, FileRedirect, IO}; write, read) in Base at process.jl:584
Mode Description
r read
r+ read, write
w write, create, truncate
w+ read, write, create, truncate
a write, create, append
a+ read, write, create, append
# 有個文字檔 sample.txt
shell> cat sample.txt

file = open("sample.txt")

# 以行為單位,讀入文字檔
file_data = readlines(file)

enumerate(file_data)

# 加上行號
for lines in enumerate(file_data)
   println(lines[1],"-> ", lines[2])
end

# 大寫列印
for line in file_data
   println(uppercase(line))
end

# 反過來列印
for line in file_data
   println(reverse(line))
end

# 計算行數
countlines("sample.txt")

# 列印第一行
first(file_data)

# 最後一行
last(file_data)
working with CSV
using DelimitedFiles

csvfile = readdlm("sample.csv", ',', String, '\n')

# 只取得第二欄資料
csvfile[:,2]

# 只取前三行資料
csvfile[1:3,:]

# 以 | 區隔資料的 sample.psv
# "Lora"|"UK"
# "James"|"UK"
readdlm("sample.psv",'|')
working with DataFrames

DataFrame 是一種類似 database 的資料結構,可以用類似 SQL 的方式存取 DataFrames

using DataFrames

# 取得所有 DataFrames 支援的 functions
names(DataFrames)

DataFrame: 2D data structure,類似 R 與 Pandas 的 DataFrame

  • DataArray: 比 julia 預設 Array type 提供更多 comparison 的功能,但目前已經 deprecated (ref: https://github.com/JuliaStats/DataArrays.jl)
  • DataFrame: 2D data structure,類似 R 與 Pandas 的 DataFrame

julia 的 Array 以 nothing 代表 missing value

julia> a = [1,2,3,nothing,5,6]
6-element Array{Union{Nothing, Int64},1}:
 1
 2
 3
  nothing
 5
 6

julia> typeof(a[4])
Nothing

DataFrame: https://juliadata.github.io/DataFrames.jl/stable/man/getting_started.html

julia> dframe = DataFrame(Names = ["John","May"], Age = [27,28])
2×2 DataFrame
│ Row │ Names  │ Age   │
│     │ String │ Int64 │
├─────┼────────┼───────┤
│ 1   │ John   │ 27    │
│ 2   │ May    │ 28    │

julia> dframe.Names
2-element Array{String,1}:
 "John"
 "May"

julia> dframe.Age
2-element Array{Int64,1}:
 27
 28

julia> names(dframe)
2-element Array{Symbol,1}:
 :Names
 :Age

julia> describe(dframe)
2×8 DataFrame
│ Row │ variable │ mean   │ min  │ median │ max │ nunique │ nmissing │ eltype   │
│     │ Symbol   │ Union… │ Any  │ Union… │ Any │ Union…  │ Nothing  │ DataType │
├─────┼──────────┼────────┼──────┼────────┼─────┼─────────┼──────────┼──────────┤
│ 1   │ Names    │        │ John │        │ May │ 2       │          │ String   │
│ 2   │ Age      │ 27.5   │ 27   │ 27.5   │ 28  │         │          │ Int64    │

另外有個獨立的 CSV 套件,可處理 csv file,讀入 CSV file 後,就得到 DataFrame 物件

using Pkg
Pkg.add("CSV")
using CSV
julia> CSV.read("sample.csv")
3×2 DataFrame
│ Row │ Lora    │ UK      │
│     │ String⍰ │ String⍰ │
├─────┼─────────┼─────────┤
│ 1   │ James   │ UK      │
│ 2   │ Raj     │ India   │
│ 3   │ May     │ America │

Linear algebra

產生 matrix

# 亂數 3x3 matrix
julia> A = rand(3,3)
3×3 Array{Float64,2}:
 0.108282  0.433033  0.247145
 0.571936  0.369386  0.547845
 0.423382  0.380503  0.77661

# array 且初始為 1
julia> ones(5)
5-element Array{Float64,1}:
 1.0
 1.0
 1.0
 1.0
 1.0

Vector 與 Matrix 的差異

Vector{T} 是 Array{T, 1} 的 alias,Matrix{T} 是Array{T, 2} 的 alias

julia> Vector{Float64} == Array{Float64,1}
true

julia> Matrix{Float64} == Array{Float64,2}
true

矩陣乘法

julia> A = rand(3,3)
3×3 Array{Float64,2}:
 0.167005  0.188954  0.0483164
 0.82603   0.440255  0.00107621
 0.137211  0.308508  0.724953

julia> b = 2*A
3×3 Array{Float64,2}:
 0.334011  0.377909  0.0966328
 1.65206   0.88051   0.00215241
 0.274422  0.617015  1.44991

julia> b= 2A
3×3 Array{Float64,2}:
 0.334011  0.377909  0.0966328
 1.65206   0.88051   0.00215241
 0.274422  0.617015  1.44991

transpose 轉置矩陣

julia> transpose_of_A = A'
3×3 LinearAlgebra.Adjoint{Float64,Array{Float64,2}}:
 0.167005   0.82603     0.137211
 0.188954   0.440255    0.308508
 0.0483164  0.00107621  0.724953

inverse 逆矩陣

julia> inv(A)
3×3 Array{Float64,2}:
 -6.31559   2.41816    0.417329
 11.8591   -2.26692   -0.787013
 -3.85134   0.507016   1.63533

Determinant 行列式

julia> using LinearAlgebra

julia> det(A)
-0.05048337143385562

Eigenvalues 特徵向量

julia> eigvals(A)
3-element Array{Float64,1}:
 -0.1016764648907107
  0.5846559789763167
  0.8492342814186749

方程式運算

julia> 5
5

julia> equation = 3x^2 + 4x + 3
98

Differential calculus

Pkg.add("Calculus")
using Calculus
names(Calculus)
julia> f(x) = sin(x)
f (generic function with 1 method)
        
julia> f'(1.0)
0.5403023058631036

julia> f'(1.0) - cos(1.0)
-5.036193684304635e-12

julia> f''(1.0) - (-sin(1.0))
-6.647716624952338e-7

julia> differentiate("cos(x) + sin(x) + exp(-x) * cos(x)", :x)
:(1 * -(sin(x)) + 1 * cos(x) + ((-1 * exp(-x)) * cos(x) + exp(-x) * (1 * -(sin(x)))))

julia> differentiate("sin(x)", :x)
:(1 * cos(x))

julia> differentiate("cos(x) + sin(y) + exp(-x) * cos(y)", [:x, :y])
2-element Array{Any,1}:
 :(1 * -(sin(x)) + ((-1 * exp(-x)) * cos(y) + exp(-x) * 0))
 :(1 * cos(y) + (0 * cos(y) + exp(-x) * (1 * -(sin(y)))))

Statistics

JuliaStats 所列出支援的 packages

  • DataFrames
  • Distributions: probability distribution
  • MultivariateStats: Multivariate statistical analysis
  • HypothesisTests
  • MLBase: Swiss knife for machine learning
  • Distances: Various distances between vectors
  • KernelDensity
  • Clustering: algorithm for data clustering
  • GLM: Generalized linear models
  • NMF: Nonnegative matrix factorization
  • RegERMs: Regularized empirical risk minimization
  • MCMC: Markov Chain Monte Carlo
  • TimeSeries: time series analysis

simple statistics
julia> using Statistics

julia> x = [10,20,30,40,50]

julia> mean(x)
30.0

# 中位數 median
julia> median(x)
30.0

julia> sum(x)
150

# 標準差
julia> std(x)
15.811388300841896

# variance 變異數
julia> var(x)
250.0

julia 也支援 cumulative operations 的 functions

  • accumulate
  • cumsum(): cumulative summation
  • cumprod(): cumulative product
julia> accumulate(+, x)
5-element Array{Int64,1}:
  10
  30
  60
 100
 150

julia> accumulate(-, x)
5-element Array{Int64,1}:
   10
  -10
  -40
  -80
 -130

julia> cumsum(x)
5-element Array{Int64,1}:
  10
  30
  60
 100
 150

julia> cumprod(x)
5-element Array{Int64,1}:
       10
      200
     6000
   240000
 12000000

julia> for i in [:cumsum,:cumprod]
               @eval print($i, "->")
               @eval println($i(x))
       end
cumsum->[10, 30, 60, 100, 150]
cumprod->[10, 200, 6000, 240000, 12000000]

statistics using DataFrames
julia> using DataFrames

julia> dframe = DataFrame(Subjects = ["Maths","Physics","Chemistry"],Marks = [90,85,95])
3×2 DataFrame
│ Row │ Subjects  │ Marks │
│     │ String    │ Int64 │
├─────┼───────────┼───────┤
│ 1   │ Maths     │ 90    │
│ 2   │ Physics   │ 85    │
│ 3   │ Chemistry │ 95    │

julia> describe(dframe)
2×8 DataFrame
│ Row │ variable │ mean   │ min       │ median │ max     │ nunique │ nmissing │ eltype   │
│     │ Symbol   │ Union… │ Any       │ Union… │ Any     │ Union…  │ Nothing  │ DataType │
├─────┼──────────┼────────┼───────────┼────────┼─────────┼─────────┼──────────┼──────────┤
│ 1   │ Subjects │        │ Chemistry │        │ Physics │ 3       │          │ String   │
│ 2   │ Marks    │ 90.0   │ 85        │ 90.0   │ 95      │         │          │ Int64    │

Pandas

對習慣使用 python 進行資料分析的使用者來說,Pandas 是比較常用的 package

julia> pandasDataframe = Pandas.DataFrame(Dict(:Subjects => ["Maths","Physics","Chemistry"],:Marks => [90,85,95]))
   Marks   Subjects
0     90      Maths
1     85    Physics
2     95  Chemistry


julia> Pandas.describe(pandasDataframe)
       Marks
count    3.0
mean    90.0
std      5.0
min     85.0
25%     87.5
50%     90.0
75%     92.5
max     95.0


julia> pandasDataframe[:Subjects]
0        Maths
1      Physics
2    Chemistry
Name: Subjects, dtype: object


julia> pandasDataframe[:Marks]
0    90
1    85
2    95
Name: Marks, dtype: int64


julia> query(pandasDataframe,:(Marks>90))
   Marks   Subjects
2     95  Chemistry

Distributions

distributions.jl 有以下功能

  • Moments (ex: mean, variance, skewness, and kurtosis), entropy, and other properties
  • Probability density/mass functions (pdf) and their logarithms (logpdf)
  • Moment generating functions and characteristic functions
  • Maximum likelihood estimation
  • Posterior w.r.t. conjugate prior and Maximum-A-Posteriori (MAP) estimation
using Pkg
Pkg.add("Distributions")
using Distributions

distribution = Normal()

# 以 normal distribution 產生 10 筆 random 資料
x = rand(distribution, 10)

Binomial()

Cauchy()

Poisson()

TimeSeries
Pkg.add("TimeSeries")
Pkg.add("MarketData")

using TimeSeries
julia> dates  = collect(Date(2017,8,1):Day(1):Date(2017,8,5))
5-element Array{Date,1}:
 2017-08-01
 2017-08-02
 2017-08-03
 2017-08-04
 2017-08-05

julia> sample_time = TimeArray(dates, rand(length(dates)))
5×1 TimeArray{Float64,1,Date,Array{Float64,1}} 2017-08-01 to 2017-08-05
│            │ A      │
├────────────┼────────┤
│ 2017-08-01 │ 0.9142 │
│ 2017-08-02 │ 0.0834 │
│ 2017-08-03 │ 0.417  │
│ 2017-08-04 │ 0.4778 │
│ 2017-08-05 │ 0.6859 │

Hypothesis testing

測試 sample data 是否有足夠的 evidence,針對整個 population of data 進行條件測試。

有兩種 hypothesis testing 測試方式

  • Null hypothesis: the statement being tested
  • Alternative hypothesis: the statement that you will be able to conclude as true
Pkg.add("HypothesisTests")
using HypothesisTests

using Distributions
julia> sampleOne = rand(Normal(), 10)
10-element Array{Float64,1}:
 -0.5347603532683008
 -0.7882658160278007
 -0.3314222340035204
 -0.9280782524368908
  0.4540819395751322
  1.056554282551302
  1.2211198338710754
 -0.7067184060685551
  0.9788402691232629
  0.39421862072760827

julia> testOne = OneSampleTTest(sampleOne)
One sample t-test
-----------------
Population details:
    parameter of interest:   Mean
    value under h_0:         0
    point estimate:          0.0815569884043313
    95% confidence interval: (-0.514, 0.6771)

Test summary:
    outcome with 95% confidence: fail to reject h_0
    two-sided p-value:           0.7638

Details:
    number of observations:   10
    t-statistic:              0.3097695342286937
    degrees of freedom:       9
    empirical standard error: 0.2632827937950805


julia> @which OneSampleTTest(sampleOne)
OneSampleTTest(v::AbstractArray{T,1}) where T<:Real in HypothesisTests at /Users/charley/.julia/packages/HypothesisTests/M3Ysg/src/t.jl:98

julia> pvalue(testOne)
0.7637885831202397

julia> pvalue(testOne, tail=:right)
0.38189429156011984

julia> pvalue(testOne, tail=:left)
0.6181057084398802

# check whether 25 successes from 1000 samples is inconsistent with a 50% success rate
julia> BinomialTest(25, 1000, 0.50)
Binomial test
-------------
Population details:
    parameter of interest:   Probability of success
    value under h_0:         0.5
    point estimate:          0.025
    95% confidence interval: (0.0162, 0.0367)

Test summary:
    outcome with 95% confidence: reject h_0
    two-sided p-value:           <1e-99

Details:
    number of observations: 1000
    number of successes:    25

Optimization

optimization 是 finding best solution from all feasible solutions 的問題,可分為兩類

  • continuous optimization problem
  • combinatorial optimization problem (discrete)

某些問題可以用 optimization 方法解決

  • Shortest path
  • Maximum flow through a network
  • Vehicle routing

juila 將 optimization packages 集合在 JuliaOpt,其中有兩個最重要的 packages,這兩個都是 AML(Algebraic modeling languages),放在 MathProgBase.jl 裡面

  • JuMP (Julia for Mathematical Programming)
  • Convex.jl

JuMP

Python 習慣使用 PuLP

Pkg.add("JuMP")
Pkg.add("Clp")
using JuMP
using Clp
julia> m = JuMP.Model()
Feasibility problem with:
 * 0 linear constraints
 * 0 variables
Solver is default solver

julia> m = JuMP.Model(solver = Clp.ClpSolver())
Feasibility problem with:
 * 0 linear constraints
 * 0 variables
Solver is Clp

optimiser.jl

using JuMP
using Clp

m = Model(solver = ClpSolver())
@variable(m, 0 <= a <= 2 )
@variable(m, 0 <= b <= 10 )

@objective(m, Max, 5a + 3*b )
@constraint(m, 1a + 5b <= 3.0 )

print(m)

status = solve(m)

println("Objective value: ", getobjectivevalue(m))
println("a = ", getvalue(a))
println("b = ", getvalue(b))

執行

$ julia optimiser.jl
Max 5 a + 3 b
Subject to
 a + 5 b ≤ 3
 0 ≤ a ≤ 2
 0 ≤ b ≤ 10
Objective value: 10.6
a = 2.0
b = 0.2
Convex.jl

用在 disciplined convex programming,使用 Convex 需要 solver,也就是 SCS

Pkg.add("Convex")
Pkg.add("SCS")

using Convex
using SCS

X = Variable(2, 2)

y = Variable()

p = minimize(vecnorm(X) + y, 2 * X <= 1, X' + y >= 1, X >= 0, y >= 0)

solve!(p)

println(X.value)

println(y.value)

p.optval

References

Learning Julia