{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"provenance":[{"file_id":"1AB3RNlLfkZBSZKu6_Wh20CkIpbxfJIve","timestamp":1663461789208}],"authorship_tag":"ABX9TyPfm8jLVXbRhqywoiNo1cNF"},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"},"gpuClass":"premium"},"cells":[{"cell_type":"markdown","source":["#計算機程式設計二\n","#第十二週上課內容\n","## Constructors\n"],"metadata":{"id":"9xoK4OIjIfZA"}},{"cell_type":"markdown","source":["## 自己定 List\n","\n","這個範例的程式碼稍微多一點，用到了 template、smart pointers、lambda functions。\n","\n","實作 List 常用的功能，例如 map、fold (reduce)、filter 等等。"],"metadata":{"id":"vDbcAirdZzO2"}},{"cell_type":"code","source":["%%writefile E10_02.cpp\n","#include <iostream>\n","#include <memory>\n","#include <cstdlib>\n","\n","\n","using namespace std;\n","template<typename T>\n","class List {\n","private:\n","    struct Node\n","    {\n","        T val;\n","        shared_ptr<const Node> next;\n","        Node(T v, const shared_ptr<const Node>& n): val{v}, next{n}  { }\n","    };\n","\n","    shared_ptr<const Node> head;\n","\n","    explicit List (const shared_ptr<const Node> & nodes) : head{nodes} { }\n","\n","public:\n","  \n","    List() { } //head{nullptr} done by shared_ptr\n","\n","    List(T val, const List & tail): head{make_shared<Node>(val, tail.head)} { }\n","\n","    bool isEmpty() const { return (head == nullptr); }\n","\n","    T car() const {\n","        if (isEmpty()) exit(EXIT_FAILURE);\n","        return head->val;\n","    }\n","    List cdr() const {\n","        if (isEmpty()) exit(EXIT_FAILURE);\n","        return List(head->next);\n","    }\n","\n","    void show() const {\n","        if (isEmpty()) std::cout << \".\\n\";\n","        else {\n","            std::cout << car() << \"->\";\n","            cdr().show();\n","        }\n","    }\n","};\n","\n","\n","template <typename T>\n","List<T> cons(T val, List<T> tail)\n","{\n","    return List<T>{val, tail};\n","}\n","\n","template<typename S, typename T, typename F>\n","List<S> mapl(F f, List<T> lst)\n","{\n","    if (lst.isEmpty())\n","        return List<S>();\n","    else\n","        return List<S>(f(lst.car()), mapl<S>(f, lst.cdr()));\n","}\n","\n","template<typename T, typename P>\n","List<T> filter(P p, List<T> lst)\n","{\n","    if (lst.isEmpty())\n","        return List<T>();\n","    else {\n","      if (p(lst.car())) {\n","        return List<T>(lst.car(), filter(p, lst.cdr()));\n","      } else {\n","        return filter(p, lst.cdr());\n","      }\n","    }\n","}\n","\n","template<typename S, typename T, typename F>\n","S foldr(F f, S accumul, List<T> lst)\n","{\n","    if (lst.isEmpty())\n","        return accumul;\n","    else\n","        return f(lst.car(), foldr(f, accumul, lst.cdr()));\n","}\n","template<typename S, typename T, typename F>\n","S foldl(F f, S accumul, List<T> lst)\n","{\n","    if (lst.isEmpty())\n","        return accumul;\n","    else\n","        return foldl(f, f(accumul, lst.car()), lst.cdr());\n","}\n","template<typename T, typename F>\n","void foreach(F f, List<T> lst)\n","{\n","    if (!lst.isEmpty()) {\n","        f(lst.car());\n","        foreach(f, lst.cdr());\n","    }\n","}\n","template<class T>\n","void print(List<T> lst)\n","{\n","    foreach([](T v)\n","    {\n","        cout << v << \"->\";\n","    }, lst);\n","    cout << \".\";\n","}\n","\n","template<typename T>\n","List<T> concat(const List<T> a, const List<T> b)\n","{\n","    if (a.isEmpty())\n","        return b;\n","    return List<T>(a.car(), concat(a.cdr(), b));\n","}\n","template<typename T>\n","T findmax(const List<T>  a)\n","{\n","    return foldl([](T acc, T x){return (acc>x) ? acc : x;}, a.car(), a);\n","}\n","\n","\n","List<int> qqsort(const List<int> a)\n","{\n","\n","    if (a.isEmpty()) return a;\n","\n","    int piv = a.car();\n","\n","    List<int> le = qqsort(filter<int>([=](int x){return piv>x;}, a.cdr()));\n","    List<int> ri = qqsort(filter<int>([=](int x){return piv<=x;}, a.cdr()));\n","    return concat<int>(le, cons(piv, ri));\n","}\n","\n","int main()\n","{\n","    List<int> l;\n","    List<int> n = cons(4, cons(5, cons(6, cons(7, cons<int>(8, l)))));\n","    l.show();\n","    n.show();\n","\n","    auto m0 = mapl<double>([](int i){return i*0.5;} , n); // List<double>\n","    m0.show();\n","    n.show();\n","    auto m = mapl<int>([](int i){return i+1;} , n); // List<int>\n","    m.show();\n","\n","    List<string> s = cons(\"abc\"s, cons(\"defg\"s, cons(\"hijkl\"s, List<string>())));\n","    s.show();\n","\n","    auto s_len = mapl<size_t>([](string r){return r.size();}, s);\n","    s_len.show();\n","\n","    \n","    m = filter<int>([](int i){ return i%2==0;}, n);\n","    m.show();\n","    \n","    s = filter<string>([](string r){ return r.size()>4;}, s);\n","    s.show();\n","\n","    \n","    cout << foldl([](int acc, int i){return acc+i;}, 0, m) << endl;\n","    print(m); // or print<int>(m);\n","    cout << endl;\n","    List<List<int>> ll;\n","    auto mm = cons(m, cons(n, ll)); // List<List<int>>\n","    foreach([](List<int> l) { cout << \"[\"; print(l); cout << \"]\" << \"->\";}, mm);\n","    cout << endl;\n","\n","    auto g = concat(n, m); //List<int>\n","    g.show();\n","    cout << findmax(g) << endl;\n","\n","    List<int> ns = cons(9, cons(10, cons(6, cons(5, cons(4, cons(8, cons(7, l)))))));\n","    cout << \"Before: \";\n","    ns.show();\n","    ns = qqsort(ns);\n","    cout << \"After: \";\n","    ns.show();\n","    \n","}\n"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"jbAgUj0HONZ1","executionInfo":{"status":"ok","timestamp":1669725209535,"user_tz":-480,"elapsed":342,"user":{"displayName":"HT Chen","userId":"17748361917871513601"}},"outputId":"84e63b3f-3dde-4be4-be19-0787b0d9e729"},"execution_count":31,"outputs":[{"output_type":"stream","name":"stdout","text":["Overwriting E10_02.cpp\n"]}]},{"cell_type":"code","source":["%%shell\n","g++ E10_02.cpp -std=c++1z -o E10_02\n","./E10_02"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"15HoZpdK5iKH","executionInfo":{"status":"ok","timestamp":1669725213759,"user_tz":-480,"elapsed":1518,"user":{"displayName":"HT Chen","userId":"17748361917871513601"}},"outputId":"4c66e977-696e-42f2-9bff-609aaf1fa8ba"},"execution_count":32,"outputs":[{"output_type":"stream","name":"stdout","text":[".\n","4->5->6->7->8->.\n","2->2.5->3->3.5->4->.\n","4->5->6->7->8->.\n","5->6->7->8->9->.\n","abc->defg->hijkl->.\n","3->4->5->.\n","4->6->8->.\n","hijkl->.\n","18\n","4->6->8->.\n","[4->6->8->.]->[4->5->6->7->8->.]->\n","4->5->6->7->8->4->6->8->.\n","8\n","Before: 9->10->6->5->4->8->7->.\n","After: 4->5->6->7->8->9->10->.\n"]},{"output_type":"execute_result","data":{"text/plain":[]},"metadata":{},"execution_count":32}]},{"cell_type":"markdown","source":["延伸閱讀：C++17 fold expression\n","\n","https://baptiste-wicht.com/posts/2015/05/cpp17-fold-expressions.html\n","\n","\n","\n","延伸閱讀：關於 explicit，copy-initialization 和 direct-initialization 的說明\n","\n","http://en.cppreference.com/w/cpp/language/explicit\n","\n","\n","\n","延伸閱讀：關於 lambda function\n","\n","http://en.cppreference.com/w/cpp/language/lambda"],"metadata":{"id":"Hded9hCPVmay"}},{"cell_type":"markdown","source":["## 實作 Integer Vectors\n","### 介紹 initializer list, move constructor, move assignment\n","\n","\n","```c++\n","#include <iostream>\n","#include <initializer_list>\n","#include <algorithm>\n","class IntVec {\n","};\n","```\n","\n","我們用一個常見的範例來當作練習:實作能夠動態增加元素的一維陣列 (vector)。為了方便解釋，我們只允許 vector 裡面放整數。我們把打算自訂的 class 取名為 IntVec，它需要三個 private members：\n","```c++\n","private:\n","    unsigned int size_;\n","    unsigned int capacity_;\n","    int* elem_;\n","```\n","\n","其中 `size_` 是用來記住 vector 中有實際上多少個元素， `capacity_` 則是目前 vector 可以容納的元素上限，另外 `elem_` 則是指標，用來指向實際存放整數的陣列的開頭位址。\n","\n","接下來就是定義所們需要的 public member functions。\n","\n","\n"],"metadata":{"id":"Kn8vOmK_hxui"}},{"cell_type":"markdown","source":["#### Constructors\n","首先是各種不同的 constructors。\n","\n","##### default constructor\n","我們要的 default constructor 應該會長得像底下這樣\n","```c++\n","    IntVec() : size{0}_, capacity_{8}, elem_{new int [capacity_]} {}\n","```\n","直接設定每個 member 的預設值，`size_` 是 0 表示 vector 裡面沒有任何元素， `capacity_` 是 8 則是預設最多能放 8 個整數，等到快放滿的時候，我們再動態調整大小。而 `elem_` 這個指標，預設會指向一個包含 8 個整數的連續空間，我們用 \n","`new int [需要的數量]` 來取得能夠放置指定數量的連續空間。\n","這個 constructor 的主體不需要做其他的事情，所以只有 `{ }`\n","當我們在主程式裡面寫\n","\n","```c++\n","\tIntVec v;\n","```\n","就會用上面的 default constructor 來產生物件。\n","\n","注意，在這個例子裡， class 裡面 member 宣告的順序不能調換成底下這樣，\n","\n","```c++\n","private:\n","    unsigned int size_;\n","    int* elem_;\n","    unsigned int capacity_;\n","```\n","\n","因為上面的初始設定中 `elem_` 會用到 `capacity_` 所以宣告順序必須先是\n","\n","```c++\n","    unsigned int capacity_;\n","    int* elem_;\n","```\n","\n","在 class 建構的時候，會依照 member 宣告的順序來產生他們的內容，而不是照他們在 initializer list 的順序，例如\n","\n","```c++\n","IntVec() : size{0}_, elem_{new int [capacity_]}, capacity_{8}  {}\n","```\n","\n","這裡即使調換順序也不會造成影響。\n","\n","\n","\n","### copy constructor\n","\n","```c++\n","    IntVec(const IntVec& w) : size_{w.size_}, capacity_{w.capacity_},\n","        elem_{new int [w.capacity_]}\n","    {\n","        std::cout << \"copy constructor\\n\";\n","        for (unsigned int i=0; i<w._size; i++)\n","            elem_[i] = w.elem_[i];\n","    }\n","```\n","當我們再產生一個新的 vector 的時候，如果希望直接複製一個既有的 vector 的內容，就會用到 copy constructor。例如底下的用法\n","```c++\n","\tIntVec u{v};\n","```\n","或是\n","```c++\n","\tIntVec u = v;\n","```\n","所以 copy constructor 要做的事情，首先是設定對應的 members，包括 `size_` 和 `capacity_` 以及指標 `elem_`。我們一樣要先取得足夠的空間，然後用 `elem_` 記住該空間的啟始位址，再來就要把 `elem_` 的內容從傳入的 IntVect 複製過來。上面的 copy constructor 函數裡的 `for` 迴圈，就是為了把傳進來的參數 `w` 的 `elem_` 逐一複製到自己的 `elem_`。\n","\n"],"metadata":{"id":"BQcmsMG4h6_g"}},{"cell_type":"markdown","source":["\n","##### initializer list constructor\n","我們也希望能使用底下的寫法來產生新的 vector\n","```c++\n","\tIntVec v{1,2,3,4};\n","```\n","要達到這個效果，我們必須定義 constructor 能夠接收 initializer list 當作參數。\n","```c++\n","    IntVec(std::initializer_list<int> lst):\n","        size_{lst.size()}\n","    {\n","        capacity_ = (lst.size()*2>8) ? lst.size()*2 : 8;\n","        elem_ = new int [capacity_];\n","        if (lst.size()>0)\n","            std::copy(lst.begin(), lst.end(), elem_);\n","    }\n","```\n","這要用到 ``` std::initializer_list<T>``` 來達成，所以我們在程式碼的最前面加了\n","`#include <initializer_list>`。\n","傳入的參數 lst 是一個由整數構成的 initializer list，當我們在程式碼裡面寫類似 `{1,2,3,4}` 這樣的東西，就會被解讀為 initializer list 然後做對應的處理。我們可以用 `.size()` 函數來取得 initializer list 的大小，然後用 `.begin()` 和 `.end()` 來取得第一個元素和最後一個元素的位址，如此一來就可以用 `std::copy` 函數，來將 initializer list 的內容複製到我們自訂的 vector 的 `elem` 指標所指到的那塊空間中。\n","\n","\n","##### 直接指定要產生的 vector 的大小\n","```c++\n","    IntVec(unsigned int sz):\n","        _size{sz},\n","        _capacity{2*sz},\n","        elem{new int [2*sz]} { std::cout << \"growing...\\n\"; }\n","\n","```\n","我們先取得兩倍大的空間，預備讓後續新增的元素可以直接放入，不需要每次都調整 `elem` 的大小。\n","有一個小地方要注意，當我們在程式碼裡面寫\n","```c++\n","\tIntVec t{30};\n","```\n","會使用 initializer list 對應的 constructor，產生一個 vector 裡面只放了一個元素 `30`。如果是\n","```c++\n","\tIntVec t(30);\n","```\n","才會是產生一個能夠放至少 30 個整數的 vector。\n","\n","#### Desctructor\n","物件消失時要把 elem 占用的空間還回去。\n","```c++\n","    ~IntVec() {\n","        delete [] elem;\n","    }\n","```\n","\n","#### Operators\n","我們還要自訂 operators。例如為了讓 `IntVec` 也能像一般的 array 使用方括號`[]`來取得元素的內容，我們在 class 裡面加入\n","```c++\n","    int & operator[](int i) {\n","    \t// no boundary check\n","        return elem[i];\n","    }\n","```\n","這樣一來，如果程式碼裡面寫\n","```c++\n","\tIntVec v{5,10,15,20};\n","    std::cout << v[2] << std::endl;\n","    v[2] = 16;\n","    std::cout << v[2] << std::endl;\n","```\n","就可以在螢幕上顯示出 v 的編號`2` 的元素 `15`，或是把值改成 `16`。\n","\n","##### copy assignment\n","```c++\n","  IntVec& operator=(const IntVec& w)\n","    {\n","        IntVec v{w};\n","        std::cout << \"copy assignment\\n\";\n","        std::swap(elem, v.elem);\n","        _size = v._size;\n","        _capacity = v._capacity;\n","        return *this;\n","    }\n","```\n","我們也重新定義 `=`，這樣才能夠在程式碼裡面寫\n","```c++\n","\tIntVec s{1,2,3,4};\n","  IntVec t{5,6,7};\n","  t = s;\n","```\n","上面的程式用到了一些慣用的寫法。首先在函數中產生一個 local object `v`，`IntVec v{w};`，它的內容直接從 `w` 複製過來，當這個函數結束的時候，`v`會被清除掉 (因為有 destructor)。接下來我們把 `elem` 和 `v.elem` 兩個指標交換，如此一來，自已 (this) 就會取得和 `w` 完全相同的 `elem` 內容，但 `v` 的 `elem` 則會變成是 this 原本的 `elem` 內容，最後當 `v` 被清除的時候，就會一併清除原本 this 所擁有的 `elem`。\n","\n","##### 重新定義 operator<<\n","這個 operator 不是 IntVec 的 member function，只是為了顯示\n","```c++\n","std::ostream& operator<<(std::ostream& os, IntVec& v)\n","{\n","    os << \"[\";\n","    for (unsigned int i=0; i<v._size; i++)\n","        os << v[i] << \", \";\n","    os << \"]\";\n","    return os;\n","}\n","```\n","所以我們在 class 裡面列為 friend，這樣才能使用 `._size`。\n","```c++\n","\tfriend std::ostream& operator<<(std::ostream& os, IntVec& v);\n","```\n","\n","##### 模仿實作 push_back 函數\n","我們希望也能用 `push_back` 在 vector 的最後加入新的資料。\n","```c++\n","    void push_back(const int val) {\n","        if (_size+1 >= _capacity) {\n","            IntVec tmp(_capacity);\n","            _capacity = tmp._capacity;\n","            for (unsigned int i=0; i<_size; i++)\n","                tmp.elem[i] = elem[i];\n","            std::swap(elem, tmp.elem);\n","        }\n","        elem[_size] = val;\n","        ++_size;\n","    }\n","```\n","正常情況只需要直接在 `elem[_size]` 放入新的資料，並且`++_size;`。\n","如果 `_capacity` 不夠放新的資料，就要先產生一個新的足夠的空間 (用 local object 方式)，把舊的資料複製過去，然後 swap，這樣當 local object 被清除時，就會一併清掉舊的資料。\n"],"metadata":{"id":"g-a2YgQLiI_P"}},{"cell_type":"markdown","source":["\n","#### 與 rvalue reference 有關的兩個 member functions\n","\n","##### move constructor\n","\n","```c++\n","    IntVec(IntVec&& w): _size{w._size}, _capacity{w._capacity}, elem{w.elem}\n","    {\n","        std::cout << \"move constructor\\n\";\n","        w._size = 0;\n","        w._capacity = 0;\n","        w.elem = nullptr;\n","    }\n","```\n","\n","##### move assignment\n","```c++\n","    IntVec& operator=(IntVec&& w) {\n","        std::cout << \"move assignment\\n\";\n","        if (this != &w) {\n","            delete [] elem;\n","\n","            elem = w.elem;\n","            _size = w._size;\n","            _capacity = w._capacity;\n","\n","            w.elem = nullptr;\n","            w._size = 0;\n","            w._capacity = 0;\n","        }\n","        return *this;\n","    }\n","```\n","\n","以 move assignment 為例，參數的 type 是 `IntVec&&`，兩個 ref 符號，表示它接收的是 rvalue reference，和 copy assignment 的參數 `IntVec&` 不同 (只有一個 ref 符號，是 lvalue reference)。所謂的 lvalue，是指可以放在等號左邊的東西，例如 `int a = 3;` 其中的整數變數 a 就是可以放在等號左邊的東西，而相對地，3 是不能放在等號左邊的東西 (我們不會寫 `3 = 5;` 或 `3 = x;`)，我們稱作 rvalue。引用 Bjarne Stroustrup \"The C++ Programming Language (4th Edition)\" 書中的話\n","\n",">> an rvalue reference is a reference to something that nobody else can assign to, so that we can safely “steal” its value.\n","\n","因為我們有實際寫出 move assignment 版本的 `operator=`，所以如果程式碼有用到\n","```c++\n","\tIntVec w, v;\n","  w = {2,3,4};\n","```\n","等號右邊的 `{2,3,4}` 是 rvalue，所以可以對應到 rvalue reference，因此 move assignment 版本的 `operator=` 會被呼叫。這時候發生的事情是，先用 initializer list 產生一個暫時的物件，然後 move assignment 會把那個暫時的物件的內容搬到 `w`，假設我們沒有實作 move assignment，則上面的式子只好用 copy assignment 來達成，這時候發生的事情則會是，先用 initializer list 產生一個暫時的物件，然後用 copy assignment 來複製，過程中還會用 copy constructor 先產生一個臨時的副本，讓 w 可以得到我們想複製的內容。所以，這種情況下，有實際做出 move assignment 可以省掉產生臨時副本。\n","\n","我們可以把上面的 move constructor 和 move assignment 範例當成慣用寫法學起來，套用到其他自訂的 class。"],"metadata":{"id":"PQKSG0mRiQEo"}},{"cell_type":"markdown","source":["## 完整的程式碼\n"],"metadata":{"id":"3Mn1B7BSiecr"}},{"cell_type":"code","source":["%%writefile E12_01.cpp\n","#include <iostream>\n","#include <initializer_list>\n","#include <algorithm>\n","class IntVec {\n","public:\n","    IntVec() :\n","        _size{0},\n","        _capacity{8},\n","        elem{new int [_capacity]} { std::cout<<\"Default constructor\\n\";}\n","\n","    IntVec(const IntVec& w) : _size{w._size}, _capacity{w._capacity},\n","        elem{new int [w._capacity]}\n","    {\n","        std::cout << \"copy constructor\\n\";\n","        for (unsigned int i=0; i<w._size; i++)\n","            elem[i] = w.elem[i];\n","    }\n","\n","    IntVec(std::initializer_list<int> lst):\n","        _size{lst.size()}\n","    {\n","        _capacity = (lst.size()*2>8) ? lst.size()*2 : 8;\n","        elem = new int [_capacity];\n","        if (lst.size()>0)\n","            std::copy(lst.begin(), lst.end(), elem);\n","    }\n","\n","    IntVec(unsigned int sz):\n","        _size{sz},\n","        _capacity{2*sz},\n","        elem{new int [2*sz]} { std::cout << \"growing...\\n\"; }\n","\n","\n","    ~IntVec() {\n","        delete [] elem;\n","    }\n","\n","    int & operator[](int i) {\n","        // no boundary check\n","        return elem[i];\n","    }\n","\n","\n","    IntVec& operator=(const IntVec& w)\n","    {\n","        IntVec v{w};\n","        std::cout << \"copy assignment\\n\";\n","        std::swap(elem, v.elem);\n","        _size = v._size;\n","        _capacity = v._capacity;\n","        return *this;\n","    }\n","\n","    // rvalue reference\n","    IntVec(IntVec&& w): _size{w._size}, _capacity{w._capacity}, elem{w.elem}\n","    {\n","        std::cout << \"move constructor\\n\";\n","        w._size = 0;\n","        w._capacity = 0;\n","        w.elem = nullptr;\n","    }\n","\n","    // rvalue reference\n","    IntVec& operator=(IntVec&& w) {\n","        std::cout << \"move assignment\\n\";\n","        if (this != &w) {\n","            delete [] elem;\n","\n","            elem = w.elem;\n","            _size = w._size;\n","            _capacity = w._capacity;\n","\n","            w.elem = nullptr;\n","            w._size = 0;\n","            w._capacity = 0;\n","        }\n","        return *this;\n","    }\n","\n","\n","    void push_back(const int val) {\n","        if (_size+1 >= _capacity) {\n","            IntVec tmp(_capacity);\n","            _capacity = tmp._capacity;\n","            for (unsigned int i=0; i<_size; i++)\n","                tmp.elem[i] = elem[i];\n","            std::swap(elem, tmp.elem);\n","        }\n","        elem[_size] = val;\n","        ++_size;\n","    }\n","\n","    friend std::ostream& operator<<(std::ostream& os, IntVec& v);\n","\n","private:\n","    unsigned int _size;\n","    unsigned int _capacity;\n","    int* elem;\n","};\n","\n","std::ostream& operator<<(std::ostream& os, IntVec& v)\n","{\n","    os << \"[\";\n","    for (unsigned int i=0; i<v._size; i++)\n","        os << v[i] << \", \";\n","    os << \"]\";\n","    return os;\n","}\n","\n","int main()\n","{\n","    IntVec t;\n","    IntVec v{1,2,3};\n","    std::cout << v << std::endl;\n","    for (int e; std::cin>>e;)\n","        v.push_back(e);\n","    std::cout << v << std::endl;\n","    IntVec w{v};\n","    std::cout << w << std::endl;\n","    w = {3,4,5};\n","    std::cout << w << std::endl;\n","    IntVec u = std::move(w);\n","    u[2] = 6;\n","    std::cout << u << std::endl;\n","    std::cout << w << std::endl;\n","}"],"metadata":{"id":"xpVN59OgiWbs"},"execution_count":null,"outputs":[]}]}