1 Danh sách (list) trong ngôn ngữ R

Trong ngôn ngữ R, danh sách (list) là một cấu trúc dữ liệu linh hoạt có thể chứa các phần tử thuộc nhiều loại dữ liệu khác nhau, bao gồm con số, chuỗi kí tự, vector, hàm và thậm chí là các danh sách khác (nested list) hoặc một cách tổng quát: bất kì R object nào. Dưới đây là một phân tích tổng quan về bản chất và đặc điểm của danh sách trong R:

Tính đa dạng: Danh sách có thể chứa đồng thời nhiều loại dữ liệu. Ví dụ, một danh sách có thể lưu đồng thời giá trị số, vector ký tự, một matrix, một dataframe, hàm và thậm chí là một danh sách khác.

Đa số kết quả phân tích thống kê thường xuất ra dưới dạng list, bên trong chứa nhiều thông tin. Ví dụ một mô hình có thể bao gồm bảng kết quả, dữ liệu gốc, các thông số kỹ thuật. Dataframe trong R cũng có bản chất là list.

Tính Đệ quy: Danh sách có thể chứa các danh sách khác. Điều này làm cho chúng trở nên đệ quy, vì cấu trúc của chúng có thể được lồng ghép vào nhau.

Kích thước Kích thước của danh sách đề cập đến số lượng phần tử mà nó chứa, R list có thể có bất kỳ kích thước nào, kể cả list rỗng (kích thước = 0)

Đặt tên: Các phần tử của danh sách có thể đặt tên. Những tên này có thể được sử dụng để tham chiếu đến các phần tử, tính năng này gần giống với key trong Python dictionary.

Chỉ mục: Các phần tử trong một danh sách có thể được truy cập sử dụng ký hiệu ngoặc vuông kép [[ ]] hoặc dollar $ nếu các phần tử được đặt tên. Ngoặc vuông đơn [ ] trả về một danh sách con thay vì chính phần tử đó.

Linh hoạt: Kích thước và nội dung của danh sách có thể được sửa đổi. Bạn có thể thêm, xóa và sửa đổi các phần tử bên trong danh sách.

Tính linh động: Trong R, loại (type) của các phần tử trong danh sách có thể thay đổi trong thời gian thi hành (giống như ngôn ngữ Python hay JavaScript, và khác với ngôn ngữ C hay Java)

Python dictionary là cấu trúc dữ liệu tương đồng nhất về tính năng so với R list, tuy nhiên Python dict có tốc độ tìm và truy xuất dữ liệu nhanh hơn do dựa trên hashtable trong khi R list có hiệu suất kém hơn do có bản chất là array.

2 Ví dụ về công dụng của list trong lập trình hàm

Giả sử ta cần viết một hàm cho phép vẽ 1 trong 3 loại biểu đồ: density plot, histogram hoặc dot plot, do người dùng tùy chọn qua parameter plot_type:

Giải pháp sơ cấp thường được nghĩ đến là dùng câu lệnh điều kiện (control flow) với if và else if như sau:

library(tidyverse)
dist_plot_1 <- function(data, x, plot_type) {
  # Check if the provided column name is valid
  if (!x %in% names(data)) {
    stop("The specified column does not exist in the data frame.")
  }
  
  # Check if the plot type is valid
  valid_plot_types <- c("kde", "hist", "dot")
  if (!plot_type %in% valid_plot_types) {
    stop("Unknown plot type. Valid plot types are: ", paste(valid_plot_types, collapse = ", "))
  }
  
  # Generate the plot based on the type
  p <- ggplot(data, aes_string(x = x))
  
  if (plot_type == "kde") {
    p <- p + geom_density(fill = "blue", alpha = 0.5) + 
      labs(title = paste("Kernel Density Estimate of", x))
  } else if (plot_type == "hist") {
    p <- p + geom_histogram(fill = "red", alpha = 0.5) + 
      labs(title = paste("Histogram of", x))
  } else if (plot_type == "dot") {
    p <- p + geom_dotplot(binaxis = 'x', dotsize = 1) + 
      labs(title = paste("Dot Plot of", x))
  }
  
  # Return the plot
  return(p)
}

Ta có thể áp dụng list để thay cho cấu trúc if_else, vì biết rằng list có thể chứa bất cứ object nào, nên ở đây ta dùng list để chứa các ggplot layers khác nhau (bản chất là hàm), với key là plot_type.

dist_plot_2a <- function(data, x, plot_type) {
  # Check if the provided column name is valid
  if (!x %in% names(data)) {
    stop("The specified column does not exist in the data frame.")
  }
  
  # Define a list of ggplot layers for each plot type
  plot_layers <- list(
    kde = geom_density(aes_string(x = x), fill = "blue", alpha = 0.5),
    hist = geom_histogram(aes_string(x = x), fill = "red", alpha = 0.5),
    dot = geom_dotplot(aes_string(x = x), binaxis = 'x', dotsize = 1)
  )
  
  # Get the ggplot layer from the list based on plot_type
  layer <- plot_layers[[plot_type]]
  
  # Check if plot_type is valid and layer exists
  if (is.null(layer)) {
    stop("Unknown plot type. Valid plot types are: 'kde', 'hist', 'dot'.")
  }
  
  # Create the ggplot object with the specified layer and return it
  p <- ggplot(data) + layer + labs(title = paste(plot_type, "of", x))
  
  return(p)
}

Ta còn có thể dùng nested_list để truy xuất cả layer (function) và title (string) cho mỗi plot_type

dist_plot_2b <- function(data, x, plot_type) {
  # Validate input
  if (!x %in% names(data)) {
    stop("The specified column does not exist in the data frame.")
  }
  
  # Mapping of plot types to ggplot layers and additional settings
  plot_layers <- list(
    kde = list(layer = geom_density(fill = "blue", alpha = 0.5), title = "Kernel Density Estimate"),
    hist = list(layer = geom_histogram(fill = "red", alpha = 0.5), title = "Histogram"),
    dot = list(layer = geom_dotplot(binaxis = 'x', dotsize = 0.5), title = "Dot Plot")
  )
  
  # Retrieve the plot settings for the specified plot_type
  plot_setting <- plot_layers[[plot_type]]
  if (is.null(plot_setting)) {
    stop("Unknown plot type. Valid plot types are: 'kde', 'hist', 'dot'.")
  }
  
  # Create the ggplot object and add the layer and title
  p <- ggplot(data, aes_string(x = x)) +
    plot_setting$layer +
    labs(title = paste(plot_setting$title, "of", x))
  
  return(p)
}

Một cách tổng quát, ta có thể dùng list để chứa các hàm anonymous với nội dung tùy thích như sau:

dist_plot_3 <- function(data, x, plot_type) {
  # Check if the provided column name is valid
  if (!x %in% names(data)) {
    stop("The specified column does not exist in the data frame.")
  }
  
  # Define a list of plot types and their corresponding ggplot2 functions
  plot_types <- list(
    "kde" = function() ggplot(data, aes_string(x = x)) + 
      geom_density(fill = "blue", alpha = 0.5),
    "hist" = function() ggplot(data, aes_string(x = x)) + 
      geom_histogram(fill = "red", alpha = 0.5),
    "dot" = function() ggplot(data, aes_string(x = x)) + 
      geom_dotplot(binaxis = 'x', dotsize = 1)
  )
  
  # Check if the plot type is valid and get the corresponding function
  plot_func <- plot_types[[plot_type]]
  if (is.null(plot_func)) {
    stop("Unknown plot type. Valid plot types are: ", paste(names(plot_types), collapse = ", "))
  }
  
  # Generate and return the plot
  p <- plot_func()
  p + labs(title = paste(plot_type, "Plot of", x))
}

Thay vì dùng trực tiếp list, trong ngôn ngữ R còn có cấu trúc điều kiện rẽ nhánh (switch), bản chất của nó cũng là một list chứa các object được tạo ra trong lúc thi hành tùy theo từ khóa điều kiện

dist_plot_4 <- function(data, x, plot_type) {
  # Check if the provided column name is valid
  if (!x %in% names(data)) {
    stop("The specified column does not exist in the data frame.")
  }
  
  # Define the plot
  p <- ggplot(data, aes_string(x = x))
  
  # Generate the plot based on the type using switch
  p <- switch(plot_type,
              kde = p + geom_density(fill = "blue", alpha = 0.5) + 
                labs(title = paste("Kernel Density Estimate of", x)),
              hist = p + geom_histogram(fill = "red", alpha = 0.5) + 
                labs(title = paste("Histogram of", x)),
              dot = p + geom_dotplot(binaxis = 'x', 
                                     dotsize = 1) + 
                labs(title = paste("Dot Plot of", x)),
              stop("Unknown plot type. Valid plot types are: kde, hist, dot")
  )
  
  # Return the plot
  return(p)
}
LS0tDQp0aXRsZTogIkRhbmggc8OhY2ggKFIgbGlzdCkgdsOgIOG7qW5nIGThu6VuZyINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjA1IFRow6FuZyAxMSBuxINtIDIwMjMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogZGVmYXVsdA0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBkZXY6IHN2Zw0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9Rn0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVCkNCmBgYA0KDQojIERhbmggc8OhY2ggKGxpc3QpIHRyb25nIG5nw7RuIG5n4buvIFINCg0KVHJvbmcgbmfDtG4gbmfhu68gUiwgZGFuaCBzw6FjaCAobGlzdCkgbMOgIG3hu5l0IGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IGxpbmggaG/huqF0IGPDsyB0aOG7gyBjaOG7qWEgY8OhYyBwaOG6p24gdOG7rSB0aHXhu5ljIG5oaeG7gXUgbG/huqFpIGThu68gbGnhu4d1IGtow6FjIG5oYXUsIGJhbyBn4buTbSBjb24gc+G7kSwgY2h14buXaSBrw60gdOG7sSwgdmVjdG9yLCBow6BtIHbDoCB0aOG6rW0gY2jDrSBsw6AgY8OhYyBkYW5oIHPDoWNoIGtow6FjIChuZXN0ZWQgbGlzdCkgaG/hurdjIG3hu5l0IGPDoWNoIHThu5VuZyBxdcOhdDogYuG6pXQga8OsIFIgb2JqZWN0IG7DoG8uIETGsOG7m2kgxJHDonkgbMOgIG3hu5l0IHBow6JuIHTDrWNoIHThu5VuZyBxdWFuIHbhu4EgYuG6o24gY2jhuqV0IHbDoCDEkeG6t2MgxJFp4buDbSBj4bunYSBkYW5oIHPDoWNoIHRyb25nIFI6DQoNCioqVMOtbmggxJFhIGThuqFuZzoqKiBEYW5oIHPDoWNoIGPDsyB0aOG7gyBjaOG7qWEgxJHhu5NuZyB0aOG7nWkgbmhp4buBdSBsb+G6oWkgZOG7ryBsaeG7h3UuIFbDrSBk4bulLCBt4buZdCBkYW5oIHPDoWNoIGPDsyB0aOG7gyBsxrB1IMSR4buTbmcgdGjhu51pIGdpw6EgdHLhu4sgc+G7kSwgdmVjdG9yIGvDvSB04buxLCBt4buZdCBtYXRyaXgsIG3hu5l0IGRhdGFmcmFtZSwgaMOgbSB2w6AgdGjhuq1tIGNow60gbMOgIG3hu5l0IGRhbmggc8OhY2gga2jDoWMuDQoNCsSQYSBz4buRIGvhur90IHF14bqjIHBow6JuIHTDrWNoIHRo4buRbmcga8OqIHRoxrDhu51uZyB4deG6pXQgcmEgZMaw4bubaSBk4bqhbmcgbGlzdCwgYsOqbiB0cm9uZyBjaOG7qWEgbmhp4buBdSB0aMO0bmcgdGluLiBWw60gZOG7pSBt4buZdCBtw7QgaMOsbmggY8OzIHRo4buDIGJhbyBn4buTbSBi4bqjbmcga+G6v3QgcXXhuqMsIGThu68gbGnhu4d1IGfhu5FjLCBjw6FjIHRow7RuZyBz4buRIGvhu7kgdGh14bqtdC4gRGF0YWZyYW1lIHRyb25nIFIgY8WpbmcgY8OzIGLhuqNuIGNo4bqldCBsw6AgbGlzdC4NCg0KKipUw61uaCDEkOG7hyBxdXkqKjogRGFuaCBzw6FjaCBjw7MgdGjhu4MgY2jhu6lhIGPDoWMgZGFuaCBzw6FjaCBraMOhYy4gxJBp4buBdSBuw6B5IGzDoG0gY2hvIGNow7puZyB0cuG7nyBuw6puIMSR4buHIHF1eSwgdsOsIGPhuqV1IHRyw7pjIGPhu6dhIGNow7puZyBjw7MgdGjhu4MgxJHGsOG7o2MgbOG7k25nIGdow6lwIHbDoG8gbmhhdS4NCg0KKipLw61jaCB0aMaw4bubYyoqIEvDrWNoIHRoxrDhu5tjIGPhu6dhIGRhbmggc8OhY2ggxJHhu4EgY+G6rXAgxJHhur9uIHPhu5EgbMaw4bujbmcgcGjhuqduIHThu60gbcOgIG7DsyBjaOG7qWEsIFIgbGlzdCBjw7MgdGjhu4MgY8OzIGLhuqV0IGvhu7Mga8OtY2ggdGjGsOG7m2MgbsOgbywga+G7gyBj4bqjIGxpc3QgcuG7l25nIChrw61jaCB0aMaw4bubYyA9IDApDQoNCioqxJDhurd0IHTDqm46KiogQ8OhYyBwaOG6p24gdOG7rSBj4bunYSBkYW5oIHPDoWNoIGPDsyB0aOG7gyDEkeG6t3QgdMOqbi4gTmjhu69uZyB0w6puIG7DoHkgY8OzIHRo4buDIMSRxrDhu6NjIHPhu60gZOG7pW5nIMSR4buDIHRoYW0gY2hp4bq/dSDEkeG6v24gY8OhYyBwaOG6p24gdOG7rSwgdMOtbmggbsSDbmcgbsOgeSBn4bqnbiBnaeG7kW5nIHbhu5tpIGtleSB0cm9uZyBQeXRob24gZGljdGlvbmFyeS4NCg0KKipDaOG7iSBt4bulYzoqKiBDw6FjIHBo4bqnbiB04butIHRyb25nIG3hu5l0IGRhbmggc8OhY2ggY8OzIHRo4buDIMSRxrDhu6NjIHRydXkgY+G6rXAgc+G7rSBk4bulbmcga8O9IGhp4buHdSBuZ2/hurdjIHZ1w7RuZyBrw6lwIFtbIF1dIGhv4bq3YyBkb2xsYXIgJCBu4bq/dSBjw6FjIHBo4bqnbiB04butIMSRxrDhu6NjIMSR4bq3dCB0w6puLiBOZ2/hurdjIHZ1w7RuZyDEkcahbiBbIF0gdHLhuqMgduG7gSBt4buZdCBkYW5oIHPDoWNoIGNvbiB0aGF5IHbDrCBjaMOtbmggcGjhuqduIHThu60gxJHDsy4NCg0KKipMaW5oIGhv4bqhdDoqKiBLw61jaCB0aMaw4bubYyB2w6AgbuG7mWkgZHVuZyBj4bunYSBkYW5oIHPDoWNoIGPDsyB0aOG7gyDEkcaw4bujYyBz4butYSDEkeG7lWkuIELhuqFuIGPDsyB0aOG7gyB0aMOqbSwgeMOzYSB2w6Agc+G7rWEgxJHhu5VpIGPDoWMgcGjhuqduIHThu60gYsOqbiB0cm9uZyBkYW5oIHPDoWNoLg0KDQoqKlTDrW5oIGxpbmggxJHhu5luZyoqOiBUcm9uZyBSLCBsb+G6oWkgKHR5cGUpIGPhu6dhIGPDoWMgcGjhuqduIHThu60gdHJvbmcgZGFuaCBzw6FjaCBjw7MgdGjhu4MgdGhheSDEkeG7lWkgdHJvbmcgdGjhu51pIGdpYW4gdGhpIGjDoG5oIChnaeG7kW5nIG5oxrAgbmfDtG4gbmfhu68gUHl0aG9uIGhheSBKYXZhU2NyaXB0LCB2w6Aga2jDoWMgduG7m2kgbmfDtG4gbmfhu68gQyBoYXkgSmF2YSkNCg0KUHl0aG9uIGRpY3Rpb25hcnkgbMOgIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IHTGsMahbmcgxJHhu5NuZyBuaOG6pXQgduG7gSB0w61uaCBuxINuZyBzbyB24bubaSBSIGxpc3QsIHR1eSBuaGnDqm4gUHl0aG9uIGRpY3QgY8OzIHThu5FjIMSR4buZIHTDrG0gdsOgIHRydXkgeHXhuqV0IGThu68gbGnhu4d1IG5oYW5oIGjGoW4gZG8gZOG7sWEgdHLDqm4gaGFzaHRhYmxlIHRyb25nIGtoaSBSIGxpc3QgY8OzIGhp4buHdSBzdeG6pXQga8OpbSBoxqFuIGRvIGPDsyBi4bqjbiBjaOG6pXQgbMOgIGFycmF5Lg0KDQojIFbDrSBk4bulIHbhu4EgY8O0bmcgZOG7pW5nIGPhu6dhIGxpc3QgdHJvbmcgbOG6rXAgdHLDrG5oIGjDoG0NCg0KR2nhuqMgc+G7rSB0YSBj4bqnbiB2aeG6v3QgbeG7mXQgaMOgbSBjaG8gcGjDqXAgduG6vSAxIHRyb25nIDMgbG/huqFpIGJp4buDdSDEkeG7kzogZGVuc2l0eSBwbG90LCBoaXN0b2dyYW0gaG/hurdjIGRvdCBwbG90LCBkbyBuZ8aw4budaSBkw7luZyB0w7l5IGNo4buNbiBxdWEgcGFyYW1ldGVyIHBsb3RfdHlwZToNCg0KR2nhuqNpIHBow6FwIHPGoSBj4bqlcCB0aMaw4budbmcgxJHGsOG7o2MgbmdoxKkgxJHhur9uIGzDoCBkw7luZyBjw6J1IGzhu4duaCDEkWnhu4F1IGtp4buHbiAoY29udHJvbCBmbG93KSB24bubaSBpZiB2w6AgZWxzZSBpZiBuaMawIHNhdToNCg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCg0KYGBge3J9DQpkaXN0X3Bsb3RfMSA8LSBmdW5jdGlvbihkYXRhLCB4LCBwbG90X3R5cGUpIHsNCiAgIyBDaGVjayBpZiB0aGUgcHJvdmlkZWQgY29sdW1uIG5hbWUgaXMgdmFsaWQNCiAgaWYgKCF4ICVpbiUgbmFtZXMoZGF0YSkpIHsNCiAgICBzdG9wKCJUaGUgc3BlY2lmaWVkIGNvbHVtbiBkb2VzIG5vdCBleGlzdCBpbiB0aGUgZGF0YSBmcmFtZS4iKQ0KICB9DQogIA0KICAjIENoZWNrIGlmIHRoZSBwbG90IHR5cGUgaXMgdmFsaWQNCiAgdmFsaWRfcGxvdF90eXBlcyA8LSBjKCJrZGUiLCAiaGlzdCIsICJkb3QiKQ0KICBpZiAoIXBsb3RfdHlwZSAlaW4lIHZhbGlkX3Bsb3RfdHlwZXMpIHsNCiAgICBzdG9wKCJVbmtub3duIHBsb3QgdHlwZS4gVmFsaWQgcGxvdCB0eXBlcyBhcmU6ICIsIHBhc3RlKHZhbGlkX3Bsb3RfdHlwZXMsIGNvbGxhcHNlID0gIiwgIikpDQogIH0NCiAgDQogICMgR2VuZXJhdGUgdGhlIHBsb3QgYmFzZWQgb24gdGhlIHR5cGUNCiAgcCA8LSBnZ3Bsb3QoZGF0YSwgYWVzX3N0cmluZyh4ID0geCkpDQogIA0KICBpZiAocGxvdF90eXBlID09ICJrZGUiKSB7DQogICAgcCA8LSBwICsgZ2VvbV9kZW5zaXR5KGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gMC41KSArIA0KICAgICAgbGFicyh0aXRsZSA9IHBhc3RlKCJLZXJuZWwgRGVuc2l0eSBFc3RpbWF0ZSBvZiIsIHgpKQ0KICB9IGVsc2UgaWYgKHBsb3RfdHlwZSA9PSAiaGlzdCIpIHsNCiAgICBwIDwtIHAgKyBnZW9tX2hpc3RvZ3JhbShmaWxsID0gInJlZCIsIGFscGhhID0gMC41KSArIA0KICAgICAgbGFicyh0aXRsZSA9IHBhc3RlKCJIaXN0b2dyYW0gb2YiLCB4KSkNCiAgfSBlbHNlIGlmIChwbG90X3R5cGUgPT0gImRvdCIpIHsNCiAgICBwIDwtIHAgKyBnZW9tX2RvdHBsb3QoYmluYXhpcyA9ICd4JywgZG90c2l6ZSA9IDEpICsgDQogICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkRvdCBQbG90IG9mIiwgeCkpDQogIH0NCiAgDQogICMgUmV0dXJuIHRoZSBwbG90DQogIHJldHVybihwKQ0KfQ0KDQpgYGANCg0KVGEgY8OzIHRo4buDIMOhcCBk4bulbmcgbGlzdCDEkeG7gyB0aGF5IGNobyBj4bqldSB0csO6YyBpZl9lbHNlLCB2w6wgYmnhur90IHLhurFuZyBsaXN0IGPDsyB0aOG7gyBjaOG7qWEgYuG6pXQgY+G7qSBvYmplY3QgbsOgbywgbsOqbiDhu58gxJHDonkgdGEgZMO5bmcgbGlzdCDEkeG7gyBjaOG7qWEgY8OhYyBnZ3Bsb3QgbGF5ZXJzIGtow6FjIG5oYXUgKGLhuqNuIGNo4bqldCBsw6AgaMOgbSksIHbhu5tpIGtleSBsw6AgcGxvdF90eXBlLg0KDQpgYGB7cn0NCmRpc3RfcGxvdF8yYSA8LSBmdW5jdGlvbihkYXRhLCB4LCBwbG90X3R5cGUpIHsNCiAgIyBDaGVjayBpZiB0aGUgcHJvdmlkZWQgY29sdW1uIG5hbWUgaXMgdmFsaWQNCiAgaWYgKCF4ICVpbiUgbmFtZXMoZGF0YSkpIHsNCiAgICBzdG9wKCJUaGUgc3BlY2lmaWVkIGNvbHVtbiBkb2VzIG5vdCBleGlzdCBpbiB0aGUgZGF0YSBmcmFtZS4iKQ0KICB9DQogIA0KICAjIERlZmluZSBhIGxpc3Qgb2YgZ2dwbG90IGxheWVycyBmb3IgZWFjaCBwbG90IHR5cGUNCiAgcGxvdF9sYXllcnMgPC0gbGlzdCgNCiAgICBrZGUgPSBnZW9tX2RlbnNpdHkoYWVzX3N0cmluZyh4ID0geCksIGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gMC41KSwNCiAgICBoaXN0ID0gZ2VvbV9oaXN0b2dyYW0oYWVzX3N0cmluZyh4ID0geCksIGZpbGwgPSAicmVkIiwgYWxwaGEgPSAwLjUpLA0KICAgIGRvdCA9IGdlb21fZG90cGxvdChhZXNfc3RyaW5nKHggPSB4KSwgYmluYXhpcyA9ICd4JywgZG90c2l6ZSA9IDEpDQogICkNCiAgDQogICMgR2V0IHRoZSBnZ3Bsb3QgbGF5ZXIgZnJvbSB0aGUgbGlzdCBiYXNlZCBvbiBwbG90X3R5cGUNCiAgbGF5ZXIgPC0gcGxvdF9sYXllcnNbW3Bsb3RfdHlwZV1dDQogIA0KICAjIENoZWNrIGlmIHBsb3RfdHlwZSBpcyB2YWxpZCBhbmQgbGF5ZXIgZXhpc3RzDQogIGlmIChpcy5udWxsKGxheWVyKSkgew0KICAgIHN0b3AoIlVua25vd24gcGxvdCB0eXBlLiBWYWxpZCBwbG90IHR5cGVzIGFyZTogJ2tkZScsICdoaXN0JywgJ2RvdCcuIikNCiAgfQ0KICANCiAgIyBDcmVhdGUgdGhlIGdncGxvdCBvYmplY3Qgd2l0aCB0aGUgc3BlY2lmaWVkIGxheWVyIGFuZCByZXR1cm4gaXQNCiAgcCA8LSBnZ3Bsb3QoZGF0YSkgKyBsYXllciArIGxhYnModGl0bGUgPSBwYXN0ZShwbG90X3R5cGUsICJvZiIsIHgpKQ0KICANCiAgcmV0dXJuKHApDQp9DQpgYGANCg0KVGEgY8OybiBjw7MgdGjhu4MgZMO5bmcgbmVzdGVkX2xpc3QgxJHhu4MgdHJ1eSB4deG6pXQgY+G6oyBsYXllciAoZnVuY3Rpb24pIHbDoCB0aXRsZSAoc3RyaW5nKSBjaG8gbeG7l2kgcGxvdF90eXBlDQoNCmBgYHtyfQ0KZGlzdF9wbG90XzJiIDwtIGZ1bmN0aW9uKGRhdGEsIHgsIHBsb3RfdHlwZSkgew0KICAjIFZhbGlkYXRlIGlucHV0DQogIGlmICgheCAlaW4lIG5hbWVzKGRhdGEpKSB7DQogICAgc3RvcCgiVGhlIHNwZWNpZmllZCBjb2x1bW4gZG9lcyBub3QgZXhpc3QgaW4gdGhlIGRhdGEgZnJhbWUuIikNCiAgfQ0KICANCiAgIyBNYXBwaW5nIG9mIHBsb3QgdHlwZXMgdG8gZ2dwbG90IGxheWVycyBhbmQgYWRkaXRpb25hbCBzZXR0aW5ncw0KICBwbG90X2xheWVycyA8LSBsaXN0KA0KICAgIGtkZSA9IGxpc3QobGF5ZXIgPSBnZW9tX2RlbnNpdHkoZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAwLjUpLCB0aXRsZSA9ICJLZXJuZWwgRGVuc2l0eSBFc3RpbWF0ZSIpLA0KICAgIGhpc3QgPSBsaXN0KGxheWVyID0gZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJyZWQiLCBhbHBoYSA9IDAuNSksIHRpdGxlID0gIkhpc3RvZ3JhbSIpLA0KICAgIGRvdCA9IGxpc3QobGF5ZXIgPSBnZW9tX2RvdHBsb3QoYmluYXhpcyA9ICd4JywgZG90c2l6ZSA9IDAuNSksIHRpdGxlID0gIkRvdCBQbG90IikNCiAgKQ0KICANCiAgIyBSZXRyaWV2ZSB0aGUgcGxvdCBzZXR0aW5ncyBmb3IgdGhlIHNwZWNpZmllZCBwbG90X3R5cGUNCiAgcGxvdF9zZXR0aW5nIDwtIHBsb3RfbGF5ZXJzW1twbG90X3R5cGVdXQ0KICBpZiAoaXMubnVsbChwbG90X3NldHRpbmcpKSB7DQogICAgc3RvcCgiVW5rbm93biBwbG90IHR5cGUuIFZhbGlkIHBsb3QgdHlwZXMgYXJlOiAna2RlJywgJ2hpc3QnLCAnZG90Jy4iKQ0KICB9DQogIA0KICAjIENyZWF0ZSB0aGUgZ2dwbG90IG9iamVjdCBhbmQgYWRkIHRoZSBsYXllciBhbmQgdGl0bGUNCiAgcCA8LSBnZ3Bsb3QoZGF0YSwgYWVzX3N0cmluZyh4ID0geCkpICsNCiAgICBwbG90X3NldHRpbmckbGF5ZXIgKw0KICAgIGxhYnModGl0bGUgPSBwYXN0ZShwbG90X3NldHRpbmckdGl0bGUsICJvZiIsIHgpKQ0KICANCiAgcmV0dXJuKHApDQp9DQpgYGANCg0KTeG7mXQgY8OhY2ggdOG7lW5nIHF1w6F0LCB0YSBjw7MgdGjhu4MgZMO5bmcgbGlzdCDEkeG7gyBjaOG7qWEgY8OhYyBow6BtIGFub255bW91cyB24bubaSBu4buZaSBkdW5nIHTDuXkgdGjDrWNoIG5oxrAgc2F1Og0KDQpgYGB7cn0NCmRpc3RfcGxvdF8zIDwtIGZ1bmN0aW9uKGRhdGEsIHgsIHBsb3RfdHlwZSkgew0KICAjIENoZWNrIGlmIHRoZSBwcm92aWRlZCBjb2x1bW4gbmFtZSBpcyB2YWxpZA0KICBpZiAoIXggJWluJSBuYW1lcyhkYXRhKSkgew0KICAgIHN0b3AoIlRoZSBzcGVjaWZpZWQgY29sdW1uIGRvZXMgbm90IGV4aXN0IGluIHRoZSBkYXRhIGZyYW1lLiIpDQogIH0NCiAgDQogICMgRGVmaW5lIGEgbGlzdCBvZiBwbG90IHR5cGVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIGdncGxvdDIgZnVuY3Rpb25zDQogIHBsb3RfdHlwZXMgPC0gbGlzdCgNCiAgICAia2RlIiA9IGZ1bmN0aW9uKCkgZ2dwbG90KGRhdGEsIGFlc19zdHJpbmcoeCA9IHgpKSArIA0KICAgICAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gMC41KSwNCiAgICAiaGlzdCIgPSBmdW5jdGlvbigpIGdncGxvdChkYXRhLCBhZXNfc3RyaW5nKHggPSB4KSkgKyANCiAgICAgIGdlb21faGlzdG9ncmFtKGZpbGwgPSAicmVkIiwgYWxwaGEgPSAwLjUpLA0KICAgICJkb3QiID0gZnVuY3Rpb24oKSBnZ3Bsb3QoZGF0YSwgYWVzX3N0cmluZyh4ID0geCkpICsgDQogICAgICBnZW9tX2RvdHBsb3QoYmluYXhpcyA9ICd4JywgZG90c2l6ZSA9IDEpDQogICkNCiAgDQogICMgQ2hlY2sgaWYgdGhlIHBsb3QgdHlwZSBpcyB2YWxpZCBhbmQgZ2V0IHRoZSBjb3JyZXNwb25kaW5nIGZ1bmN0aW9uDQogIHBsb3RfZnVuYyA8LSBwbG90X3R5cGVzW1twbG90X3R5cGVdXQ0KICBpZiAoaXMubnVsbChwbG90X2Z1bmMpKSB7DQogICAgc3RvcCgiVW5rbm93biBwbG90IHR5cGUuIFZhbGlkIHBsb3QgdHlwZXMgYXJlOiAiLCBwYXN0ZShuYW1lcyhwbG90X3R5cGVzKSwgY29sbGFwc2UgPSAiLCAiKSkNCiAgfQ0KICANCiAgIyBHZW5lcmF0ZSBhbmQgcmV0dXJuIHRoZSBwbG90DQogIHAgPC0gcGxvdF9mdW5jKCkNCiAgcCArIGxhYnModGl0bGUgPSBwYXN0ZShwbG90X3R5cGUsICJQbG90IG9mIiwgeCkpDQp9DQpgYGANCg0KVGhheSB2w6wgZMO5bmcgdHLhu7FjIHRp4bq/cCBsaXN0LCB0cm9uZyBuZ8O0biBuZ+G7ryBSIGPDsm4gY8OzIGPhuqV1IHRyw7pjIMSRaeG7gXUga2nhu4duIHLhur0gbmjDoW5oIChzd2l0Y2gpLCBi4bqjbiBjaOG6pXQgY+G7p2EgbsOzIGPFqW5nIGzDoCBt4buZdCBsaXN0IGNo4bupYSBjw6FjIG9iamVjdCDEkcaw4bujYyB04bqhbyByYSB0cm9uZyBsw7pjIHRoaSBow6BuaCB0w7l5IHRoZW8gdOG7qyBraMOzYSDEkWnhu4F1IGtp4buHbg0KDQpgYGB7cn0NCmRpc3RfcGxvdF80IDwtIGZ1bmN0aW9uKGRhdGEsIHgsIHBsb3RfdHlwZSkgew0KICAjIENoZWNrIGlmIHRoZSBwcm92aWRlZCBjb2x1bW4gbmFtZSBpcyB2YWxpZA0KICBpZiAoIXggJWluJSBuYW1lcyhkYXRhKSkgew0KICAgIHN0b3AoIlRoZSBzcGVjaWZpZWQgY29sdW1uIGRvZXMgbm90IGV4aXN0IGluIHRoZSBkYXRhIGZyYW1lLiIpDQogIH0NCiAgDQogICMgRGVmaW5lIHRoZSBwbG90DQogIHAgPC0gZ2dwbG90KGRhdGEsIGFlc19zdHJpbmcoeCA9IHgpKQ0KICANCiAgIyBHZW5lcmF0ZSB0aGUgcGxvdCBiYXNlZCBvbiB0aGUgdHlwZSB1c2luZyBzd2l0Y2gNCiAgcCA8LSBzd2l0Y2gocGxvdF90eXBlLA0KICAgICAgICAgICAgICBrZGUgPSBwICsgZ2VvbV9kZW5zaXR5KGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gMC41KSArIA0KICAgICAgICAgICAgICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiS2VybmVsIERlbnNpdHkgRXN0aW1hdGUgb2YiLCB4KSksDQogICAgICAgICAgICAgIGhpc3QgPSBwICsgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJyZWQiLCBhbHBoYSA9IDAuNSkgKyANCiAgICAgICAgICAgICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkhpc3RvZ3JhbSBvZiIsIHgpKSwNCiAgICAgICAgICAgICAgZG90ID0gcCArIGdlb21fZG90cGxvdChiaW5heGlzID0gJ3gnLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkb3RzaXplID0gMSkgKyANCiAgICAgICAgICAgICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkRvdCBQbG90IG9mIiwgeCkpLA0KICAgICAgICAgICAgICBzdG9wKCJVbmtub3duIHBsb3QgdHlwZS4gVmFsaWQgcGxvdCB0eXBlcyBhcmU6IGtkZSwgaGlzdCwgZG90IikNCiAgKQ0KICANCiAgIyBSZXR1cm4gdGhlIHBsb3QNCiAgcmV0dXJuKHApDQp9DQpgYGANCg0K