1 CHƯƠNG 1: GIỚI THIỆU CHUNG VỀ PACKAGE PURRR

1.1 Đặt vấn đề

Trong thời đại hiện nay, phân tích dữ liệu đóng vai trò vô cùng quan trọng và mang lại nhiều lợi ích trong nhiều lĩnh vực khác nhau. Các công việc như là xử lý dữ liệu cũng như phân tích văn bản ngày càng phổ biến và đòi hỏi sự tỉ mỉ hơn. Trong lĩnh vực xử lý và phân tích dữ liệu, người dùng thường đối mặt với nhiều thách thức và khó khăn trong việc làm việc với dữ liệu lớn . Các công việc như nhập dữ liệu, xử lý dữ liệu thường đòi hỏi nhiều công đoạn phức tạp và mất thời gian. Hiện nay, hầu hết các lĩnh vực từ công ty đến các doanh nghiệp lớn nhỏ đều sử dụng phân tích dữ liệu. Đối với các công ty và doanh nghiệp hoạt động trong lĩnh vực kinh doanh, phân tích dữ liệu giúp họ hiểu hơn về khách hàng của mình. Thông thường, hướng phân tích dữ liệu thuận tiện và nhanh gọn mà mọi người tới là các ứng dụng phân tích dữ liệu trong thực tiễn như Excel, BI Tools, FineReport, R và Python,…Đặc biệt là trong các ngành liên quan tới tài chính, ngành mà tập hợp hầu hết những kiểu dữ liệu có thể phân tích thông qua ngôn ngữ lập trình R hiện nay. Để giải quyết những thách thức này, thì có rất nhiều công cụ hoàn toàn giúp ta có thể giải quyết được một cách nhanh gọn và tiết kiệm thời gian hiệu quả. Trong bài viết này, chúng em sẽ giới thiệu về Purrr, một công cụ giúp chúng ta có thể dễ dàng phân tích và xử lý dữ liệu.

1.2 Giới thiệu về Purrr

1.2.1 Nguồn gốc

Gói Purrr được phát triển bởi Hadley Wickham, một nhà phân tích dữ liệu và nhà phát triển phần mềm nổi tiếng trong cộng đồng R. Hadley Wickham cũng là tác giả của nhiều gói R khác như “dplyr”, “ggplot2” và “tidyr”, được sử dụng rộng rãi trong ngành phân tích dữ liệu.

Việc tạo ra gói Purrr nhằm mục đích đơn giản hóa và tăng cường khả năng xử lý dữ liệu trong R, đặc biệt là khi làm việc với các phép toán lặp lại trên các cấu trúc dữ liệu.

1.2.2 Tổng quan về gói Purrr

Package Purrr trong ngôn ngữ lập trình R là một gói mở rộng mạnh mẽ của ngôn ngữ, được sử dụng để làm việc với các cấu trúc dữ liệu dạng vector, danh sách và các loại dữ liệu khác trong R. Gói này giúp cải thiện tính linh hoạt và hiệu suất khi thực hiện các phép toán lặp lại trên các phần tử của các cấu trúc dữ liệu đó.

Package purrr là 1 package rất mạnh, thiết kế rất thông minh, làm cho việc viết code trở lên đơn giản, dễ hiểu. Package Purrr này cung cấp một loạt các hàm như map(), map2(), pmap(), walk(), reduce(),… để thực hiện các phép toán lặp lại trên các phần tử của các vector, danh sách hoặc bất kỳ cấu trúc dữ liệu nào khác. Một hàm được sử dụng phổ biến trong package Purrr này là hàm map(), sự ra đời của nó đã thay đổi hoàn toàn cách thức viết vòng lặp trong R. Vốn trước kia viết vòng lặp dựa trên 2 nhóm công cụ chính là các vòng lặp cổ điển (for, while, …) hoặc những hàm apply (sapply, lapply…), nhưng bây giờ hàm map() cho phép bạn thay thế nhiều vòng lặp for bằng mã ngắn gọn hơn và dễ đọc hơn.

1.Hàm map()

map(.x, ~function(.x,…))

Mỗi bước trên lộ trình của vòng lặp tương ứng với một điều kiện (.x), được dùng như 1 tham số/dữ liệu trong hàm f . Tùy bản chất của dữ liệu đầu vào (thường là 1 list), (.x) có thể là vector, matrix, dataframe, hay 1 con số … , và đóng vai trò dữ liệu đầu vào, tùy chỉnh, tham số để cá biệt hóa nội dung hàm f.

Có nhiều hàm map trong package Purrr, mỗi hàm có công dụng khác nhau và xuất kết quả khác nhau. Ví dụ : hàm map() mặc định xuất kết quả là 1 danh sách, hàm map_df() trả về một khung dữ liệu bằng cách liên kết hàng các phần tử riêng lẻ, hàm map_dbl() hay map_int() xuất kết quả là 1 vector số thực hay số nguyên …

Các họ hàm map_*().

Các hàm họ map_*() sử dụng cho vòng lặp (loop). Được thiết kế rất tiện và loại bỏ những thao tác thừa đối với việc sử dụng vòng lặp for, và đầu ra xác định rõ ràng hơn so với vòng lặp while. Cụ thể như sau:

Đối với vòng lặp for ta phải xác định trước số phần tử của object (nếu không muốn giảm hiệu năng của hàm). Còn đối với hàm map thì không cần làm công việc như vậy mà không bị ảnh hướng tới hiệu năng.

Thiết kế phù hợp để thực hiện theo chu trình (pipe %>%)

Các hàm họ map_*():

  • map(): tạo thành một danh sách.

  • map_lgl(): tạo ra một vectơ logic.

  • map_int(): tạo thành một vectơ số nguyên.

  • map_dbl(): tạo thành một vectơ kép

  • map_chr(): tạo thành một vectơ ký tự.

2.Hàm pmap(): là một hàm trong gói “purrr” của ngôn ngữ lập trình R. Hàm này được sử dụng để áp dụng một hàm lên các phần tử từ nhiều danh sách hoặc vector cùng một lúc. Nó giúp thực hiện các phép toán song song trên các cấu trúc dữ liệu đó.

pmap(.l, .f, ...)
  • .l là một danh sách (list) chứa các vector hoặc danh sách chứa các phần tử đầu vào.
  • .f là một hàm mà bạn muốn áp dụng lên các phần tử tương ứng từ các danh sách trong
  • “…” là các tham số bổ sung mà bạn có thể truyền vào hàm .f.

Hàm pmap() rất hữu ích khi bạn muốn áp dụng một hàm lên các phần tử từ nhiều danh sách hoặc vector cùng một lúc và thu thập kết quả thành một danh sách.

3.Hàm walk(): là một hàm trong gói “purrr” của ngôn ngữ lập trình R. Hàm walk() rất hữu ích khi bạn muốn thực hiện một hành động trên các phần tử của một cấu trúc dữ liệu mà không cần thu thập kết quả trả về từ mỗi phần tử đó. Thay vào đó, nó cho phép bạn thực hiện một hành động “đi qua” các phần tử một cách tuần tự.

walk(.x, .f, ...)
  • .x là danh sách hoặc vector cần áp dụng hành động hoặc gọi hàm lên.
  • .f là một hàm mà bạn muốn áp dụng lên từng phần tử của .x.
  • … là các đối số tùy chọn được truyền vào hàm .f

4.Hàm reduce(): là một hàm trong gói “purrr” của ngôn ngữ lập trình R. Hàm này được sử dụng để áp dụng một phép tổ hợp lên các phần tử của một vector hoặc danh sách để thu được một kết quả duy nhất.

reduce(.x, .f, ...)

5.Hàm flatten(): trong gói “purrr” của R được sử dụng để trích xuất các phần tử từ một danh sách lồng nhau và biến đổi chúng thành một danh sách đơn giản hơn, không còn cấu trúc lồng nhau.

flatten(.x, .simplify = TRUE)
  • .x là danh sách hoặc vector chứa các phần tử cần trích xuất.
  • .simplify là một đối số tùy chọn để xác định liệu danh sách trả về sau khi trích xuất có được đơn giản hóa hay không. Khi .simplify = TRUE, danh sách sẽ được đơn giản hóa thành vector nếu có thể. Khi .simplify = FALSE, danh sách sẽ được trả về mà không đơn giản hóa.

6.Hàm pluck(): trong gói “purrr” của R được sử dụng để trích xuất giá trị từ một danh sách dựa trên các chỉ số hoặc tên.

pluck(.x, ..., .default = NULL)
  • .x là danh sách cần trích xuất giá trị từ.
  • … là các đối số chứa chỉ số hoặc tên của các phần tử cần trích xuất. Bạn có thể truyền nhiều chỉ số hoặc tên trong dấu … để trích xuất nhiều phần tử cùng một lúc.
  • .default là một giá trị mặc định tùy chọn, được trả về nếu phần tử cần trích xuất không tồn tại trong danh sách. Nếu không có giá trị .default được chỉ định, nếu phần tử không tồn tại, hàm pluck() sẽ trả về NULL.

7.Hàm transpose(): trong gói “purrr” của R được sử dụng để chuyển đổi dữ liệu dạng danh sách lồng nhau thành một danh sách mới với các phần tử được chuyển đổi thành cột và các danh sách con thành các hàng.

transpose(.x, ...)

1.2.3 Cài đặt gói Purrr

Để sử dụng package Purrr trên R, ta thực hiệ các bước sau:

Bước 1: Cài đặt package Purrr Chúng ta có thể cài đặt package Purrr bàng cách chạy câu lệnh trên R:

install.packages("Purrr")

Bước 2: Gọi package Purrr bằng câu lệnh trên R

library(purrr)
## Warning: package 'purrr' was built under R version 4.3.1

Bước 3: Sau khi gọi package Purrr ta có thể sử dụng các hàm được nói ở trên của package này để xử lí dữ liệu

1.2.4 Mục tiêu và lợi ích

Mục tiêu chính của package Purrr trong R là cung cấp một cách tiếp cận mạnh mẽ và linh hoạt để làm việc với dữ liệu dạng danh sách và vector trong ngôn ngữ lập trình R. Các lợi ích của gói Purrr bao gồm:

Lập trình dạng hàm: gói Purrr cung cấp một loạt các hàm như map(), map2(), pmap(),… giúp bạn áp dụng một hàm lên các phần tử trong danh sách hoặc vector một cách dễ dàng. Điều này cho phép bạn viết code ngắn gọn, sáng tạo và dễ đọc hơn.

Tự động hóa các thao tác lặp lại: Thay vì viết các vòng lặp thủ công để thực hiện các thao tác trên từng phần tử, gói Purrr cho phép bạn áp dụng các hàm một cách tự động trên toàn bộ danh sách hoặc vector một cách hiệu quả. Điều này giúp giảm đáng kể lượng mã lặp lại và tăng hiệu suất của code.

Xử lý dữ liệu dạng danh sách: Khi làm việc với dữ liệu phức tạp, thường có nhiều danh sách lồng nhau hoặc dữ liệu có cấu trúc không đồng nhất. Gói Purrr cung cấp các hàm như flatten(), pluck(), transpose() để trích xuất, biến đổi và tổ chức lại dữ liệu dạng danh sách một cách dễ dàng.

Điều khiển quá trình tính toán: gói Purrr cung cấp các hàm như walk(), invoke_map() cho phép bạn điều khiển quá trình tính toán và thực thi các tác vụ phức tạp như tính toán song song.

Tích hợp với các gói dữ liệu phổ biến: purrr tương thích tốt với nhiều gói dữ liệu phổ biến như “dplyr”, “tidyr” và “ggplot2”. Bạn có thể kết hợp các hàm từ các gói này để xử lý dữ liệu một cách linh hoạt và hiệu quả.

1.3 Lý do chọn chủ đề

Khi tiếp xúc với ngôn ngữ R, chúng em thấy có rất nhiều gói bổ ích hỗ trợ trong quá trình phân tích dữ liệu. Tuy nhiên chúng em chọn gói Purrr trong R vì nó là một công cụ mạnh mẽ và linh hoạt để làm việc với dữ liệu dạng danh sách và vector, giúp chúng em viết code ngắn gọn, giảm sự lặp lại mã, tăng tính linh hoạt và dễ đọc. Nó cũng tích hợp tốt với các gói dữ liệu phổ biến và cung cấp các hàm hữu ích cho xử lý dữ liệu phức tạp và quá trình tính toán phức tạp, điều này giúp chúng em tìm hiểu thêm được nhiều gói tương thích với gói Purrr có thể kết hợp các hàm từ các gói này để xử lý dữ liệu một cách linh hoạt và hiệu quả.

2 CHƯƠNG 2: ĐƠN GIẢN HÓA VỚI PACKAGE PURRR

Lặp lại là một cách hiệu quả để máy tính thực hiện công việc cho bạn. Nó cũng có thể là một lĩnh vực mã hóa dễ mắc nhiều lỗi chính tả và lỗi đơn giản. Gói purrr giúp đơn giản hóa việc lặp lại để bạn có thể tập trung vào bước tiếp theo, thay vì tìm lỗi chính tả.

2.1 Sức mạnh của phép lặp

2.1.1 Giới thiệu về phép lặp

Hãy tưởng tượng rằng bạn cần đọc hàng trăm tệp có cấu trúc tương tự và thực hiện một hành động trên chúng. Bạn không muốn viết hàng trăm dòng mã lặp đi lặp lại để đọc trong tất cả các tệp hoặc để thực hiện hành động. Thay vào đó, bạn muốn lặp lại chúng. Lặp lại là quá trình thực hiện cùng một quy trình cho nhiều đầu vào. Khả năng lặp lại là điều quan trọng để làm cho mã của bạn hiệu quả và mạnh mẽ khi làm việc với danh sách. Các nhà dịch tễ học thường phải phân tích lặp lại trên các phân nhóm như quốc gia, quận hoặc nhóm tuổi. Đây chỉ là một vài trong số rất nhiều tình huống yêu cầu việc lặp lại. Mã hóa các thao tác lặp lại của bạn bằng cách sử dụng các phương pháp bên dưới sẽ giúp bạn thực hiện các tác vụ lặp đi lặp lại như vậy nhanh hơn, giảm khả năng xảy ra lỗi và giảm độ dài code.

Chương này sẽ giới thiệu hai cách tiếp cận đối với các thao tác lặp lại: sử dụng các vòng lặp for và package purrr.

vòng lặp for lặp lại code trên một loạt đầu vào, nhưng ít phổ biến hơn trong R so với các ngôn ngữ lập trình khác. Tuy nhiên, chúng tôi giới thiệu chúng ở đây như một công cụ học tập và tham khảo Package purrr là phương pháp tiếp cận tidyverse đối với các thao tác lặp lại - nó hoạt động bằng cách “maps” (áp dụng) một hàm trên nhiều đầu vào (giá trị, cột, datasets, v.v.) Trong chương này, chúng tôi sẽ lấy một số ví dụ như:

  • Nhập và xuất nhiều tệp
  • Tạo các đường cong dịch bệnh cho nhiều tỉnh
  • Chạy T-tests cho nhiều cột trong dataframe Trong phần purrr, chúng tôi cũng sẽ cung cấp một số ví dụ về cách tạo và xử lý danh sách lists.

2.1.2 Chuẩn bị

Nhập dữ liệu từ: https://drive.google.com/open?id=1OXW-hpweD6BfwhP2-PfEDNoHmXvptlHd&usp=drive_copy

Tệp chứa các ca bệnh và ca tử vong do dịch Ebola được xác nhận từ tháng 8 năm 2014 đến tháng 3 năm 2016 trên toàn thế giới. Gồm có 4 biến với 2485 quan sát:

  1. Đất nước

  2. Ngày khảo sát

  3. Ca bệnh

  4. Ca tử vong

library(readr)
linelist <- read_csv("D:\\\\ebola_2014_2016_clean.csv")
## Rows: 2485 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): Country
## dbl  (2): Cumulative no. of confirmed, probable and suspected cases, Cumulat...
## date (1): Date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
linelist
## # A tibble: 2,485 × 4
##    Country      Date       Cumulative no. of confirmed,…¹ Cumulative no. of co…²
##    <chr>        <date>                              <dbl>                  <dbl>
##  1 Guinea       2014-08-29                            648                    430
##  2 Nigeria      2014-08-29                             19                      7
##  3 Sierra Leone 2014-08-29                           1026                    422
##  4 Liberia      2014-08-29                           1378                    694
##  5 Sierra Leone 2014-09-05                           1261                    491
##  6 Nigeria      2014-09-05                             22                      8
##  7 Liberia      2014-09-05                           1871                   1089
##  8 Guinea       2014-09-05                            812                    517
##  9 Senegal      2014-09-05                              1                      0
## 10 Senegal      2014-09-08                              3                      0
## # ℹ 2,475 more rows
## # ℹ abbreviated names:
## #   ¹​`Cumulative no. of confirmed, probable and suspected cases`,
## #   ²​`Cumulative no. of confirmed, probable and suspected deaths`
names(linelist) <- c('Country','Date','Cases','Deaths')

2.1.3 Vòng lặp for trong R

Vòng lặp for không được nhấn mạnh trong R, nhưng phổ biến trong các ngôn ngữ lập trình khác. Khi mới bắt đầu, chúng có thể hữu ích để học và thực hành vì chúng dễ “khám phá”, “gỡ lỗi” hơn và nắm bắt chính xác những gì đang xảy ra cho mỗi lần lặp, đặc biệt là khi bạn chưa cảm thấy thoải mái khi viết các hàm của riêng mình.

Cấu phần cốt lõi Một vòng lặp for có ba phần cốt lõi:

  • Chuỗi các phần tử cần lặp lại
  • Các thao tác để tiến hành cho mỗi phần tử trong chuỗi
  • Vùng chứa cho kết quả (tùy chọn) Cú pháp cơ bản là: for (phần tử trong chuỗi) {các thao tác thực hiện với phần tử}. Lưu ý dấu ngoặc đơn và dấu ngoặc nhọn. Kết quả có thể được in ra console hoặc được lưu trữ trong một đối tượng vùng chứa.

Dưới đây là một ví dụ đơn giản về vòng lặp for.

for (num in c(1,2,3,4,5)) {  
  print(num + 2)            
}                           
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7

sau khi thự hiện câu lệnh trên R ta có được kết quả chạy từ 1 đến 5 và cộng thêm 2 của từng phần tử

Chuỗi

Đây là phần “for” của vòng lặp for - các thao tác sẽ chạy “cho (for)” từng phần tử trong chuỗi. Chuỗi có thể là một loạt các giá trị (ví dụ: tên của khu vực pháp lý, bệnh, tên cột, phần tử danh sách, v.v.) hoặc nó có thể là một chuỗi các số liên tiếp (ví dụ: 1,2,3,4,5). Mỗi cách tiếp cận được mô tả dưới đây có các tiện ích riêng của chúng.

Cấu trúc cơ bản của biểu thức chuỗi là item in vector.

Bạn có thể viết bất kỳ ký tự hoặc từ nào thay cho “item” (ví dụ: “i”, “num”, “hosp”, “district”, v.v.). Giá trị của “item” này thay đổi theo từng lần lặp lại của vòng lặp, tiếp tục qua từng giá trị trong vector. Vector có thể là các giá trị ký tự, tên cột hoặc có thể là một chuỗi số - đây là những giá trị sẽ thay đổi theo mỗi lần lặp. Bạn có thể sử dụng chúng trong các thao tác vòng lặp for bằng cách sử dụng thuật ngữ “item”. Ví dụ: chuỗi giá trị ký tự

Trong ví dụ này, một vòng lặp được thực hiện cho mỗi giá trị được xác định trước trong một vector ký tự của tên đất nước

# make vector of the country names
country_names <- unique(linelist$Country)
country_names # print
##  [1] "Guinea"                   "Nigeria"                 
##  [3] "Sierra Leone"             "Liberia"                 
##  [5] "Senegal"                  "United States of America"
##  [7] "Spain"                    "Mali"                    
##  [9] "United Kingdom"           "Italy"

Chúng tôi đã chọn thuật ngữ hosp để đại diện cho các giá trị từ vector country_names. Đối với lần lặp đầu tiên của vòng lặp, giá trị của hosp sẽ là country_names[[1]]. Đối với vòng lặp thứ hai, nó sẽ là country_names[[2]]. Và cứ như thế…

for (hosp in country_names)

Ví dụ: chuỗi tên cột

Đây là một biến thể của chuỗi ký tự ở trên, trong đó tên của một đối tượng R hiện có được trích xuất và trở thành vector. Ví dụ, tên cột của dataframe. Trong code hoạt động của vòng lặp for, tên cột có thể được sử dụng để lập chỉ mục (tập hợp con) dataframe ban đầu của chúng.

Dưới đây, chuỗi là names() (tên cột) của dataframe linelist. Tên “item” của chúng ta là col, sẽ đại diện cho từng tên cột khi các vòng lặp diễn ra.

Với ví dụ này, chúng tôi bao gồm code thao tác bên trong vòng lặp for, được chạy cho mọi giá trị trong chuỗi. Trong code này, các giá trị trình tự (tên cột) được sử dụng để chỉ mục (tập hợp con) từng phần tử một trong linelist. Như đã dạy trong chương R cơ bản, dấu ngoặc vuông kép [[]] được sử dụng cho tập hợp con. Cột kết quả được chuyển đến is.na(), sau đó đến sum() để tạo ra số giá trị trong cột bị thiếu. Kết quả được in ra console - một số cho mỗi cột.

Một lưu ý về lập chỉ mục với tên cột - bất cứ khi nào tham chiếu đến chính cột đó, đừng chỉ viết “col”! col chỉ đại diện cho tên cột ký tự! Để tham chiếu đến toàn bộ cột, bạn phải sử dụng tên cột dưới dạng chỉ mục trên linelist thông qua linelist[[col]].

for (col in names(linelist)){       
  print(sum(is.na(linelist[[col]])))
}
## [1] 0
## [1] 0
## [1] 8
## [1] 0

Dãy số

Theo cách tiếp cận này, dãy số là một chuỗi các số liên tiếp. Do đó, giá trị của “item” không phải là giá trị ký tự (ví dụ: “Country” hoặc “date”) mà là một số. Điều này rất hữu ích cho việc lặp qua các dataframes, vì bạn có thể sử dụng số “item” bên trong vòng lặp for để lập chỉ mục dataframe theo số hàng.

Ví dụ: giả sử bạn muốn lặp qua mọi hàng trong dataframe của mình và trích xuất thông tin nhất định. “Item” của bạn sẽ là số hàng số. Thông thường, “item” trong trường hợp này được viết là i.

Quá trình vòng lặp for có thể được giải thích bằng lời là “đối với mọi mục trong chuỗi số từ 1 đến tổng số hàng trong dataframe của tôi, hãy thực hiện X”. Đối với lần lặp đầu tiên của vòng lặp, giá trị của “item” i sẽ là 1. Đối với lần lặp thứ hai,i sẽ là 2, v.v.

Đây là hình thức của chuỗi trong code: for (i in 1:nrow(linelist)) {OPERATIONS CODE} trong đó i đại diện cho “item”và 1:nrow(linelist) tạo ra một chuỗi liên tiếp số từ 1 đến số hàng trong linelist.

for (i in 1:nrow(linelist)){} 

Nếu bạn muốn chuỗi là số, nhưng bạn đang bắt đầu từ một vector (không phải dataframe), hãy sử dụng hàm tắt seq_along() để trả về một dãy số cho mỗi phần tử trong vector. Ví dụ: for (i in seq_along(hospital_names) {OPERATIONS CODE}.

Đoạn code dưới đây thực sự trả về các số, sẽ trở thành giá trị của i trong vòng lặp tương ứng của chúng

seq_along(country_names)
##  [1]  1  2  3  4  5  6  7  8  9 10

Một lợi thế của việc sử dụng các số trong chuỗi là cũng dễ dàng sử dụng số i để lập chỉ mục vùng chứa lưu trữ các kết quả đầu ra của vòng lặp.

2.1.4 Kiểm tra vòng lặp for

Để kiểm tra vòng lặp của mình, bạn có thể chạy lệnh để gán tạm thời “item”, chẳng hạn như i <- 10 hoặc hosp <- “Country”. Thực hiện việc này bên ngoài vòng lặp và sau đó chỉ chạy code thao tác của bạn (code trong dấu ngoặc nhọn) để xem liệu kết quả mong đợi có được tạo ra hay không.

2.1.5 Lặp lại biểu đồ

chúng ta hãy cố gắng vẽ biểu đồ đường cong dịch bệnh cho mỗi đất nước. bằng cách sử dụng package incidence2 như bên dưới:

# Convert the Date column to character vector (if not already)
linelist$Date <- as.character(linelist$Date)

# Create the incidence object
outbreak <- incidence2::incidence(
  x = linelist,          
  date_index 
 
= "Date",   
  interval = "week",    
  groups
= "Country"     
)

# Plot 
plot(outbreak,                      
     fill = "Country",               
     color = "black",                
     title
= "Biểu đồ dịch bệnh" 
)
## Using more colors (10) than this palette can handle (6); some colors will be interpolated.
## Consider using `muted` palette instead?

Qua đồ thị trên, ta thấy tình hình dịch bệnh gây thiệt hại lớn về người và gián đoạn kinh tế xã hội trong khu vực, chủ yếu ở Guinea, Italy và Liberia. Được ghi nhận ở Guinea vào tháng 12 năm 2013. Sau đó, bệnh lây lan sang Liberia và Sierra Leone lân cận, với những đợt bùng phát nhỏ xảy ra ở những nơi khác.

2.1.6 Theo dõi tiến trình của một vòng lặp

Một vòng lặp có nhiều lần lặp có thể chạy trong nhiều phút hoặc thậm chí hàng giờ. Do đó, có thể hữu ích khi in tiến trình ra R console. Câu lệnh if dưới đây có thể được đặt trong các thao tác vòng lặp để in mỗi số thứ 100. Chỉ cần điều chỉnh nó để i là “item” trong vòng lặp của bạn.

for (i in seq_len(nrow(linelist))){
    if(i %% 100==0){   
    print(i)
}}
## [1] 100
## [1] 200
## [1] 300
## [1] 400
## [1] 500
## [1] 600
## [1] 700
## [1] 800
## [1] 900
## [1] 1000
## [1] 1100
## [1] 1200
## [1] 1300
## [1] 1400
## [1] 1500
## [1] 1600
## [1] 1700
## [1] 1800
## [1] 1900
## [1] 2000
## [1] 2100
## [1] 2200
## [1] 2300
## [1] 2400

2.2 Các hàm của package Purrr

Khi phân tích dữ liệu phức tạp, ta thường xuyên phải thực hiện một nhóm các phân tích tương tự nhau cho các nhóm dữ liệu khác nhau. Việc sử dụng các hàm làm đơn vị thao tác cơ bản và phối hợp các hàm với nhau được gọi là lập trình chức năng hàm (functional programming). Để đơn giản, ta xét ví dụ sau.

Sử dụng tập dữ liệu iris, với mỗi nhóm của Species, xây dựng mô hình hồi quy giữa Sepal.Length và Petal.Length, so sánh giá trị r.squared giữa các mô hình.

Với cách làm thông thường, ta sẽ phải thức hiện theo thứ tự sau:

  • Tạo các data.frame cho từng giá trị của Species
  • Với mỗi data.frame vừa tạo, xây dựng mô hình lm
  • Với mỗi mô hình vừa tạo, chiết xuất giá trị r.squared và lưu vào một data.frame

Cách triển khai trên có thể sử dụng vòng lặp trong R với phương án như sau

library(dplyr)
library(purrr)
category <- iris$Species %>% levels %>% as.character()
model_result <- data.frame()
for (i in category){
  df <- iris %>% filter(Species == i)
  model <- lm(Sepal.Length ~ Sepal.Width, data = df)
  model_summary <- summary(model)
  df_temp <- data.frame(species = i,
                        r.square = model_summary$r.squared)
  model_result <- bind_rows(model_result, df_temp)
}
head(model_result)
##      species  r.square
## 1     setosa 0.5513756
## 2 versicolor 0.2765821
## 3  virginica 0.2090573

Tuy nhiên, với lập trình chức năng hàm, ta có thể làm rất đơn giản như sau.

library(purrr)
iris %>% 
  split(.$Species) %>% 
  map(~lm(Sepal.Length ~ Sepal.Width, data = .)) %>% 
  map(summary) %>% 
  map_dbl("r.squared")
##     setosa versicolor  virginica 
##  0.5513756  0.2765821  0.2090573

2.2.1 Lập trình chức năng hàm cơ bản với map qua package purrr

Trong bài viết này, chúng ta sẽ tìm hiểu các cách thức cơ bản lập trình chức năng hàm cơ bản với map qua package purrr. Việc nắm vững kiến thức và kỹ năng lập trình hàm có rất nhiều ứng dụng trong công việc phân tích, giúp giảm thiểu rất lớn thời gian phân tích, làm cho quá trình phân tích mạch lạc hơn rất nhiều trong các bài toán khám phá dữ liệu

Một hàm cốt lõi của purrr là map(), hàm này “maps” (áp dụng) một hàm cho từng phần tử đầu vào của danh sách/vector bạn cung cấp.

Cú pháp cơ bản là map(.x = SEQUENCE, .f = HÀM, CÁC ĐỐI SỐ KHÁC). Chi tiết hơn như sau:

.x = là các đầu vào mà hàm .f sẽ được áp dụng lặp đi lặp lại - ví dụ: vector của tên các khu vực pháp lý, các cột trong data frame hoặc danh sách các data frame .f = là hàm áp dụng cho từng phần tử của đầu vào .x - nó có thể là một hàm như print() đã tồn tại hoặc một hàm tùy chỉnh mà bạn xác định. Hàm thường được viết sau dấu ngã ~ .

Thêm một số lưu ý về cú pháp:

Nếu hàm không cần chỉ định thêm đối số, nó có thể được viết không có dấu ngoặc đơn và không có dấu ngã (ví dụ: .f = mean). Để cung cấp các đối số sẽ có cùng giá trị cho mỗi lần lặp, hãy cung cấp chúng trong map() nhưng bên ngoài đối số .f =, chẳng hạn như na.rm = T trong map(.x = my_list, .f = mean, na.rm=T). Bạn có thể sử dụng .x (hoặc đơn giản là .) bên trong hàm .f = làm trình giữ chỗ cho giá trị .x của lần lặp đó Sử dụng cú pháp dấu ngã (~) để kiểm soát hàm nhiều hơn - viết hàm như bình thường với dấu ngoặc đơn, chẳng hạn như: map(.x = my_list, .f = ~mean(., na.rm = T)). Sử dụng cú pháp này đặc biệt nếu giá trị của một đối số sẽ thay đổi mỗi lần lặp lại hoặc nếu nó là chính giá trị .x

Hàm map làm hàm tổng quát, ngoài ra, map còn có các biến thể chính sau

Câu lênh:

  • map(): tạo thành một danh sách.

  • map_lgl(): tạo ra một vectơ logic.

  • map_int(): tạo thành một vectơ số nguyên.

  • map_dbl(): tạo thành một vectơ kép

  • map_chr(): tạo thành một vectơ ký tự.

# Dạng list
iris %>% map(class)
## $Sepal.Length
## [1] "numeric"
## 
## $Sepal.Width
## [1] "numeric"
## 
## $Petal.Length
## [1] "numeric"
## 
## $Petal.Width
## [1] "numeric"
## 
## $Species
## [1] "factor"
# Dạng char
iris %>% map_chr(class)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
##    "numeric"    "numeric"    "numeric"    "numeric"     "factor"
# Dạng data.frame
iris %>% map_df(class)
## # A tibble: 1 × 5
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##   <chr>        <chr>       <chr>        <chr>       <chr>  
## 1 numeric      numeric     numeric      numeric     factor

Map theo điều kiện với map_ifmap_at

Tương tự với map, nhóm map_if và map_at cho phép tính toán theo điều kiện hoặc vị trí của list. Xem ví dụ sau.

# map_if
iris %>% 
  map_if(is.numeric, as.character) %>% 
  as.data.frame %>% 
  str
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: chr  "5.1" "4.9" "4.7" "4.6" ...
##  $ Sepal.Width : chr  "3.5" "3" "3.2" "3.1" ...
##  $ Petal.Length: chr  "1.4" "1.4" "1.3" "1.5" ...
##  $ Petal.Width : chr  "0.2" "0.2" "0.2" "0.2" ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
# map_at
iris %>% 
  map_at(c(1,2), as.character) %>% 
  str
## List of 5
##  $ Sepal.Length: chr [1:150] "5.1" "4.9" "4.7" "4.6" ...
##  $ Sepal.Width : chr [1:150] "3.5" "3" "3.2" "3.1" ...
##  $ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Lưu ý: Với trường hợp có hai biến đầu vào, có thể sử dụng nhóm hàm map2. Ví dụ

# Không chạy
map_dbl(1:3, 4:6, sum)
map2_dbl(1:3, 4:6, sum)
## [1] 5 7 9

Với các trường hợp phức tạp, ta cần vận dụng linh hoạt.

Ví dụ: Với mỗi dòng trong iris , tách thành dataframe riêng và xoay chiều dữ liêu. Tên các cột trở thành biến attribute, giá trị các cột trở thành biến value.

library(tidyverse)
get_data <- function(data, i){
 df <- data %>% 
    slice(i) %>% t %>% 
    as.data.frame
 result <- data.frame(attribute = rownames(df),
                      value = df[,1])
 rownames(result) <- NULL
 return(result)
}
get_data(mtcars, 3)
##    attribute  value
## 1        mpg  22.80
## 2        cyl   4.00
## 3       disp 108.00
## 4         hp  93.00
## 5       drat   3.85
## 6         wt   2.32
## 7       qsec  18.61
## 8         vs   1.00
## 9         am   1.00
## 10      gear   4.00
## 11      carb   1.00
get_data(iris, 1)
##      attribute  value
## 1 Sepal.Length    5.1
## 2  Sepal.Width    3.5
## 3 Petal.Length    1.4
## 4  Petal.Width    0.2
## 5      Species setosa
map2(replicate(3, iris, simplify = F),
     c(1:3), get_data)
## [[1]]
##      attribute  value
## 1 Sepal.Length    5.1
## 2  Sepal.Width    3.5
## 3 Petal.Length    1.4
## 4  Petal.Width    0.2
## 5      Species setosa
## 
## [[2]]
##      attribute  value
## 1 Sepal.Length    4.9
## 2  Sepal.Width      3
## 3 Petal.Length    1.4
## 4  Petal.Width    0.2
## 5      Species setosa
## 
## [[3]]
##      attribute  value
## 1 Sepal.Length    4.7
## 2  Sepal.Width    3.2
## 3 Petal.Length    1.3
## 4  Petal.Width    0.2
## 5      Species setosa

2.2.1.1 Lặp lại n lần một phép tính

Dưới đây là dữ liệu điểm thi THPT năm 2018. Tải dữ liệu và trích xuất riêng điểm môn Toán.

library(tidyverse)
df=read.csv("https://raw.githubusercontent.com/kinokoberuji/R-Tutorials/master/Provinces.csv")%>%as_tibble()

df%>%filter(Math!="NA")%>%dplyr::select(Math,Province)->math_df

Object math_df có thể xem như 1 quần thể, với đại lượng cần khảo sát là điểm thi môn Toán. Kích thước khá lớn của quần thể này nhằm chứng tỏ tốc độ tính toán rất nhanh khi sử dụng hàm map

Ví dụ đầu tiên, ta muốn đếm số thí sinh cho mỗi tỉnh thành. Phép đếm này có thể thực hiện dễ dàng bằng hàm group_by và tally của package dplyr:

math_df%>%group_by(Province)%>%tally()
## # A tibble: 59 × 2
##    Province              n
##    <chr>             <int>
##  1 BA RIA - VUNG TAU 11831
##  2 BAC GIANG         19526
##  3 BAC KAN            2831
##  4 BAC LIEU           5335
##  5 BAC NINH          14790
##  6 BEN TRE           11705
##  7 BINH DINH         17724
##  8 BINH DUONG        11263
##  9 BINH PHUOC        10180
## 10 BINH THUAN        11675
## # ℹ 49 more rows

Tuy nhiên ta còn có thể làm bằng hàm map: Do kết quả đếm là 1 số nguyên, nên ta dùng hàm map_int

math_df%>%split(.$Province)%>%
 map_int(~nrow(.x))
## BA RIA - VUNG TAU         BAC GIANG           BAC KAN          BAC LIEU 
##             11831             19526              2831              5335 
##          BAC NINH           BEN TRE         BINH DINH        BINH DUONG 
##             14790             11705             17724             11263 
##        BINH PHUOC        BINH THUAN            CA MAU           CAN THO 
##             10180             11675              9222             13013 
##          CAO BANG           DA NANG           DAK LAK          DAK NONG 
##              4360              6094             21884              6327 
##         DIEN BIEN          DONG NAI         DONG THAP           GIA LAI 
##              5372             28595             14331             12719 
##                HA          HA GIANG            HA NAM            HA NOI 
##             16211              3089              8657             37993 
##         HAI DUONG         HAI PHONG         HAU GIANG       HO CHI MINH 
##             19933              5094              6176             78035 
##          HOA BINH          HUNG YEN        KIEN GIANG           KON TUM 
##              8903             12860             13413              4425 
##         KHANH HOA          LAI CHAU          LAM DONG          LANG SON 
##             13471              3204             14928              8813 
##           LAO CAO           LONG AN          NAM DINH         NINH BINH 
##              6187             14045              5086              9569 
##        NINH THUAN           PHU THO           PHU YEN        QUANG BINH 
##              5738             13633             10690              9546 
##         QUANG NAM        QUANG NGAI         QUANG TRI         SOC TRANG 
##             17463             12598              7775              9300 
##          TAY NINH        TIEN GIANG       TUYEN QUANG         THAI BINH 
##              8834             14088              7542             21401 
##       THAI NGUYEN         THANH HOA  THUA THIEN - HUE          TRA VINH 
##              8965             14038             12303              8152 
##         VINH LONG         VINH PHUC           YEN BAI 
##             10550             12565              6974

Phép tính này có thể phức tạp hơn, như trong ví dụ sau đây , dùng 1 hàm map để thi hành hàng loạt quy trình ước tính bách Phân vị thứ 50,75,90,95,97.5 và 99 theo phương pháp Harell Davis cho điểm số mô toán ở mỗi tỉnh thành:

math_df %>% split(.$Province) %>% 
  map(.,~ Hmisc::hdquantile(.x$Math,probs =c(0.5,0.75,0.9,0.95,0.975,0.99)))->hdquantile
hdquantile
## $`BA RIA - VUNG TAU`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.400000 6.199905 6.800000 7.200000 7.563219 7.914723 
## 
## $`BAC GIANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600000 5.792610 6.600047 7.189397 7.590387 7.999703 
## 
## $`BAC KAN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.802286 4.796259 5.620354 6.176663 6.656565 7.312336 
## 
## $`BAC LIEU`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.999999 5.799994 6.416026 6.839013 7.246998 7.757645 
## 
## $`BAC NINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000000 6.200000 7.008658 7.589541 7.968934 8.289604 
## 
## $`BEN TRE`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000639 5.803305 6.587759 6.964479 7.204021 7.601284 
## 
## $`BINH DINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000000 5.999996 6.687782 7.184730 7.424387 7.835696 
## 
## $`BINH DUONG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.200003 6.000000 6.637496 7.005745 7.401851 7.818840 
## 
## $`BINH PHUOC`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000000 5.999208 6.602674 7.031692 7.400041 7.799399 
## 
## $`BINH THUAN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000279 5.800391 6.558797 6.915288 7.202357 7.604272 
## 
## $`CA MAU`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600074 5.597877 6.204399 6.608821 7.000109 7.389151 
## 
## $`CAN THO`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.948913 5.800121 6.600000 7.001230 7.406297 7.814570 
## 
## $`CAO BANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.612234 4.796204 5.786611 6.268932 6.638211 7.109105 
## 
## $`DA NANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.200024 6.199945 6.929389 7.380615 7.680195 8.183563 
## 
## $`DAK LAK`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600000 5.799993 6.600000 7.000304 7.400001 7.800323 
## 
## $`DAK NONG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.689001 5.651065 6.404651 6.809554 7.213390 7.710722 
## 
## $`DIEN BIEN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.999999 4.997339 5.846457 6.403061 6.894208 7.574555 
## 
## $`DONG NAI`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000002 5.821474 6.600000 6.998716 7.215888 7.605066 
## 
## $`DONG THAP`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.005363 5.835720 6.599980 6.999996 7.308687 7.634320 
## 
## $`GIA LAI`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600007 5.662857 6.528453 6.999836 7.390578 7.795967 
## 
## $HA
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600001 5.793788 6.617909 7.200128 7.601877 8.000393 
## 
## $`HA GIANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.199973 4.197598 5.259416 6.229332 7.381538 8.859825 
## 
## $`HA NAM`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.389075 6.222072 7.000000 7.400356 7.798130 8.089240 
## 
## $`HA NOI`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.518258 6.400000 7.200000 7.600000 7.999888 8.232922 
## 
## $`HAI DUONG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.002566 6.132803 6.879413 7.399975 7.784518 8.132778 
## 
## $`HAI PHONG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.399776 6.306874 7.019763 7.532636 7.835441 8.348669 
## 
## $`HAU GIANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.497970 5.400000 6.199130 6.597712 6.979154 7.352020 
## 
## $`HO CHI MINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.402175 6.200000 6.847869 7.259305 7.600000 8.000000 
## 
## $`HOA BINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.400000 4.568732 5.942534 6.727072 7.382928 8.085400 
## 
## $`HUNG YEN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.999759 6.114800 7.000000 7.520065 7.817950 8.208815 
## 
## $`KIEN GIANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600000 5.580261 6.200390 6.601734 7.000125 7.400027 
## 
## $`KON TUM`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.740124 5.829327 6.759417 7.208534 7.582251 8.022827 
## 
## $`KHANH HOA`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000000 5.999999 6.608153 7.029322 7.400095 7.799822 
## 
## $`LAI CHAU`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.199999 4.996757 5.796905 6.232124 6.623815 7.289272 
## 
## $`LAM DONG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.200000 6.000000 6.600000 7.000000 7.370035 7.779855 
## 
## $`LANG SON`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.800000 4.799994 5.796326 6.355083 6.801790 7.343663 
## 
## $`LAO CAO`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.424726 5.448715 6.399516 6.805285 7.243621 7.705071 
## 
## $`LONG AN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.000000 5.800012 6.599788 6.999757 7.333981 7.608846 
## 
## $`NAM DINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.629939 6.573560 7.200025 7.598604 7.894011 8.214838 
## 
## $`NINH BINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.200239 6.203598 7.084748 7.589983 7.884826 8.280619 
## 
## $`NINH THUAN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.598125 5.599795 6.397556 6.798608 7.105838 7.539479 
## 
## $`PHU THO`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.600509 5.779899 6.600007 7.098902 7.585875 8.002866 
## 
## $`PHU YEN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.799998 5.800000 6.599776 7.001897 7.404600 7.858065 
## 
## $`QUANG BINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.199971 5.372796 6.220980 6.798398 7.212988 7.762785 
## 
## $`QUANG NAM`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.800000 5.800351 6.603358 7.161329 7.542755 7.965503 
## 
## $`QUANG NGAI`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.999999 6.000000 6.799613 7.200003 7.597685 7.998864 
## 
## $`QUANG TRI`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.614974 5.800038 6.616468 7.183950 7.556546 7.896133 
## 
## $`SOC TRANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.450690 5.400004 6.202225 6.746961 7.109587 7.469744 
## 
## $`TAY NINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.001001 5.803440 6.598148 6.989110 7.238366 7.732689 
## 
## $`TIEN GIANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.318650 6.094053 6.800000 7.199996 7.451647 7.823484 
## 
## $`TUYEN QUANG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.916420 4.823591 5.807896 6.397089 6.864155 7.381700 
## 
## $`THAI BINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 5.224243 6.313677 7.063399 7.594094 7.809082 8.203210 
## 
## $`THAI NGUYEN`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.389841 5.418470 6.399962 6.976738 7.361931 7.802083 
## 
## $`THANH HOA`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.403070 5.802741 6.808442 7.399999 7.799977 8.187719 
## 
## $`THUA THIEN - HUE`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.996273 6.000006 6.800311 7.259246 7.605339 8.011145 
## 
## $`TRA VINH`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.464179 5.400100 6.199054 6.599700 6.995502 7.389520 
## 
## $`VINH LONG`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.998800 5.800000 6.426894 6.843735 7.200953 7.604226 
## 
## $`VINH PHUC`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 4.999999 6.177329 6.999957 7.402583 7.799999 8.193502 
## 
## $`YEN BAI`
##    0.500    0.750    0.900    0.950    0.975    0.990 
## 3.800545 4.960180 5.997619 6.602036 7.117912 7.639461

Ghi chú: phép ước tính phân vị theo Harell Davis có sử dụng bootstrap, nên rất phổ biến và không phụ thuộc vào đặc tính phân bố, khác với mô tả thông thường. Các bạn có thể tin chắc rằng nếu ở một tỉnh nào đó mà có HDquantile thứ 99 cao hơn trị số này cho quần thể chung, thì ở khu vực đó đã có điều bất thường xảy ra, thí dụ ở Hà Giang trị số HDQ 99 là 8.859, cao hơn rất nhiều so với cả nước (chỉ có 8.0 điểm)

Hmisc::hdquantile(math_df$Math,probs =c(0.5,0.75,0.9,0.95,0.975,0.99))
## 0.500 0.750 0.900 0.950 0.975 0.990 
##   5.0   6.0   6.8   7.2   7.6   8.0

2.2.1.2 Chọn mẫu ngẫu nhiên lặp lại

Một ví dụ khác, lần này ta làm 1 thí nghiệm: Từ quần thể 741024 thí sinh cả nước, ta sẽ chọn mẫu ngẫu nhiên từ 1000 đến 30000 cá thể và tính giá trị trung bình, trung vị, bách phân vị thứ 5 và thứ 99 cho từng mẫu.

Đây là 1 vòng lặp có nội dung: Chọn mẫu n lần với kích thước tăng dần từ 1000 : 30000, và mỗi lần xuất ra 5 chỉ số thống kê mô tả như trên.

Ta có thể dùng vòng lặp for loop để thực hiện, nhưng ở đây em dùng hàm map.

Vì mỗi hàm map chỉ làm 1 công việc duy nhất, nên quy trình cần kết nối 2 hàm map, hàm map thứ nhất làm việc chọn mẫu, hàm map thứ hai làm thống kê mô tả. Do hàm map thứ hai xuất ra 1 dataframe nên nó sẽ là hàm map_df

math_pop<-math_df$Math

sample_n=seq(from=1000, to=30000,by=1000)

s <- map(sample_n,~sample(math_pop,.x, replace = F))%>%
  map_df(~data_frame(Size=length(.x),
                     Mean=mean(.x),
                     Median=median(.x),
                     P5=quantile(.x,probs=0.05),
                     P99=quantile(.x,probs=0.99),
                     ))
## Warning: `data_frame()` was deprecated in tibble 1.1.0.
## ℹ Please use `tibble()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
s
## # A tibble: 30 × 5
##     Size  Mean Median    P5   P99
##    <int> <dbl>  <dbl> <dbl> <dbl>
##  1  1000  4.92    5     2.4  8   
##  2  2000  4.83    4.8   2.6  7.80
##  3  3000  4.89    5     2.6  8   
##  4  4000  4.87    5     2.4  8   
##  5  5000  4.90    5     2.4  8   
##  6  6000  4.92    5     2.6  8   
##  7  7000  4.87    5     2.4  8   
##  8  8000  4.87    5     2.4  8   
##  9  9000  4.89    5     2.4  8   
## 10 10000  4.88    5     2.4  8   
## # ℹ 20 more rows

sử dụng hàm ggplot để vẽ biểu đồ

s%>%ggplot(aes(x=Size))+
  geom_pointrange(aes(y=Median,
                      ymin=P5,ymax=P99,
                      col=Size),
                  show.legend = F)+
  theme_bw()+
  scale_y_continuous(limits = c(0,10))+
  geom_hline(yintercept = median(math_pop),linetype=2,col="red")+
  coord_flip()

2.2.1.3 Dựng hàng loạt mô hình khác nhau cho cùng dữ liệu

Trong ví dụ tiếp theo, em sẽ dùng bộ dữ liệu gapminder, mục tiêu là dựng 5 mô hình khác nhau ước lượng tuổi thọ trung bình theo dân số, thu nhập bình quân, năm và yếu tố địa lý, cho từng quốc gia

library(gapminder)
data(gapminder)

5 mô hình cần dựng có nội dung

f1 = lifeExp ~ pop
f2 = lifeExp ~ gdpPercap
f3 = lifeExp ~ pop + gdpPercap
f4 = lifeExp ~ pop + gdpPercap + year
f5 = lifeExp ~ pop + gdpPercap + year + continent

f1
## lifeExp ~ pop
f2
## lifeExp ~ gdpPercap
f3
## lifeExp ~ pop + gdpPercap
f4
## lifeExp ~ pop + gdpPercap + year
f5 
## lifeExp ~ pop + gdpPercap + year + continent

Ta có thể kết hợp 2 hàm map, hàm thứ nhất dựng 5 mô hình với 5 công thức khác nhau, hàm map thứ 2 trích xuất kết quả summary cho từng mô hình

formulas <- list(f1,f2,f3,f4,f5)

mod <- map (formulas, ~ lm(.x, data=gapminder))%>%
  map(~ summary(.x))

mod
## [[1]]
## 
## Call:
## lm(formula = .x, data = gapminder)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -35.70 -11.13   1.07  11.45  22.91 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 5.924e+01  3.243e-01 182.688  < 2e-16 ***
## pop         7.904e-09  2.943e-09   2.685  0.00731 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 12.89 on 1702 degrees of freedom
## Multiple R-squared:  0.004219,   Adjusted R-squared:  0.003634 
## F-statistic: 7.212 on 1 and 1702 DF,  p-value: 0.007314
## 
## 
## [[2]]
## 
## Call:
## lm(formula = .x, data = gapminder)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -82.754  -7.758   2.176   8.225  18.426 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 5.396e+01  3.150e-01  171.29   <2e-16 ***
## gdpPercap   7.649e-04  2.579e-05   29.66   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 10.49 on 1702 degrees of freedom
## Multiple R-squared:  0.3407, Adjusted R-squared:  0.3403 
## F-statistic: 879.6 on 1 and 1702 DF,  p-value: < 2.2e-16
## 
## 
## [[3]]
## 
## Call:
## lm(formula = .x, data = gapminder)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -82.754  -7.745   2.055   8.212  18.534 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 5.365e+01  3.225e-01  166.36  < 2e-16 ***
## pop         9.728e-09  2.385e-09    4.08 4.72e-05 ***
## gdpPercap   7.676e-04  2.568e-05   29.89  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 10.44 on 1701 degrees of freedom
## Multiple R-squared:  0.3471, Adjusted R-squared:  0.3463 
## F-statistic: 452.2 on 2 and 1701 DF,  p-value: < 2.2e-16
## 
## 
## [[4]]
## 
## Call:
## lm(formula = .x, data = gapminder)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -67.497  -7.075   1.121   7.701  19.640 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -4.115e+02  2.767e+01 -14.872  < 2e-16 ***
## pop          6.353e-09  2.218e-09   2.864  0.00423 ** 
## gdpPercap    6.729e-04  2.444e-05  27.529  < 2e-16 ***
## year         2.354e-01  1.400e-02  16.812  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 9.673 on 1700 degrees of freedom
## Multiple R-squared:  0.4402, Adjusted R-squared:  0.4392 
## F-statistic: 445.6 on 3 and 1700 DF,  p-value: < 2.2e-16
## 
## 
## [[5]]
## 
## Call:
## lm(formula = .x, data = gapminder)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -28.4051  -4.0550   0.2317   4.5073  20.0217 
## 
## Coefficients:
##                     Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       -5.185e+02  1.989e+01 -26.062   <2e-16 ***
## pop                1.791e-09  1.634e-09   1.096    0.273    
## gdpPercap          2.985e-04  2.002e-05  14.908   <2e-16 ***
## year               2.863e-01  1.006e-02  28.469   <2e-16 ***
## continentAmericas  1.429e+01  4.946e-01  28.898   <2e-16 ***
## continentAsia      9.375e+00  4.719e-01  19.869   <2e-16 ***
## continentEurope    1.936e+01  5.182e-01  37.361   <2e-16 ***
## continentOceania   2.056e+01  1.469e+00  13.995   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 6.883 on 1696 degrees of freedom
## Multiple R-squared:  0.7172, Adjusted R-squared:  0.716 
## F-statistic: 614.5 on 7 and 1696 DF,  p-value: < 2.2e-16

Bây giờ ta thay đổi hàm map thứ hai thành map_df để trích xuất riêng trị số AIC và công thức mỗi mô hình

mod <- map (formulas, ~ lm(.x, data=gapminder))%>%
  map_df(~ data_frame(formula = format(formula(.x)),
                      AIC=AIC(.x),
                      stringAsFactors=F))

mod
## # A tibble: 5 × 3
##   formula                                         AIC stringAsFactors
##   <chr>                                         <dbl> <lgl>          
## 1 lifeExp ~ pop                                13553. FALSE          
## 2 lifeExp ~ gdpPercap                          12850. FALSE          
## 3 lifeExp ~ pop + gdpPercap                    12836. FALSE          
## 4 lifeExp ~ pop + gdpPercap + year             12576. FALSE          
## 5 lifeExp ~ pop + gdpPercap + year + continent 11420. FALSE

Một thủ thuật khác, đó là nêu trực tiếp tên của thành phần cần trích xuất từ dữ liệu đầu ra của hàm map đi trước (trở thành .x của hàm map đi sau)

gapminder %>% split(.$country) %>% 
  map (.,~ lm(lifeExp ~ year, data=.x))%>%
map(~ summary(.x))%>%
  map_dbl("r.squared")
##              Afghanistan                  Albania                  Algeria 
##               0.94771226               0.91057777               0.98511721 
##                   Angola                Argentina                Australia 
##               0.88781463               0.99556810               0.97964774 
##                  Austria                  Bahrain               Bangladesh 
##               0.99213401               0.96673981               0.98936087 
##                  Belgium                    Benin                  Bolivia 
##               0.99454056               0.96660199               0.98454156 
##   Bosnia and Herzegovina                 Botswana                   Brazil 
##               0.89569829               0.03402340               0.99804741 
##                 Bulgaria             Burkina Faso                  Burundi 
##               0.54654217               0.91871050               0.76599597 
##                 Cambodia                 Cameroon                   Canada 
##               0.63869222               0.68017839               0.99638552 
## Central African Republic                     Chad                    Chile 
##               0.49324448               0.87237550               0.98279710 
##                    China                 Colombia                  Comoros 
##               0.87127734               0.96787344               0.99685076 
##         Congo, Dem. Rep.              Congo, Rep.               Costa Rica 
##               0.34820278               0.51966079               0.96174767 
##            Cote d'Ivoire                  Croatia                     Cuba 
##               0.28337240               0.93243047               0.92406716 
##           Czech Republic                  Denmark                 Djibouti 
##               0.91668191               0.97066797               0.97437134 
##       Dominican Republic                  Ecuador                    Egypt 
##               0.97060781               0.99456626               0.99030424 
##              El Salvador        Equatorial Guinea                  Eritrea 
##               0.95567201               0.99686864               0.95727334 
##                 Ethiopia                  Finland                   France 
##               0.96850263               0.99383835               0.99762458 
##                    Gabon                   Gambia                  Germany 
##               0.81276621               0.98923562               0.98950568 
##                    Ghana                   Greece                Guatemala 
##               0.98409873               0.97196085               0.99666377 
##                   Guinea            Guinea-Bissau                    Haiti 
##               0.97831518               0.98455829               0.98761338 
##                 Honduras         Hong Kong, China                  Hungary 
##               0.97730026               0.97230183               0.79501875 
##                  Iceland                    India                Indonesia 
##               0.97032657               0.96843652               0.99711418 
##                     Iran                     Iraq                  Ireland 
##               0.99501535               0.54578420               0.98414574 
##                   Israel                    Italy                  Jamaica 
##               0.99478290               0.99336612               0.80565904 
##                    Japan                   Jordan                    Kenya 
##               0.95959563               0.96975008               0.44255729 
##         Korea, Dem. Rep.              Korea, Rep.                   Kuwait 
##               0.70306306               0.98765101               0.95235423 
##                  Lebanon                  Lesotho                  Liberia 
##               0.94172582               0.08485635               0.51175640 
##                    Libya               Madagascar                   Malawi 
##               0.98333149               0.99465364               0.83995446 
##                 Malaysia                     Mali               Mauritania 
##               0.94650639               0.99545140               0.99767430 
##                Mauritius                   Mexico                 Mongolia 
##               0.93478457               0.98520444               0.98731309 
##               Montenegro                  Morocco               Mozambique 
##               0.80186521               0.99458312               0.77427932 
##                  Myanmar                  Namibia                    Nepal 
##               0.87937750               0.43702163               0.99154171 
##              Netherlands              New Zealand                Nicaragua 
##               0.98221566               0.95358464               0.99677615 
##                    Niger                  Nigeria                   Norway 
##               0.89768664               0.87010508               0.96290057 
##                     Oman                 Pakistan                   Panama 
##               0.97479461               0.99724965               0.95119516 
##                 Paraguay                     Peru              Philippines 
##               0.98298650               0.98847401               0.99142260 
##                   Poland                 Portugal              Puerto Rico 
##               0.83966315               0.96903508               0.90781912 
##                  Reunion                  Romania                   Rwanda 
##               0.96607180               0.80556666               0.01715964 
##    Sao Tome and Principe             Saudi Arabia                  Senegal 
##               0.95525936               0.97208439               0.99054417 
##                   Serbia             Sierra Leone                Singapore 
##               0.87880538               0.96015054               0.98794751 
##          Slovak Republic                 Slovenia                  Somalia 
##               0.79174822               0.96604327               0.84442863 
##             South Africa                    Spain                Sri Lanka 
##               0.31246865               0.96489456               0.94771469 
##                    Sudan                Swaziland                   Sweden 
##               0.99214243               0.06821087               0.99548216 
##              Switzerland                    Syria                   Taiwan 
##               0.99739086               0.98416512               0.95707113 
##                 Tanzania                 Thailand                     Togo 
##               0.76421876               0.96738440               0.90580373 
##      Trinidad and Tobago                  Tunisia                   Turkey 
##               0.79800744               0.98070422               0.98533264 
##                   Uganda           United Kingdom            United States 
##               0.34215382               0.98443596               0.98592016 
##                  Uruguay                Venezuela                  Vietnam 
##               0.97683072               0.94652607               0.98941189 
##       West Bank and Gaza              Yemen, Rep.                   Zambia 
##               0.97048087               0.98117240               0.05983644 
##                 Zimbabwe 
##               0.05623196

2.2.1.4 Thực hiện quy trình bất kì trên hàng loạt mẫu

Trong thí dụ minh họa tiếp theo, em sẽ dùng dữ liệu cigarette , với nội dung diễn tiến giá bán lẻ thuốc lá tại 49 bang của Mỹ từ năm 1962 đến 1992.

library(gamlss)
nC<-detectCores()
cigarette <- read.csv("https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/master/csv/plm/Cigar.csv")
head(cigarette)
##   X state year price  pop  pop16  cpi      ndi sales pimin
## 1 1     1   63  28.6 3383 2236.5 30.6 1558.305  93.9  26.1
## 2 2     1   64  29.8 3431 2276.7 31.0 1684.073  95.4  27.5
## 3 3     1   65  29.8 3486 2327.5 31.5 1809.842  98.5  28.9
## 4 4     1   66  31.5 3524 2369.7 32.4 1915.160  96.4  29.5
## 5 5     1   67  31.6 3533 2393.7 33.4 2023.546  95.5  29.6
## 6 6     1   68  35.6 3522 2405.2 34.8 2202.486  88.4  32.0
cigarette$state=factor(cigarette$state)
cigarette$year=factor(cigarette$year-62)

2.2.1.5 Một số ví dụ đối với hàm map_*()

  • Tính trung bình
map_dbl(airquality, mean)
##     Ozone   Solar.R      Wind      Temp     Month       Day 
##        NA        NA  9.957516 77.882353  6.993464 15.803922
  • Sử dụng với toán tử pipe
airquality %>% map_int(length)
##   Ozone Solar.R    Wind    Temp   Month     Day 
##     153     153     153     153     153     153
airquality %>% map_dbl(mean, trim = 0.5)
##   Ozone Solar.R    Wind    Temp   Month     Day 
##      NA      NA     9.7    79.0     7.0    16.0
  • Sử dụng hàm ẩn (anonymous function)

Nếu viết dạng đầy đủ

models <- mtcars %>%
    split(.$cyl) %>%
    map(function(df) lm(mpg ~ wt, data = df))

Viết dạng hàm ẩn (ngắn gọn hơn)

models <- mtcars %>%
    split(.$cyl) %>%
    map(~lm(mpg ~ wt, data = .))

Chú ý: “.” thay cho object đã tạo ra trước đó (pronoun)

  • Tính r.squared của các mô hình: Dạng đầy đủ
models %>%
    map(summary) %>%
    map_dbl(~.$r.squared)
##         4         6         8 
## 0.5086326 0.4645102 0.4229655
  • Hay ngắn gọn hơn khi trích tên của object (trích từ các thành phần đã được đặt tên)
models %>%
    map(summary) %>%
    map_dbl("r.squared")
##         4         6         8 
## 0.5086326 0.4645102 0.4229655

2.2.1.6 Một số ví dụ đối với hàm pmap_* () và map2_*()

Cú pháp của hàm:

map2(.x, .y, .f, …)
pmap(.l, .f, …)

Thay vì vòng lặp đối với 1 vector (1 agrument) như hàm map thì hàm map2_() và pmap_() tạo vòng lặp với 2 hay nhiều agrument

Ví dụ với map2

mu1 <- c(5, 10, 12)
sigma1 <- c(2, 4, 5)
map2(mu1, sigma1, rnorm, n = 5) %>% str()
## List of 3
##  $ : num [1:5] 4.06 3.5 7.89 4 4.89
##  $ : num [1:5] 16.99 4.2 11.21 7.91 11.59
##  $ : num [1:5] 8.61 11.87 9.86 11 18

Ví dụ với pmap

rnorm(n, mean, sd)

TH1: Thêm list n1 vào vòng lặp

n1 <- list(1, 3, 5)
args1 <- list(n1, mu1, sigma1) # Nếu tên khác agrument thì phải sắp xếp đúng thứ tự
set.seed(158)
args1 %>%
    pmap(rnorm) %>%
    str()
## List of 3
##  $ : num 6.66
##  $ : num [1:3] 11.14 8.57 5.36
##  $ : num [1:5] 14.43 7.29 12.29 11.62 5.45

Thêm vector n2 vào vòng lặp (thay đổi 1 chút thứ tự các agrument)

n2 <- c(1, 3, 5)
args2 <- list(mean = mu1, sd = sigma1, n = n2)
set.seed(158)
args2 %>%
    pmap(rnorm) %>%
    str()
## List of 3
##  $ : num 6.66
##  $ : num [1:3] 11.14 8.57 5.36
##  $ : num [1:5] 14.43 7.29 12.29 11.62 5.45

Vòng lặp với data.frame

set.seed(158)
params <- data.frame(mean = mu1, sd = sigma1, n = n2)
params %>%
    pmap(rnorm) %>%
    str()
## List of 3
##  $ : num 6.66
##  $ : num [1:3] 11.14 8.57 5.36
##  $ : num [1:5] 14.43 7.29 12.29 11.62 5.45

Chú ý:

  • Hàm pmap lặp theo thứ tự phần tử không phân biệt là list() hoặc vector c() hay data.frame.
  • Nếu đầu vào đúng tên với agrument trong hàm thì không phân biệt việc sắp xếp agrument nào trước, agrument nào sau

2.2.2 Sửa đổi giá trị với modify()

Tương tự như map, modify cho áp dụng hàm vào một nhóm các list. Tuy nhiên, khác với map, modify cho ra kết quả với cấu trúc dữ liệu ban đâu.

library(tidyverse)
  • map đổi cấu trúc của dataframe
library(tidyverse)
# map đổi cấu trúc của dataframe
iris %>% 
  map_if(is.factor, as.character) %>% 
  str
## List of 5
##  $ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num [1:150] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : chr [1:150] "setosa" "setosa" "setosa" "setosa" ...
  • modify giữ nguyên cấu trúc
# modify giữ nguyên cấu trúc
iris %>% 
  modify_if(is.factor, as.character) %>% 
  str
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : chr  "setosa" "setosa" "setosa" "setosa" ...

2.2.3 Tạo hàm nhanh với as_mapper

as_mapper cho phép tạo hàm nhanh, đặc biệt hữu ích khi ta chỉ muốn tạo và sử dụng một hàm trong một vài trường hợp đặc biệt.

  • Công thức tổng quát:
# Với một tham số
as_mapper(~f(.x))
# Với hai tham số
as_mapper(f(.x, .y))
  • Xem các ví dụ sau:
# Cộng 10 vào mỗi giá trị
map_dbl(1:3, ~ .x+10)
## [1] 11 12 13
# Cộng hai vector với nhau
map2_dbl(1:3, 5:7, ~.x + .y)
## [1]  6  8 10
# Cách viết khác
map2_dbl(1:3, 5:7, as_mapper(~.x + .y))
## [1]  6  8 10

2.2.4 Hàm safely()

Cho kết quả là 1 list gồm 2 phần tử:

  • result: Trả về kết quả của hàm. Nếu gặp lỗi thì = NULL

  • error : Trả về lỗi của object. Nếu không gặp lỗi thì = NULL

(hàm safely tương tự với hàm try của gói base)

Ví dụ với hàm log

safe_log <- safely(log)
str(safe_log(10))
## List of 2
##  $ result: num 2.3
##  $ error : NULL
str(safe_log("a"))
## List of 2
##  $ result: NULL
##  $ error :List of 2
##   ..$ message: chr "non-numeric argument to mathematical function"
##   ..$ call   : language .Primitive("log")(x, base)
##   ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

Thiết kế của hàm safely được sử dụng tốt với hàm map

x <- list(1, 10, "a")
y <- x %>% map(safely(log))
str(y)
## List of 3
##  $ :List of 2
##   ..$ result: num 0
##   ..$ error : NULL
##  $ :List of 2
##   ..$ result: num 2.3
##   ..$ error : NULL
##  $ :List of 2
##   ..$ result: NULL
##   ..$ error :List of 2
##   .. ..$ message: chr "non-numeric argument to mathematical function"
##   .. ..$ call   : language .Primitive("log")(x, base)
##   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

Kết hợp với hàm purrr:transpose sẽ tách được riêng list thành 2 phần: 1 phần result và 1 phần là error

y <- y %>% transpose()
str(y)
## List of 2
##  $ result:List of 3
##   ..$ : num 0
##   ..$ : num 2.3
##   ..$ : NULL
##  $ error :List of 3
##   ..$ : NULL
##   ..$ : NULL
##   ..$ :List of 2
##   .. ..$ message: chr "non-numeric argument to mathematical function"
##   .. ..$ call   : language .Primitive("log")(x, base)
##   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

Đầu ra của result và error

is_ok <- y$error %>% map_lgl(is_null)
x[!is_ok]
## [[1]]
## [1] "a"
y$result[is_ok] %>% flatten_dbl() # flatten_ tương tự với hàm unlist
## [1] 0.000000 2.302585

2.2.5 Hàm possibly()

Tương tự với hàm safely() luôn cho kết quả succeed. Nhưng hàm này đơn giản hơn safely() vì nó cho phép gán giá trị default khi gặp lỗi

x <- list(1, 10, "a")
x %>% map_dbl(possibly(log, NA_real_))
## [1] 0.000000 2.302585       NA

2.2.6 Hàm quietly()

Tương tự như safely() nhưng thay vì đầu ra là error, hàm này cho ra output, messages và warnings

x <- list(1, -1)
x %>% map(quietly(log)) %>% str()
## List of 2
##  $ :List of 4
##   ..$ result  : num 0
##   ..$ output  : chr ""
##   ..$ warnings: chr(0) 
##   ..$ messages: chr(0) 
##  $ :List of 4
##   ..$ result  : num NaN
##   ..$ output  : chr ""
##   ..$ warnings: chr "NaNs produced"
##   ..$ messages: chr(0)

Chú ý: Các hàm bắt lỗi khác với các hàm thông thường ở chỗ: hàm thông thường ~ verb, còn các hàm này ~ adverb. Tức là chỉ ra cách thức thực hiện các hàm thông thường

2.2.7 Xây dựng chuỗi các hàm liên tiếp với compose():

Đầu tiên tôi ví dụ 1 đoạn code:

library(broom)
lm(Sepal.Length ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)
## # A tibble: 3 × 5
##   term              estimate std.error statistic   p.value
##   <chr>                <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept)           5.01    0.0728     68.8  1.13e-113
## 2 Speciesversicolor     0.93    0.103       9.03 8.77e- 16
## 3 Speciesvirginica      1.58    0.103      15.4  2.21e- 32
lm(Sepal.Length ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)
## # A tibble: 3 × 5
##   term              estimate std.error statistic   p.value
##   <chr>                <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept)           5.01    0.0728     68.8  1.13e-113
## 2 Speciesversicolor     0.93    0.103       9.03 8.77e- 16
## 3 Speciesvirginica      1.58    0.103      15.4  2.21e- 32
lm(Sepal.Width ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)
## # A tibble: 3 × 5
##   term              estimate std.error statistic   p.value
##   <chr>                <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept)          3.43     0.0480     71.4  5.71e-116
## 2 Speciesversicolor   -0.658    0.0679     -9.69 1.83e- 17
## 3 Speciesvirginica    -0.454    0.0679     -6.68 4.54e- 10
lm(Sepal.Length ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)
## # A tibble: 3 × 5
##   term              estimate std.error statistic   p.value
##   <chr>                <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept)           5.01    0.0728     68.8  1.13e-113
## 2 Speciesversicolor     0.93    0.103       9.03 8.77e- 16
## 3 Speciesvirginica      1.58    0.103      15.4  2.21e- 32

Nếu chúng ta viết code như trên thông thường mắt của chúng ta sẽ chỉ tập trung vào những điểm giống nhau thay vì tập trung vào những điểm thay đổi của các dòng code. Vì vậy, coder rất dễ mắc lỗi khi viết nhiều dòng tương tự nhau. Chú ý rằng nếu code lặp lại trên 2 lần thì nên sử dụng function. Với purrr thay vì viết hàm mới thì chúng ta nên modify hoặc kết hợp các hàm lại với nhau.

So sánh 2 cách viết:

tidy(lm(Sepal.Length ~ Species, data = iris))
## # A tibble: 3 × 5
##   term              estimate std.error statistic   p.value
##   <chr>                <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept)           5.01    0.0728     68.8  1.13e-113
## 2 Speciesversicolor     0.93    0.103       9.03 8.77e- 16
## 3 Speciesvirginica      1.58    0.103      15.4  2.21e- 32

và cách 2 sử dụng hàm compose:

tidy_lm <- compose(tidy, lm)
tidy_lm(Sepal.Length ~ Species, data = iris)
## # A tibble: 3 × 5
##   term              estimate std.error statistic   p.value
##   <chr>                <dbl>     <dbl>     <dbl>     <dbl>
## 1 (Intercept)           5.01    0.0728     68.8  1.13e-113
## 2 Speciesversicolor     0.93    0.103       9.03 8.77e- 16
## 3 Speciesvirginica      1.58    0.103      15.4  2.21e- 32

Như vậy, bằng cách kết hợp hàm lm và tidy (theo nguyên tắc từ phải sang trái) chúng ta có hàm tidy_lm ngắn gọn

Sử dụng as_mapper để convert 1 object sang function và cũng sử dụng hàm compose

clean_lm <- compose(as_mapper(~ arrange(.x, desc(p.value))), 
                    as_mapper(~ filter(.x, p.value < 0.05)),
                    tidy, 
                    lm)
clean_lm(Sepal.Length ~ Sepal.Width, data = iris)
## # A tibble: 1 × 5
##   term        estimate std.error statistic  p.value
##   <chr>          <dbl>     <dbl>     <dbl>    <dbl>
## 1 (Intercept)     6.53     0.479      13.6 6.47e-28

2.2.8 Partial():

Cách viết thông thường khi tính giá trị trung bình với vector có giá trị NA

mean(airquality$Ozone, na.rm = TRUE)
## [1] 42.12931

Sử dụng hàm partial:

mean_na_rm <- partial(mean, na.rm = TRUE)
mean_na_rm(airquality$Ozone)
## [1] 42.12931

kết quả tương tự đoạn code trên nhưng ngắn gọn hơn

3 CHƯƠNG 3: KẾT LUẬN

Gói purrr là một gói mạnh mẽ và linh hoạt trong R, cung cấp các công cụ lập trình chức năng để làm việc với danh sách, vectơ và các cấu trúc dữ liệu khác. Giống như bất kỳ gói nào, purrr có những ưu điểm và nhược điểm riêng.

3.1 Ưu điểm của Purrr:

  • Mô hình lập trình hàm: gói Purrr cung cấp một loạt các hàm như map(), map2(), pmap(),… giúp bạn áp dụng một hàm lên các phần tử trong danh sách hoặc vector một cách dễ dàng. Điều này cho phép bạn viết code ngắn gọn, sáng tạo và dễ đọc hơn.
  • Tự động hóa các thao tác lặp lại: Thay vì viết các vòng lặp thủ công để thực hiện các thao tác trên từng phần tử, gói Purrr cho phép bạn áp dụng các hàm một cách tự động trên toàn bộ danh sách hoặc vector một cách hiệu quả. Điều này giúp giảm đáng kể lượng mã lặp lại và tăng hiệu suất của code.

3.2 Nhược điểm của Purrr

Gói purrr trong R cung cấp nhiều lợi thế cho lập trình chức năng và làm việc với danh sách và vectơ, nhưng nó cũng có một số nhược điểm tiềm ẩn:

  • Như với bất kỳ gói nào giới thiệu một mô hình lập trình khác, có thể có một đường cong học tập cho những người không quen thuộc với các khái niệm lập trình chức năng. Người dùng đã quen với lập trình mệnh lệnh có thể thấy khó thích ứng với cách tiếp cận chức năng.

  • Phụ thuộc vào tidyverse: Mặc dù purrr là một phần của tidyverse, là tập hợp các gói được sử dụng rộng rãi, nhưng điều đó cũng có nghĩa là việc sử dụng purrr mang lại các phụ thuộc bổ sung. Nếu bạn đang làm việc trong một môi trường không khuyến khích hoặc hạn chế việc thêm các gói bổ sung, thì đây có thể là một vấn đề đáng lo ngại.

  • Thách thức gỡ lỗi: Mã gỡ lỗi chủ yếu dựa vào lập trình chức năng có thể phức tạp hơn mã mệnh lệnh truyền thống. Việc hiểu quy trình hoạt động và gỡ lỗi các bước trung gian có thể cần thêm nỗ lực.

  • Độ phức tạp trừu tượng: Việc sử dụng các hàm bậc cao hơn và các hàm như map()có thể giới thiệu các lớp trừu tượng có thể làm cho mã ít đơn giản hơn đối với một số người đọc. Cân bằng tính trừu tượng và khả năng đọc là rất quan trọng để đảm bảo khả năng duy trì mã.

  • Trong một số trường hợp, việc sử dụng purrrvà các kỹ thuật lập trình chức năng có thể dẫn đến mã dài dòng hơn so với các lựa chọn thay thế vector hóa, đơn giản hơn. Điều này có thể làm cho mã khó đọc và khó bảo trì hơn.

Bất chấp những hạn chế tiềm ẩn này, điều cần thiết là phải nhớ rằng purrrlập trình chức năng trong R mang lại lợi ích đáng kể cho nhiều vụ thao tác dữ liệu. Nó là một công cụ mạnh mẽ để làm việc với các cấu trúc dữ liệu phức tạp và có thể dẫn đến mã ngắn gọn, biểu cảm và dễ đọc hơn. Quyết định sử dụng purrr nên xem xét trường hợp sử dụng cụ thể, cấu trúc dữ liệu, yêu cầu hiệu suất cũng như mức độ quen thuộc và sở thích của nhóm phát triển. Trong nhiều trường hợp, purrr có thể nâng cao đáng kể khả năng thao tác dữ liệu và khả năng đọc mã R.

Tài liệu tham khảo:

  1. https://cran.r-project.org/web/packages/purrr/purrr.pdf
  2. Cẩm nang dịch tễ học với R
LS0tDQp0aXRsZTogIlTDjE0gSEnhu4JVIFbhu4AgUEFDS0FHRSBQVVJSUiINCmF1dGhvcjogIsSQ4bq2TkcgVEhBTkggVFLDmkMgJiBOR1VZ4buETiBD4bqoTSBOR1VZw4pOIg0KZGF0ZTogIjIwMjMtMDctMjAiDQpvdXRwdXQ6IA0KIGh0bWxfZG9jdW1lbnQ6DQogICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgIHRvYzogdHJ1ZQ0KICAgICB0b2NfZGVwdGg6IDQNCiAgICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgICBoaWdobGlnaHQ6IGthdGUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojICoqQ0jGr8agTkcgMTogR0nhu5pJIFRISeG7hlUgQ0hVTkcgVuG7gCBQQUNLQUdFIFBVUlJSKioNCg0KIyMgKirEkOG6t3QgduG6pW4gxJHhu4EqKiANCg0KVHJvbmcgdGjhu51pIMSR4bqhaSBoaeG7h24gbmF5LCBwaMOibiB0w61jaCBk4buvIGxp4buHdSDEkcOzbmcgdmFpIHRyw7IgdsO0IGPDuW5nIHF1YW4gdHLhu41uZyB2w6AgbWFuZyBs4bqhaSBuaGnhu4F1IGzhu6NpIMOtY2ggdHJvbmcgbmhp4buBdSBsxKluaCB24buxYyBraMOhYyBuaGF1LiBDw6FjIGPDtG5nIHZp4buHYyBuaMawIGzDoCB44butIGzDvSBk4buvIGxp4buHdSBjxaluZyBuaMawIHBow6JuIHTDrWNoIHbEg24gYuG6o24gbmfDoHkgY8OgbmcgcGjhu5UgYmnhur9uIHbDoCDEkcOyaSBo4buPaSBz4buxIHThu4kgbeG7iSBoxqFuLiBUcm9uZyBsxKluaCB24buxYyB44butIGzDvSB2w6AgcGjDom4gdMOtY2ggZOG7ryBsaeG7h3UsIG5nxrDhu51pIGTDuW5nIHRoxrDhu51uZyDEkeG7kWkgbeG6t3QgduG7m2kgbmhp4buBdSB0aMOhY2ggdGjhu6ljIHbDoCBraMOzIGtoxINuIHRyb25nIHZp4buHYyBsw6BtIHZp4buHYyB24bubaSBk4buvIGxp4buHdSBs4bubbiAuIEPDoWMgY8O0bmcgdmnhu4djIG5oxrAgbmjhuq1wIGThu68gbGnhu4d1LCB44butIGzDvSBk4buvIGxp4buHdSB0aMaw4budbmcgxJHDsmkgaOG7j2kgbmhp4buBdSBjw7RuZyDEkW/huqFuIHBo4bupYyB04bqhcCB2w6AgbeG6pXQgdGjhu51pIGdpYW4uIEhp4buHbiBuYXksIGjhuqd1IGjhur90IGPDoWMgbMSpbmggduG7sWMgdOG7qyBjw7RuZyB0eSDEkeG6v24gY8OhYyBkb2FuaCBuZ2hp4buHcCBs4bubbiBuaOG7jyDEkeG7gXUgc+G7rSBk4bulbmcgcGjDom4gdMOtY2ggZOG7ryBsaeG7h3UuIMSQ4buRaSB24bubaSBjw6FjIGPDtG5nIHR5IHbDoCBkb2FuaCBuZ2hp4buHcCBob+G6oXQgxJHhu5luZyB0cm9uZyBsxKluaCB24buxYyBraW5oIGRvYW5oLCBwaMOibiB0w61jaCBk4buvIGxp4buHdSBnacO6cCBo4buNIGhp4buDdSBoxqFuIHbhu4Ega2jDoWNoIGjDoG5nIGPhu6dhIG3DrG5oLiBUaMO0bmcgdGjGsOG7nW5nLCBoxrDhu5tuZyBwaMOibiB0w61jaCBk4buvIGxp4buHdSB0aHXhuq1uIHRp4buHbiB2w6AgbmhhbmggZ+G7jW4gbcOgIG3hu41pIG5nxrDhu51pIHThu5tpIGzDoCBjw6FjIOG7qW5nIGThu6VuZyBwaMOibiB0w61jaCBk4buvIGxp4buHdSB0cm9uZyB0aOG7sWMgdGnhu4VuIG5oxrAgRXhjZWwsIEJJIFRvb2xzLCBGaW5lUmVwb3J0LCBSIHbDoCBQeXRob24s4oCmxJDhurdjIGJp4buHdCBsw6AgdHJvbmcgY8OhYyBuZ8OgbmggbGnDqm4gcXVhbiB04bubaSB0w6BpIGNow61uaCwgbmfDoG5oIG3DoCB04bqtcCBo4bujcCBo4bqndSBo4bq/dCBuaOG7r25nIGtp4buDdSBk4buvIGxp4buHdSBjw7MgdGjhu4MgcGjDom4gdMOtY2ggdGjDtG5nIHF1YSBuZ8O0biBuZ+G7ryBs4bqtcCB0csOsbmggUiBoaeG7h24gbmF5LiDEkOG7gyBnaeG6o2kgcXV54bq/dCBuaOG7r25nIHRow6FjaCB0aOG7qWMgbsOgeSwgdGjDrCBjw7MgcuG6pXQgbmhp4buBdSBjw7RuZyBj4bulIGhvw6BuIHRvw6BuIGdpw7pwIHRhIGPDsyB0aOG7gyBnaeG6o2kgcXV54bq/dCDEkcaw4bujYyBt4buZdCBjw6FjaCBuaGFuaCBn4buNbiB2w6AgdGnhur90IGtp4buHbSB0aOG7nWkgZ2lhbiBoaeG7h3UgcXXhuqMuIFRyb25nIGLDoGkgdmnhur90IG7DoHksIGNow7puZyBlbSBz4bq9IGdp4bubaSB0aGnhu4d1IHbhu4EgYFB1cnJyYCwgbeG7mXQgY8O0bmcgY+G7pSBnacO6cCBjaMO6bmcgdGEgY8OzIHRo4buDIGThu4UgZMOgbmcgcGjDom4gdMOtY2ggdsOgIHjhu60gbMO9IGThu68gbGnhu4d1Lg0KDQojIyAqKkdp4bubaSB0aGnhu4d1IHbhu4EgUHVycnIqKg0KDQojIyMgTmd14buTbiBn4buRYw0KDQpHw7NpIGBQdXJycmAgxJHGsOG7o2MgcGjDoXQgdHJp4buDbiBi4bufaSBIYWRsZXkgV2lja2hhbSwgbeG7mXQgbmjDoCBwaMOibiB0w61jaCBk4buvIGxp4buHdSB2w6AgbmjDoCBwaMOhdCB0cmnhu4NuIHBo4bqnbiBt4buBbSBu4buVaSB0aeG6v25nIHRyb25nIGPhu5luZyDEkeG7k25nIFIuIEhhZGxleSBXaWNraGFtIGPFqW5nIGzDoCB0w6FjIGdp4bqjIGPhu6dhIG5oaeG7gXUgZ8OzaSBSIGtow6FjIG5oxrAgImRwbHlyIiwgImdncGxvdDIiIHbDoCAidGlkeXIiLCDEkcaw4bujYyBz4butIGThu6VuZyBy4buZbmcgcsOjaSB0cm9uZyBuZ8OgbmggcGjDom4gdMOtY2ggZOG7ryBsaeG7h3UuDQoNClZp4buHYyB04bqhbyByYSBnw7NpIFB1cnJyIG5o4bqxbSBt4bulYyDEkcOtY2ggxJHGoW4gZ2nhuqNuIGjDs2EgdsOgIHTEg25nIGPGsOG7nW5nIGto4bqjIG7Eg25nIHjhu60gbMO9IGThu68gbGnhu4d1IHRyb25nIFIsIMSR4bq3YyBiaeG7h3QgbMOgIGtoaSBsw6BtIHZp4buHYyB24bubaSBjw6FjIHBow6lwIHRvw6FuIGzhurdwIGzhuqFpIHRyw6puIGPDoWMgY+G6pXUgdHLDumMgZOG7ryBsaeG7h3UuDQoNCiMjIyBU4buVbmcgcXVhbiB24buBIGfDs2kgUHVycnINCg0KUGFja2FnZSBgUHVycnJgIHRyb25nIG5nw7RuIG5n4buvIGzhuq1wIHRyw6xuaCBSIGzDoCBt4buZdCBnw7NpIG3hu58gcuG7mW5nIG3huqFuaCBt4bq9IGPhu6dhIG5nw7RuIG5n4buvLCDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyBsw6BtIHZp4buHYyB24bubaSBjw6FjIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IGThuqFuZyB2ZWN0b3IsIGRhbmggc8OhY2ggdsOgIGPDoWMgbG/huqFpIGThu68gbGnhu4d1IGtow6FjIHRyb25nIFIuIEfDs2kgbsOgeSBnacO6cCBj4bqjaSB0aGnhu4duIHTDrW5oIGxpbmggaG/huqF0IHbDoCBoaeG7h3Ugc3XhuqV0IGtoaSB0aOG7sWMgaGnhu4duIGPDoWMgcGjDqXAgdG/DoW4gbOG6t3AgbOG6oWkgdHLDqm4gY8OhYyBwaOG6p24gdOG7rSBj4bunYSBjw6FjIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IMSRw7MuDQoNClBhY2thZ2UgYHB1cnJyYCBsw6AgMSBwYWNrYWdlIHLhuqV0IG3huqFuaCwgdGhp4bq/dCBr4bq/IHLhuqV0IHRow7RuZyBtaW5oLCBsw6BtIGNobyB2aeG7h2Mgdmnhur90IGNvZGUgdHLhu58gbMOqbiDEkcahbiBnaeG6o24sIGThu4UgaGnhu4N1LiBQYWNrYWdlIFB1cnJyIG7DoHkgY3VuZyBj4bqlcCBt4buZdCBsb+G6oXQgY8OhYyBow6BtIG5oxrAgbWFwKCksIG1hcDIoKSwgcG1hcCgpLCB3YWxrKCksIHJlZHVjZSgpLC4uLiDEkeG7gyB0aOG7sWMgaGnhu4duIGPDoWMgcGjDqXAgdG/DoW4gbOG6t3AgbOG6oWkgdHLDqm4gY8OhYyBwaOG6p24gdOG7rSBj4bunYSBjw6FjIHZlY3RvciwgZGFuaCBzw6FjaCBob+G6t2MgYuG6pXQga+G7syBj4bqldSB0csO6YyBk4buvIGxp4buHdSBuw6BvIGtow6FjLiBN4buZdCBow6BtIMSRxrDhu6NjIHPhu60gZOG7pW5nIHBo4buVIGJp4bq/biB0cm9uZyBwYWNrYWdlIGBQdXJycmAgbsOgeSBsw6AgaMOgbSBtYXAoKSwgc+G7sSByYSDEkeG7nWkgY+G7p2EgbsOzIMSRw6MgdGhheSDEkeG7lWkgaG/DoG4gdG/DoG4gY8OhY2ggdGjhu6ljIHZp4bq/dCB2w7JuZyBs4bq3cCB0cm9uZyBSLiBW4buRbiB0csaw4bubYyBraWEgdmnhur90IHbDsm5nIGzhurdwIGThu7FhIHRyw6puIDIgbmjDs20gY8O0bmcgY+G7pSBjaMOtbmggbMOgIGPDoWMgdsOybmcgbOG6t3AgY+G7lSDEkWnhu4NuIChmb3IsIHdoaWxlLCDigKYpIGhv4bq3YyBuaOG7r25nIGjDoG0gYXBwbHkgKHNhcHBseSwgbGFwcGx54oCmKSwgbmjGsG5nIGLDonkgZ2nhu50gaMOgbSBtYXAoKSBjaG8gcGjDqXAgYuG6oW4gdGhheSB0aOG6vyBuaGnhu4F1IHbDsm5nIGzhurdwIGZvciBi4bqxbmcgbcOjIG5n4bqvbiBn4buNbiBoxqFuIHbDoCBk4buFIMSR4buNYyBoxqFuLiANCg0KKioxLkjDoG0gbWFwKCkqKg0KDQpgYGANCm1hcCgueCwgfmZ1bmN0aW9uKC54LOKApikpDQpgYGANCg0KTeG7l2kgYsaw4bubYyB0csOqbiBs4buZIHRyw6xuaCBj4bunYSB2w7JuZyBs4bq3cCB0xrDGoW5nIOG7qW5nIHbhu5tpIG3hu5l0IMSRaeG7gXUga2nhu4duICgueCksIMSRxrDhu6NjIGTDuW5nIG5oxrAgMSB0aGFtIHPhu5EvZOG7ryBsaeG7h3UgdHJvbmcgaMOgbSBmIC4gVMO5eSBi4bqjbiBjaOG6pXQgY+G7p2EgZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8gKHRoxrDhu51uZyBsw6AgMSBsaXN0KSwgKC54KSBjw7MgdGjhu4MgbMOgIHZlY3RvciwgbWF0cml4LCBkYXRhZnJhbWUsIGhheSAxIGNvbiBz4buRIOKApiAsIHbDoCDEkcOzbmcgdmFpIHRyw7IgZOG7ryBsaeG7h3UgxJHhuqd1IHbDoG8sIHTDuXkgY2jhu4luaCwgdGhhbSBz4buRIMSR4buDIGPDoSBiaeG7h3QgaMOzYSBu4buZaSBkdW5nIGjDoG0gZi4NCg0KQ8OzIG5oaeG7gXUgaMOgbSBtYXAgdHJvbmcgcGFja2FnZSBgUHVycnJgLCBt4buXaSBow6BtIGPDsyBjw7RuZyBk4bulbmcga2jDoWMgbmhhdSB2w6AgeHXhuqV0IGvhur90IHF14bqjIGtow6FjIG5oYXUuIFbDrSBk4bulIDogaMOgbSBtYXAoKSBt4bq3YyDEkeG7i25oIHh14bqldCBr4bq/dCBxdeG6oyBsw6AgMSBkYW5oIHPDoWNoLCBow6BtIG1hcF9kZigpIHRy4bqjIHbhu4EgbeG7mXQga2h1bmcgZOG7ryBsaeG7h3UgYuG6sW5nIGPDoWNoIGxpw6puIGvhur90IGjDoG5nIGPDoWMgcGjhuqduIHThu60gcmnDqm5nIGzhurssIGjDoG0gbWFwX2RibCgpIGhheSBtYXBfaW50KCkgeHXhuqV0IGvhur90IHF14bqjIGzDoCAxIHZlY3RvciBz4buRIHRo4buxYyBoYXkgc+G7kSBuZ3V5w6puIC4uLg0KDQpDw6FjIGjhu40gaMOgbSBtYXBfKigpLg0KDQpDw6FjIGjDoG0gaOG7jSBtYXBfKigpIHPhu60gZOG7pW5nIGNobyB2w7JuZyBs4bq3cCAobG9vcCkuIMSQxrDhu6NjIHRoaeG6v3Qga+G6vyBy4bqldCB0aeG7h24gdsOgIGxv4bqhaSBi4buPIG5o4buvbmcgdGhhbyB0w6FjIHRo4burYSDEkeG7kWkgduG7m2kgdmnhu4djIHPhu60gZOG7pW5nIHbDsm5nIGzhurdwIGZvciwgdsOgIMSR4bqndSByYSB4w6FjIMSR4buLbmggcsO1IHLDoG5nIGjGoW4gc28gduG7m2kgdsOybmcgbOG6t3Agd2hpbGUuIEPhu6UgdGjhu4MgbmjGsCBzYXU6DQoNCsSQ4buRaSB24bubaSB2w7JuZyBs4bq3cCBmb3IgdGEgcGjhuqNpIHjDoWMgxJHhu4tuaCB0csaw4bubYyBz4buRIHBo4bqnbiB04butIGPhu6dhIG9iamVjdCAobuG6v3Uga2jDtG5nIG114buRbiBnaeG6o20gaGnhu4d1IG7Eg25nIGPhu6dhIGjDoG0pLiBDw7JuIMSR4buRaSB24bubaSBow6BtIG1hcCB0aMOsIGtow7RuZyBj4bqnbiBsw6BtIGPDtG5nIHZp4buHYyBuaMawIHbhuq15IG3DoCBraMO0bmcgYuG7iyDhuqNuaCBoxrDhu5tuZyB04bubaSBoaeG7h3UgbsSDbmcuDQoNClRoaeG6v3Qga+G6vyBwaMO5IGjhu6NwIMSR4buDIHRo4buxYyBoaeG7h24gdGhlbyBjaHUgdHLDrG5oIChwaXBlICU+JSkNCg0KQ8OhYyBow6BtIGjhu40gbWFwXyooKTogDQoNCi0gbWFwKCk6IHThuqFvIHRow6BuaCBt4buZdCBkYW5oIHPDoWNoLg0KDQotIG1hcF9sZ2woKTogdOG6oW8gcmEgbeG7mXQgdmVjdMahIGxvZ2ljLg0KDQotIG1hcF9pbnQoKTogdOG6oW8gdGjDoG5oIG3hu5l0IHZlY3TGoSBz4buRIG5ndXnDqm4uDQoNCi0gbWFwX2RibCgpOiB04bqhbyB0aMOgbmggbeG7mXQgdmVjdMahIGvDqXANCg0KLSBtYXBfY2hyKCk6IHThuqFvIHRow6BuaCBt4buZdCB2ZWN0xqEga8O9IHThu7EuDQoNCioqMi5Iw6BtIHBtYXAoKSoqOiBsw6AgbeG7mXQgaMOgbSB0cm9uZyBnw7NpICJwdXJyciIgY+G7p2EgbmfDtG4gbmfhu68gbOG6rXAgdHLDrG5oIFIuIEjDoG0gbsOgeSDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyDDoXAgZOG7pW5nIG3hu5l0IGjDoG0gbMOqbiBjw6FjIHBo4bqnbiB04butIHThu6sgbmhp4buBdSBkYW5oIHPDoWNoIGhv4bq3YyB2ZWN0b3IgY8O5bmcgbeG7mXQgbMO6Yy4gTsOzIGdpw7pwIHRo4buxYyBoaeG7h24gY8OhYyBwaMOpcCB0b8OhbiBzb25nIHNvbmcgdHLDqm4gY8OhYyBj4bqldSB0csO6YyBk4buvIGxp4buHdSDEkcOzLg0KDQpgYGANCnBtYXAoLmwsIC5mLCAuLi4pDQpgYGANCg0KLSAubCBsw6AgbeG7mXQgZGFuaCBzw6FjaCAobGlzdCkgY2jhu6lhIGPDoWMgdmVjdG9yIGhv4bq3YyBkYW5oIHPDoWNoIGNo4bupYSBjw6FjIHBo4bqnbiB04butIMSR4bqndSB2w6BvLg0KLSAuZiBsw6AgbeG7mXQgaMOgbSBtw6AgYuG6oW4gbXXhu5FuIMOhcCBk4bulbmcgbMOqbiBjw6FjIHBo4bqnbiB04butIHTGsMahbmcg4bupbmcgdOG7qyBjw6FjIGRhbmggc8OhY2ggdHJvbmcNCi0gIi4uLiIgbMOgIGPDoWMgdGhhbSBz4buRIGLhu5Ugc3VuZyBtw6AgYuG6oW4gY8OzIHRo4buDIHRydXnhu4FuIHbDoG8gaMOgbSAuZi4NCg0KSMOgbSBwbWFwKCkgcuG6pXQgaOG7r3Ugw61jaCBraGkgYuG6oW4gbXXhu5FuIMOhcCBk4bulbmcgbeG7mXQgaMOgbSBsw6puIGPDoWMgcGjhuqduIHThu60gdOG7qyBuaGnhu4F1IGRhbmggc8OhY2ggaG/hurdjIHZlY3RvciBjw7luZyBt4buZdCBsw7pjIHbDoCB0aHUgdGjhuq1wIGvhur90IHF14bqjIHRow6BuaCBt4buZdCBkYW5oIHPDoWNoLg0KDQoqKjMuSMOgbSB3YWxrKCkqKjogbMOgIG3hu5l0IGjDoG0gdHJvbmcgZ8OzaSAicHVycnIiIGPhu6dhIG5nw7RuIG5n4buvIGzhuq1wIHRyw6xuaCBSLiBIw6BtIHdhbGsoKSBy4bqldCBo4buvdSDDrWNoIGtoaSBi4bqhbiBtdeG7kW4gdGjhu7FjIGhp4buHbiBt4buZdCBow6BuaCDEkeG7mW5nIHRyw6puIGPDoWMgcGjhuqduIHThu60gY+G7p2EgbeG7mXQgY+G6pXUgdHLDumMgZOG7ryBsaeG7h3UgbcOgIGtow7RuZyBj4bqnbiB0aHUgdGjhuq1wIGvhur90IHF14bqjIHRy4bqjIHbhu4EgdOG7qyBt4buXaSBwaOG6p24gdOG7rSDEkcOzLiBUaGF5IHbDoG8gxJHDsywgbsOzIGNobyBwaMOpcCBi4bqhbiB0aOG7sWMgaGnhu4duIG3hu5l0IGjDoG5oIMSR4buZbmcgIsSRaSBxdWEiIGPDoWMgcGjhuqduIHThu60gbeG7mXQgY8OhY2ggdHXhuqduIHThu7EuDQoNCmBgYA0Kd2FsaygueCwgLmYsIC4uLikNCmBgYA0KDQotIC54IGzDoCBkYW5oIHPDoWNoIGhv4bq3YyB2ZWN0b3IgY+G6p24gw6FwIGThu6VuZyBow6BuaCDEkeG7mW5nIGhv4bq3YyBn4buNaSBow6BtIGzDqm4uDQotIC5mIGzDoCBt4buZdCBow6BtIG3DoCBi4bqhbiBtdeG7kW4gw6FwIGThu6VuZyBsw6puIHThu6tuZyBwaOG6p24gdOG7rSBj4bunYSAueC4gDQotIC4uLiBsw6AgY8OhYyDEkeG7kWkgc+G7kSB0w7l5IGNo4buNbiDEkcaw4bujYyB0cnV54buBbiB2w6BvIGjDoG0gLmYNCg0KKio0LkjDoG0gcmVkdWNlKCkqKjogbMOgIG3hu5l0IGjDoG0gdHJvbmcgZ8OzaSAicHVycnIiIGPhu6dhIG5nw7RuIG5n4buvIGzhuq1wIHRyw6xuaCBSLiBIw6BtIG7DoHkgxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4Mgw6FwIGThu6VuZyBt4buZdCBwaMOpcCB04buVIGjhu6NwIGzDqm4gY8OhYyBwaOG6p24gdOG7rSBj4bunYSBt4buZdCB2ZWN0b3IgaG/hurdjIGRhbmggc8OhY2ggxJHhu4MgdGh1IMSRxrDhu6NjIG3hu5l0IGvhur90IHF14bqjIGR1eSBuaOG6pXQuDQoNCmBgYA0KcmVkdWNlKC54LCAuZiwgLi4uKQ0KYGBgDQoNCioqNS5Iw6BtIGZsYXR0ZW4oKSoqOiB0cm9uZyBnw7NpICJwdXJyciIgY+G7p2EgUiDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyB0csOtY2ggeHXhuqV0IGPDoWMgcGjhuqduIHThu60gdOG7qyBt4buZdCBkYW5oIHPDoWNoIGzhu5NuZyBuaGF1IHbDoCBiaeG6v24gxJHhu5VpIGNow7puZyB0aMOgbmggbeG7mXQgZGFuaCBzw6FjaCDEkcahbiBnaeG6o24gaMahbiwga2jDtG5nIGPDsm4gY+G6pXUgdHLDumMgbOG7k25nIG5oYXUuDQoNCmBgYA0KZmxhdHRlbigueCwgLnNpbXBsaWZ5ID0gVFJVRSkNCmBgYA0KDQotIC54IGzDoCBkYW5oIHPDoWNoIGhv4bq3YyB2ZWN0b3IgY2jhu6lhIGPDoWMgcGjhuqduIHThu60gY+G6p24gdHLDrWNoIHh14bqldC4NCi0gLnNpbXBsaWZ5IGzDoCBt4buZdCDEkeG7kWkgc+G7kSB0w7l5IGNo4buNbiDEkeG7gyB4w6FjIMSR4buLbmggbGnhu4d1IGRhbmggc8OhY2ggdHLhuqMgduG7gSBzYXUga2hpIHRyw61jaCB4deG6pXQgY8OzIMSRxrDhu6NjIMSRxqFuIGdp4bqjbiBow7NhIGhheSBraMO0bmcuIEtoaSAuc2ltcGxpZnkgPSBUUlVFLCBkYW5oIHPDoWNoIHPhur0gxJHGsOG7o2MgxJHGoW4gZ2nhuqNuIGjDs2EgdGjDoG5oIHZlY3RvciBu4bq/dSBjw7MgdGjhu4MuIEtoaSAuc2ltcGxpZnkgPSBGQUxTRSwgZGFuaCBzw6FjaCBz4bq9IMSRxrDhu6NjIHRy4bqjIHbhu4EgbcOgIGtow7RuZyDEkcahbiBnaeG6o24gaMOzYS4NCg0KKio2LkjDoG0gcGx1Y2soKSoqOiB0cm9uZyBnw7NpICJwdXJyciIgY+G7p2EgUiDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyB0csOtY2ggeHXhuqV0IGdpw6EgdHLhu4sgdOG7qyBt4buZdCBkYW5oIHPDoWNoIGThu7FhIHRyw6puIGPDoWMgY2jhu4kgc+G7kSBob+G6t2MgdMOqbi4NCg0KYGBgDQpwbHVjaygueCwgLi4uLCAuZGVmYXVsdCA9IE5VTEwpDQpgYGANCg0KLSAueCBsw6AgZGFuaCBzw6FjaCBj4bqnbiB0csOtY2ggeHXhuqV0IGdpw6EgdHLhu4sgdOG7qy4NCi0gLi4uIGzDoCBjw6FjIMSR4buRaSBz4buRIGNo4bupYSBjaOG7iSBz4buRIGhv4bq3YyB0w6puIGPhu6dhIGPDoWMgcGjhuqduIHThu60gY+G6p24gdHLDrWNoIHh14bqldC4gQuG6oW4gY8OzIHRo4buDIHRydXnhu4FuIG5oaeG7gXUgY2jhu4kgc+G7kSBob+G6t2MgdMOqbiB0cm9uZyBk4bqldSAuLi4gxJHhu4MgdHLDrWNoIHh14bqldCBuaGnhu4F1IHBo4bqnbiB04butIGPDuW5nIG3hu5l0IGzDumMuDQotIC5kZWZhdWx0IGzDoCBt4buZdCBnacOhIHRy4buLIG3hurdjIMSR4buLbmggdMO5eSBjaOG7jW4sIMSRxrDhu6NjIHRy4bqjIHbhu4EgbuG6v3UgcGjhuqduIHThu60gY+G6p24gdHLDrWNoIHh14bqldCBraMO0bmcgdOG7k24gdOG6oWkgdHJvbmcgZGFuaCBzw6FjaC4gTuG6v3Uga2jDtG5nIGPDsyBnacOhIHRy4buLIC5kZWZhdWx0IMSRxrDhu6NjIGNo4buJIMSR4buLbmgsIG7hur91IHBo4bqnbiB04butIGtow7RuZyB04buTbiB04bqhaSwgaMOgbSBwbHVjaygpIHPhur0gdHLhuqMgduG7gSBOVUxMLg0KDQoqKjcuSMOgbSB0cmFuc3Bvc2UoKSoqOiB0cm9uZyBnw7NpICJwdXJyciIgY+G7p2EgUiDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyBjaHV54buDbiDEkeG7lWkgZOG7ryBsaeG7h3UgZOG6oW5nIGRhbmggc8OhY2ggbOG7k25nIG5oYXUgdGjDoG5oIG3hu5l0IGRhbmggc8OhY2ggbeG7m2kgduG7m2kgY8OhYyBwaOG6p24gdOG7rSDEkcaw4bujYyBjaHV54buDbiDEkeG7lWkgdGjDoG5oIGPhu5l0IHbDoCBjw6FjIGRhbmggc8OhY2ggY29uIHRow6BuaCBjw6FjIGjDoG5nLg0KDQpgYGANCnRyYW5zcG9zZSgueCwgLi4uKQ0KYGBgDQoNCiMjIyBDw6BpIMSR4bq3dCBnw7NpIFB1cnJyDQoNCsSQ4buDIHPhu60gZOG7pW5nIHBhY2thZ2UgUHVycnIgdHLDqm4gUiwgdGEgdGjhu7FjIGhp4buHIGPDoWMgYsaw4bubYyBzYXU6DQoNCioqQsaw4bubYyAxKio6IEPDoGkgxJHhurd0IHBhY2thZ2UgUHVycnINCkNow7puZyB0YSBjw7MgdGjhu4MgY8OgaSDEkeG6t3QgcGFja2FnZSBQdXJyciBiw6BuZyBjw6FjaCBjaOG6oXkgY8OidSBs4buHbmggdHLDqm4gUjoNCg0KYGBgDQppbnN0YWxsLnBhY2thZ2VzKCJQdXJyciIpDQpgYGANCioqQsaw4bubYyAyKio6IEfhu41pIHBhY2thZ2UgUHVycnIgYuG6sW5nIGPDonUgbOG7h25oIHRyw6puIFINCg0KYGBge3J9DQpsaWJyYXJ5KHB1cnJyKQ0KYGBgDQoNCioqQsaw4bubYyAzKio6IFNhdSBraGkgZ+G7jWkgcGFja2FnZSBQdXJyciB0YSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgY8OhYyBow6BtIMSRxrDhu6NjIG7Ds2kg4bufIHRyw6puIGPhu6dhIHBhY2thZ2UgbsOgeSDEkeG7gyB44butIGzDrSBk4buvIGxp4buHdSANCg0KIyMjIE3hu6VjIHRpw6p1IHbDoCBs4bujaSDDrWNoIA0KDQpN4bulYyB0acOqdSBjaMOtbmggY+G7p2EgcGFja2FnZSBgUHVycnJgIHRyb25nIFIgbMOgIGN1bmcgY+G6pXAgbeG7mXQgY8OhY2ggdGnhur9wIGPhuq1uIG3huqFuaCBt4bq9IHbDoCBsaW5oIGhv4bqhdCDEkeG7gyBsw6BtIHZp4buHYyB24bubaSBk4buvIGxp4buHdSBk4bqhbmcgZGFuaCBzw6FjaCB2w6AgdmVjdG9yIHRyb25nIG5nw7RuIG5n4buvIGzhuq1wIHRyw6xuaCBSLiBDw6FjIGzhu6NpIMOtY2ggY+G7p2EgZ8OzaSBgUHVycnJgIGJhbyBn4buTbToNCg0KKipM4bqtcCB0csOsbmggZOG6oW5nIGjDoG0qKjogZ8OzaSBgUHVycnJgIGN1bmcgY+G6pXAgbeG7mXQgbG/huqF0IGPDoWMgaMOgbSBuaMawIG1hcCgpLCBtYXAyKCksIHBtYXAoKSwuLi4gZ2nDunAgYuG6oW4gw6FwIGThu6VuZyBt4buZdCBow6BtIGzDqm4gY8OhYyBwaOG6p24gdOG7rSB0cm9uZyBkYW5oIHPDoWNoIGhv4bq3YyB2ZWN0b3IgbeG7mXQgY8OhY2ggZOG7hSBkw6BuZy4gxJBp4buBdSBuw6B5IGNobyBwaMOpcCBi4bqhbiB2aeG6v3QgY29kZSBuZ+G6r24gZ+G7jW4sIHPDoW5nIHThuqFvIHbDoCBk4buFIMSR4buNYyBoxqFuLg0KDQoqKlThu7EgxJHhu5luZyBow7NhIGPDoWMgdGhhbyB0w6FjIGzhurdwIGzhuqFpKio6IFRoYXkgdsOsIHZp4bq/dCBjw6FjIHbDsm5nIGzhurdwIHRo4bunIGPDtG5nIMSR4buDIHRo4buxYyBoaeG7h24gY8OhYyB0aGFvIHTDoWMgdHLDqm4gdOG7q25nIHBo4bqnbiB04butLCBnw7NpIGBQdXJycmAgY2hvIHBow6lwIGLhuqFuIMOhcCBk4bulbmcgY8OhYyBow6BtIG3hu5l0IGPDoWNoIHThu7EgxJHhu5luZyB0csOqbiB0b8OgbiBi4buZIGRhbmggc8OhY2ggaG/hurdjIHZlY3RvciBt4buZdCBjw6FjaCBoaeG7h3UgcXXhuqMuIMSQaeG7gXUgbsOgeSBnacO6cCBnaeG6o20gxJHDoW5nIGvhu4MgbMaw4bujbmcgbcOjIGzhurdwIGzhuqFpIHbDoCB0xINuZyBoaeG7h3Ugc3XhuqV0IGPhu6dhIGNvZGUuDQoNCioqWOG7rSBsw70gZOG7ryBsaeG7h3UgZOG6oW5nIGRhbmggc8OhY2gqKjogS2hpIGzDoG0gdmnhu4djIHbhu5tpIGThu68gbGnhu4d1IHBo4bupYyB04bqhcCwgdGjGsOG7nW5nIGPDsyBuaGnhu4F1IGRhbmggc8OhY2ggbOG7k25nIG5oYXUgaG/hurdjIGThu68gbGnhu4d1IGPDsyBj4bqldSB0csO6YyBraMO0bmcgxJHhu5NuZyBuaOG6pXQuIEfDs2kgYFB1cnJyYCBjdW5nIGPhuqVwIGPDoWMgaMOgbSBuaMawIGZsYXR0ZW4oKSwgcGx1Y2soKSwgdHJhbnNwb3NlKCkgxJHhu4MgdHLDrWNoIHh14bqldCwgYmnhur9uIMSR4buVaSB2w6AgdOG7lSBjaOG7qWMgbOG6oWkgZOG7ryBsaeG7h3UgZOG6oW5nIGRhbmggc8OhY2ggbeG7mXQgY8OhY2ggZOG7hSBkw6BuZy4NCg0KKirEkGnhu4F1IGtoaeG7g24gcXXDoSB0csOsbmggdMOtbmggdG/DoW4qKjogZ8OzaSBgUHVycnJgIGN1bmcgY+G6pXAgY8OhYyBow6BtIG5oxrAgd2FsaygpLCBpbnZva2VfbWFwKCkgY2hvIHBow6lwIGLhuqFuIMSRaeG7gXUga2hp4buDbiBxdcOhIHRyw6xuaCB0w61uaCB0b8OhbiB2w6AgdGjhu7FjIHRoaSBjw6FjIHTDoWMgduG7pSBwaOG7qWMgdOG6oXAgbmjGsCB0w61uaCB0b8OhbiBzb25nIHNvbmcuDQoNCioqVMOtY2ggaOG7o3AgduG7m2kgY8OhYyBnw7NpIGThu68gbGnhu4d1IHBo4buVIGJp4bq/bioqOiBgcHVycnJgIHTGsMahbmcgdGjDrWNoIHThu5F0IHbhu5tpIG5oaeG7gXUgZ8OzaSBk4buvIGxp4buHdSBwaOG7lSBiaeG6v24gbmjGsCAiZHBseXIiLCAidGlkeXIiIHbDoCAiZ2dwbG90MiIuIELhuqFuIGPDsyB0aOG7gyBr4bq/dCBo4bujcCBjw6FjIGjDoG0gdOG7qyBjw6FjIGfDs2kgbsOgeSDEkeG7gyB44butIGzDvSBk4buvIGxp4buHdSBt4buZdCBjw6FjaCBsaW5oIGhv4bqhdCB2w6AgaGnhu4d1IHF14bqjLg0KDQoNCiMjICoqTMO9IGRvIGNo4buNbiBjaOG7pyDEkeG7gSoqIA0KDQpLaGkgdGnhur9wIHjDumMgduG7m2kgbmfDtG4gbmfhu68gUiwgY2jDum5nIGVtIHRo4bqleSBjw7MgcuG6pXQgbmhp4buBdSBnw7NpIGLhu5Ugw61jaCBo4buXIHRy4bujIHRyb25nIHF1w6EgdHLDrG5oIHBow6JuIHTDrWNoIGThu68gbGnhu4d1LiBUdXkgbmhpw6puIGNow7puZyBlbSBjaOG7jW4gZ8OzaSBgUHVycnJgIHRyb25nIFIgdsOsIG7DsyBsw6AgbeG7mXQgY8O0bmcgY+G7pSBt4bqhbmggbeG6vSB2w6AgbGluaCBob+G6oXQgxJHhu4MgbMOgbSB2aeG7h2MgduG7m2kgZOG7ryBsaeG7h3UgZOG6oW5nIGRhbmggc8OhY2ggdsOgIHZlY3RvciwgZ2nDunAgY2jDum5nIGVtIHZp4bq/dCBjb2RlIG5n4bqvbiBn4buNbiwgZ2nhuqNtIHPhu7EgbOG6t3AgbOG6oWkgbcOjLCB0xINuZyB0w61uaCBsaW5oIGhv4bqhdCB2w6AgZOG7hSDEkeG7jWMuIE7DsyBjxaluZyB0w61jaCBo4bujcCB04buRdCB24bubaSBjw6FjIGfDs2kgZOG7ryBsaeG7h3UgcGjhu5UgYmnhur9uIHbDoCBjdW5nIGPhuqVwIGPDoWMgaMOgbSBo4buvdSDDrWNoIGNobyB44butIGzDvSBk4buvIGxp4buHdSBwaOG7qWMgdOG6oXAgdsOgIHF1w6EgdHLDrG5oIHTDrW5oIHRvw6FuIHBo4bupYyB04bqhcCwgxJFp4buBdSBuw6B5IGdpw7pwIGNow7puZyBlbSB0w6xtIGhp4buDdSB0aMOqbSDEkcaw4bujYyBuaGnhu4F1IGfDs2kgdMawxqFuZyB0aMOtY2ggduG7m2kgZ8OzaSBQdXJyciBjw7MgdGjhu4Mga+G6v3QgaOG7o3AgY8OhYyBow6BtIHThu6sgY8OhYyBnw7NpIG7DoHkgxJHhu4MgeOG7rSBsw70gZOG7ryBsaeG7h3UgbeG7mXQgY8OhY2ggbGluaCBob+G6oXQgdsOgIGhp4buHdSBxdeG6oy4NCg0KDQojICoqQ0jGr8agTkcgMjogxJDGoE4gR0nhuqJOIEjDk0EgVuG7mkkgUEFDS0FHRSBQVVJSUioqDQoNCkzhurdwIGzhuqFpIGzDoCBt4buZdCBjw6FjaCBoaeG7h3UgcXXhuqMgxJHhu4MgbcOheSB0w61uaCB0aOG7sWMgaGnhu4duIGPDtG5nIHZp4buHYyBjaG8gYuG6oW4uIE7DsyBjxaluZyBjw7MgdGjhu4MgbMOgIG3hu5l0IGzEqW5oIHbhu7FjIG3DoyBow7NhIGThu4UgbeG6r2Mgbmhp4buBdSBs4buXaSBjaMOtbmggdOG6oyB2w6AgbOG7l2kgxJHGoW4gZ2nhuqNuLiBHw7NpIHB1cnJyIGdpw7pwIMSRxqFuIGdp4bqjbiBow7NhIHZp4buHYyBs4bq3cCBs4bqhaSDEkeG7gyBi4bqhbiBjw7MgdGjhu4MgdOG6rXAgdHJ1bmcgdsOgbyBixrDhu5tjIHRp4bq/cCB0aGVvLCB0aGF5IHbDrCB0w6xtIGzhu5dpIGNow61uaCB04bqjLg0KDQojIyAqKlPhu6ljIG3huqFuaCBj4bunYSBwaMOpcCBs4bq3cCoqDQoNCiMjIyBHaeG7m2kgdGhp4buHdSB24buBIHBow6lwIGzhurdwDQoNCkjDo3kgdMaw4bufbmcgdMaw4bujbmcgcuG6sW5nIGLhuqFuIGPhuqduIMSR4buNYyBow6BuZyB0csSDbSB04buHcCBjw7MgY+G6pXUgdHLDumMgdMawxqFuZyB04buxIHbDoCB0aOG7sWMgaGnhu4duIG3hu5l0IGjDoG5oIMSR4buZbmcgdHLDqm4gY2jDum5nLiBC4bqhbiBraMO0bmcgbXXhu5FuIHZp4bq/dCBow6BuZyB0csSDbSBkw7JuZyBtw6MgbOG6t3AgxJFpIGzhurdwIGzhuqFpIMSR4buDIMSR4buNYyB0cm9uZyB04bqldCBj4bqjIGPDoWMgdOG7h3AgaG/hurdjIMSR4buDIHRo4buxYyBoaeG7h24gaMOgbmggxJHhu5luZy4gVGhheSB2w6BvIMSRw7MsIGLhuqFuIG114buRbiBs4bq3cCBs4bqhaSBjaMO6bmcuIEzhurdwIGzhuqFpIGzDoCBxdcOhIHRyw6xuaCB0aOG7sWMgaGnhu4duIGPDuW5nIG3hu5l0IHF1eSB0csOsbmggY2hvIG5oaeG7gXUgxJHhuqd1IHbDoG8uIEto4bqjIG7Eg25nIGzhurdwIGzhuqFpIGzDoCDEkWnhu4F1IHF1YW4gdHLhu41uZyDEkeG7gyBsw6BtIGNobyBtw6MgY+G7p2EgYuG6oW4gaGnhu4d1IHF14bqjIHbDoCBt4bqhbmggbeG6vSBraGkgbMOgbSB2aeG7h2MgduG7m2kgZGFuaCBzw6FjaC4NCkPDoWMgbmjDoCBk4buLY2ggdOG7hSBo4buNYyB0aMaw4budbmcgcGjhuqNpIHBow6JuIHTDrWNoIGzhurdwIGzhuqFpIHRyw6puIGPDoWMgcGjDom4gbmjDs20gbmjGsCBxdeG7kWMgZ2lhLCBxdeG6rW4gaG/hurdjIG5ow7NtIHR14buVaS4gxJDDonkgY2jhu4kgbMOgIG3hu5l0IHbDoGkgdHJvbmcgc+G7kSBy4bqldCBuaGnhu4F1IHTDrG5oIGh14buRbmcgecOqdSBj4bqndSB2aeG7h2MgbOG6t3AgbOG6oWkuIE3DoyBow7NhIGPDoWMgdGhhbyB0w6FjIGzhurdwIGzhuqFpIGPhu6dhIGLhuqFuIGLhurFuZyBjw6FjaCBz4butIGThu6VuZyBjw6FjIHBoxrDGoW5nIHBow6FwIGLDqm4gZMaw4bubaSBz4bq9IGdpw7pwIGLhuqFuIHRo4buxYyBoaeG7h24gY8OhYyB0w6FjIHbhu6UgbOG6t3AgxJFpIGzhurdwIGzhuqFpIG5oxrAgduG6rXkgbmhhbmggaMahbiwgZ2nhuqNtIGto4bqjIG7Eg25nIHjhuqN5IHJhIGzhu5dpIHbDoCBnaeG6o20gxJHhu5kgZMOgaSBjb2RlLg0KDQpDaMawxqFuZyBuw6B5IHPhur0gZ2nhu5tpIHRoaeG7h3UgaGFpIGPDoWNoIHRp4bq/cCBj4bqtbiDEkeG7kWkgduG7m2kgY8OhYyB0aGFvIHTDoWMgbOG6t3AgbOG6oWk6IHPhu60gZOG7pW5nIGPDoWMgdsOybmcgbOG6t3AgZm9yIHbDoCBwYWNrYWdlIHB1cnJyLg0KDQp2w7JuZyBs4bq3cCBmb3IgbOG6t3AgbOG6oWkgY29kZSB0csOqbiBt4buZdCBsb+G6oXQgxJHhuqd1IHbDoG8sIG5oxrBuZyDDrXQgcGjhu5UgYmnhur9uIGjGoW4gdHJvbmcgUiBzbyB24bubaSBjw6FjIG5nw7RuIG5n4buvIGzhuq1wIHRyw6xuaCBraMOhYy4gVHV5IG5oacOqbiwgY2jDum5nIHTDtGkgZ2nhu5tpIHRoaeG7h3UgY2jDum5nIOG7nyDEkcOieSBuaMawIG3hu5l0IGPDtG5nIGPhu6UgaOG7jWMgdOG6rXAgdsOgIHRoYW0ga2jhuqNvDQpQYWNrYWdlIHB1cnJyIGzDoCBwaMawxqFuZyBwaMOhcCB0aeG6v3AgY+G6rW4gdGlkeXZlcnNlIMSR4buRaSB24bubaSBjw6FjIHRoYW8gdMOhYyBs4bq3cCBs4bqhaSAtIG7DsyBob+G6oXQgxJHhu5luZyBi4bqxbmcgY8OhY2gg4oCcbWFwc+KAnSAow6FwIGThu6VuZykgbeG7mXQgaMOgbSB0csOqbiBuaGnhu4F1IMSR4bqndSB2w6BvIChnacOhIHRy4buLLCBj4buZdCwgZGF0YXNldHMsIHYudi4pDQpUcm9uZyBjaMawxqFuZyBuw6B5LCBjaMO6bmcgdMO0aSBz4bq9IGzhuqV5IG3hu5l0IHPhu5EgdsOtIGThu6UgbmjGsDoNCg0KLSBOaOG6rXAgdsOgIHh14bqldCBuaGnhu4F1IHThu4dwDQotIFThuqFvIGPDoWMgxJHGsOG7nW5nIGNvbmcgZOG7i2NoIGLhu4duaCBjaG8gbmhp4buBdSB04buJbmgNCi0gQ2jhuqF5IFQtdGVzdHMgY2hvIG5oaeG7gXUgY+G7mXQgdHJvbmcgZGF0YWZyYW1lDQpUcm9uZyBwaOG6p24gcHVycnIsIGNow7puZyB0w7RpIGPFqW5nIHPhur0gY3VuZyBj4bqlcCBt4buZdCBz4buRIHbDrSBk4bulIHbhu4EgY8OhY2ggdOG6oW8gdsOgIHjhu60gbMO9IGRhbmggc8OhY2ggbGlzdHMuDQoNCiMjIyBDaHXhuqluIGLhu4sNCg0KTmjhuq1wIGThu68gbGnhu4d1IHThu6s6IGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9vcGVuP2lkPTFPWFctaHB3ZUQ2QmZ3aFAyLVBmRUROb0htWHZwdGxIZCZ1c3A9ZHJpdmVfY29weQ0KDQpU4buHcCBjaOG7qWEgY8OhYyBjYSBi4buHbmggdsOgIGNhIHThu60gdm9uZyBkbyBk4buLY2ggRWJvbGEgxJHGsOG7o2MgeMOhYyBuaOG6rW4gdOG7qyB0aMOhbmcgOCBuxINtIDIwMTQgxJHhur9uIHRow6FuZyAzIG7Eg20gMjAxNiB0csOqbiB0b8OgbiB0aOG6vyBnaeG7m2kuIEfhu5NtIGPDsyA0IGJp4bq/biB24bubaSAyNDg1IHF1YW4gc8OhdDoNCg0KMS4gxJDhuqV0IG7GsOG7m2MNCg0KMi4gTmfDoHkga2jhuqNvIHPDoXQNCg0KMy4gQ2EgYuG7h25oDQoNCjQuIENhIHThu60gdm9uZw0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHIpDQpsaW5lbGlzdCA8LSByZWFkX2NzdigiRDpcXFxcZWJvbGFfMjAxNF8yMDE2X2NsZWFuLmNzdiIpDQpsaW5lbGlzdA0KbmFtZXMobGluZWxpc3QpIDwtIGMoJ0NvdW50cnknLCdEYXRlJywnQ2FzZXMnLCdEZWF0aHMnKQ0KYGBgDQoNCg0KIyMjIFbDsm5nIGzhurdwIGZvciB0cm9uZyBSDQoNClbDsm5nIGzhurdwIGZvciBraMO0bmcgxJHGsOG7o2MgbmjhuqVuIG3huqFuaCB0cm9uZyBSLCBuaMawbmcgcGjhu5UgYmnhur9uIHRyb25nIGPDoWMgbmfDtG4gbmfhu68gbOG6rXAgdHLDrG5oIGtow6FjLiBLaGkgbeG7m2kgYuG6r3QgxJHhuqd1LCBjaMO6bmcgY8OzIHRo4buDIGjhu691IMOtY2ggxJHhu4MgaOG7jWMgdsOgIHRo4buxYyBow6BuaCB2w6wgY2jDum5nIGThu4Ug4oCca2jDoW0gcGjDoeKAnSwg4oCcZ+G7oSBs4buXaeKAnSBoxqFuIHbDoCBu4bqvbSBi4bqvdCBjaMOtbmggeMOhYyBuaOG7r25nIGfDrCDEkWFuZyB44bqjeSByYSBjaG8gbeG7l2kgbOG6p24gbOG6t3AsIMSR4bq3YyBiaeG7h3QgbMOgIGtoaSBi4bqhbiBjaMawYSBj4bqjbSB0aOG6pXkgdGhv4bqjaSBtw6FpIGtoaSB2aeG6v3QgY8OhYyBow6BtIGPhu6dhIHJpw6puZyBtw6xuaC4NCg0KQ+G6pXUgcGjhuqduIGPhu5F0IGzDtWkNCk3hu5l0IHbDsm5nIGzhurdwIGZvciBjw7MgYmEgcGjhuqduIGPhu5F0IGzDtWk6DQoNCi0gQ2h14buXaSBjw6FjIHBo4bqnbiB04butIGPhuqduIGzhurdwIGzhuqFpDQotIEPDoWMgdGhhbyB0w6FjIMSR4buDIHRp4bq/biBow6BuaCBjaG8gbeG7l2kgcGjhuqduIHThu60gdHJvbmcgY2h14buXaQ0KLSBWw7luZyBjaOG7qWEgY2hvIGvhur90IHF14bqjICh0w7l5IGNo4buNbikNCkPDuiBwaMOhcCBjxqEgYuG6o24gbMOgOiBmb3IgKHBo4bqnbiB04butIHRyb25nIGNodeG7l2kpIHtjw6FjIHRoYW8gdMOhYyB0aOG7sWMgaGnhu4duIHbhu5tpIHBo4bqnbiB04butfS4gTMawdSDDvSBk4bqldSBuZ2/hurdjIMSRxqFuIHbDoCBk4bqldSBuZ2/hurdjIG5o4buNbi4gS+G6v3QgcXXhuqMgY8OzIHRo4buDIMSRxrDhu6NjIGluIHJhIGNvbnNvbGUgaG/hurdjIMSRxrDhu6NjIGzGsHUgdHLhu68gdHJvbmcgbeG7mXQgxJHhu5FpIHTGsOG7o25nIHbDuW5nIGNo4bupYS4NCg0KRMaw4bubaSDEkcOieSBsw6AgbeG7mXQgdsOtIGThu6UgxJHGoW4gZ2nhuqNuIHbhu4EgdsOybmcgbOG6t3AgZm9yLg0KYGBge3J9DQpmb3IgKG51bSBpbiBjKDEsMiwzLDQsNSkpIHsgIA0KICBwcmludChudW0gKyAyKSAgICAgICAgICAgIA0KfSAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KYGBgDQpzYXUga2hpIHRo4buxIGhp4buHbiBjw6J1IGzhu4duaCB0csOqbiBSIHRhIGPDsyDEkcaw4bujYyBr4bq/dCBxdeG6oyBjaOG6oXkgdOG7qyAxIMSR4bq/biA1IHbDoCBj4buZbmcgdGjDqm0gMiBj4bunYSB04burbmcgcGjhuqduIHThu60NCg0KKipDaHXhu5dpKioNCg0KxJDDonkgbMOgIHBo4bqnbiDigJxmb3LigJ0gY+G7p2EgdsOybmcgbOG6t3AgZm9yIC0gY8OhYyB0aGFvIHTDoWMgc+G6vSBjaOG6oXkg4oCcY2hvIChmb3Ip4oCdIHThu6tuZyBwaOG6p24gdOG7rSB0cm9uZyBjaHXhu5dpLiBDaHXhu5dpIGPDsyB0aOG7gyBsw6AgbeG7mXQgbG/huqF0IGPDoWMgZ2nDoSB0cuG7iyAodsOtIGThu6U6IHTDqm4gY+G7p2Ega2h1IHbhu7FjIHBow6FwIGzDvSwgYuG7h25oLCB0w6puIGPhu5l0LCBwaOG6p24gdOG7rSBkYW5oIHPDoWNoLCB2LnYuKSBob+G6t2MgbsOzIGPDsyB0aOG7gyBsw6AgbeG7mXQgY2h14buXaSBjw6FjIHPhu5EgbGnDqm4gdGnhur9wICh2w60gZOG7pTogMSwyLDMsNCw1KS4gTeG7l2kgY8OhY2ggdGnhur9wIGPhuq1uIMSRxrDhu6NjIG3DtCB04bqjIGTGsOG7m2kgxJHDonkgY8OzIGPDoWMgdGnhu4duIMOtY2ggcmnDqm5nIGPhu6dhIGNow7puZy4NCg0KQ+G6pXUgdHLDumMgY8ahIGLhuqNuIGPhu6dhIGJp4buDdSB0aOG7qWMgY2h14buXaSBsw6AgaXRlbSBpbiB2ZWN0b3IuDQoNCkLhuqFuIGPDsyB0aOG7gyB2aeG6v3QgYuG6pXQga+G7syBrw70gdOG7sSBob+G6t2MgdOG7qyBuw6BvIHRoYXkgY2hvIOKAnGl0ZW3igJ0gKHbDrSBk4bulOiDigJxp4oCdLCDigJxudW3igJ0sIOKAnGhvc3DigJ0sIOKAnGRpc3RyaWN04oCdLCB2LnYuKS4gR2nDoSB0cuG7iyBj4bunYSDigJxpdGVt4oCdIG7DoHkgdGhheSDEkeG7lWkgdGhlbyB04burbmcgbOG6p24gbOG6t3AgbOG6oWkgY+G7p2EgdsOybmcgbOG6t3AsIHRp4bq/cCB04bulYyBxdWEgdOG7q25nIGdpw6EgdHLhu4sgdHJvbmcgdmVjdG9yLg0KVmVjdG9yIGPDsyB0aOG7gyBsw6AgY8OhYyBnacOhIHRy4buLIGvDvSB04buxLCB0w6puIGPhu5l0IGhv4bq3YyBjw7MgdGjhu4MgbMOgIG3hu5l0IGNodeG7l2kgc+G7kSAtIMSRw6J5IGzDoCBuaOG7r25nIGdpw6EgdHLhu4sgc+G6vSB0aGF5IMSR4buVaSB0aGVvIG3hu5dpIGzhuqduIGzhurdwLiBC4bqhbiBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgY2jDum5nIHRyb25nIGPDoWMgdGhhbyB0w6FjIHbDsm5nIGzhurdwIGZvciBi4bqxbmcgY8OhY2ggc+G7rSBk4bulbmcgdGh14bqtdCBuZ+G7ryDigJxpdGVt4oCdLg0KVsOtIGThu6U6IGNodeG7l2kgZ2nDoSB0cuG7iyBrw70gdOG7sQ0KDQpUcm9uZyB2w60gZOG7pSBuw6B5LCBt4buZdCB2w7JuZyBs4bq3cCDEkcaw4bujYyB0aOG7sWMgaGnhu4duIGNobyBt4buXaSBnacOhIHRy4buLIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCB0csaw4bubYyB0cm9uZyBt4buZdCB2ZWN0b3Iga8O9IHThu7EgY+G7p2EgdMOqbiDEkeG6pXQgbsaw4bubYw0KDQpgYGB7cn0NCiMgbWFrZSB2ZWN0b3Igb2YgdGhlIGNvdW50cnkgbmFtZXMNCmNvdW50cnlfbmFtZXMgPC0gdW5pcXVlKGxpbmVsaXN0JENvdW50cnkpDQpjb3VudHJ5X25hbWVzICMgcHJpbnQNCmBgYA0KDQpDaMO6bmcgdMO0aSDEkcOjIGNo4buNbiB0aHXhuq10IG5n4buvIGhvc3AgxJHhu4MgxJHhuqFpIGRp4buHbiBjaG8gY8OhYyBnacOhIHRy4buLIHThu6sgdmVjdG9yIGNvdW50cnlfbmFtZXMuIMSQ4buRaSB24bubaSBs4bqnbiBs4bq3cCDEkeG6p3UgdGnDqm4gY+G7p2EgdsOybmcgbOG6t3AsIGdpw6EgdHLhu4sgY+G7p2EgaG9zcCBz4bq9IGzDoCBjb3VudHJ5X25hbWVzW1sxXV0uIMSQ4buRaSB24bubaSB2w7JuZyBs4bq3cCB0aOG7qSBoYWksIG7DsyBz4bq9IGzDoCBjb3VudHJ5X25hbWVzW1syXV0uIFbDoCBj4bupIG5oxrAgdGjhur/igKYNCg0KYGBgDQpmb3IgKGhvc3AgaW4gY291bnRyeV9uYW1lcykNCmBgYA0KDQoqKlbDrSBk4bulOiBjaHXhu5dpIHTDqm4gY+G7mXQqKg0KDQrEkMOieSBsw6AgbeG7mXQgYmnhur9uIHRo4buDIGPhu6dhIGNodeG7l2kga8O9IHThu7Eg4bufIHRyw6puLCB0cm9uZyDEkcOzIHTDqm4gY+G7p2EgbeG7mXQgxJHhu5FpIHTGsOG7o25nIFIgaGnhu4duIGPDsyDEkcaw4bujYyB0csOtY2ggeHXhuqV0IHbDoCB0cuG7nyB0aMOgbmggdmVjdG9yLiBWw60gZOG7pSwgdMOqbiBj4buZdCBj4bunYSBkYXRhZnJhbWUuIFRyb25nIGNvZGUgaG/huqF0IMSR4buZbmcgY+G7p2EgdsOybmcgbOG6t3AgZm9yLCB0w6puIGPhu5l0IGPDsyB0aOG7gyDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyBs4bqtcCBjaOG7iSBt4bulYyAodOG6rXAgaOG7o3AgY29uKSBkYXRhZnJhbWUgYmFuIMSR4bqndSBj4bunYSBjaMO6bmcuDQoNCkTGsOG7m2kgxJHDonksIGNodeG7l2kgbMOgIG5hbWVzKCkgKHTDqm4gY+G7mXQpIGPhu6dhIGRhdGFmcmFtZSBsaW5lbGlzdC4gVMOqbiDigJxpdGVt4oCdIGPhu6dhIGNow7puZyB0YSBsw6AgY29sLCBz4bq9IMSR4bqhaSBkaeG7h24gY2hvIHThu6tuZyB0w6puIGPhu5l0IGtoaSBjw6FjIHbDsm5nIGzhurdwIGRp4buFbiByYS4NCg0KVuG7m2kgdsOtIGThu6UgbsOgeSwgY2jDum5nIHTDtGkgYmFvIGfhu5NtIGNvZGUgdGhhbyB0w6FjIGLDqm4gdHJvbmcgdsOybmcgbOG6t3AgZm9yLCDEkcaw4bujYyBjaOG6oXkgY2hvIG3hu41pIGdpw6EgdHLhu4sgdHJvbmcgY2h14buXaS4gVHJvbmcgY29kZSBuw6B5LCBjw6FjIGdpw6EgdHLhu4sgdHLDrG5oIHThu7EgKHTDqm4gY+G7mXQpIMSRxrDhu6NjIHPhu60gZOG7pW5nIMSR4buDIGNo4buJIG3hu6VjICh04bqtcCBo4bujcCBjb24pIHThu6tuZyBwaOG6p24gdOG7rSBt4buZdCB0cm9uZyBsaW5lbGlzdC4gTmjGsCDEkcOjIGThuqF5IHRyb25nIGNoxrDGoW5nIFIgY8ahIGLhuqNuLCBk4bqldSBuZ2/hurdjIHZ1w7RuZyBrw6lwIFtbXV0gxJHGsOG7o2Mgc+G7rSBk4bulbmcgY2hvIHThuq1wIGjhu6NwIGNvbi4gQ+G7mXQga+G6v3QgcXXhuqMgxJHGsOG7o2MgY2h1eeG7g24gxJHhur9uIGlzLm5hKCksIHNhdSDEkcOzIMSR4bq/biBzdW0oKSDEkeG7gyB04bqhbyByYSBz4buRIGdpw6EgdHLhu4sgdHJvbmcgY+G7mXQgYuG7iyB0aGnhur91LiBL4bq/dCBxdeG6oyDEkcaw4bujYyBpbiByYSBjb25zb2xlIC0gbeG7mXQgc+G7kSBjaG8gbeG7l2kgY+G7mXQuDQoNCk3hu5l0IGzGsHUgw70gduG7gSBs4bqtcCBjaOG7iSBt4bulYyB24bubaSB0w6puIGPhu5l0IC0gYuG6pXQgY+G7qSBraGkgbsOgbyB0aGFtIGNoaeG6v3UgxJHhur9uIGNow61uaCBj4buZdCDEkcOzLCDEkeG7q25nIGNo4buJIHZp4bq/dCDigJxjb2zigJ0hIGNvbCBjaOG7iSDEkeG6oWkgZGnhu4duIGNobyB0w6puIGPhu5l0IGvDvSB04buxISDEkOG7gyB0aGFtIGNoaeG6v3UgxJHhur9uIHRvw6BuIGLhu5kgY+G7mXQsIGLhuqFuIHBo4bqjaSBz4butIGThu6VuZyB0w6puIGPhu5l0IGTGsOG7m2kgZOG6oW5nIGNo4buJIG3hu6VjIHRyw6puIGxpbmVsaXN0IHRow7RuZyBxdWEgbGluZWxpc3RbW2NvbF1dLg0KDQpgYGB7cn0NCmZvciAoY29sIGluIG5hbWVzKGxpbmVsaXN0KSl7ICAgICAgIA0KICBwcmludChzdW0oaXMubmEobGluZWxpc3RbW2NvbF1dKSkpDQp9DQpgYGANCg0KKipEw6N5IHPhu5EqKg0KDQpUaGVvIGPDoWNoIHRp4bq/cCBj4bqtbiBuw6B5LCBkw6N5IHPhu5EgbMOgIG3hu5l0IGNodeG7l2kgY8OhYyBz4buRIGxpw6puIHRp4bq/cC4gRG8gxJHDsywgZ2nDoSB0cuG7iyBj4bunYSDigJxpdGVt4oCdIGtow7RuZyBwaOG6o2kgbMOgIGdpw6EgdHLhu4sga8O9IHThu7EgKHbDrSBk4bulOiDigJxDb3VudHJ54oCdIGhv4bq3YyDigJxkYXRl4oCdKSBtw6AgbMOgIG3hu5l0IHPhu5EuIMSQaeG7gXUgbsOgeSBy4bqldCBo4buvdSDDrWNoIGNobyB2aeG7h2MgbOG6t3AgcXVhIGPDoWMgZGF0YWZyYW1lcywgdsOsIGLhuqFuIGPDsyB0aOG7gyBz4butIGThu6VuZyBz4buRIOKAnGl0ZW3igJ0gYsOqbiB0cm9uZyB2w7JuZyBs4bq3cCBmb3IgxJHhu4MgbOG6rXAgY2jhu4kgbeG7pWMgZGF0YWZyYW1lIHRoZW8gc+G7kSBow6BuZy4NCg0KVsOtIGThu6U6IGdp4bqjIHPhu60gYuG6oW4gbXXhu5FuIGzhurdwIHF1YSBt4buNaSBow6BuZyB0cm9uZyBkYXRhZnJhbWUgY+G7p2EgbcOsbmggdsOgIHRyw61jaCB4deG6pXQgdGjDtG5nIHRpbiBuaOG6pXQgxJHhu4tuaC4g4oCcSXRlbeKAnSBj4bunYSBi4bqhbiBz4bq9IGzDoCBz4buRIGjDoG5nIHPhu5EuIFRow7RuZyB0aMaw4budbmcsIOKAnGl0ZW3igJ0gdHJvbmcgdHLGsOG7nW5nIGjhu6NwIG7DoHkgxJHGsOG7o2Mgdmnhur90IGzDoCBpLg0KDQpRdcOhIHRyw6xuaCB2w7JuZyBs4bq3cCBmb3IgY8OzIHRo4buDIMSRxrDhu6NjIGdp4bqjaSB0aMOtY2ggYuG6sW5nIGzhu51pIGzDoCDigJzEkeG7kWkgduG7m2kgbeG7jWkgbeG7pWMgdHJvbmcgY2h14buXaSBz4buRIHThu6sgMSDEkeG6v24gdOG7lW5nIHPhu5EgaMOgbmcgdHJvbmcgZGF0YWZyYW1lIGPhu6dhIHTDtGksIGjDo3kgdGjhu7FjIGhp4buHbiBY4oCdLiDEkOG7kWkgduG7m2kgbOG6p24gbOG6t3AgxJHhuqd1IHRpw6puIGPhu6dhIHbDsm5nIGzhurdwLCBnacOhIHRy4buLIGPhu6dhIOKAnGl0ZW3igJ0gaSBz4bq9IGzDoCAxLiDEkOG7kWkgduG7m2kgbOG6p24gbOG6t3AgdGjhu6kgaGFpLGkgc+G6vSBsw6AgMiwgdi52Lg0KDQrEkMOieSBsw6AgaMOsbmggdGjhu6ljIGPhu6dhIGNodeG7l2kgdHJvbmcgY29kZTogZm9yIChpIGluIDE6bnJvdyhsaW5lbGlzdCkpIHtPUEVSQVRJT05TIENPREV9IHRyb25nIMSRw7MgaSDEkeG6oWkgZGnhu4duIGNobyDigJxpdGVt4oCddsOgIDE6bnJvdyhsaW5lbGlzdCkgdOG6oW8gcmEgbeG7mXQgY2h14buXaSBsacOqbiB0aeG6v3Agc+G7kSB04burIDEgxJHhur9uIHPhu5EgaMOgbmcgdHJvbmcgbGluZWxpc3QuDQoNCg0KYGBge3J9DQpmb3IgKGkgaW4gMTpucm93KGxpbmVsaXN0KSl7fSANCg0KYGBgDQoNCk7hur91IGLhuqFuIG114buRbiBjaHXhu5dpIGzDoCBz4buRLCBuaMawbmcgYuG6oW4gxJFhbmcgYuG6r3QgxJHhuqd1IHThu6sgbeG7mXQgdmVjdG9yIChraMO0bmcgcGjhuqNpIGRhdGFmcmFtZSksIGjDo3kgc+G7rSBk4bulbmcgaMOgbSB04bqvdCBzZXFfYWxvbmcoKSDEkeG7gyB0cuG6oyB24buBIG3hu5l0IGTDo3kgc+G7kSBjaG8gbeG7l2kgcGjhuqduIHThu60gdHJvbmcgdmVjdG9yLiBWw60gZOG7pTogZm9yIChpIGluIHNlcV9hbG9uZyhob3NwaXRhbF9uYW1lcykge09QRVJBVElPTlMgQ09ERX0uDQoNCsSQb+G6oW4gY29kZSBkxrDhu5tpIMSRw6J5IHRo4buxYyBz4buxIHRy4bqjIHbhu4EgY8OhYyBz4buRLCBz4bq9IHRy4bufIHRow6BuaCBnacOhIHRy4buLIGPhu6dhIGkgdHJvbmcgdsOybmcgbOG6t3AgdMawxqFuZyDhu6luZyBj4bunYSBjaMO6bmcNCg0KYGBge3J9DQpzZXFfYWxvbmcoY291bnRyeV9uYW1lcykNCmBgYA0KDQpN4buZdCBs4bujaSB0aOG6vyBj4bunYSB2aeG7h2Mgc+G7rSBk4bulbmcgY8OhYyBz4buRIHRyb25nIGNodeG7l2kgbMOgIGPFqW5nIGThu4UgZMOgbmcgc+G7rSBk4bulbmcgc+G7kSBpIMSR4buDIGzhuq1wIGNo4buJIG3hu6VjIHbDuW5nIGNo4bupYSBsxrB1IHRy4buvIGPDoWMga+G6v3QgcXXhuqMgxJHhuqd1IHJhIGPhu6dhIHbDsm5nIGzhurdwLg0KDQojIyMgS2nhu4NtIHRyYSB2w7JuZyBs4bq3cCBmb3INCg0KxJDhu4Mga2nhu4NtIHRyYSB2w7JuZyBs4bq3cCBj4bunYSBtw6xuaCwgYuG6oW4gY8OzIHRo4buDIGNo4bqheSBs4buHbmggxJHhu4MgZ8OhbiB04bqhbSB0aOG7nWkg4oCcaXRlbeKAnSwgY2jhurNuZyBo4bqhbiBuaMawIGkgPC0gMTAgaG/hurdjIGhvc3AgPC0gIkNvdW50cnkiLiBUaOG7sWMgaGnhu4duIHZp4buHYyBuw6B5IGLDqm4gbmdvw6BpIHbDsm5nIGzhurdwIHbDoCBzYXUgxJHDsyBjaOG7iSBjaOG6oXkgY29kZSB0aGFvIHTDoWMgY+G7p2EgYuG6oW4gKGNvZGUgdHJvbmcgZOG6pXUgbmdv4bq3YyBuaOG7jW4pIMSR4buDIHhlbSBsaeG7h3Uga+G6v3QgcXXhuqMgbW9uZyDEkeG7o2kgY8OzIMSRxrDhu6NjIHThuqFvIHJhIGhheSBraMO0bmcuDQoNCiMjIyBM4bq3cCBs4bqhaSBiaeG7g3UgxJHhu5MNCg0KY2jDum5nIHRhIGjDo3kgY+G7kSBn4bqvbmcgduG6vSBiaeG7g3UgxJHhu5MgxJHGsOG7nW5nIGNvbmcgZOG7i2NoIGLhu4duaCBjaG8gbeG7l2kgxJHhuqV0IG7GsOG7m2MuIGLhurFuZyBjw6FjaCBz4butIGThu6VuZyBwYWNrYWdlIGluY2lkZW5jZTIgbmjGsCBiw6puIGTGsOG7m2k6DQoNCg0KYGBge3J9DQojIENvbnZlcnQgdGhlIERhdGUgY29sdW1uIHRvIGNoYXJhY3RlciB2ZWN0b3IgKGlmIG5vdCBhbHJlYWR5KQ0KbGluZWxpc3QkRGF0ZSA8LSBhcy5jaGFyYWN0ZXIobGluZWxpc3QkRGF0ZSkNCg0KIyBDcmVhdGUgdGhlIGluY2lkZW5jZSBvYmplY3QNCm91dGJyZWFrIDwtIGluY2lkZW5jZTI6OmluY2lkZW5jZSgNCiAgeCA9IGxpbmVsaXN0LCAgICAgICAgICANCiAgZGF0ZV9pbmRleCANCiANCj0gIkRhdGUiLCAgIA0KICBpbnRlcnZhbCA9ICJ3ZWVrIiwgICAgDQogIGdyb3Vwcw0KPSAiQ291bnRyeSIgICAgIA0KKQ0KDQojIFBsb3QgDQpwbG90KG91dGJyZWFrLCAgICAgICAgICAgICAgICAgICAgICANCiAgICAgZmlsbCA9ICJDb3VudHJ5IiwgICAgICAgICAgICAgICANCiAgICAgY29sb3IgPSAiYmxhY2siLCAgICAgICAgICAgICAgICANCiAgICAgdGl0bGUNCj0gIkJp4buDdSDEkeG7kyBk4buLY2ggYuG7h25oIiANCikNCmBgYA0KDQpRdWEgxJHhu5MgdGjhu4sgdHLDqm4sIHRhIHRo4bqleSB0w6xuaCBow6xuaCBk4buLY2ggYuG7h25oIGfDonkgdGhp4buHdCBo4bqhaSBs4bubbiB24buBIG5nxrDhu51pIHbDoCBnacOhbiDEkW/huqFuIGtpbmggdOG6vyB4w6MgaOG7mWkgdHJvbmcga2h1IHbhu7FjLCBjaOG7pyB54bq/dSDhu58gR3VpbmVhLCBJdGFseSB2w6AgTGliZXJpYS4gxJDGsOG7o2MgZ2hpIG5o4bqtbiDhu58gR3VpbmVhIHbDoG8gdGjDoW5nIDEyIG7Eg20gMjAxMy4gU2F1IMSRw7MsIGLhu4duaCBsw6J5IGxhbiBzYW5nIExpYmVyaWEgdsOgIFNpZXJyYSBMZW9uZSBsw6JuIGPhuq1uLCB24bubaSBuaOG7r25nIMSR4bujdCBiw7luZyBwaMOhdCBuaOG7jyB44bqjeSByYSDhu58gbmjhu69uZyBuxqFpIGtow6FjLg0KDQojIyMgVGhlbyBkw7VpIHRp4bq/biB0csOsbmggY+G7p2EgbeG7mXQgdsOybmcgbOG6t3ANCk3hu5l0IHbDsm5nIGzhurdwIGPDsyBuaGnhu4F1IGzhuqduIGzhurdwIGPDsyB0aOG7gyBjaOG6oXkgdHJvbmcgbmhp4buBdSBwaMO6dCBob+G6t2MgdGjhuq1tIGNow60gaMOgbmcgZ2nhu50uIERvIMSRw7MsIGPDsyB0aOG7gyBo4buvdSDDrWNoIGtoaSBpbiB0aeG6v24gdHLDrG5oIHJhIFIgY29uc29sZS4gQ8OidSBs4buHbmggaWYgZMaw4bubaSDEkcOieSBjw7MgdGjhu4MgxJHGsOG7o2MgxJHhurd0IHRyb25nIGPDoWMgdGhhbyB0w6FjIHbDsm5nIGzhurdwIMSR4buDIGluIG3hu5dpIHPhu5EgdGjhu6kgMTAwLiBDaOG7iSBj4bqnbiDEkWnhu4F1IGNo4buJbmggbsOzIMSR4buDIGkgbMOgIOKAnGl0ZW3igJ0gdHJvbmcgdsOybmcgbOG6t3AgY+G7p2EgYuG6oW4uDQoNCmBgYHtyfQ0KZm9yIChpIGluIHNlcV9sZW4obnJvdyhsaW5lbGlzdCkpKXsNCiAgICBpZihpICUlIDEwMD09MCl7ICAgDQogICAgcHJpbnQoaSkNCn19DQpgYGANCg0KDQojIyAqKkPDoWMgaMOgbSBj4bunYSBwYWNrYWdlIFB1cnJyKioNCg0KS2hpIHBow6JuIHTDrWNoIGThu68gbGnhu4d1IHBo4bupYyB04bqhcCwgdGEgdGjGsOG7nW5nIHh1ecOqbiBwaOG6o2kgdGjhu7FjIGhp4buHbiBt4buZdCBuaMOzbSBjw6FjIHBow6JuIHTDrWNoIHTGsMahbmcgdOG7sSBuaGF1IGNobyBjw6FjIG5ow7NtIGThu68gbGnhu4d1IGtow6FjIG5oYXUuIFZp4buHYyBz4butIGThu6VuZyBjw6FjIGjDoG0gbMOgbSDEkcahbiB24buLIHRoYW8gdMOhYyBjxqEgYuG6o24gdsOgIHBo4buRaSBo4bujcCBjw6FjIGjDoG0gduG7m2kgbmhhdSDEkcaw4bujYyBn4buNaSBsw6AgbOG6rXAgdHLDrG5oIGNo4bupYyBuxINuZyBow6BtIChmdW5jdGlvbmFsIHByb2dyYW1taW5nKS4gxJDhu4MgxJHGoW4gZ2nhuqNuLCB0YSB4w6l0IHbDrSBk4bulIHNhdS4NCg0KU+G7rSBk4bulbmcgdOG6rXAgZOG7ryBsaeG7h3UgKippcmlzKiosIHbhu5tpIG3hu5dpIG5ow7NtIGPhu6dhIFNwZWNpZXMsIHjDonkgZOG7sW5nIG3DtCBow6xuaCBo4buTaSBxdXkgZ2nhu69hIFNlcGFsLkxlbmd0aCB2w6AgUGV0YWwuTGVuZ3RoLCBzbyBzw6FuaCBnacOhIHRy4buLIHIuc3F1YXJlZCBnaeG7r2EgY8OhYyBtw7QgaMOsbmguDQoNClbhu5tpIGPDoWNoIGzDoG0gdGjDtG5nIHRoxrDhu51uZywgdGEgc+G6vSBwaOG6o2kgdGjhu6ljIGhp4buHbiB0aGVvIHRo4bupIHThu7Egc2F1Og0KDQotIFThuqFvIGPDoWMgZGF0YS5mcmFtZSBjaG8gdOG7q25nIGdpw6EgdHLhu4sgY+G7p2EgU3BlY2llcw0KLSBW4bubaSBt4buXaSBkYXRhLmZyYW1lIHbhu6thIHThuqFvLCB4w6J5IGThu7FuZyBtw7QgaMOsbmggbG0NCi0gVuG7m2kgbeG7l2kgbcO0IGjDrG5oIHbhu6thIHThuqFvLCBjaGnhur90IHh14bqldCBnacOhIHRy4buLIHIuc3F1YXJlZCB2w6AgbMawdSB2w6BvIG3hu5l0IGRhdGEuZnJhbWUNCg0KQ8OhY2ggdHJp4buDbiBraGFpIHRyw6puIGPDsyB0aOG7gyBz4butIGThu6VuZyB2w7JuZyBs4bq3cCB0cm9uZyBSIHbhu5tpIHBoxrDGoW5nIMOhbiBuaMawIHNhdQ0KDQpgYGB7ciBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocHVycnIpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShwdXJycikNCmNhdGVnb3J5IDwtIGlyaXMkU3BlY2llcyAlPiUgbGV2ZWxzICU+JSBhcy5jaGFyYWN0ZXIoKQ0KbW9kZWxfcmVzdWx0IDwtIGRhdGEuZnJhbWUoKQ0KZm9yIChpIGluIGNhdGVnb3J5KXsNCiAgZGYgPC0gaXJpcyAlPiUgZmlsdGVyKFNwZWNpZXMgPT0gaSkNCiAgbW9kZWwgPC0gbG0oU2VwYWwuTGVuZ3RoIH4gU2VwYWwuV2lkdGgsIGRhdGEgPSBkZikNCiAgbW9kZWxfc3VtbWFyeSA8LSBzdW1tYXJ5KG1vZGVsKQ0KICBkZl90ZW1wIDwtIGRhdGEuZnJhbWUoc3BlY2llcyA9IGksDQogICAgICAgICAgICAgICAgICAgICAgICByLnNxdWFyZSA9IG1vZGVsX3N1bW1hcnkkci5zcXVhcmVkKQ0KICBtb2RlbF9yZXN1bHQgPC0gYmluZF9yb3dzKG1vZGVsX3Jlc3VsdCwgZGZfdGVtcCkNCn0NCmhlYWQobW9kZWxfcmVzdWx0KQ0KYGBgDQoNClR1eSBuaGnDqm4sIHbhu5tpIGzhuq1wIHRyw6xuaCBjaOG7qWMgbsSDbmcgaMOgbSwgdGEgY8OzIHRo4buDIGzDoG0gcuG6pXQgxJHGoW4gZ2nhuqNuIG5oxrAgc2F1Lg0KYGBge3J9DQpsaWJyYXJ5KHB1cnJyKQ0KaXJpcyAlPiUgDQogIHNwbGl0KC4kU3BlY2llcykgJT4lIA0KICBtYXAofmxtKFNlcGFsLkxlbmd0aCB+IFNlcGFsLldpZHRoLCBkYXRhID0gLikpICU+JSANCiAgbWFwKHN1bW1hcnkpICU+JSANCiAgbWFwX2RibCgici5zcXVhcmVkIikNCmBgYA0KDQojIyMgKipM4bqtcCB0csOsbmggY2jhu6ljIG7Eg25nIGjDoG0gY8ahIGLhuqNuIHbhu5tpIG1hcCBxdWEgcGFja2FnZSBwdXJycioqDQoNClRyb25nIGLDoGkgdmnhur90IG7DoHksIGNow7puZyB0YSBz4bq9IHTDrG0gaGnhu4N1IGPDoWMgY8OhY2ggdGjhu6ljIGPGoSBi4bqjbiBs4bqtcCB0csOsbmggY2jhu6ljIG7Eg25nIGjDoG0gY8ahIGLhuqNuIHbhu5tpIG1hcCBxdWEgcGFja2FnZSBwdXJyci4gVmnhu4djIG7huq9tIHbhu69uZyBraeG6v24gdGjhu6ljIHbDoCBr4bu5IG7Eg25nIGzhuq1wIHRyw6xuaCBow6BtIGPDsyBy4bqldCBuaGnhu4F1IOG7qW5nIGThu6VuZyB0cm9uZyBjw7RuZyB2aeG7h2MgcGjDom4gdMOtY2gsIGdpw7pwIGdp4bqjbSB0aGnhu4N1IHLhuqV0IGzhu5tuIHRo4budaSBnaWFuIHBow6JuIHTDrWNoLCBsw6BtIGNobyBxdcOhIHRyw6xuaCBwaMOibiB0w61jaCBt4bqhY2ggbOG6oWMgaMahbiBy4bqldCBuaGnhu4F1IHRyb25nIGPDoWMgYsOgaSB0b8OhbiBraMOhbSBwaMOhIGThu68gbGnhu4d1DQoNCk3hu5l0IGjDoG0gY+G7kXQgbMO1aSBj4bunYSBwdXJyciBsw6AgbWFwKCksIGjDoG0gbsOgeSDigJxtYXBz4oCdICjDoXAgZOG7pW5nKSBt4buZdCBow6BtIGNobyB04burbmcgcGjhuqduIHThu60gxJHhuqd1IHbDoG8gY+G7p2EgZGFuaCBzw6FjaC92ZWN0b3IgYuG6oW4gY3VuZyBj4bqlcC4NCg0KQ8O6IHBow6FwIGPGoSBi4bqjbiBsw6AgKiptYXAoLnggPSBTRVFVRU5DRSwgLmYgPSBIw4BNLCBDw4FDIMSQ4buQSSBT4buQIEtIw4FDKSoqLiBDaGkgdGnhur90IGjGoW4gbmjGsCBzYXU6DQoNCi54ID0gbMOgIGPDoWMgxJHhuqd1IHbDoG8gbcOgIGjDoG0gLmYgc+G6vSDEkcaw4bujYyDDoXAgZOG7pW5nIGzhurdwIMSRaSBs4bq3cCBs4bqhaSAtIHbDrSBk4bulOiB2ZWN0b3IgY+G7p2EgdMOqbiBjw6FjIGtodSB24buxYyBwaMOhcCBsw70sIGPDoWMgY+G7mXQgdHJvbmcgZGF0YSBmcmFtZSBob+G6t2MgZGFuaCBzw6FjaCBjw6FjIGRhdGEgZnJhbWUNCi5mID0gbMOgIGjDoG0gw6FwIGThu6VuZyBjaG8gdOG7q25nIHBo4bqnbiB04butIGPhu6dhIMSR4bqndSB2w6BvIC54IC0gbsOzIGPDsyB0aOG7gyBsw6AgbeG7mXQgaMOgbSBuaMawIHByaW50KCkgxJHDoyB04buTbiB04bqhaSBob+G6t2MgbeG7mXQgaMOgbSB0w7l5IGNo4buJbmggbcOgIGLhuqFuIHjDoWMgxJHhu4tuaC4gSMOgbSB0aMaw4budbmcgxJHGsOG7o2Mgdmnhur90IHNhdSBk4bqldSBuZ8OjIH4gLg0KDQoqKlRow6ptIG3hu5l0IHPhu5EgbMawdSDDvSB24buBIGPDuiBwaMOhcCoqOg0KDQpO4bq/dSBow6BtIGtow7RuZyBj4bqnbiBjaOG7iSDEkeG7i25oIHRow6ptIMSR4buRaSBz4buRLCBuw7MgY8OzIHRo4buDIMSRxrDhu6NjIHZp4bq/dCBraMO0bmcgY8OzIGThuqV1IG5nb+G6t2MgxJHGoW4gdsOgIGtow7RuZyBjw7MgZOG6pXUgbmfDoyAodsOtIGThu6U6IC5mID0gbWVhbikuIMSQ4buDIGN1bmcgY+G6pXAgY8OhYyDEkeG7kWkgc+G7kSBz4bq9IGPDsyBjw7luZyBnacOhIHRy4buLIGNobyBt4buXaSBs4bqnbiBs4bq3cCwgaMOjeSBjdW5nIGPhuqVwIGNow7puZyB0cm9uZyBtYXAoKSBuaMawbmcgYsOqbiBuZ2/DoGkgxJHhu5FpIHPhu5EgLmYgPSwgY2jhurNuZyBo4bqhbiBuaMawIG5hLnJtID0gVCB0cm9uZyAqKm1hcCgueCA9IG15X2xpc3QsIC5mID0gbWVhbiwgbmEucm09VCkqKi4NCkLhuqFuIGPDsyB0aOG7gyBz4butIGThu6VuZyAueCAoaG/hurdjIMSRxqFuIGdp4bqjbiBsw6AgLikgYsOqbiB0cm9uZyBow6BtIC5mID0gbMOgbSB0csOsbmggZ2nhu68gY2jhu5cgY2hvIGdpw6EgdHLhu4sgLnggY+G7p2EgbOG6p24gbOG6t3AgxJHDsw0KU+G7rSBk4bulbmcgY8O6IHBow6FwIGThuqV1IG5nw6MgKH4pIMSR4buDIGtp4buDbSBzb8OhdCBow6BtIG5oaeG7gXUgaMahbiAtIHZp4bq/dCBow6BtIG5oxrAgYsOsbmggdGjGsOG7nW5nIHbhu5tpIGThuqV1IG5nb+G6t2MgxJHGoW4sIGNo4bqzbmcgaOG6oW4gbmjGsDogKiptYXAoLnggPSBteV9saXN0LCAuZiA9IH5tZWFuKC4sIG5hLnJtID0gVCkpKiouIFPhu60gZOG7pW5nIGPDuiBwaMOhcCBuw6B5IMSR4bq3YyBiaeG7h3QgbuG6v3UgZ2nDoSB0cuG7iyBj4bunYSBt4buZdCDEkeG7kWkgc+G7kSBz4bq9IHRoYXkgxJHhu5VpIG3hu5dpIGzhuqduIGzhurdwIGzhuqFpIGhv4bq3YyBu4bq/dSBuw7MgbMOgIGNow61uaCBnacOhIHRy4buLIC54DQoNCkjDoG0gbWFwIGzDoG0gaMOgbSB04buVbmcgcXXDoXQsIG5nb8OgaSByYSwgbWFwIGPDsm4gY8OzIGPDoWMgYmnhur9uIHRo4buDIGNow61uaCBzYXUNCg0KQ8OidSBsw6puaDoNCg0KLSBtYXAoKTogdOG6oW8gdGjDoG5oIG3hu5l0IGRhbmggc8OhY2guDQoNCi0gbWFwX2xnbCgpOiB04bqhbyByYSBt4buZdCB2ZWN0xqEgbG9naWMuDQoNCi0gbWFwX2ludCgpOiB04bqhbyB0aMOgbmggbeG7mXQgdmVjdMahIHPhu5Egbmd1ecOqbi4NCg0KLSBtYXBfZGJsKCk6IHThuqFvIHRow6BuaCBt4buZdCB2ZWN0xqEga8OpcA0KDQotIG1hcF9jaHIoKTogdOG6oW8gdGjDoG5oIG3hu5l0IHZlY3TGoSBrw70gdOG7sS4NCg0KYGBge3J9DQojIEThuqFuZyBsaXN0DQppcmlzICU+JSBtYXAoY2xhc3MpDQpgYGANCg0KYGBge3J9DQojIEThuqFuZyBjaGFyDQppcmlzICU+JSBtYXBfY2hyKGNsYXNzKQ0KYGBgDQoNCmBgYHtyfQ0KIyBE4bqhbmcgZGF0YS5mcmFtZQ0KaXJpcyAlPiUgbWFwX2RmKGNsYXNzKQ0KYGBgDQoNCk1hcCB0aGVvIMSRaeG7gXUga2nhu4duIHbhu5tpICoqbWFwX2lmKiogdsOgICoqbWFwX2F0KioNCg0KVMawxqFuZyB04buxIHbhu5tpIG1hcCwgbmjDs20gbWFwX2lmIHbDoCBtYXBfYXQgY2hvIHBow6lwIHTDrW5oIHRvw6FuIHRoZW8gxJFp4buBdSBraeG7h24gaG/hurdjIHbhu4sgdHLDrSBj4bunYSBsaXN0LiBYZW0gdsOtIGThu6Ugc2F1Lg0KDQpgYGB7cn0NCiMgbWFwX2lmDQppcmlzICU+JSANCiAgbWFwX2lmKGlzLm51bWVyaWMsIGFzLmNoYXJhY3RlcikgJT4lIA0KICBhcy5kYXRhLmZyYW1lICU+JSANCiAgc3RyDQpgYGANCg0KYGBge3J9DQojIG1hcF9hdA0KaXJpcyAlPiUgDQogIG1hcF9hdChjKDEsMiksIGFzLmNoYXJhY3RlcikgJT4lIA0KICBzdHINCmBgYA0KDQoqKkzGsHUgw70qKjogVuG7m2kgdHLGsOG7nW5nIGjhu6NwIGPDsyBoYWkgYmnhur9uIMSR4bqndSB2w6BvLCBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgbmjDs20gaMOgbSBtYXAyLiBWw60gZOG7pQ0KDQpgYGANCiMgS2jDtG5nIGNo4bqheQ0KbWFwX2RibCgxOjMsIDQ6Niwgc3VtKQ0KYGBgDQpgYGB7cn0NCm1hcDJfZGJsKDE6MywgNDo2LCBzdW0pDQpgYGANCg0KVuG7m2kgY8OhYyB0csaw4budbmcgaOG7o3AgcGjhu6ljIHThuqFwLCB0YSBj4bqnbiB24bqtbiBk4bulbmcgbGluaCBob+G6oXQuDQoNClbDrSBk4bulOiBW4bubaSBt4buXaSBkw7JuZyB0cm9uZyAqKmlyaXMqKiAsIHTDoWNoIHRow6BuaCBkYXRhZnJhbWUgcmnDqm5nIHbDoCB4b2F5IGNoaeG7gXUgZOG7ryBsacOqdS4gVMOqbiBjw6FjIGPhu5l0IHRy4bufIHRow6BuaCBiaeG6v24gYXR0cmlidXRlLCBnacOhIHRy4buLIGPDoWMgY+G7mXQgdHLhu58gdGjDoG5oIGJp4bq/biB2YWx1ZS4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpnZXRfZGF0YSA8LSBmdW5jdGlvbihkYXRhLCBpKXsNCiBkZiA8LSBkYXRhICU+JSANCiAgICBzbGljZShpKSAlPiUgdCAlPiUgDQogICAgYXMuZGF0YS5mcmFtZQ0KIHJlc3VsdCA8LSBkYXRhLmZyYW1lKGF0dHJpYnV0ZSA9IHJvd25hbWVzKGRmKSwNCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGRmWywxXSkNCiByb3duYW1lcyhyZXN1bHQpIDwtIE5VTEwNCiByZXR1cm4ocmVzdWx0KQ0KfQ0KZ2V0X2RhdGEobXRjYXJzLCAzKQ0KYGBgDQpgYGB7cn0NCmdldF9kYXRhKGlyaXMsIDEpDQpgYGANCmBgYHtyfQ0KbWFwMihyZXBsaWNhdGUoMywgaXJpcywgc2ltcGxpZnkgPSBGKSwNCiAgICAgYygxOjMpLCBnZXRfZGF0YSkNCmBgYA0KDQojIyMjIEzhurdwIGzhuqFpIG4gbOG6p24gbeG7mXQgcGjDqXAgdMOtbmggDQoNCkTGsOG7m2kgxJHDonkgbMOgIGThu68gbGnhu4d1IMSRaeG7g20gdGhpIFRIUFQgbsSDbSAyMDE4LiBU4bqjaSBk4buvIGxp4buHdSB2w6AgdHLDrWNoIHh14bqldCByacOqbmcgxJFp4buDbSBtw7RuIFRvw6FuLiANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpgYGB7cn0NCmRmPXJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20va2lub2tvYmVydWppL1ItVHV0b3JpYWxzL21hc3Rlci9Qcm92aW5jZXMuY3N2IiklPiVhc190aWJibGUoKQ0KDQpkZiU+JWZpbHRlcihNYXRoIT0iTkEiKSU+JWRwbHlyOjpzZWxlY3QoTWF0aCxQcm92aW5jZSktPm1hdGhfZGYNCmBgYA0KDQpPYmplY3QgbWF0aF9kZiBjw7MgdGjhu4MgeGVtIG5oxrAgMSBxdeG6p24gdGjhu4MsIHbhu5tpIMSR4bqhaSBsxrDhu6NuZyBj4bqnbiBraOG6o28gc8OhdCBsw6AgxJFp4buDbSB0aGkgbcO0biBUb8Ohbi4gS8OtY2ggdGjGsOG7m2Mga2jDoSBs4bubbiBj4bunYSBxdeG6p24gdGjhu4MgbsOgeSBuaOG6sW0gY2jhu6luZyB04buPIHThu5FjIMSR4buZIHTDrW5oIHRvw6FuIHLhuqV0IG5oYW5oIGtoaSBz4butIGThu6VuZyBow6BtIG1hcA0KDQpWw60gZOG7pSDEkeG6p3UgdGnDqm4sIHRhIG114buRbiDEkeG6v20gc+G7kSB0aMOtIHNpbmggY2hvIG3hu5dpIHThu4luaCB0aMOgbmguIFBow6lwIMSR4bq/bSBuw6B5IGPDsyB0aOG7gyB0aOG7sWMgaGnhu4duIGThu4UgZMOgbmcgYuG6sW5nIGjDoG0gZ3JvdXBfYnkgdsOgIHRhbGx5IGPhu6dhIHBhY2thZ2UgZHBseXI6DQoNCg0KYGBge3J9DQptYXRoX2RmJT4lZ3JvdXBfYnkoUHJvdmluY2UpJT4ldGFsbHkoKQ0KYGBgDQoNClR1eSBuaGnDqm4gdGEgY8OybiBjw7MgdGjhu4MgbMOgbSBi4bqxbmcgaMOgbSBtYXA6IERvIGvhur90IHF14bqjIMSR4bq/bSBsw6AgMSBz4buRIG5ndXnDqm4sIG7Dqm4gdGEgZMO5bmcgaMOgbSBtYXBfaW50DQoNCmBgYHtyfQ0KbWF0aF9kZiU+JXNwbGl0KC4kUHJvdmluY2UpJT4lDQogbWFwX2ludCh+bnJvdygueCkpDQpgYGANCg0KUGjDqXAgdMOtbmggbsOgeSBjw7MgdGjhu4MgcGjhu6ljIHThuqFwIGjGoW4sIG5oxrAgdHJvbmcgdsOtIGThu6Ugc2F1IMSRw6J5ICwgZMO5bmcgMSBow6BtIG1hcCDEkeG7gyB0aGkgaMOgbmggaMOgbmcgbG/huqF0IHF1eSB0csOsbmggxrDhu5tjIHTDrW5oIGLDoWNoIFBow6JuIHbhu4sgdGjhu6kgNTAsNzUsOTAsOTUsOTcuNSB2w6AgOTkgdGhlbyBwaMawxqFuZyBwaMOhcCBIYXJlbGwgRGF2aXMgY2hvIMSRaeG7g20gc+G7kSBtw7QgdG/DoW4g4bufIG3hu5dpIHThu4luaCB0aMOgbmg6DQoNCmBgYHtyfQ0KbWF0aF9kZiAlPiUgc3BsaXQoLiRQcm92aW5jZSkgJT4lIA0KICBtYXAoLix+IEhtaXNjOjpoZHF1YW50aWxlKC54JE1hdGgscHJvYnMgPWMoMC41LDAuNzUsMC45LDAuOTUsMC45NzUsMC45OSkpKS0+aGRxdWFudGlsZQ0KaGRxdWFudGlsZQ0KYGBgDQoNCkdoaSBjaMO6OiBwaMOpcCDGsOG7m2MgdMOtbmggcGjDom4gduG7iyB0aGVvIEhhcmVsbCBEYXZpcyBjw7Mgc+G7rSBk4bulbmcgYm9vdHN0cmFwLCBuw6puIHLhuqV0IHBo4buVIGJp4bq/biB2w6Aga2jDtG5nIHBo4bulIHRodeG7mWMgdsOgbyDEkeG6t2MgdMOtbmggcGjDom4gYuG7kSwga2jDoWMgduG7m2kgbcO0IHThuqMgdGjDtG5nIHRoxrDhu51uZy4gQ8OhYyBi4bqhbiBjw7MgdGjhu4MgdGluIGNo4bqvYyBy4bqxbmcgbuG6v3Ug4bufIG3hu5l0IHThu4luaCBuw6BvIMSRw7MgbcOgIGPDsyBIRHF1YW50aWxlIHRo4bupIDk5IGNhbyBoxqFuIHRy4buLIHPhu5EgbsOgeSBjaG8gcXXhuqduIHRo4buDIGNodW5nLCB0aMOsIOG7nyBraHUgduG7sWMgxJHDsyDEkcOjIGPDsyDEkWnhu4F1IGLhuqV0IHRoxrDhu51uZyB44bqjeSByYSwgdGjDrSBk4bulIOG7nyBIw6AgR2lhbmcgdHLhu4sgc+G7kSBIRFEgOTkgbMOgIDguODU5LCBjYW8gaMahbiBy4bqldCBuaGnhu4F1IHNvIHbhu5tpIGPhuqMgbsaw4bubYyAoY2jhu4kgY8OzIDguMCDEkWnhu4NtKQ0KDQpgYGB7cn0NCkhtaXNjOjpoZHF1YW50aWxlKG1hdGhfZGYkTWF0aCxwcm9icyA9YygwLjUsMC43NSwwLjksMC45NSwwLjk3NSwwLjk5KSkNCmBgYA0KIyMjIyBDaOG7jW4gbeG6q3Ugbmfhuqt1IG5oacOqbiBs4bq3cCBs4bqhaSANCg0KTeG7mXQgdsOtIGThu6Uga2jDoWMsIGzhuqduIG7DoHkgdGEgbMOgbSAxIHRow60gbmdoaeG7h206IFThu6sgcXXhuqduIHRo4buDIDc0MTAyNCB0aMOtIHNpbmggY+G6oyBuxrDhu5tjLCB0YSBz4bq9IGNo4buNbiBt4bqrdSBuZ+G6q3Ugbmhpw6puIHThu6sgMTAwMCDEkeG6v24gMzAwMDAgY8OhIHRo4buDIHbDoCB0w61uaCBnacOhIHRy4buLIHRydW5nIGLDrG5oLCB0cnVuZyB24buLLCBiw6FjaCBwaMOibiB24buLIHRo4bupIDUgdsOgIHRo4bupIDk5IGNobyB04burbmcgbeG6q3UuDQoNCsSQw6J5IGzDoCAxIHbDsm5nIGzhurdwIGPDsyBu4buZaSBkdW5nOiBDaOG7jW4gbeG6q3UgbiBs4bqnbiB24bubaSBrw61jaCB0aMaw4bubYyB0xINuZyBk4bqnbiB04burIDEwMDAgOiAzMDAwMCwgdsOgIG3hu5dpIGzhuqduIHh14bqldCByYSA1IGNo4buJIHPhu5EgdGjhu5FuZyBrw6ogbcO0IHThuqMgbmjGsCB0csOqbi4NCg0KVGEgY8OzIHRo4buDIGTDuW5nIHbDsm5nIGzhurdwIGZvciBsb29wIMSR4buDIHRo4buxYyBoaeG7h24sIG5oxrBuZyDhu58gxJHDonkgZW0gZMO5bmcgaMOgbSBtYXAuDQoNClbDrCBt4buXaSBow6BtIG1hcCBjaOG7iSBsw6BtIDEgY8O0bmcgdmnhu4djIGR1eSBuaOG6pXQsIG7Dqm4gcXV5IHRyw6xuaCBj4bqnbiBr4bq/dCBu4buRaSAyIGjDoG0gbWFwLCBow6BtIG1hcCB0aOG7qSBuaOG6pXQgbMOgbSB2aeG7h2MgY2jhu41uIG3huqt1LCBow6BtIG1hcCB0aOG7qSBoYWkgbMOgbSB0aOG7kW5nIGvDqiBtw7QgdOG6oy4gRG8gaMOgbSBtYXAgdGjhu6kgaGFpIHh14bqldCByYSAxIGRhdGFmcmFtZSBuw6puIG7DsyBz4bq9IGzDoCBow6BtIG1hcF9kZg0KDQpgYGB7cn0NCm1hdGhfcG9wPC1tYXRoX2RmJE1hdGgNCg0Kc2FtcGxlX249c2VxKGZyb209MTAwMCwgdG89MzAwMDAsYnk9MTAwMCkNCg0KcyA8LSBtYXAoc2FtcGxlX24sfnNhbXBsZShtYXRoX3BvcCwueCwgcmVwbGFjZSA9IEYpKSU+JQ0KICBtYXBfZGYofmRhdGFfZnJhbWUoU2l6ZT1sZW5ndGgoLngpLA0KICAgICAgICAgICAgICAgICAgICAgTWVhbj1tZWFuKC54KSwNCiAgICAgICAgICAgICAgICAgICAgIE1lZGlhbj1tZWRpYW4oLngpLA0KICAgICAgICAgICAgICAgICAgICAgUDU9cXVhbnRpbGUoLngscHJvYnM9MC4wNSksDQogICAgICAgICAgICAgICAgICAgICBQOTk9cXVhbnRpbGUoLngscHJvYnM9MC45OSksDQogICAgICAgICAgICAgICAgICAgICApKQ0KDQpzDQpgYGANCg0Kc+G7rSBk4bulbmcgaMOgbSBnZ3Bsb3QgxJHhu4MgduG6vSBiaeG7g3UgxJHhu5MgDQoNCmBgYHtyfQ0KcyU+JWdncGxvdChhZXMoeD1TaXplKSkrDQogIGdlb21fcG9pbnRyYW5nZShhZXMoeT1NZWRpYW4sDQogICAgICAgICAgICAgICAgICAgICAgeW1pbj1QNSx5bWF4PVA5OSwNCiAgICAgICAgICAgICAgICAgICAgICBjb2w9U2l6ZSksDQogICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpKw0KICB0aGVtZV9idygpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDEwKSkrDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IG1lZGlhbihtYXRoX3BvcCksbGluZXR5cGU9Mixjb2w9InJlZCIpKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQojIyMjIEThu7FuZyBow6BuZyBsb+G6oXQgbcO0IGjDrG5oIGtow6FjIG5oYXUgY2hvIGPDuW5nIGThu68gbGnhu4d1IA0KDQpUcm9uZyB2w60gZOG7pSB0aeG6v3AgdGhlbywgZW0gc+G6vSBkw7luZyBi4buZIGThu68gbGnhu4d1ICoqZ2FwbWluZGVyKiosIG3hu6VjIHRpw6p1IGzDoCBk4buxbmcgNSBtw7QgaMOsbmgga2jDoWMgbmhhdSDGsOG7m2MgbMaw4bujbmcgdHXhu5VpIHRo4buNIHRydW5nIGLDrG5oIHRoZW8gZMOibiBz4buRLCB0aHUgbmjhuq1wIGLDrG5oIHF1w6JuLCBuxINtIHbDoCB54bq/dSB04buRIMSR4buLYSBsw70sIGNobyB04burbmcgcXXhu5FjIGdpYQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShnYXBtaW5kZXIpDQoNCmBgYA0KDQpgYGB7cn0NCmRhdGEoZ2FwbWluZGVyKQ0KYGBgDQoNCjUgbcO0IGjDrG5oIGPhuqduIGThu7FuZyBjw7MgbuG7mWkgZHVuZyANCg0KYGBge3J9DQpmMSA9IGxpZmVFeHAgfiBwb3ANCmYyID0gbGlmZUV4cCB+IGdkcFBlcmNhcA0KZjMgPSBsaWZlRXhwIH4gcG9wICsgZ2RwUGVyY2FwDQpmNCA9IGxpZmVFeHAgfiBwb3AgKyBnZHBQZXJjYXAgKyB5ZWFyDQpmNSA9IGxpZmVFeHAgfiBwb3AgKyBnZHBQZXJjYXAgKyB5ZWFyICsgY29udGluZW50DQoNCmYxDQpmMg0KZjMNCmY0DQpmNSANCg0KYGBgDQpUYSBjw7MgdGjhu4Mga+G6v3QgaOG7o3AgMiBow6BtIG1hcCwgaMOgbSB0aOG7qSBuaOG6pXQgZOG7sW5nIDUgbcO0IGjDrG5oIHbhu5tpIDUgY8O0bmcgdGjhu6ljIGtow6FjIG5oYXUsIGjDoG0gbWFwIHRo4bupIDIgdHLDrWNoIHh14bqldCBr4bq/dCBxdeG6oyBzdW1tYXJ5IGNobyB04burbmcgbcO0IGjDrG5oDQoNCmBgYHtyfQ0KZm9ybXVsYXMgPC0gbGlzdChmMSxmMixmMyxmNCxmNSkNCg0KbW9kIDwtIG1hcCAoZm9ybXVsYXMsIH4gbG0oLngsIGRhdGE9Z2FwbWluZGVyKSklPiUNCiAgbWFwKH4gc3VtbWFyeSgueCkpDQoNCm1vZA0KYGBgDQoNCkLDonkgZ2nhu50gdGEgdGhheSDEkeG7lWkgaMOgbSBtYXAgdGjhu6kgaGFpIHRow6BuaCBtYXBfZGYgxJHhu4MgdHLDrWNoIHh14bqldCByacOqbmcgdHLhu4sgc+G7kSBBSUMgdsOgIGPDtG5nIHRo4bupYyBt4buXaSBtw7QgaMOsbmgNCg0KYGBge3J9DQptb2QgPC0gbWFwIChmb3JtdWxhcywgfiBsbSgueCwgZGF0YT1nYXBtaW5kZXIpKSU+JQ0KICBtYXBfZGYofiBkYXRhX2ZyYW1lKGZvcm11bGEgPSBmb3JtYXQoZm9ybXVsYSgueCkpLA0KICAgICAgICAgICAgICAgICAgICAgIEFJQz1BSUMoLngpLA0KICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ0FzRmFjdG9ycz1GKSkNCg0KbW9kDQpgYGANCk3hu5l0IHRo4bunIHRodeG6rXQga2jDoWMsIMSRw7MgbMOgIG7DqnUgdHLhu7FjIHRp4bq/cCB0w6puIGPhu6dhIHRow6BuaCBwaOG6p24gY+G6p24gdHLDrWNoIHh14bqldCB04burIGThu68gbGnhu4d1IMSR4bqndSByYSBj4bunYSBow6BtIG1hcCDEkWkgdHLGsOG7m2MgKHRy4bufIHRow6BuaCAueCBj4bunYSBow6BtIG1hcCDEkWkgc2F1KQ0KDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgc3BsaXQoLiRjb3VudHJ5KSAlPiUgDQogIG1hcCAoLix+IGxtKGxpZmVFeHAgfiB5ZWFyLCBkYXRhPS54KSklPiUNCm1hcCh+IHN1bW1hcnkoLngpKSU+JQ0KICBtYXBfZGJsKCJyLnNxdWFyZWQiKQ0KYGBgDQoNCiMjIyMgVGjhu7FjIGhp4buHbiBxdXkgdHLDrG5oIGLhuqV0IGvDrCB0csOqbiBow6BuZyBsb+G6oXQgbeG6q3UNCg0KVHJvbmcgdGjDrSBk4bulIG1pbmggaOG7jWEgdGnhur9wIHRoZW8sIGVtIHPhur0gZMO5bmcgZOG7ryBsaeG7h3UgY2lnYXJldHRlICwgduG7m2kgbuG7mWkgZHVuZyBkaeG7hW4gdGnhur9uIGdpw6EgYsOhbiBs4bq7IHRodeG7kWMgbMOhIHThuqFpIDQ5IGJhbmcgY+G7p2EgTeG7uSB04burIG7Eg20gMTk2MiDEkeG6v24gMTk5Mi4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZ2FtbHNzKQ0KDQpgYGANCg0KYGBge3J9DQpuQzwtZGV0ZWN0Q29yZXMoKQ0KY2lnYXJldHRlIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdmluY2VudGFyZWxidW5kb2NrL1JkYXRhc2V0cy9tYXN0ZXIvY3N2L3BsbS9DaWdhci5jc3YiKQ0KaGVhZChjaWdhcmV0dGUpDQpjaWdhcmV0dGUkc3RhdGU9ZmFjdG9yKGNpZ2FyZXR0ZSRzdGF0ZSkNCmNpZ2FyZXR0ZSR5ZWFyPWZhY3RvcihjaWdhcmV0dGUkeWVhci02MikNCmBgYA0KDQojIyMjIE3hu5l0IHPhu5EgdsOtIGThu6UgxJHhu5FpIHbhu5tpIGjDoG0gbWFwXyooKQ0KDQotIFTDrW5oIHRydW5nIGLDrG5oDQpgYGB7cn0NCm1hcF9kYmwoYWlycXVhbGl0eSwgbWVhbikNCmBgYA0KDQotIFPhu60gZOG7pW5nIHbhu5tpIHRvw6FuIHThu60gcGlwZQ0KYGBge3J9DQphaXJxdWFsaXR5ICU+JSBtYXBfaW50KGxlbmd0aCkNCmBgYA0KDQpgYGB7cn0NCmFpcnF1YWxpdHkgJT4lIG1hcF9kYmwobWVhbiwgdHJpbSA9IDAuNSkNCmBgYA0KDQotIFPhu60gZOG7pW5nIGjDoG0g4bqpbiAoYW5vbnltb3VzIGZ1bmN0aW9uKQ0KDQpO4bq/dSB2aeG6v3QgZOG6oW5nIMSR4bqneSDEkeG7pw0KYGBge3J9DQptb2RlbHMgPC0gbXRjYXJzICU+JQ0KICAgIHNwbGl0KC4kY3lsKSAlPiUNCiAgICBtYXAoZnVuY3Rpb24oZGYpIGxtKG1wZyB+IHd0LCBkYXRhID0gZGYpKQ0KYGBgDQoNClZp4bq/dCBk4bqhbmcgaMOgbSDhuqluIChuZ+G6r24gZ+G7jW4gaMahbikNCmBgYHtyfQ0KbW9kZWxzIDwtIG10Y2FycyAlPiUNCiAgICBzcGxpdCguJGN5bCkgJT4lDQogICAgbWFwKH5sbShtcGcgfiB3dCwgZGF0YSA9IC4pKQ0KYGBgDQoNCioqQ2jDuiDDvSoqOiDigJwu4oCdIHRoYXkgY2hvIG9iamVjdCDEkcOjIHThuqFvIHJhIHRyxrDhu5tjIMSRw7MgKHByb25vdW4pDQoNCi0gVMOtbmggci5zcXVhcmVkIGPhu6dhIGPDoWMgbcO0IGjDrG5oOg0KROG6oW5nIMSR4bqneSDEkeG7pw0KDQpgYGB7cn0NCm1vZGVscyAlPiUNCiAgICBtYXAoc3VtbWFyeSkgJT4lDQogICAgbWFwX2RibCh+LiRyLnNxdWFyZWQpDQpgYGANCg0KLSBIYXkgbmfhuq9uIGfhu41uIGjGoW4ga2hpIHRyw61jaCB0w6puIGPhu6dhIG9iamVjdCAodHLDrWNoIHThu6sgY8OhYyB0aMOgbmggcGjhuqduIMSRw6MgxJHGsOG7o2MgxJHhurd0IHTDqm4pDQpgYGB7cn0NCm1vZGVscyAlPiUNCiAgICBtYXAoc3VtbWFyeSkgJT4lDQogICAgbWFwX2RibCgici5zcXVhcmVkIikNCmBgYA0KDQojIyMjIE3hu5l0IHPhu5EgdsOtIGThu6UgxJHhu5FpIHbhu5tpIGjDoG0gcG1hcF8qICgpIHbDoCBtYXAyXyooKQ0KDQpDw7ogcGjDoXAgY+G7p2EgaMOgbToNCg0KYGBgDQptYXAyKC54LCAueSwgLmYsIOKApikNCmBgYA0KYGBgDQpwbWFwKC5sLCAuZiwg4oCmKQ0KYGBgDQpUaGF5IHbDrCB2w7JuZyBs4bq3cCDEkeG7kWkgduG7m2kgMSB2ZWN0b3IgKDEgYWdydW1lbnQpIG5oxrAgaMOgbSBtYXAgdGjDrCBow6BtIG1hcDJfKCkgdsOgIHBtYXBfKCkgdOG6oW8gdsOybmcgbOG6t3AgduG7m2kgMiBoYXkgbmhp4buBdSBhZ3J1bWVudA0KDQpWw60gZOG7pSB24bubaSBtYXAyDQpgYGB7cn0NCm11MSA8LSBjKDUsIDEwLCAxMikNCnNpZ21hMSA8LSBjKDIsIDQsIDUpDQptYXAyKG11MSwgc2lnbWExLCBybm9ybSwgbiA9IDUpICU+JSBzdHIoKQ0KYGBgDQoNCioqVsOtIGThu6UgduG7m2kgcG1hcCoqDQoNCmBgYA0Kcm5vcm0obiwgbWVhbiwgc2QpDQpgYGANCg0KVEgxOiBUaMOqbSBsaXN0IG4xIHbDoG8gdsOybmcgbOG6t3ANCg0KYGBge3J9DQpuMSA8LSBsaXN0KDEsIDMsIDUpDQphcmdzMSA8LSBsaXN0KG4xLCBtdTEsIHNpZ21hMSkgIyBO4bq/dSB0w6puIGtow6FjIGFncnVtZW50IHRow6wgcGjhuqNpIHPhuq9wIHjhur9wIMSRw7puZyB0aOG7qSB04buxDQpzZXQuc2VlZCgxNTgpDQphcmdzMSAlPiUNCiAgICBwbWFwKHJub3JtKSAlPiUNCiAgICBzdHIoKQ0KYGBgDQoNCg0KVGjDqm0gdmVjdG9yIG4yIHbDoG8gdsOybmcgbOG6t3AgKHRoYXkgxJHhu5VpIDEgY2jDunQgdGjhu6kgdOG7sSBjw6FjIGFncnVtZW50KQ0KYGBge3J9DQpuMiA8LSBjKDEsIDMsIDUpDQphcmdzMiA8LSBsaXN0KG1lYW4gPSBtdTEsIHNkID0gc2lnbWExLCBuID0gbjIpDQpzZXQuc2VlZCgxNTgpDQphcmdzMiAlPiUNCiAgICBwbWFwKHJub3JtKSAlPiUNCiAgICBzdHIoKQ0KYGBgDQoNClbDsm5nIGzhurdwIHbhu5tpIGRhdGEuZnJhbWUNCmBgYHtyfQ0Kc2V0LnNlZWQoMTU4KQ0KcGFyYW1zIDwtIGRhdGEuZnJhbWUobWVhbiA9IG11MSwgc2QgPSBzaWdtYTEsIG4gPSBuMikNCnBhcmFtcyAlPiUNCiAgICBwbWFwKHJub3JtKSAlPiUNCiAgICBzdHIoKQ0KYGBgDQoNCioqQ2jDuiDDvToqKg0KDQotIEjDoG0gcG1hcCBs4bq3cCB0aGVvIHRo4bupIHThu7EgcGjhuqduIHThu60ga2jDtG5nIHBow6JuIGJp4buHdCBsw6AgbGlzdCgpIGhv4bq3YyB2ZWN0b3IgYygpIGhheSBkYXRhLmZyYW1lLg0KLSBO4bq/dSDEkeG6p3UgdsOgbyDEkcO6bmcgdMOqbiB24bubaSBhZ3J1bWVudCB0cm9uZyBow6BtIHRow6wga2jDtG5nIHBow6JuIGJp4buHdCB2aeG7h2Mgc+G6r3AgeOG6v3AgYWdydW1lbnQgbsOgbyB0csaw4bubYywgYWdydW1lbnQgbsOgbyBzYXUNCg0KIyMjICoqU+G7rWEgxJHhu5VpIGdpw6EgdHLhu4sgduG7m2kgbW9kaWZ5KCkgKioNClTGsMahbmcgdOG7sSBuaMawIG1hcCwgbW9kaWZ5IGNobyDDoXAgZOG7pW5nIGjDoG0gdsOgbyBt4buZdCBuaMOzbSBjw6FjIGxpc3QuIFR1eSBuaGnDqm4sIGtow6FjIHbhu5tpIG1hcCwgbW9kaWZ5IGNobyByYSBr4bq/dCBxdeG6oyB24bubaSBj4bqldSB0csO6YyBk4buvIGxp4buHdSBiYW4gxJHDonUuDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQotIG1hcCDEkeG7lWkgY+G6pXUgdHLDumMgY+G7p2EgZGF0YWZyYW1lDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KIyBtYXAgxJHhu5VpIGPhuqV1IHRyw7pjIGPhu6dhIGRhdGFmcmFtZQ0KaXJpcyAlPiUgDQogIG1hcF9pZihpcy5mYWN0b3IsIGFzLmNoYXJhY3RlcikgJT4lIA0KICBzdHINCmBgYA0KDQotIG1vZGlmeSBnaeG7ryBuZ3V5w6puIGPhuqV1IHRyw7pjDQpgYGB7cn0NCiMgbW9kaWZ5IGdp4buvIG5ndXnDqm4gY+G6pXUgdHLDumMNCmlyaXMgJT4lIA0KICBtb2RpZnlfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JSANCiAgc3RyDQpgYGANCg0KIyMjICoqVOG6oW8gaMOgbSBuaGFuaCB24bubaSBhc19tYXBwZXIqKg0KYXNfbWFwcGVyIGNobyBwaMOpcCB04bqhbyBow6BtIG5oYW5oLCDEkeG6t2MgYmnhu4d0IGjhu691IMOtY2gga2hpIHRhIGNo4buJIG114buRbiB04bqhbyB2w6Agc+G7rSBk4bulbmcgbeG7mXQgaMOgbSB0cm9uZyBt4buZdCB2w6BpIHRyxrDhu51uZyBo4bujcCDEkeG6t2MgYmnhu4d0Lg0KDQotIEPDtG5nIHRo4bupYyB04buVbmcgcXXDoXQ6DQpgYGANCiMgVuG7m2kgbeG7mXQgdGhhbSBz4buRDQphc19tYXBwZXIofmYoLngpKQ0KIyBW4bubaSBoYWkgdGhhbSBz4buRDQphc19tYXBwZXIoZigueCwgLnkpKQ0KYGBgDQoNCi0gWGVtIGPDoWMgdsOtIGThu6Ugc2F1Og0KYGBge3J9DQojIEPhu5luZyAxMCB2w6BvIG3hu5dpIGdpw6EgdHLhu4sNCm1hcF9kYmwoMTozLCB+IC54KzEwKQ0KYGBgDQoNCmBgYHtyfQ0KIyBD4buZbmcgaGFpIHZlY3RvciB24bubaSBuaGF1DQptYXAyX2RibCgxOjMsIDU6Nywgfi54ICsgLnkpDQpgYGANCg0KYGBge3J9DQojIEPDoWNoIHZp4bq/dCBraMOhYw0KbWFwMl9kYmwoMTozLCA1OjcsIGFzX21hcHBlcih+LnggKyAueSkpDQpgYGANCg0KIyMjICoqSMOgbSBzYWZlbHkoKSoqDQoNCkNobyBr4bq/dCBxdeG6oyBsw6AgMSBsaXN0IGfhu5NtIDIgcGjhuqduIHThu606DQoNCi0gcmVzdWx0OiBUcuG6oyB24buBIGvhur90IHF14bqjIGPhu6dhIGjDoG0uIE7hur91IGfhurdwIGzhu5dpIHRow6wgPSBOVUxMDQoNCi0gZXJyb3IgOiBUcuG6oyB24buBIGzhu5dpIGPhu6dhIG9iamVjdC4gTuG6v3Uga2jDtG5nIGfhurdwIGzhu5dpIHRow6wgPSBOVUxMDQoNCihow6BtIHNhZmVseSB0xrDGoW5nIHThu7EgduG7m2kgaMOgbSB0cnkgY+G7p2EgZ8OzaSBiYXNlKQ0KDQpWw60gZOG7pSB24bubaSBow6BtIGxvZw0KYGBge3J9DQpzYWZlX2xvZyA8LSBzYWZlbHkobG9nKQ0Kc3RyKHNhZmVfbG9nKDEwKSkNCmBgYA0KDQpgYGB7cn0NCnN0cihzYWZlX2xvZygiYSIpKQ0KYGBgDQoNClRoaeG6v3Qga+G6vyBj4bunYSBow6BtIHNhZmVseSDEkcaw4bujYyBz4butIGThu6VuZyB04buRdCB24bubaSBow6BtIG1hcA0KYGBge3J9DQp4IDwtIGxpc3QoMSwgMTAsICJhIikNCnkgPC0geCAlPiUgbWFwKHNhZmVseShsb2cpKQ0Kc3RyKHkpDQpgYGANCg0KS+G6v3QgaOG7o3AgduG7m2kgaMOgbSBwdXJycjp0cmFuc3Bvc2Ugc+G6vSB0w6FjaCDEkcaw4bujYyByacOqbmcgbGlzdCB0aMOgbmggMiBwaOG6p246IDEgcGjhuqduIHJlc3VsdCB2w6AgMSBwaOG6p24gbMOgIGVycm9yDQoNCmBgYHtyfQ0KeSA8LSB5ICU+JSB0cmFuc3Bvc2UoKQ0Kc3RyKHkpDQpgYGANCg0KxJDhuqd1IHJhIGPhu6dhIHJlc3VsdCB2w6AgZXJyb3INCmBgYHtyfQ0KaXNfb2sgPC0geSRlcnJvciAlPiUgbWFwX2xnbChpc19udWxsKQ0KeFshaXNfb2tdDQpgYGANCg0KYGBge3J9DQp5JHJlc3VsdFtpc19va10gJT4lIGZsYXR0ZW5fZGJsKCkgIyBmbGF0dGVuXyB0xrDGoW5nIHThu7EgduG7m2kgaMOgbSB1bmxpc3QNCmBgYA0KDQojIyMgKipIw6BtIHBvc3NpYmx5KCkqKg0KVMawxqFuZyB04buxIHbhu5tpIGjDoG0gc2FmZWx5KCkgbHXDtG4gY2hvIGvhur90IHF14bqjIHN1Y2NlZWQuIE5oxrBuZyBow6BtIG7DoHkgxJHGoW4gZ2nhuqNuIGjGoW4gc2FmZWx5KCkgdsOsIG7DsyBjaG8gcGjDqXAgZ8OhbiBnacOhIHRy4buLIGRlZmF1bHQga2hpIGfhurdwIGzhu5dpDQpgYGB7cn0NCnggPC0gbGlzdCgxLCAxMCwgImEiKQ0KeCAlPiUgbWFwX2RibChwb3NzaWJseShsb2csIE5BX3JlYWxfKSkNCmBgYA0KDQojIyMgKipIw6BtIHF1aWV0bHkoKSoqDQpUxrDGoW5nIHThu7EgbmjGsCBzYWZlbHkoKSBuaMawbmcgdGhheSB2w6wgxJHhuqd1IHJhIGzDoCBlcnJvciwgaMOgbSBuw6B5IGNobyByYSBvdXRwdXQsIG1lc3NhZ2VzIHbDoCB3YXJuaW5ncw0KYGBge3J9DQp4IDwtIGxpc3QoMSwgLTEpDQp4ICU+JSBtYXAocXVpZXRseShsb2cpKSAlPiUgc3RyKCkNCmBgYA0KDQoqKkNow7ogw70qKjogQ8OhYyBow6BtIGLhuq90IGzhu5dpIGtow6FjIHbhu5tpIGPDoWMgaMOgbSB0aMO0bmcgdGjGsOG7nW5nIOG7nyBjaOG7lzogaMOgbSB0aMO0bmcgdGjGsOG7nW5nIH4gdmVyYiwgY8OybiBjw6FjIGjDoG0gbsOgeSB+IGFkdmVyYi4gVOG7qWMgbMOgIGNo4buJIHJhIGPDoWNoIHRo4bupYyB0aOG7sWMgaGnhu4duIGPDoWMgaMOgbSB0aMO0bmcgdGjGsOG7nW5nDQoNCiMjIyAqKljDonkgZOG7sW5nIGNodeG7l2kgY8OhYyBow6BtIGxpw6puIHRp4bq/cCB24bubaSBjb21wb3NlKCk6ICoqDQoNCsSQ4bqndSB0acOqbiB0w7RpIHbDrSBk4bulIDEgxJFv4bqhbiBjb2RlOg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShicm9vbSkNCmBgYA0KDQpgYGB7cn0NCmxtKFNlcGFsLkxlbmd0aCB+IFNwZWNpZXMsIGRhdGEgPSBpcmlzKSAlPiUgdGlkeSgpICU+JSBmaWx0ZXIocC52YWx1ZSA8IDAuMDUpDQpsbShTZXBhbC5MZW5ndGggfiBTcGVjaWVzLCBkYXRhID0gaXJpcykgJT4lIHRpZHkoKSAlPiUgZmlsdGVyKHAudmFsdWUgPCAwLjA1KQ0KbG0oU2VwYWwuV2lkdGggfiBTcGVjaWVzLCBkYXRhID0gaXJpcykgJT4lIHRpZHkoKSAlPiUgZmlsdGVyKHAudmFsdWUgPCAwLjA1KQ0KbG0oU2VwYWwuTGVuZ3RoIH4gU3BlY2llcywgZGF0YSA9IGlyaXMpICU+JSB0aWR5KCkgJT4lIGZpbHRlcihwLnZhbHVlIDwgMC4wNSkNCmBgYA0KDQpO4bq/dSBjaMO6bmcgdGEgdmnhur90IGNvZGUgbmjGsCB0csOqbiB0aMO0bmcgdGjGsOG7nW5nIG3huq90IGPhu6dhIGNow7puZyB0YSBz4bq9IGNo4buJIHThuq1wIHRydW5nIHbDoG8gbmjhu69uZyDEkWnhu4NtIGdp4buRbmcgbmhhdSB0aGF5IHbDrCB04bqtcCB0cnVuZyB2w6BvIG5o4buvbmcgxJFp4buDbSB0aGF5IMSR4buVaSBj4bunYSBjw6FjIGTDsm5nIGNvZGUuIFbDrCB24bqteSwgY29kZXIgcuG6pXQgZOG7hSBt4bqvYyBs4buXaSBraGkgdmnhur90IG5oaeG7gXUgZMOybmcgdMawxqFuZyB04buxIG5oYXUuIENow7ogw70gcuG6sW5nIG7hur91IGNvZGUgbOG6t3AgbOG6oWkgdHLDqm4gMiBs4bqnbiB0aMOsIG7Dqm4gc+G7rSBk4bulbmcgZnVuY3Rpb24uIFbhu5tpIHB1cnJyIHRoYXkgdsOsIHZp4bq/dCBow6BtIG3hu5tpIHRow6wgY2jDum5nIHRhIG7Dqm4gbW9kaWZ5IGhv4bq3YyBr4bq/dCBo4bujcCBjw6FjIGjDoG0gbOG6oWkgduG7m2kgbmhhdS4NCg0KU28gc8OhbmggMiBjw6FjaCB2aeG6v3Q6DQpgYGB7cn0NCnRpZHkobG0oU2VwYWwuTGVuZ3RoIH4gU3BlY2llcywgZGF0YSA9IGlyaXMpKQ0KYGBgDQoNCnbDoCBjw6FjaCAyIHPhu60gZOG7pW5nIGjDoG0gY29tcG9zZToNCmBgYHtyfQ0KdGlkeV9sbSA8LSBjb21wb3NlKHRpZHksIGxtKQ0KdGlkeV9sbShTZXBhbC5MZW5ndGggfiBTcGVjaWVzLCBkYXRhID0gaXJpcykNCmBgYA0KDQpOaMawIHbhuq15LCBi4bqxbmcgY8OhY2gga+G6v3QgaOG7o3AgaMOgbSBsbSB2w6AgdGlkeSAodGhlbyBuZ3V5w6puIHThuq9jIHThu6sgcGjhuqNpIHNhbmcgdHLDoWkpIGNow7puZyB0YSBjw7MgaMOgbSB0aWR5X2xtIG5n4bqvbiBn4buNbg0KDQpT4butIGThu6VuZyBhc19tYXBwZXIgxJHhu4MgY29udmVydCAxIG9iamVjdCBzYW5nIGZ1bmN0aW9uIHbDoCBjxaluZyBz4butIGThu6VuZyBow6BtIGNvbXBvc2UNCmBgYHtyfQ0KY2xlYW5fbG0gPC0gY29tcG9zZShhc19tYXBwZXIofiBhcnJhbmdlKC54LCBkZXNjKHAudmFsdWUpKSksIA0KICAgICAgICAgICAgICAgICAgICBhc19tYXBwZXIofiBmaWx0ZXIoLngsIHAudmFsdWUgPCAwLjA1KSksDQogICAgICAgICAgICAgICAgICAgIHRpZHksIA0KICAgICAgICAgICAgICAgICAgICBsbSkNCmNsZWFuX2xtKFNlcGFsLkxlbmd0aCB+IFNlcGFsLldpZHRoLCBkYXRhID0gaXJpcykNCmBgYA0KDQojIyMgKipQYXJ0aWFsKCk6ICoqDQoNCkPDoWNoIHZp4bq/dCB0aMO0bmcgdGjGsOG7nW5nIGtoaSB0w61uaCBnacOhIHRy4buLIHRydW5nIGLDrG5oIHbhu5tpIHZlY3RvciBjw7MgZ2nDoSB0cuG7iyBOQQ0KYGBge3J9DQptZWFuKGFpcnF1YWxpdHkkT3pvbmUsIG5hLnJtID0gVFJVRSkNCmBgYA0KDQpT4butIGThu6VuZyBow6BtIHBhcnRpYWw6DQpgYGB7cn0NCm1lYW5fbmFfcm0gPC0gcGFydGlhbChtZWFuLCBuYS5ybSA9IFRSVUUpDQptZWFuX25hX3JtKGFpcnF1YWxpdHkkT3pvbmUpDQpgYGANCg0Ka+G6v3QgcXXhuqMgdMawxqFuZyB04buxIMSRb+G6oW4gY29kZSB0csOqbiBuaMawbmcgbmfhuq9uIGfhu41uIGjGoW4NCg0KIyAqKkNIxq/GoE5HIDM6IEvhur5UIExV4bqsTioqDQoNCkfDs2kgYHB1cnJyYCBsw6AgbeG7mXQgZ8OzaSBt4bqhbmggbeG6vSB2w6AgbGluaCBob+G6oXQgdHJvbmcgUiwgY3VuZyBj4bqlcCBjw6FjIGPDtG5nIGPhu6UgbOG6rXAgdHLDrG5oIGNo4bupYyBuxINuZyDEkeG7gyBsw6BtIHZp4buHYyB24bubaSBkYW5oIHPDoWNoLCB2ZWN0xqEgdsOgIGPDoWMgY+G6pXUgdHLDumMgZOG7ryBsaeG7h3Uga2jDoWMuIEdp4buRbmcgbmjGsCBi4bqldCBr4buzIGfDs2kgbsOgbywgYHB1cnJyYCBjw7Mgbmjhu69uZyDGsHUgxJFp4buDbSB2w6AgbmjGsOG7o2MgxJFp4buDbSByacOqbmcuDQoNCiMjICoqxq91IMSRaeG7g20gY+G7p2EgUHVycnI6KioNCg0KLSAqKk3DtCBow6xuaCBs4bqtcCB0csOsbmggaMOgbToqKiBnw7NpIFB1cnJyIGN1bmcgY+G6pXAgbeG7mXQgbG/huqF0IGPDoWMgaMOgbSBuaMawIG1hcCgpLCBtYXAyKCksIHBtYXAoKSwuLi4gZ2nDunAgYuG6oW4gw6FwIGThu6VuZyBt4buZdCBow6BtIGzDqm4gY8OhYyBwaOG6p24gdOG7rSB0cm9uZyBkYW5oIHPDoWNoIGhv4bq3YyB2ZWN0b3IgbeG7mXQgY8OhY2ggZOG7hSBkw6BuZy4gxJBp4buBdSBuw6B5IGNobyBwaMOpcCBi4bqhbiB2aeG6v3QgY29kZSBuZ+G6r24gZ+G7jW4sIHPDoW5nIHThuqFvIHbDoCBk4buFIMSR4buNYyBoxqFuLg0KLSAqKlThu7EgxJHhu5luZyBow7NhIGPDoWMgdGhhbyB0w6FjIGzhurdwIGzhuqFpKio6IFRoYXkgdsOsIHZp4bq/dCBjw6FjIHbDsm5nIGzhurdwIHRo4bunIGPDtG5nIMSR4buDIHRo4buxYyBoaeG7h24gY8OhYyB0aGFvIHTDoWMgdHLDqm4gdOG7q25nIHBo4bqnbiB04butLCBnw7NpIFB1cnJyIGNobyBwaMOpcCBi4bqhbiDDoXAgZOG7pW5nIGPDoWMgaMOgbSBt4buZdCBjw6FjaCB04buxIMSR4buZbmcgdHLDqm4gdG/DoG4gYuG7mSBkYW5oIHPDoWNoIGhv4bq3YyB2ZWN0b3IgbeG7mXQgY8OhY2ggaGnhu4d1IHF14bqjLiDEkGnhu4F1IG7DoHkgZ2nDunAgZ2nhuqNtIMSRw6FuZyBr4buDIGzGsOG7o25nIG3DoyBs4bq3cCBs4bqhaSB2w6AgdMSDbmcgaGnhu4d1IHN14bqldCBj4bunYSBjb2RlLg0KDQojIyAqKk5oxrDhu6NjIMSRaeG7g20gY+G7p2EgUHVycnIqKg0KR8OzaSBwdXJyciB0cm9uZyBSIGN1bmcgY+G6pXAgbmhp4buBdSBs4bujaSB0aOG6vyBjaG8gbOG6rXAgdHLDrG5oIGNo4bupYyBuxINuZyB2w6AgbMOgbSB2aeG7h2MgduG7m2kgZGFuaCBzw6FjaCB2w6AgdmVjdMahLCBuaMawbmcgbsOzIGPFqW5nIGPDsyBt4buZdCBz4buRIG5oxrDhu6NjIMSRaeG7g20gdGnhu4FtIOG6qW46DQoNCi0gTmjGsCB24bubaSBi4bqldCBr4buzIGfDs2kgbsOgbyBnaeG7m2kgdGhp4buHdSBt4buZdCBtw7QgaMOsbmggbOG6rXAgdHLDrG5oIGtow6FjLCBjw7MgdGjhu4MgY8OzIG3hu5l0IMSRxrDhu51uZyBjb25nIGjhu41jIHThuq1wIGNobyBuaOG7r25nIG5nxrDhu51pIGtow7RuZyBxdWVuIHRodeG7mWMgduG7m2kgY8OhYyBraMOhaSBuaeG7h20gbOG6rXAgdHLDrG5oIGNo4bupYyBuxINuZy4gTmfGsOG7nWkgZMO5bmcgxJHDoyBxdWVuIHbhu5tpIGzhuq1wIHRyw6xuaCBt4buHbmggbOG7h25oIGPDsyB0aOG7gyB0aOG6pXkga2jDsyB0aMOtY2gg4bupbmcgduG7m2kgY8OhY2ggdGnhur9wIGPhuq1uIGNo4bupYyBuxINuZy4NCg0KLSBQaOG7pSB0aHXhu5ljIHbDoG8gdGlkeXZlcnNlOiBN4bq3YyBkw7kgcHVycnIgbMOgIG3hu5l0IHBo4bqnbiBj4bunYSB0aWR5dmVyc2UsIGzDoCB04bqtcCBo4bujcCBjw6FjIGfDs2kgxJHGsOG7o2Mgc+G7rSBk4bulbmcgcuG7mW5nIHLDo2ksIG5oxrBuZyDEkWnhu4F1IMSRw7MgY8WpbmcgY8OzIG5naMSpYSBsw6Agdmnhu4djIHPhu60gZOG7pW5nIHB1cnJyIG1hbmcgbOG6oWkgY8OhYyBwaOG7pSB0aHXhu5ljIGLhu5Ugc3VuZy4gTuG6v3UgYuG6oW4gxJFhbmcgbMOgbSB2aeG7h2MgdHJvbmcgbeG7mXQgbcO0aSB0csaw4budbmcga2jDtG5nIGtodXnhur9uIGtow61jaCBob+G6t2MgaOG6oW4gY2jhur8gdmnhu4djIHRow6ptIGPDoWMgZ8OzaSBi4buVIHN1bmcsIHRow6wgxJHDonkgY8OzIHRo4buDIGzDoCBt4buZdCB24bqlbiDEkeG7gSDEkcOhbmcgbG8gbmfhuqFpLg0KDQotIFRow6FjaCB0aOG7qWMgZ+G7oSBs4buXaTogTcOjIGfhu6EgbOG7l2kgY2jhu6cgeeG6v3UgZOG7sWEgdsOgbyBs4bqtcCB0csOsbmggY2jhu6ljIG7Eg25nIGPDsyB0aOG7gyBwaOG7qWMgdOG6oXAgaMahbiBtw6MgbeG7h25oIGzhu4duaCB0cnV54buBbiB0aOG7kW5nLiBWaeG7h2MgaGnhu4N1IHF1eSB0csOsbmggaG/huqF0IMSR4buZbmcgdsOgIGfhu6EgbOG7l2kgY8OhYyBixrDhu5tjIHRydW5nIGdpYW4gY8OzIHRo4buDIGPhuqduIHRow6ptIG7hu5cgbOG7sWMuDQoNCi0gxJDhu5kgcGjhu6ljIHThuqFwIHRy4burdSB0xrDhu6NuZzogVmnhu4djIHPhu60gZOG7pW5nIGPDoWMgaMOgbSBi4bqtYyBjYW8gaMahbiB2w6AgY8OhYyBow6BtIG5oxrAgbWFwKCljw7MgdGjhu4MgZ2nhu5tpIHRoaeG7h3UgY8OhYyBs4bubcCB0cuG7q3UgdMaw4bujbmcgY8OzIHRo4buDIGzDoG0gY2hvIG3DoyDDrXQgxJHGoW4gZ2nhuqNuIGjGoW4gxJHhu5FpIHbhu5tpIG3hu5l0IHPhu5EgbmfGsOG7nWkgxJHhu41jLiBDw6JuIGLhurFuZyB0w61uaCB0cuG7q3UgdMaw4bujbmcgdsOgIGto4bqjIG7Eg25nIMSR4buNYyBsw6AgcuG6pXQgcXVhbiB0cuG7jW5nIMSR4buDIMSR4bqjbSBi4bqjbyBraOG6oyBuxINuZyBkdXkgdHLDrCBtw6MuDQoNCi0gVHJvbmcgbeG7mXQgc+G7kSB0csaw4budbmcgaOG7o3AsIHZp4buHYyBz4butIGThu6VuZyBwdXJycnbDoCBjw6FjIGvhu7kgdGh14bqtdCBs4bqtcCB0csOsbmggY2jhu6ljIG7Eg25nIGPDsyB0aOG7gyBk4bqrbiDEkeG6v24gbcOjIGTDoGkgZMOybmcgaMahbiBzbyB24bubaSBjw6FjIGzhu7FhIGNo4buNbiB0aGF5IHRo4bq/IHZlY3RvciBow7NhLCDEkcahbiBnaeG6o24gaMahbi4gxJBp4buBdSBuw6B5IGPDsyB0aOG7gyBsw6BtIGNobyBtw6Mga2jDsyDEkeG7jWMgdsOgIGtow7MgYuG6o28gdHLDrCBoxqFuLg0KDQpC4bqldCBjaOG6pXAgbmjhu69uZyBo4bqhbiBjaOG6vyB0aeG7gW0g4bqpbiBuw6B5LCDEkWnhu4F1IGPhuqduIHRoaeG6v3QgbMOgIHBo4bqjaSBuaOG7myBy4bqxbmcgcHVycnJs4bqtcCB0csOsbmggY2jhu6ljIG7Eg25nIHRyb25nIFIgbWFuZyBs4bqhaSBs4bujaSDDrWNoIMSRw6FuZyBr4buDIGNobyBuaGnhu4F1IHbhu6UgdGhhbyB0w6FjIGThu68gbGnhu4d1LiBOw7MgbMOgIG3hu5l0IGPDtG5nIGPhu6UgbeG6oW5oIG3hur0gxJHhu4MgbMOgbSB2aeG7h2MgduG7m2kgY8OhYyBj4bqldSB0csO6YyBk4buvIGxp4buHdSBwaOG7qWMgdOG6oXAgdsOgIGPDsyB0aOG7gyBk4bqrbiDEkeG6v24gbcOjIG5n4bqvbiBn4buNbiwgYmnhu4N1IGPhuqNtIHbDoCBk4buFIMSR4buNYyBoxqFuLiBRdXnhur90IMSR4buLbmggc+G7rSBk4bulbmcgYHB1cnJyYCBuw6puIHhlbSB4w6l0IHRyxrDhu51uZyBo4bujcCBz4butIGThu6VuZyBj4bulIHRo4buDLCBj4bqldSB0csO6YyBk4buvIGxp4buHdSwgecOqdSBj4bqndSBoaeG7h3Ugc3XhuqV0IGPFqW5nIG5oxrAgbeG7qWMgxJHhu5kgcXVlbiB0aHXhu5ljIHbDoCBz4bufIHRow61jaCBj4bunYSBuaMOzbSBwaMOhdCB0cmnhu4NuLiBUcm9uZyBuaGnhu4F1IHRyxrDhu51uZyBo4bujcCwgYHB1cnJyYCBjw7MgdGjhu4MgbsOibmcgY2FvIMSRw6FuZyBr4buDIGto4bqjIG7Eg25nIHRoYW8gdMOhYyBk4buvIGxp4buHdSB2w6Aga2jhuqMgbsSDbmcgxJHhu41jIG3DoyBSLg0KDQpUw6BpIGxp4buHdSB0aGFtIGto4bqjbzoNCg0KMS4gaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3B1cnJyL3B1cnJyLnBkZg0KMi4gQ+G6qW0gbmFuZyBk4buLY2ggdOG7hSBo4buNYyB24bubaSBSDQoNCg==