The data I used :

Customer Personality Analysis is a detailed analysis of a company’s ideal customers. It helps a business to better understand its customers and makes it easier for them to modify products according to the specific needs, behaviors and concerns of different types of customers.

Customer personality analysis helps a business to modify its product based on its target customers from different types of customer segments. For example, instead of spending money to market a new product to every customer in the company’s database, a company can analyze which customer segment is most likely to buy the product and then market the product only on that particular segment.

=======================================================

The first code cell is dedicated to setting up the environment. It checks for missing packages, installs any that are not already installed, and loads the necessary libraries. This ensures that all subsequent code can run without issues related to missing software.

# Install necessary packages if they are not already installed
if (!require("data.table")) install.packages("data.table")
Loading required package: data.table
Warning: package ‘data.table’ was built under R version 4.3.2Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
data.table 1.14.8 using 10 threads (see ?getDTthreads).  Latest news: r-datatable.com
if (!require("dplyr")) install.packages("dplyr")
Loading required package: dplyr
Warning: package ‘dplyr’ was built under R version 4.3.2
Attaching package: ‘dplyr’

The following objects are masked from ‘package:data.table’:

    between, first, last

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
# Load the necessary libraries
library(data.table)
library(dplyr)

This cell loads the dataset from a CSV file using tabulation as a separator. It then displays the structure and the first few rows of the dataframe for a preliminary inspection of the data.

# Load the dataframe, separator here is the tabulation "\t"
df <- fread('marketing_campaign.csv', sep = '\t')

# View the first few rows of the dataframe
head(df)

# Display information about the dataframe
str(df)
Classes ‘data.table’ and 'data.frame':  2240 obs. of  29 variables:
 $ ID                 : int  5524 2174 4141 6182 5324 7446 965 6177 4855 5899 ...
 $ Year_Birth         : int  1957 1954 1965 1984 1981 1967 1971 1985 1974 1950 ...
 $ Education          : chr  "Graduation" "Graduation" "Graduation" "Graduation" ...
 $ Marital_Status     : chr  "Single" "Single" "Together" "Together" ...
 $ Income             : int  58138 46344 71613 26646 58293 62513 55635 33454 30351 5648 ...
 $ Kidhome            : int  0 1 0 1 1 0 0 1 1 1 ...
 $ Teenhome           : int  0 1 0 0 0 1 1 0 0 1 ...
 $ Dt_Customer        : chr  "04-09-2012" "08-03-2014" "21-08-2013" "10-02-2014" ...
 $ Recency            : int  58 38 26 26 94 16 34 32 19 68 ...
 $ MntWines           : int  635 11 426 11 173 520 235 76 14 28 ...
 $ MntFruits          : int  88 1 49 4 43 42 65 10 0 0 ...
 $ MntMeatProducts    : int  546 6 127 20 118 98 164 56 24 6 ...
 $ MntFishProducts    : int  172 2 111 10 46 0 50 3 3 1 ...
 $ MntSweetProducts   : int  88 1 21 3 27 42 49 1 3 1 ...
 $ MntGoldProds       : int  88 6 42 5 15 14 27 23 2 13 ...
 $ NumDealsPurchases  : int  3 2 1 2 5 2 4 2 1 1 ...
 $ NumWebPurchases    : int  8 1 8 2 5 6 7 4 3 1 ...
 $ NumCatalogPurchases: int  10 1 2 0 3 4 3 0 0 0 ...
 $ NumStorePurchases  : int  4 2 10 4 6 10 7 4 2 0 ...
 $ NumWebVisitsMonth  : int  7 5 4 6 5 6 6 8 9 20 ...
 $ AcceptedCmp3       : int  0 0 0 0 0 0 0 0 0 1 ...
 $ AcceptedCmp4       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ AcceptedCmp5       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ AcceptedCmp1       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ AcceptedCmp2       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Complain           : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Z_CostContact      : int  3 3 3 3 3 3 3 3 3 3 ...
 $ Z_Revenue          : int  11 11 11 11 11 11 11 11 11 11 ...
 $ Response           : int  1 0 0 0 0 0 0 0 1 0 ...
 - attr(*, ".internal.selfref")=<externalptr> 

The code calculates and prints the percentage of missing values for each column in the dataset. This is critical for understanding data completeness and determining the necessity of data cleaning or imputation.

# Define a function to calculate the percentage of missing values
per_missing <- function(df) {
  missing_values <- sapply(df, function(x) sum(is.na(x)))
  non_missing_values <- sapply(df, function(x) sum(!is.na(x)))
  per_miss <- missing_values / non_missing_values * 100
  return(per_miss)
}

# Print the percentage of missing values for the first 10 columns
print(head(per_missing(df), 10))
            ID     Year_Birth      Education Marital_Status         Income        Kidhome       Teenhome    Dt_Customer        Recency       MntWines 
      0.000000       0.000000       0.000000       0.000000       1.083032       0.000000       0.000000       0.000000       0.000000       0.000000 

Here, missing values in the ‘Income’ column are imputed with the median income. This choice avoids the influence of outliers that might skew the mean and calculates the frequency of unique values in ‘Z_CostContact’ and ‘Z_Revenue’. Upon finding no variability, these columns are dropped as they provide no informative value for analysis

# Filling the NaN or Null values with the median
df$Income[is.na(df$Income)] <- median(df$Income, na.rm = TRUE)

# Print the percentage of missing values for the first 10 columns
print(head(per_missing(df), 10))
            ID     Year_Birth      Education Marital_Status         Income        Kidhome       Teenhome    Dt_Customer        Recency       MntWines 
             0              0              0              0              0              0              0              0              0              0 
# Get the value counts for Z_CostContact and Z_Revenue
Z_CostContact_values <- table(df$Z_CostContact)
Z_Revenue_values <- table(df$Z_Revenue)

# Print the value counts
print(Z_CostContact_values)

   3 
2240 
print(Z_Revenue_values)

  11 
2240 
# Drop the 'Z_CostContact' and 'Z_Revenue' columns since they are no use for the segmentation
df <- df %>% select(-c(Z_CostContact, Z_Revenue))

This function call generates bar plots for each categorical variable in the dataset, providing a visual frequency distribution which aids in understanding the data composition.

# Load necessary libraries for data manipulation and plotting
if (!require("ggplot2")) install.packages("ggplot2")
Loading required package: ggplot2
Warning: package ‘ggplot2’ was built under R version 4.3.2
library(ggplot2)

unique_cat_var <- function(df) {
  col <- names(df)[sapply(df, is.character) & names(df) != 'Dt_Customer']
  
  # Print the columns that will be plotted
  print(paste("Plotting for columns:", paste(col, collapse = ", ")))
  for (i in col) {
    # Create the plot
    p <- ggplot(data = df, aes_string(x = i)) +
      geom_bar(aes(y = (..count..)/sum(..count..) * 100)) +
      ylab("Frequency of Occurrence (%)") +
      theme_minimal() +
      ggtitle(paste("Distribution of", i))
    
    # Print the plot
    print(p)
  }
}

# Call the function
unique_cat_var(df)
[1] "Plotting for columns: Education, Marital_Status"
Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
Please use tidy evaluation idioms with `aes()`. 
See also `vignette("ggplot2-in-packages")` for more information.

The ‘Dt_Customer’ column is converted from character to Date format to facilitate any operations that require date arithmetic.



# Converting 'Dt_Customer' to Date format
df$Dt_Customer <- as.Date(df$Dt_Customer, format="%d-%m-%Y")
# Display the first few rows of the dataframe
head(df)
# Converting 'Dt_Customer' to Date format in R
# Ensure the format matches the date format in your data
df$Dt_Customer <- as.Date(df$Dt_Customer, format="%d-%m-%Y")

# Display the first few rows of the dataframe
head(df)
NA

These visualizations are used to detect outliers and understand the distribution and relationships between different numerical variables.

# Load the ggplot2 library
if (!require("ggplot2")) install.packages("ggplot2")
library(ggplot2)

# Create the scatter plot
ggplot(df, aes(x = Year_Birth, y = Income)) +
  geom_point() +  # Add points
  geom_vline(xintercept = 1910, linetype = "solid") +  # Vertical line at x = 1910
  geom_hline(yintercept = 150000, color = "red", linetype = "solid") +  # Horizontal line at y = 150000
  theme_minimal()  # Optional: Adds a minimalistic theme

# Load necessary libraries
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("dplyr")) install.packages("dplyr")

library(ggplot2)
library(dplyr)

# Boxplot for 'Income'
ggplot(df, aes(y = Income)) +
  geom_boxplot() +
  coord_flip() +
  theme_minimal()

NA
NA
NA
# Boxplot for 'Year_Birth'
ggplot(df, aes(y = Year_Birth)) +
  geom_boxplot() +
  coord_flip() +
  theme_minimal()

NA
NA

Outliers are removed from the data based on domain knowledge, which can improve the performance of clustering algorithms by reducing noise.

# Dropping the outliers by setting a cap on Age and Income
df <- df %>% filter(Year_Birth > 1910, Income < 150000)

# Print the total number of data points after removing the outliers
print(paste("The total number of data-points after removing the outliers are:", nrow(df)))
[1] "The total number of data-points after removing the outliers are: 2229"
# Assuming df$Dt_Customer is already converted to Date type in R
# Print the maximum date in 'Dt_Customer'
print(max(df$Dt_Customer))
[1] "2014-06-29"
# Print a specific date
print(as.Date("2014-10-04"))
[1] "2014-10-04"

This cell includes comprehensive feature engineering, which involves creating new features that may better capture the underlying patterns and relationships for the clustering task.

# Load necessary packages
if (!require("dplyr")) install.packages("dplyr")
if (!require("lubridate")) install.packages("lubridate")
Loading required package: lubridate
Warning: package ‘lubridate’ was built under R version 4.3.2
Attaching package: ‘lubridate’

The following objects are masked from ‘package:data.table’:

    hour, isoweek, mday, minute, month, quarter, second, wday, week, yday, year

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union
library(dplyr)
library(lubridate)

# Create the Age attribute
current_year <- year(max(df$Dt_Customer, na.rm = TRUE))
df <- df %>% mutate(Age = current_year - Year_Birth)
# Create the Total_spent attribute
df <- df %>% mutate(Total_spent = MntWines + MntFruits + MntMeatProducts + MntFishProducts + MntSweetProducts + MntGoldProds)
last_date <- max(df$Dt_Customer, na.rm = TRUE)
df <- df %>% mutate(Eldership = as.numeric(difftime(last_date, Dt_Customer, units = "days")))

# Create the Eldership attribute
last_date <- max(df$Dt_Customer, na.rm = TRUE)
df <- df %>% mutate(Eldership = as.numeric(last_date - Dt_Customer))

# Remap Marital Status and Education attributes
df$Marital_Status <- ifelse(df$Marital_Status %in% c('Divorced', 'Single', 'Absurd', 'Widow', 'YOLO'), 'Alone', 'In couple')
df$Education <- ifelse(df$Education %in% c('Basic', '2n Cycle'), 'Undergraduate', ifelse(df$Education %in% c('Graduation', 'Master', 'PhD'), 'Postgraduate', df$Education))

# Create Children and Has_Child Attributes
df <- df %>% mutate(Children = Kidhome + Teenhome, Has_child = ifelse(Children > 0, 1, 0))

# Create reduction accepted
df <- df %>% mutate(Promo_Accepted = AcceptedCmp3 + AcceptedCmp4 + AcceptedCmp5 + AcceptedCmp1 + AcceptedCmp2 + Response)

# Display the first few rows of the dataframe
head(df)

Mathematical Explanation:

  1. Standardization:

  2. Covariance Matrix:

  3. Eigen Decomposition:

  4. Explained Variance:

  5. Cumulative Explained Variance:

  6. Data Projection:

  7. Eigenvalues and Eigenvectors:

    • pca$sdev^2: This gives the eigenvalues, which are the variances of the principal components.

    • pca$rotation: This matrix contains the eigenvectors. Each column represents an eigenvector corresponding to a principal component.

  8. Explained Variance:

    • var_exp <- (eig_vals / tot) * 100: Here, you calculated the percentage of variance explained by each principal component. This is derived from the eigenvalues, which measure the variance along each principal component.
  9. Cumulative Explained Variance:

    • cum_var_exp <- cumsum(var_exp): This is the running total of the explained variance. It helps in understanding how many principal components are needed to capture most of the variability in the data.
  10. Data Projection (not explicitly shown in the code provided but implied with the use of the prcomp function):

    • The PCA components (scores) would be contained in pca$x after the PCA computation, which is the projection of the original data onto the new principal component axes.
  11. Standardization (mentioned but not performed because the data is already scaled):

    • scale. = FALSE parameter in prcomp(X_scaled, scale. = FALSE): Indicates that the data has been pre-scaled, so prcomp should not scale it again.
  12. Visualization:

    • The ggplot2 code creates a bar plot and a step plot to visualize the individual and cumulative explained variance. This helps in determining how many principal components should be retained. The dashed lines at a particular principal component and at the 90% cumulative variance threshold provide visual cues for this decision.
  13. Correlation Matrix of PCA Components:

    • cor(PCA_ds): Although PCA components are theoretically orthogonal, this part of the code calculates the correlation matrix of the principal components, which should ideally show no correlation if the PCA was successful. Then, it visualizes the correlation matrix using a heatmap to ensure that the principal components are indeed uncorrelated.
# Ensure the dplyr package is installed and loaded
if (!require("dplyr")) install.packages("dplyr")
library(dplyr)

# Copy the dataframe and drop the specified columns
X <- df %>% 
  select(-c(Dt_Customer, ID, Year_Birth, AcceptedCmp3, AcceptedCmp4, AcceptedCmp5, AcceptedCmp1, AcceptedCmp2, Response, Complain))
# Load necessary libraries
if (!require("dplyr")) install.packages("dplyr")
library(dplyr)

# Convert data.table to dataframe if necessary
X <- as.data.frame(X)

# Select only categorical variables
cat_variables <- names(X)[sapply(X, is.character)]

# Convert categorical variables to factors
X[cat_variables] <- lapply(X[cat_variables], factor)

# Convert factors to numeric (integer encoding)
X[cat_variables] <- lapply(X[cat_variables], as.integer)

Standardization makes the data compatible for clustering, and the correlation heatmap visualizes the relationships between different variables, which can inform feature selection.

# Load necessary libraries
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("reshape2")) install.packages("reshape2")
Loading required package: reshape2
Warning: package ‘reshape2’ was built under R version 4.3.2
Attaching package: ‘reshape2’

The following objects are masked from ‘package:data.table’:

    dcast, melt
library(ggplot2)
library(reshape2)

# Standardize the dataset
X_scaled <- as.data.frame(scale(X))

# Calculate the correlation matrix
corr <- cor(X_scaled, use = "complete.obs")

# Melt the correlation matrix for ggplot2
corr_melted <- melt(corr)

# Plot the heatmap
# Plot the heatmap with adjusted label sizes
ggplot(corr_melted, aes(Var1, Var2, fill = value)) +
  geom_tile() +
  scale_fill_gradient2(low = "blue", high = "red", mid = "white", 
                       midpoint = 0, limit = c(-1,1), space = "Lab", 
                       name="Pearson\nCorrelation") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, vjust = 1, size = 7, hjust = 1), # Reduced size here
        axis.text.y = element_text(size = 7)) + # Reduced size here
  coord_fixed()


# Perform PCA
pca <- prcomp(X_scaled, scale. = FALSE)  # scale. = FALSE since data is already scaled

# Eigenvalues (squared singular values)
eig_vals <- pca$sdev^2

# Eigenvectors
eig_vecs <- pca$rotation

# Calculation of Explained Variance from the eigenvalues
tot <- sum(eig_vals)
var_exp <- (eig_vals / tot) * 100  # Individual explained variance
cum_var_exp <- cumsum(var_exp)  # Cumulative explained variance

# Display results
print("Individual explained variance:")
[1] "Individual explained variance:"
print(var_exp)
 [1] 3.563896e+01 1.155031e+01 6.640557e+00 5.315775e+00 4.581099e+00 4.424258e+00 4.300946e+00 3.740996e+00 3.328853e+00 2.986258e+00 2.632113e+00 2.498659e+00 1.974972e+00 1.907933e+00 1.734079e+00
[16] 1.655578e+00 1.386549e+00 1.260457e+00 9.650458e-01 8.523071e-01 6.243027e-01 1.829674e-29 5.492255e-30
print("Cumulative explained variance:")
[1] "Cumulative explained variance:"
print(cum_var_exp)
 [1]  35.63896  47.18926  53.82982  59.14560  63.72669  68.15095  72.45190  76.19289  79.52175  82.50801  85.14012  87.63878  89.61375  91.52168  93.25576  94.91134  96.29789  97.55834  98.52339  99.37570
[21] 100.00000 100.00000 100.00000

Principal Component Analysis (PCA) is conducted to reduce dimensionality while retaining most of the variability in the data. This can improve clustering results by focusing on the most informative aspects of the data

# Load necessary libraries
if (!require("ggplot2")) install.packages("ggplot2")
library(ggplot2)

# Create a data frame for plotting
num_components <- length(var_exp)
df_pca <- data.frame(
  Component = 1:num_components,
  Individual = var_exp,
  Cumulative = cum_var_exp
)

# Create the plot
ggplot(df_pca, aes(x = Component)) +
  geom_bar(aes(y = Individual), stat = "identity", fill = "green", alpha = 0.3333) +
  geom_step(aes(y = Cumulative), direction = "mid") +
  geom_vline(xintercept = 12, linetype = "dashed") +
  geom_hline(yintercept = 90, color = "red", linetype = "dashed") +
  labs(x = "Principal components", y = "Explained variance ratio") +
  ggtitle("PCA Explained Variance") +
  theme_minimal() +
  theme(axis.title = element_text(size = 12),
        axis.text = element_text(size = 10)) +
  scale_x_continuous(breaks = 1:num_components) +
  scale_y_continuous(limits = c(0, 100), expand = c(0, 0)) +
  geom_text(aes(y = Cumulative, label = ifelse(Cumulative >= 90 & Cumulative < 91, "90% Variance", "")), 
            vjust = -0.5, hjust = -0.1, size = 3) +
  guides(fill = guide_legend(title = "Variance"))


# Display the plot
print(ggplot_object)
Error: object 'ggplot_object' not found
# Load necessary library
if (!require("stats")) install.packages("stats")
library(stats)

# Performing PCA with 13 components
pca <- prcomp(X_scaled, center = TRUE, scale. = FALSE, n.components = 13)
Warning: In prcomp.default(X_scaled, center = TRUE, scale. = FALSE, n.components = 13) :
 extra argument ‘n.components’ will be disregarded
# Create a new dataset with the PCA components
PCA_ds <- as.data.frame(pca$x)
names(PCA_ds) <- paste("PCA", 1:13, sep="")

# Describing the PCA dataset
summary(PCA_ds)
      PCA1              PCA2              PCA3                PCA4               PCA5               PCA6               PCA7              PCA8              PCA9              PCA10         
 Min.   :-8.0115   Min.   :-5.6331   Min.   :-5.643417   Min.   :-5.10986   Min.   :-3.40925   Min.   :-2.90823   Min.   :-2.1721   Min.   :-3.9927   Min.   :-4.23279   Min.   :-5.58260  
 1st Qu.:-2.3517   1st Qu.:-1.2219   1st Qu.:-0.845908   1st Qu.:-0.53992   1st Qu.:-0.66812   1st Qu.:-0.70017   1st Qu.:-0.7953   1st Qu.:-0.3618   1st Qu.:-0.56668   1st Qu.:-0.37981  
 Median : 0.8391   Median : 0.1812   Median : 0.001538   Median :-0.00627   Median :-0.04246   Median : 0.01233   Median :-0.1122   Median : 0.1852   Median :-0.03753   Median : 0.04629  
 Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.000000   Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.00000  
 3rd Qu.: 2.6001   3rd Qu.: 1.3322   3rd Qu.: 0.861202   3rd Qu.: 0.55008   3rd Qu.: 0.64841   3rd Qu.: 0.71222   3rd Qu.: 0.7363   3rd Qu.: 0.6353   3rd Qu.: 0.57465   3rd Qu.: 0.46616  
 Max.   : 5.7445   Max.   : 4.0049   Max.   : 3.366752   Max.   : 4.86567   Max.   : 3.81089   Max.   : 2.79845   Max.   : 3.3368   Max.   : 2.1438   Max.   : 2.83915   Max.   : 3.09653  
     PCA11              PCA12              PCA13                NA                  NA                  NA                  NA                 NA                NA                 NA          
 Min.   :-5.90052   Min.   :-6.73088   Min.   :-3.23855   Min.   :-3.511707   Min.   :-3.833712   Min.   :-5.404859   Min.   :-3.51685   Min.   :-3.8793   Min.   :-3.48885   Min.   :-1.68799  
 1st Qu.:-0.47450   1st Qu.:-0.36964   1st Qu.:-0.39814   1st Qu.:-0.202507   1st Qu.:-0.276136   1st Qu.:-0.221899   1st Qu.:-0.31800   1st Qu.:-0.3108   1st Qu.:-0.22397   1st Qu.:-0.31382  
 Median : 0.05563   Median : 0.01958   Median :-0.02591   Median : 0.006125   Median : 0.004122   Median :-0.002098   Median :-0.02745   Median :-0.0249   Median :-0.02272   Median : 0.06562  
 Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.000000   Mean   : 0.000000   Mean   : 0.000000   Mean   : 0.00000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.00000  
 3rd Qu.: 0.51645   3rd Qu.: 0.35428   3rd Qu.: 0.41004   3rd Qu.: 0.214407   3rd Qu.: 0.320311   3rd Qu.: 0.230142   3rd Qu.: 0.26279   3rd Qu.: 0.2609   3rd Qu.: 0.21736   3rd Qu.: 0.32945  
 Max.   : 2.45427   Max.   : 7.34334   Max.   : 4.76153   Max.   : 5.499598   Max.   : 3.257568   Max.   : 3.064481   Max.   : 3.44359   Max.   : 3.8466   Max.   : 1.95129   Max.   : 2.08344  
       NA                 NA                   NA            
 Min.   :-1.66311   Min.   :-7.524e-15   Min.   :-4.460e-15  
 1st Qu.:-0.22940   1st Qu.:-4.902e-16   1st Qu.:-8.346e-16  
 Median :-0.01991   Median : 4.004e-16   Median : 2.581e-17  
 Mean   : 0.00000   Mean   :-3.939e-17   Mean   : 2.161e-17  
 3rd Qu.: 0.22833   3rd Qu.: 1.237e-15   3rd Qu.: 8.030e-16  
 Max.   : 3.78256   Max.   : 4.231e-15   Max.   : 4.966e-15  
# Load necessary libraries
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("reshape2")) install.packages("reshape2")

library(ggplot2)
library(reshape2)

# Calculate the correlation matrix for PCA components
corr <- cor(PCA_ds)

# Melt the correlation matrix for ggplot2
corr_melted <- melt(corr)

# Plot the heatmap
ggplot(corr_melted, aes(Var1, Var2, fill = value)) +
  geom_tile() +
  scale_fill_gradient2(low = "blue", high = "red", mid = "white", 
                       midpoint = 0, limit = c(-1, 1), space = "Lab", 
                       name = "Correlation") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, vjust = 1, size = 10, hjust = 1),
        axis.text.y = element_text(size = 10)) +
  coord_fixed()

Determining the optimal number of clusters using the Elbow method The Elbow method plots the percentage of variance explained by each number of potential clusters. We look for the number of clusters at which the variance explained begins to level off (“elbow point”).

if (!require("factoextra")) install.packages("factoextra")
Loading required package: factoextra
Warning: package ‘factoextra’ was built under R version 4.3.2Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(factoextra)
# Determining the optimal number of clusters using the Elbow method
set.seed(123)  # Set seed for reproducibility
fviz_nbclust(PCA_ds, kmeans, method = "wss") +
  geom_vline(xintercept = 4, linetype = 2)  # Adjust the xintercept as needed

The K-means clustering algorithm is applied to the data, and the resulting cluster assignments are appended to the dataframe. This is a critical step in segmenting the data into distinct groups.

# Perform K-means clustering
set.seed(123)  # Setting a seed for reproducibility
kmeans_result <- kmeans(X_scaled, centers = 4)

# Add the cluster assignments to your original dataframe
X$Cluster <- as.factor(kmeans_result$cluster)

A scatter plot is created to visualize how the clusters differ in terms of customer income and total spending, providing insights into the customer segmentation.

# Create a scatter plot
ggplot(X, aes(x = Total_spent, y = Income, color = Cluster)) +
  geom_point() +
  labs(title = "Cluster's Profile Based On Income And Spending", x = "Total Spent", y = "Income") +
  theme_minimal() +
  scale_color_brewer(palette = "Set1") +
  theme(legend.title = element_blank())

This visualization shows how promotional strategies are received by different customer segments, which can guide marketing efforts.

# Load the ggplot2 package
if (!require("ggplot2")) install.packages("ggplot2")
library(ggplot2)

# Create a count plot for Promo_Accepted grouped by Cluster
ggplot(X, aes(x = Promo_Accepted, fill = Cluster)) +
  geom_bar(position = "dodge") +
  labs(title = "Count Of Promotion Accepted", x = "Number Of Total Accepted Promotions", y = "Count") +
  theme_minimal() +
  scale_fill_brewer(palette = "Set1") +
  theme(legend.title = element_blank())

NA
NA
NA

The final visualization shows the average spending in different product categories for each cluster, indicating the preferences and behaviors within each customer segment.

# Define the product categories
products <- c('MntWines', 'MntFruits', 'MntMeatProducts', 'MntFishProducts', 'MntSweetProducts', 'MntGoldProds')

# Calculate the mean values for each cluster
values <- aggregate(. ~ Cluster, data = X[, c("Cluster", products)], FUN = mean)

# Load necessary libraries
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("reshape2")) install.packages("reshape2")

library(ggplot2)
library(reshape2)

# Reshape the data for plotting
values_melted <- melt(values, id.vars = 'Cluster')

# Create bar plots
ggplot(values_melted, aes(x = Cluster, y = value, fill = Cluster)) +
  geom_bar(stat = "identity", position = position_dodge()) +
  facet_wrap(~ variable, scales = "free", ncol = 3) +
  labs(y = "Mean Value", x = "Cluster") +
  theme_minimal() +
  scale_fill_brewer(palette = "Set1") +
  theme(legend.position = "none")

LS0tDQp0aXRsZTogIkNsdXN0ZXJpbmcgUHJvamVjdCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgKipUaGUgZGF0YSBJIHVzZWQgOioqDQoNCkN1c3RvbWVyIFBlcnNvbmFsaXR5IEFuYWx5c2lzIGlzIGEgZGV0YWlsZWQgYW5hbHlzaXMgb2YgYSBjb21wYW55J3MgaWRlYWwgY3VzdG9tZXJzLiBJdCBoZWxwcyBhIGJ1c2luZXNzIHRvIGJldHRlciB1bmRlcnN0YW5kIGl0cyBjdXN0b21lcnMgYW5kIG1ha2VzIGl0IGVhc2llciBmb3IgdGhlbSB0byBtb2RpZnkgcHJvZHVjdHMgYWNjb3JkaW5nIHRvIHRoZSBzcGVjaWZpYyBuZWVkcywgYmVoYXZpb3JzIGFuZCBjb25jZXJucyBvZiBkaWZmZXJlbnQgdHlwZXMgb2YgY3VzdG9tZXJzLg0KDQpDdXN0b21lciBwZXJzb25hbGl0eSBhbmFseXNpcyBoZWxwcyBhIGJ1c2luZXNzIHRvIG1vZGlmeSBpdHMgcHJvZHVjdCBiYXNlZCBvbiBpdHMgdGFyZ2V0IGN1c3RvbWVycyBmcm9tIGRpZmZlcmVudCB0eXBlcyBvZiBjdXN0b21lciBzZWdtZW50cy4gRm9yIGV4YW1wbGUsIGluc3RlYWQgb2Ygc3BlbmRpbmcgbW9uZXkgdG8gbWFya2V0IGEgbmV3IHByb2R1Y3QgdG8gZXZlcnkgY3VzdG9tZXIgaW4gdGhlIGNvbXBhbnkncyBkYXRhYmFzZSwgYSBjb21wYW55IGNhbiBhbmFseXplIHdoaWNoIGN1c3RvbWVyIHNlZ21lbnQgaXMgbW9zdCBsaWtlbHkgdG8gYnV5IHRoZSBwcm9kdWN0IGFuZCB0aGVuIG1hcmtldCB0aGUgcHJvZHVjdCBvbmx5IG9uIHRoYXQgcGFydGljdWxhciBzZWdtZW50Lg0KDQo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNClRoZSBmaXJzdCBjb2RlIGNlbGwgaXMgZGVkaWNhdGVkIHRvIHNldHRpbmcgdXAgdGhlIGVudmlyb25tZW50LiBJdCBjaGVja3MgZm9yIG1pc3NpbmcgcGFja2FnZXMsIGluc3RhbGxzIGFueSB0aGF0IGFyZSBub3QgYWxyZWFkeSBpbnN0YWxsZWQsIGFuZCBsb2FkcyB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcy4gVGhpcyBlbnN1cmVzIHRoYXQgYWxsIHN1YnNlcXVlbnQgY29kZSBjYW4gcnVuIHdpdGhvdXQgaXNzdWVzIHJlbGF0ZWQgdG8gbWlzc2luZyBzb2Z0d2FyZS4NCg0KYGBge3J9DQojIEluc3RhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzIGlmIHRoZXkgYXJlIG5vdCBhbHJlYWR5IGluc3RhbGxlZA0KaWYgKCFyZXF1aXJlKCJkYXRhLnRhYmxlIikpIGluc3RhbGwucGFja2FnZXMoImRhdGEudGFibGUiKQ0KaWYgKCFyZXF1aXJlKCJkcGx5ciIpKSBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQoNCiMgTG9hZCB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcw0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShkcGx5cikNCg0KYGBgDQoNClRoaXMgY2VsbCBsb2FkcyB0aGUgZGF0YXNldCBmcm9tIGEgQ1NWIGZpbGUgdXNpbmcgdGFidWxhdGlvbiBhcyBhIHNlcGFyYXRvci4gSXQgdGhlbiBkaXNwbGF5cyB0aGUgc3RydWN0dXJlIGFuZCB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgdGhlIGRhdGFmcmFtZSBmb3IgYSBwcmVsaW1pbmFyeSBpbnNwZWN0aW9uIG9mIHRoZSBkYXRhLg0KDQpgYGB7cn0NCiMgTG9hZCB0aGUgZGF0YWZyYW1lLCBzZXBhcmF0b3IgaGVyZSBpcyB0aGUgdGFidWxhdGlvbiAiXHQiDQpkZiA8LSBmcmVhZCgnbWFya2V0aW5nX2NhbXBhaWduLmNzdicsIHNlcCA9ICdcdCcpDQoNCiMgVmlldyB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgdGhlIGRhdGFmcmFtZQ0KaGVhZChkZikNCg0KIyBEaXNwbGF5IGluZm9ybWF0aW9uIGFib3V0IHRoZSBkYXRhZnJhbWUNCnN0cihkZikNCmBgYA0KDQpUaGUgY29kZSBjYWxjdWxhdGVzIGFuZCBwcmludHMgdGhlIHBlcmNlbnRhZ2Ugb2YgbWlzc2luZyB2YWx1ZXMgZm9yIGVhY2ggY29sdW1uIGluIHRoZSBkYXRhc2V0LiBUaGlzIGlzIGNyaXRpY2FsIGZvciB1bmRlcnN0YW5kaW5nIGRhdGEgY29tcGxldGVuZXNzIGFuZCBkZXRlcm1pbmluZyB0aGUgbmVjZXNzaXR5IG9mIGRhdGEgY2xlYW5pbmcgb3IgaW1wdXRhdGlvbi4NCg0KYGBge3J9DQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiBtaXNzaW5nIHZhbHVlcw0KcGVyX21pc3NpbmcgPC0gZnVuY3Rpb24oZGYpIHsNCiAgbWlzc2luZ192YWx1ZXMgPC0gc2FwcGx5KGRmLCBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKQ0KICBub25fbWlzc2luZ192YWx1ZXMgPC0gc2FwcGx5KGRmLCBmdW5jdGlvbih4KSBzdW0oIWlzLm5hKHgpKSkNCiAgcGVyX21pc3MgPC0gbWlzc2luZ192YWx1ZXMgLyBub25fbWlzc2luZ192YWx1ZXMgKiAxMDANCiAgcmV0dXJuKHBlcl9taXNzKQ0KfQ0KDQojIFByaW50IHRoZSBwZXJjZW50YWdlIG9mIG1pc3NpbmcgdmFsdWVzIGZvciB0aGUgZmlyc3QgMTAgY29sdW1ucw0KcHJpbnQoaGVhZChwZXJfbWlzc2luZyhkZiksIDEwKSkNCg0KYGBgDQoNCkhlcmUsIG1pc3NpbmcgdmFsdWVzIGluIHRoZSAnSW5jb21lJyBjb2x1bW4gYXJlIGltcHV0ZWQgd2l0aCB0aGUgbWVkaWFuIGluY29tZS4gVGhpcyBjaG9pY2UgYXZvaWRzIHRoZSBpbmZsdWVuY2Ugb2Ygb3V0bGllcnMgdGhhdCBtaWdodCBza2V3IHRoZSBtZWFuIGFuZCBjYWxjdWxhdGVzIHRoZSBmcmVxdWVuY3kgb2YgdW5pcXVlIHZhbHVlcyBpbiAnWl9Db3N0Q29udGFjdCcgYW5kICdaX1JldmVudWUnLiBVcG9uIGZpbmRpbmcgbm8gdmFyaWFiaWxpdHksIHRoZXNlIGNvbHVtbnMgYXJlIGRyb3BwZWQgYXMgdGhleSBwcm92aWRlIG5vIGluZm9ybWF0aXZlIHZhbHVlIGZvciBhbmFseXNpcw0KDQpgYGB7cn0NCiMgRmlsbGluZyB0aGUgTmFOIG9yIE51bGwgdmFsdWVzIHdpdGggdGhlIG1lZGlhbg0KZGYkSW5jb21lW2lzLm5hKGRmJEluY29tZSldIDwtIG1lZGlhbihkZiRJbmNvbWUsIG5hLnJtID0gVFJVRSkNCg0KIyBQcmludCB0aGUgcGVyY2VudGFnZSBvZiBtaXNzaW5nIHZhbHVlcyBmb3IgdGhlIGZpcnN0IDEwIGNvbHVtbnMNCnByaW50KGhlYWQocGVyX21pc3NpbmcoZGYpLCAxMCkpDQoNCiMgR2V0IHRoZSB2YWx1ZSBjb3VudHMgZm9yIFpfQ29zdENvbnRhY3QgYW5kIFpfUmV2ZW51ZQ0KWl9Db3N0Q29udGFjdF92YWx1ZXMgPC0gdGFibGUoZGYkWl9Db3N0Q29udGFjdCkNClpfUmV2ZW51ZV92YWx1ZXMgPC0gdGFibGUoZGYkWl9SZXZlbnVlKQ0KDQojIFByaW50IHRoZSB2YWx1ZSBjb3VudHMNCnByaW50KFpfQ29zdENvbnRhY3RfdmFsdWVzKQ0KcHJpbnQoWl9SZXZlbnVlX3ZhbHVlcykNCg0KDQojIERyb3AgdGhlICdaX0Nvc3RDb250YWN0JyBhbmQgJ1pfUmV2ZW51ZScgY29sdW1ucyBzaW5jZSB0aGV5IGFyZSBubyB1c2UgZm9yIHRoZSBzZWdtZW50YXRpb24NCmRmIDwtIGRmICU+JSBzZWxlY3QoLWMoWl9Db3N0Q29udGFjdCwgWl9SZXZlbnVlKSkNCg0KYGBgDQoNClRoaXMgZnVuY3Rpb24gY2FsbCBnZW5lcmF0ZXMgYmFyIHBsb3RzIGZvciBlYWNoIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGluIHRoZSBkYXRhc2V0LCBwcm92aWRpbmcgYSB2aXN1YWwgZnJlcXVlbmN5IGRpc3RyaWJ1dGlvbiB3aGljaCBhaWRzIGluIHVuZGVyc3RhbmRpbmcgdGhlIGRhdGEgY29tcG9zaXRpb24uDQoNCmBgYHtyfQ0KIyBMb2FkIG5lY2Vzc2FyeSBsaWJyYXJpZXMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uIGFuZCBwbG90dGluZw0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQp1bmlxdWVfY2F0X3ZhciA8LSBmdW5jdGlvbihkZikgew0KICBjb2wgPC0gbmFtZXMoZGYpW3NhcHBseShkZiwgaXMuY2hhcmFjdGVyKSAmIG5hbWVzKGRmKSAhPSAnRHRfQ3VzdG9tZXInXQ0KICANCiAgIyBQcmludCB0aGUgY29sdW1ucyB0aGF0IHdpbGwgYmUgcGxvdHRlZA0KICBwcmludChwYXN0ZSgiUGxvdHRpbmcgZm9yIGNvbHVtbnM6IiwgcGFzdGUoY29sLCBjb2xsYXBzZSA9ICIsICIpKSkNCiAgZm9yIChpIGluIGNvbCkgew0KICAgICMgQ3JlYXRlIHRoZSBwbG90DQogICAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRmLCBhZXNfc3RyaW5nKHggPSBpKSkgKw0KICAgICAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS9zdW0oLi5jb3VudC4uKSAqIDEwMCkpICsNCiAgICAgIHlsYWIoIkZyZXF1ZW5jeSBvZiBPY2N1cnJlbmNlICglKSIpICsNCiAgICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgICBnZ3RpdGxlKHBhc3RlKCJEaXN0cmlidXRpb24gb2YiLCBpKSkNCiAgICANCiAgICAjIFByaW50IHRoZSBwbG90DQogICAgcHJpbnQocCkNCiAgfQ0KfQ0KDQojIENhbGwgdGhlIGZ1bmN0aW9uDQp1bmlxdWVfY2F0X3ZhcihkZikNCg0KYGBgDQoNClRoZSAnRHRfQ3VzdG9tZXInIGNvbHVtbiBpcyBjb252ZXJ0ZWQgZnJvbSBjaGFyYWN0ZXIgdG8gRGF0ZSBmb3JtYXQgdG8gZmFjaWxpdGF0ZSBhbnkgb3BlcmF0aW9ucyB0aGF0IHJlcXVpcmUgZGF0ZSBhcml0aG1ldGljLg0KDQpgYGB7cn0NCg0KDQojIENvbnZlcnRpbmcgJ0R0X0N1c3RvbWVyJyB0byBEYXRlIGZvcm1hdA0KZGYkRHRfQ3VzdG9tZXIgPC0gYXMuRGF0ZShkZiREdF9DdXN0b21lciwgZm9ybWF0PSIlZC0lbS0lWSIpDQoNCg0KDQpgYGANCg0KYGBge3J9DQojIERpc3BsYXkgdGhlIGZpcnN0IGZldyByb3dzIG9mIHRoZSBkYXRhZnJhbWUNCmhlYWQoZGYpDQpgYGANCg0KYGBge3J9DQojIENvbnZlcnRpbmcgJ0R0X0N1c3RvbWVyJyB0byBEYXRlIGZvcm1hdCBpbiBSDQojIEVuc3VyZSB0aGUgZm9ybWF0IG1hdGNoZXMgdGhlIGRhdGUgZm9ybWF0IGluIHlvdXIgZGF0YQ0KZGYkRHRfQ3VzdG9tZXIgPC0gYXMuRGF0ZShkZiREdF9DdXN0b21lciwgZm9ybWF0PSIlZC0lbS0lWSIpDQoNCiMgRGlzcGxheSB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgdGhlIGRhdGFmcmFtZQ0KaGVhZChkZikNCg0KYGBgDQoNClRoZXNlIHZpc3VhbGl6YXRpb25zIGFyZSB1c2VkIHRvIGRldGVjdCBvdXRsaWVycyBhbmQgdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9uIGFuZCByZWxhdGlvbnNoaXBzIGJldHdlZW4gZGlmZmVyZW50IG51bWVyaWNhbCB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KIyBMb2FkIHRoZSBnZ3Bsb3QyIGxpYnJhcnkNCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCg0KIyBDcmVhdGUgdGhlIHNjYXR0ZXIgcGxvdA0KZ2dwbG90KGRmLCBhZXMoeCA9IFllYXJfQmlydGgsIHkgPSBJbmNvbWUpKSArDQogIGdlb21fcG9pbnQoKSArICAjIEFkZCBwb2ludHMNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMTkxMCwgbGluZXR5cGUgPSAic29saWQiKSArICAjIFZlcnRpY2FsIGxpbmUgYXQgeCA9IDE5MTANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMTUwMDAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJzb2xpZCIpICsgICMgSG9yaXpvbnRhbCBsaW5lIGF0IHkgPSAxNTAwMDANCiAgdGhlbWVfbWluaW1hbCgpICAjIE9wdGlvbmFsOiBBZGRzIGEgbWluaW1hbGlzdGljIHRoZW1lDQoNCmBgYA0KDQpgYGB7cn0NCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzDQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQppZiAoIXJlcXVpcmUoImRwbHlyIikpIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCg0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCg0KIyBCb3hwbG90IGZvciAnSW5jb21lJw0KZ2dwbG90KGRmLCBhZXMoeSA9IEluY29tZSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCiMgQm94cGxvdCBmb3IgJ1llYXJfQmlydGgnDQpnZ3Bsb3QoZGYsIGFlcyh5ID0gWWVhcl9CaXJ0aCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQpgYGANCg0KT3V0bGllcnMgYXJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YSBiYXNlZCBvbiBkb21haW4ga25vd2xlZGdlLCB3aGljaCBjYW4gaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgY2x1c3RlcmluZyBhbGdvcml0aG1zIGJ5IHJlZHVjaW5nIG5vaXNlLg0KDQpgYGB7cn0NCiMgRHJvcHBpbmcgdGhlIG91dGxpZXJzIGJ5IHNldHRpbmcgYSBjYXAgb24gQWdlIGFuZCBJbmNvbWUNCmRmIDwtIGRmICU+JSBmaWx0ZXIoWWVhcl9CaXJ0aCA+IDE5MTAsIEluY29tZSA8IDE1MDAwMCkNCg0KIyBQcmludCB0aGUgdG90YWwgbnVtYmVyIG9mIGRhdGEgcG9pbnRzIGFmdGVyIHJlbW92aW5nIHRoZSBvdXRsaWVycw0KcHJpbnQocGFzdGUoIlRoZSB0b3RhbCBudW1iZXIgb2YgZGF0YS1wb2ludHMgYWZ0ZXIgcmVtb3ZpbmcgdGhlIG91dGxpZXJzIGFyZToiLCBucm93KGRmKSkpDQpgYGANCg0KYGBge3J9DQojIEFzc3VtaW5nIGRmJER0X0N1c3RvbWVyIGlzIGFscmVhZHkgY29udmVydGVkIHRvIERhdGUgdHlwZSBpbiBSDQojIFByaW50IHRoZSBtYXhpbXVtIGRhdGUgaW4gJ0R0X0N1c3RvbWVyJw0KcHJpbnQobWF4KGRmJER0X0N1c3RvbWVyKSkNCg0KIyBQcmludCBhIHNwZWNpZmljIGRhdGUNCnByaW50KGFzLkRhdGUoIjIwMTQtMTAtMDQiKSkNCg0KYGBgDQoNClRoaXMgY2VsbCBpbmNsdWRlcyBjb21wcmVoZW5zaXZlIGZlYXR1cmUgZW5naW5lZXJpbmcsIHdoaWNoIGludm9sdmVzIGNyZWF0aW5nIG5ldyBmZWF0dXJlcyB0aGF0IG1heSBiZXR0ZXIgY2FwdHVyZSB0aGUgdW5kZXJseWluZyBwYXR0ZXJucyBhbmQgcmVsYXRpb25zaGlwcyBmb3IgdGhlIGNsdXN0ZXJpbmcgdGFzay4NCg0KYGBge3J9DQojIExvYWQgbmVjZXNzYXJ5IHBhY2thZ2VzDQppZiAoIXJlcXVpcmUoImRwbHlyIikpIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCmlmICghcmVxdWlyZSgibHVicmlkYXRlIikpIGluc3RhbGwucGFja2FnZXMoImx1YnJpZGF0ZSIpDQoNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KIyBDcmVhdGUgdGhlIEFnZSBhdHRyaWJ1dGUNCmN1cnJlbnRfeWVhciA8LSB5ZWFyKG1heChkZiREdF9DdXN0b21lciwgbmEucm0gPSBUUlVFKSkNCmRmIDwtIGRmICU+JSBtdXRhdGUoQWdlID0gY3VycmVudF95ZWFyIC0gWWVhcl9CaXJ0aCkNCiMgQ3JlYXRlIHRoZSBUb3RhbF9zcGVudCBhdHRyaWJ1dGUNCmRmIDwtIGRmICU+JSBtdXRhdGUoVG90YWxfc3BlbnQgPSBNbnRXaW5lcyArIE1udEZydWl0cyArIE1udE1lYXRQcm9kdWN0cyArIE1udEZpc2hQcm9kdWN0cyArIE1udFN3ZWV0UHJvZHVjdHMgKyBNbnRHb2xkUHJvZHMpDQpsYXN0X2RhdGUgPC0gbWF4KGRmJER0X0N1c3RvbWVyLCBuYS5ybSA9IFRSVUUpDQpkZiA8LSBkZiAlPiUgbXV0YXRlKEVsZGVyc2hpcCA9IGFzLm51bWVyaWMoZGlmZnRpbWUobGFzdF9kYXRlLCBEdF9DdXN0b21lciwgdW5pdHMgPSAiZGF5cyIpKSkNCg0KIyBDcmVhdGUgdGhlIEVsZGVyc2hpcCBhdHRyaWJ1dGUNCmxhc3RfZGF0ZSA8LSBtYXgoZGYkRHRfQ3VzdG9tZXIsIG5hLnJtID0gVFJVRSkNCmRmIDwtIGRmICU+JSBtdXRhdGUoRWxkZXJzaGlwID0gYXMubnVtZXJpYyhsYXN0X2RhdGUgLSBEdF9DdXN0b21lcikpDQoNCiMgUmVtYXAgTWFyaXRhbCBTdGF0dXMgYW5kIEVkdWNhdGlvbiBhdHRyaWJ1dGVzDQpkZiRNYXJpdGFsX1N0YXR1cyA8LSBpZmVsc2UoZGYkTWFyaXRhbF9TdGF0dXMgJWluJSBjKCdEaXZvcmNlZCcsICdTaW5nbGUnLCAnQWJzdXJkJywgJ1dpZG93JywgJ1lPTE8nKSwgJ0Fsb25lJywgJ0luIGNvdXBsZScpDQpkZiRFZHVjYXRpb24gPC0gaWZlbHNlKGRmJEVkdWNhdGlvbiAlaW4lIGMoJ0Jhc2ljJywgJzJuIEN5Y2xlJyksICdVbmRlcmdyYWR1YXRlJywgaWZlbHNlKGRmJEVkdWNhdGlvbiAlaW4lIGMoJ0dyYWR1YXRpb24nLCAnTWFzdGVyJywgJ1BoRCcpLCAnUG9zdGdyYWR1YXRlJywgZGYkRWR1Y2F0aW9uKSkNCg0KIyBDcmVhdGUgQ2hpbGRyZW4gYW5kIEhhc19DaGlsZCBBdHRyaWJ1dGVzDQpkZiA8LSBkZiAlPiUgbXV0YXRlKENoaWxkcmVuID0gS2lkaG9tZSArIFRlZW5ob21lLCBIYXNfY2hpbGQgPSBpZmVsc2UoQ2hpbGRyZW4gPiAwLCAxLCAwKSkNCg0KIyBDcmVhdGUgcmVkdWN0aW9uIGFjY2VwdGVkDQpkZiA8LSBkZiAlPiUgbXV0YXRlKFByb21vX0FjY2VwdGVkID0gQWNjZXB0ZWRDbXAzICsgQWNjZXB0ZWRDbXA0ICsgQWNjZXB0ZWRDbXA1ICsgQWNjZXB0ZWRDbXAxICsgQWNjZXB0ZWRDbXAyICsgUmVzcG9uc2UpDQoNCiMgRGlzcGxheSB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgdGhlIGRhdGFmcmFtZQ0KaGVhZChkZikNCmBgYA0KDQojIyMgKipNYXRoZW1hdGljYWwgRXhwbGFuYXRpb246KioNCg0KMS4gICoqU3RhbmRhcmRpemF0aW9uKio6ICFbXShpbWFnZXMvU2NyZWVuc2hvdCUyMDIwMjQtMDEtMzAlMjAxNDE1MTEucG5nKXt3aWR0aD0iOTYiIGhlaWdodD0iMjQifQ0KDQoyLiAgKipDb3ZhcmlhbmNlIE1hdHJpeCoqOiAhW10oaW1hZ2VzL1NjcmVlbnNob3QlMjAyMDI0LTAxLTMwJTIwMTQxNTIwLnBuZyl7d2lkdGg9IjExNSIgaGVpZ2h0PSIyMyJ9DQoNCjMuICAqKkVpZ2VuIERlY29tcG9zaXRpb24qKjogIVtdKGltYWdlcy9TY3JlZW5zaG90JTIwMjAyNC0wMS0zMCUyMDE0MTUyNS5wbmcpe3dpZHRoPSI4MyIgaGVpZ2h0PSIyMyJ9DQoNCjQuICAqKkV4cGxhaW5lZCBWYXJpYW5jZSoqOiAhW10oaW1hZ2VzL1NjcmVlbnNob3QlMjAyMDI0LTAxLTMwJTIwMTQxNTMxLnBuZyl7d2lkdGg9IjEwOCIgaGVpZ2h0PSIyNiJ9DQoNCjUuICAqKkN1bXVsYXRpdmUgRXhwbGFpbmVkIFZhcmlhbmNlKio6ICFbXShpbWFnZXMvU2NyZWVuc2hvdCUyMDIwMjQtMDEtMzAlMjAxNDE1MzkucG5nKXt3aWR0aD0iMTkxIiBoZWlnaHQ9IjI0In0NCg0KNi4gICoqRGF0YSBQcm9qZWN0aW9uKio6ICFbXShpbWFnZXMvU2NyZWVuc2hvdCUyMDIwMjQtMDEtMzAlMjAxNDE1NDMucG5nKXt3aWR0aD0iMTEzIn0NCg0KNy4gICoqRWlnZW52YWx1ZXMgYW5kIEVpZ2VudmVjdG9ycyoqOg0KDQogICAgLSAgICoqYHBjYSRzZGV2XjJgKio6IFRoaXMgZ2l2ZXMgdGhlIGVpZ2VudmFsdWVzLCB3aGljaCBhcmUgdGhlIHZhcmlhbmNlcyBvZiB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMuDQoNCiAgICAtICAgKipgcGNhJHJvdGF0aW9uYCoqOiBUaGlzIG1hdHJpeCBjb250YWlucyB0aGUgZWlnZW52ZWN0b3JzLiBFYWNoIGNvbHVtbiByZXByZXNlbnRzIGFuIGVpZ2VudmVjdG9yIGNvcnJlc3BvbmRpbmcgdG8gYSBwcmluY2lwYWwgY29tcG9uZW50Lg0KDQo4LiAgKipFeHBsYWluZWQgVmFyaWFuY2UqKjoNCg0KICAgIC0gICAqKmB2YXJfZXhwIDwtIChlaWdfdmFscyAvIHRvdCkgKiAxMDBgKio6IEhlcmUsIHlvdSBjYWxjdWxhdGVkIHRoZSBwZXJjZW50YWdlIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIHByaW5jaXBhbCBjb21wb25lbnQuIFRoaXMgaXMgZGVyaXZlZCBmcm9tIHRoZSBlaWdlbnZhbHVlcywgd2hpY2ggbWVhc3VyZSB0aGUgdmFyaWFuY2UgYWxvbmcgZWFjaCBwcmluY2lwYWwgY29tcG9uZW50Lg0KDQo5LiAgKipDdW11bGF0aXZlIEV4cGxhaW5lZCBWYXJpYW5jZSoqOg0KDQogICAgLSAgICoqYGN1bV92YXJfZXhwIDwtIGN1bXN1bSh2YXJfZXhwKWAqKjogVGhpcyBpcyB0aGUgcnVubmluZyB0b3RhbCBvZiB0aGUgZXhwbGFpbmVkIHZhcmlhbmNlLiBJdCBoZWxwcyBpbiB1bmRlcnN0YW5kaW5nIGhvdyBtYW55IHByaW5jaXBhbCBjb21wb25lbnRzIGFyZSBuZWVkZWQgdG8gY2FwdHVyZSBtb3N0IG9mIHRoZSB2YXJpYWJpbGl0eSBpbiB0aGUgZGF0YS4NCg0KMTAuICoqRGF0YSBQcm9qZWN0aW9uKiogKG5vdCBleHBsaWNpdGx5IHNob3duIGluIHRoZSBjb2RlIHByb3ZpZGVkIGJ1dCBpbXBsaWVkIHdpdGggdGhlIHVzZSBvZiB0aGUgKipgcHJjb21wYCoqIGZ1bmN0aW9uKToNCg0KICAgIC0gICBUaGUgUENBIGNvbXBvbmVudHMgKHNjb3Jlcykgd291bGQgYmUgY29udGFpbmVkIGluICoqYHBjYSR4YCoqIGFmdGVyIHRoZSBQQ0EgY29tcHV0YXRpb24sIHdoaWNoIGlzIHRoZSBwcm9qZWN0aW9uIG9mIHRoZSBvcmlnaW5hbCBkYXRhIG9udG8gdGhlIG5ldyBwcmluY2lwYWwgY29tcG9uZW50IGF4ZXMuDQoNCjExLiAqKlN0YW5kYXJkaXphdGlvbioqIChtZW50aW9uZWQgYnV0IG5vdCBwZXJmb3JtZWQgYmVjYXVzZSB0aGUgZGF0YSBpcyBhbHJlYWR5IHNjYWxlZCk6DQoNCiAgICAtICAgKipgc2NhbGUuID0gRkFMU0VgKiogcGFyYW1ldGVyIGluICoqYHByY29tcChYX3NjYWxlZCwgc2NhbGUuID0gRkFMU0UpYCoqOiBJbmRpY2F0ZXMgdGhhdCB0aGUgZGF0YSBoYXMgYmVlbiBwcmUtc2NhbGVkLCBzbyAqKmBwcmNvbXBgKiogc2hvdWxkIG5vdCBzY2FsZSBpdCBhZ2Fpbi4NCg0KMTIuICoqVmlzdWFsaXphdGlvbioqOg0KDQogICAgLSAgIFRoZSBnZ3Bsb3QyIGNvZGUgY3JlYXRlcyBhIGJhciBwbG90IGFuZCBhIHN0ZXAgcGxvdCB0byB2aXN1YWxpemUgdGhlIGluZGl2aWR1YWwgYW5kIGN1bXVsYXRpdmUgZXhwbGFpbmVkIHZhcmlhbmNlLiBUaGlzIGhlbHBzIGluIGRldGVybWluaW5nIGhvdyBtYW55IHByaW5jaXBhbCBjb21wb25lbnRzIHNob3VsZCBiZSByZXRhaW5lZC4gVGhlIGRhc2hlZCBsaW5lcyBhdCBhIHBhcnRpY3VsYXIgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmQgYXQgdGhlIDkwJSBjdW11bGF0aXZlIHZhcmlhbmNlIHRocmVzaG9sZCBwcm92aWRlIHZpc3VhbCBjdWVzIGZvciB0aGlzIGRlY2lzaW9uLg0KDQoxMy4gKipDb3JyZWxhdGlvbiBNYXRyaXggb2YgUENBIENvbXBvbmVudHMqKjoNCg0KICAgIC0gICAqKmBjb3IoUENBX2RzKWAqKjogQWx0aG91Z2ggUENBIGNvbXBvbmVudHMgYXJlIHRoZW9yZXRpY2FsbHkgb3J0aG9nb25hbCwgdGhpcyBwYXJ0IG9mIHRoZSBjb2RlIGNhbGN1bGF0ZXMgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBvZiB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMsIHdoaWNoIHNob3VsZCBpZGVhbGx5IHNob3cgbm8gY29ycmVsYXRpb24gaWYgdGhlIFBDQSB3YXMgc3VjY2Vzc2Z1bC4gVGhlbiwgaXQgdmlzdWFsaXplcyB0aGUgY29ycmVsYXRpb24gbWF0cml4IHVzaW5nIGEgaGVhdG1hcCB0byBlbnN1cmUgdGhhdCB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgYXJlIGluZGVlZCB1bmNvcnJlbGF0ZWQuDQoNCmBgYHtyfQ0KIyBFbnN1cmUgdGhlIGRwbHlyIHBhY2thZ2UgaXMgaW5zdGFsbGVkIGFuZCBsb2FkZWQNCmlmICghcmVxdWlyZSgiZHBseXIiKSkgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQ0KbGlicmFyeShkcGx5cikNCg0KIyBDb3B5IHRoZSBkYXRhZnJhbWUgYW5kIGRyb3AgdGhlIHNwZWNpZmllZCBjb2x1bW5zDQpYIDwtIGRmICU+JSANCiAgc2VsZWN0KC1jKER0X0N1c3RvbWVyLCBJRCwgWWVhcl9CaXJ0aCwgQWNjZXB0ZWRDbXAzLCBBY2NlcHRlZENtcDQsIEFjY2VwdGVkQ21wNSwgQWNjZXB0ZWRDbXAxLCBBY2NlcHRlZENtcDIsIFJlc3BvbnNlLCBDb21wbGFpbikpDQoNCmBgYA0KDQpgYGB7cn0NCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzDQppZiAoIXJlcXVpcmUoImRwbHlyIikpIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikNCmxpYnJhcnkoZHBseXIpDQoNCiMgQ29udmVydCBkYXRhLnRhYmxlIHRvIGRhdGFmcmFtZSBpZiBuZWNlc3NhcnkNClggPC0gYXMuZGF0YS5mcmFtZShYKQ0KDQojIFNlbGVjdCBvbmx5IGNhdGVnb3JpY2FsIHZhcmlhYmxlcw0KY2F0X3ZhcmlhYmxlcyA8LSBuYW1lcyhYKVtzYXBwbHkoWCwgaXMuY2hhcmFjdGVyKV0NCg0KIyBDb252ZXJ0IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0byBmYWN0b3JzDQpYW2NhdF92YXJpYWJsZXNdIDwtIGxhcHBseShYW2NhdF92YXJpYWJsZXNdLCBmYWN0b3IpDQoNCiMgQ29udmVydCBmYWN0b3JzIHRvIG51bWVyaWMgKGludGVnZXIgZW5jb2RpbmcpDQpYW2NhdF92YXJpYWJsZXNdIDwtIGxhcHBseShYW2NhdF92YXJpYWJsZXNdLCBhcy5pbnRlZ2VyKQ0KDQoNCmBgYA0KDQpTdGFuZGFyZGl6YXRpb24gbWFrZXMgdGhlIGRhdGEgY29tcGF0aWJsZSBmb3IgY2x1c3RlcmluZywgYW5kIHRoZSBjb3JyZWxhdGlvbiBoZWF0bWFwIHZpc3VhbGl6ZXMgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBkaWZmZXJlbnQgdmFyaWFibGVzLCB3aGljaCBjYW4gaW5mb3JtIGZlYXR1cmUgc2VsZWN0aW9uLg0KDQpgYGB7cn0NCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzDQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQppZiAoIXJlcXVpcmUoInJlc2hhcGUyIikpIGluc3RhbGwucGFja2FnZXMoInJlc2hhcGUyIikNCg0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShyZXNoYXBlMikNCg0KIyBTdGFuZGFyZGl6ZSB0aGUgZGF0YXNldA0KWF9zY2FsZWQgPC0gYXMuZGF0YS5mcmFtZShzY2FsZShYKSkNCg0KIyBDYWxjdWxhdGUgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeA0KY29yciA8LSBjb3IoWF9zY2FsZWQsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQ0KDQojIE1lbHQgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBmb3IgZ2dwbG90Mg0KY29ycl9tZWx0ZWQgPC0gbWVsdChjb3JyKQ0KDQojIFBsb3QgdGhlIGhlYXRtYXANCiMgUGxvdCB0aGUgaGVhdG1hcCB3aXRoIGFkanVzdGVkIGxhYmVsIHNpemVzDQpnZ3Bsb3QoY29ycl9tZWx0ZWQsIGFlcyhWYXIxLCBWYXIyLCBmaWxsID0gdmFsdWUpKSArDQogIGdlb21fdGlsZSgpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIsIG1pZCA9ICJ3aGl0ZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDAsIGxpbWl0ID0gYygtMSwxKSwgc3BhY2UgPSAiTGFiIiwgDQogICAgICAgICAgICAgICAgICAgICAgIG5hbWU9IlBlYXJzb25cbkNvcnJlbGF0aW9uIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDEsIHNpemUgPSA3LCBoanVzdCA9IDEpLCAjIFJlZHVjZWQgc2l6ZSBoZXJlDQogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSkgKyAjIFJlZHVjZWQgc2l6ZSBoZXJlDQogIGNvb3JkX2ZpeGVkKCkNCmBgYA0KDQpgYGB7cn0NCg0KIyBQZXJmb3JtIFBDQQ0KcGNhIDwtIHByY29tcChYX3NjYWxlZCwgc2NhbGUuID0gRkFMU0UpICAjIHNjYWxlLiA9IEZBTFNFIHNpbmNlIGRhdGEgaXMgYWxyZWFkeSBzY2FsZWQNCg0KIyBFaWdlbnZhbHVlcyAoc3F1YXJlZCBzaW5ndWxhciB2YWx1ZXMpDQplaWdfdmFscyA8LSBwY2Ekc2Rldl4yDQoNCiMgRWlnZW52ZWN0b3JzDQplaWdfdmVjcyA8LSBwY2Ekcm90YXRpb24NCg0KIyBDYWxjdWxhdGlvbiBvZiBFeHBsYWluZWQgVmFyaWFuY2UgZnJvbSB0aGUgZWlnZW52YWx1ZXMNCnRvdCA8LSBzdW0oZWlnX3ZhbHMpDQp2YXJfZXhwIDwtIChlaWdfdmFscyAvIHRvdCkgKiAxMDAgICMgSW5kaXZpZHVhbCBleHBsYWluZWQgdmFyaWFuY2UNCmN1bV92YXJfZXhwIDwtIGN1bXN1bSh2YXJfZXhwKSAgIyBDdW11bGF0aXZlIGV4cGxhaW5lZCB2YXJpYW5jZQ0KDQojIERpc3BsYXkgcmVzdWx0cw0KcHJpbnQoIkluZGl2aWR1YWwgZXhwbGFpbmVkIHZhcmlhbmNlOiIpDQpwcmludCh2YXJfZXhwKQ0KcHJpbnQoIkN1bXVsYXRpdmUgZXhwbGFpbmVkIHZhcmlhbmNlOiIpDQpwcmludChjdW1fdmFyX2V4cCkNCg0KYGBgDQoNClByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgaXMgY29uZHVjdGVkIHRvIHJlZHVjZSBkaW1lbnNpb25hbGl0eSB3aGlsZSByZXRhaW5pbmcgbW9zdCBvZiB0aGUgdmFyaWFiaWxpdHkgaW4gdGhlIGRhdGEuIFRoaXMgY2FuIGltcHJvdmUgY2x1c3RlcmluZyByZXN1bHRzIGJ5IGZvY3VzaW5nIG9uIHRoZSBtb3N0IGluZm9ybWF0aXZlIGFzcGVjdHMgb2YgdGhlIGRhdGENCg0KYGBge3J9DQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcw0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIENyZWF0ZSBhIGRhdGEgZnJhbWUgZm9yIHBsb3R0aW5nDQpudW1fY29tcG9uZW50cyA8LSBsZW5ndGgodmFyX2V4cCkNCmRmX3BjYSA8LSBkYXRhLmZyYW1lKA0KICBDb21wb25lbnQgPSAxOm51bV9jb21wb25lbnRzLA0KICBJbmRpdmlkdWFsID0gdmFyX2V4cCwNCiAgQ3VtdWxhdGl2ZSA9IGN1bV92YXJfZXhwDQopDQoNCiMgQ3JlYXRlIHRoZSBwbG90DQpnZ3Bsb3QoZGZfcGNhLCBhZXMoeCA9IENvbXBvbmVudCkpICsNCiAgZ2VvbV9iYXIoYWVzKHkgPSBJbmRpdmlkdWFsKSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiZ3JlZW4iLCBhbHBoYSA9IDAuMzMzMykgKw0KICBnZW9tX3N0ZXAoYWVzKHkgPSBDdW11bGF0aXZlKSwgZGlyZWN0aW9uID0gIm1pZCIpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMTIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gOTAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicyh4ID0gIlByaW5jaXBhbCBjb21wb25lbnRzIiwgeSA9ICJFeHBsYWluZWQgdmFyaWFuY2UgcmF0aW8iKSArDQogIGdndGl0bGUoIlBDQSBFeHBsYWluZWQgVmFyaWFuY2UiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDE6bnVtX2NvbXBvbmVudHMpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTAwKSwgZXhwYW5kID0gYygwLCAwKSkgKw0KICBnZW9tX3RleHQoYWVzKHkgPSBDdW11bGF0aXZlLCBsYWJlbCA9IGlmZWxzZShDdW11bGF0aXZlID49IDkwICYgQ3VtdWxhdGl2ZSA8IDkxLCAiOTAlIFZhcmlhbmNlIiwgIiIpKSwgDQogICAgICAgICAgICB2anVzdCA9IC0wLjUsIGhqdXN0ID0gLTAuMSwgc2l6ZSA9IDMpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiVmFyaWFuY2UiKSkNCg0KIyBEaXNwbGF5IHRoZSBwbG90DQpwcmludChnZ3Bsb3Rfb2JqZWN0KQ0KDQpgYGANCg0KYGBge3J9DQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcnkNCmlmICghcmVxdWlyZSgic3RhdHMiKSkgaW5zdGFsbC5wYWNrYWdlcygic3RhdHMiKQ0KbGlicmFyeShzdGF0cykNCg0KIyBQZXJmb3JtaW5nIFBDQSB3aXRoIDEzIGNvbXBvbmVudHMNCnBjYSA8LSBwcmNvbXAoWF9zY2FsZWQsIGNlbnRlciA9IFRSVUUsIHNjYWxlLiA9IEZBTFNFLCBuLmNvbXBvbmVudHMgPSAxMykNCg0KIyBDcmVhdGUgYSBuZXcgZGF0YXNldCB3aXRoIHRoZSBQQ0EgY29tcG9uZW50cw0KUENBX2RzIDwtIGFzLmRhdGEuZnJhbWUocGNhJHgpDQpuYW1lcyhQQ0FfZHMpIDwtIHBhc3RlKCJQQ0EiLCAxOjEzLCBzZXA9IiIpDQoNCiMgRGVzY3JpYmluZyB0aGUgUENBIGRhdGFzZXQNCnN1bW1hcnkoUENBX2RzKQ0KDQpgYGANCg0KYGBge3J9DQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcw0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KaWYgKCFyZXF1aXJlKCJyZXNoYXBlMiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJyZXNoYXBlMiIpDQoNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmVzaGFwZTIpDQoNCiMgQ2FsY3VsYXRlIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggZm9yIFBDQSBjb21wb25lbnRzDQpjb3JyIDwtIGNvcihQQ0FfZHMpDQoNCiMgTWVsdCB0aGUgY29ycmVsYXRpb24gbWF0cml4IGZvciBnZ3Bsb3QyDQpjb3JyX21lbHRlZCA8LSBtZWx0KGNvcnIpDQoNCiMgUGxvdCB0aGUgaGVhdG1hcA0KZ2dwbG90KGNvcnJfbWVsdGVkLCBhZXMoVmFyMSwgVmFyMiwgZmlsbCA9IHZhbHVlKSkgKw0KICBnZW9tX3RpbGUoKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLCBtaWQgPSAid2hpdGUiLCANCiAgICAgICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSAwLCBsaW1pdCA9IGMoLTEsIDEpLCBzcGFjZSA9ICJMYWIiLCANCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJDb3JyZWxhdGlvbiIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBzaXplID0gMTAsIGhqdXN0ID0gMSksDQogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsNCiAgY29vcmRfZml4ZWQoKQ0KDQpgYGANCg0KRGV0ZXJtaW5pbmcgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIHVzaW5nIHRoZSBFbGJvdyBtZXRob2QgVGhlIEVsYm93IG1ldGhvZCBwbG90cyB0aGUgcGVyY2VudGFnZSBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgZWFjaCBudW1iZXIgb2YgcG90ZW50aWFsIGNsdXN0ZXJzLiBXZSBsb29rIGZvciB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGF0IHdoaWNoIHRoZSB2YXJpYW5jZSBleHBsYWluZWQgYmVnaW5zIHRvIGxldmVsIG9mZiAoImVsYm93IHBvaW50IikuDQoNCiFbXShpbWFnZXMvU2NyZWVuc2hvdCUyMDIwMjQtMDEtMzAlMjAxNDMwMTAucG5nKXt3aWR0aD0iMzQ2In0NCg0KYGBge3J9DQppZiAoIXJlcXVpcmUoImZhY3RvZXh0cmEiKSkgaW5zdGFsbC5wYWNrYWdlcygiZmFjdG9leHRyYSIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQojIERldGVybWluaW5nIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyB1c2luZyB0aGUgRWxib3cgbWV0aG9kDQpzZXQuc2VlZCgxMjMpICAjIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCmZ2aXpfbmJjbHVzdChQQ0FfZHMsIGttZWFucywgbWV0aG9kID0gIndzcyIpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbGluZXR5cGUgPSAyKSAgIyBBZGp1c3QgdGhlIHhpbnRlcmNlcHQgYXMgbmVlZGVkDQoNCmBgYA0KDQpUaGUgSy1tZWFucyBjbHVzdGVyaW5nIGFsZ29yaXRobSBpcyBhcHBsaWVkIHRvIHRoZSBkYXRhLCBhbmQgdGhlIHJlc3VsdGluZyBjbHVzdGVyIGFzc2lnbm1lbnRzIGFyZSBhcHBlbmRlZCB0byB0aGUgZGF0YWZyYW1lLiBUaGlzIGlzIGEgY3JpdGljYWwgc3RlcCBpbiBzZWdtZW50aW5nIHRoZSBkYXRhIGludG8gZGlzdGluY3QgZ3JvdXBzLg0KDQpgYGB7cn0NCiMgUGVyZm9ybSBLLW1lYW5zIGNsdXN0ZXJpbmcNCnNldC5zZWVkKDEyMykgICMgU2V0dGluZyBhIHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQ0Ka21lYW5zX3Jlc3VsdCA8LSBrbWVhbnMoWF9zY2FsZWQsIGNlbnRlcnMgPSA0KQ0KDQojIEFkZCB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyB0byB5b3VyIG9yaWdpbmFsIGRhdGFmcmFtZQ0KWCRDbHVzdGVyIDwtIGFzLmZhY3RvcihrbWVhbnNfcmVzdWx0JGNsdXN0ZXIpDQoNCmBgYA0KDQpBIHNjYXR0ZXIgcGxvdCBpcyBjcmVhdGVkIHRvIHZpc3VhbGl6ZSBob3cgdGhlIGNsdXN0ZXJzIGRpZmZlciBpbiB0ZXJtcyBvZiBjdXN0b21lciBpbmNvbWUgYW5kIHRvdGFsIHNwZW5kaW5nLCBwcm92aWRpbmcgaW5zaWdodHMgaW50byB0aGUgY3VzdG9tZXIgc2VnbWVudGF0aW9uLg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgc2NhdHRlciBwbG90DQpnZ3Bsb3QoWCwgYWVzKHggPSBUb3RhbF9zcGVudCwgeSA9IEluY29tZSwgY29sb3IgPSBDbHVzdGVyKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBsYWJzKHRpdGxlID0gIkNsdXN0ZXIncyBQcm9maWxlIEJhc2VkIE9uIEluY29tZSBBbmQgU3BlbmRpbmciLCB4ID0gIlRvdGFsIFNwZW50IiwgeSA9ICJJbmNvbWUiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpUaGlzIHZpc3VhbGl6YXRpb24gc2hvd3MgaG93IHByb21vdGlvbmFsIHN0cmF0ZWdpZXMgYXJlIHJlY2VpdmVkIGJ5IGRpZmZlcmVudCBjdXN0b21lciBzZWdtZW50cywgd2hpY2ggY2FuIGd1aWRlIG1hcmtldGluZyBlZmZvcnRzLg0KDQpgYGB7cn0NCiMgTG9hZCB0aGUgZ2dwbG90MiBwYWNrYWdlDQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCiMgQ3JlYXRlIGEgY291bnQgcGxvdCBmb3IgUHJvbW9fQWNjZXB0ZWQgZ3JvdXBlZCBieSBDbHVzdGVyDQpnZ3Bsb3QoWCwgYWVzKHggPSBQcm9tb19BY2NlcHRlZCwgZmlsbCA9IENsdXN0ZXIpKSArDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBsYWJzKHRpdGxlID0gIkNvdW50IE9mIFByb21vdGlvbiBBY2NlcHRlZCIsIHggPSAiTnVtYmVyIE9mIFRvdGFsIEFjY2VwdGVkIFByb21vdGlvbnMiLCB5ID0gIkNvdW50IikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KDQoNCmBgYA0KDQpUaGUgZmluYWwgdmlzdWFsaXphdGlvbiBzaG93cyB0aGUgYXZlcmFnZSBzcGVuZGluZyBpbiBkaWZmZXJlbnQgcHJvZHVjdCBjYXRlZ29yaWVzIGZvciBlYWNoIGNsdXN0ZXIsIGluZGljYXRpbmcgdGhlIHByZWZlcmVuY2VzIGFuZCBiZWhhdmlvcnMgd2l0aGluIGVhY2ggY3VzdG9tZXIgc2VnbWVudC4NCg0KYGBge3J9DQojIERlZmluZSB0aGUgcHJvZHVjdCBjYXRlZ29yaWVzDQpwcm9kdWN0cyA8LSBjKCdNbnRXaW5lcycsICdNbnRGcnVpdHMnLCAnTW50TWVhdFByb2R1Y3RzJywgJ01udEZpc2hQcm9kdWN0cycsICdNbnRTd2VldFByb2R1Y3RzJywgJ01udEdvbGRQcm9kcycpDQoNCiMgQ2FsY3VsYXRlIHRoZSBtZWFuIHZhbHVlcyBmb3IgZWFjaCBjbHVzdGVyDQp2YWx1ZXMgPC0gYWdncmVnYXRlKC4gfiBDbHVzdGVyLCBkYXRhID0gWFssIGMoIkNsdXN0ZXIiLCBwcm9kdWN0cyldLCBGVU4gPSBtZWFuKQ0KDQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcw0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KaWYgKCFyZXF1aXJlKCJyZXNoYXBlMiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJyZXNoYXBlMiIpDQoNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmVzaGFwZTIpDQoNCiMgUmVzaGFwZSB0aGUgZGF0YSBmb3IgcGxvdHRpbmcNCnZhbHVlc19tZWx0ZWQgPC0gbWVsdCh2YWx1ZXMsIGlkLnZhcnMgPSAnQ2x1c3RlcicpDQoNCiMgQ3JlYXRlIGJhciBwbG90cw0KZ2dwbG90KHZhbHVlc19tZWx0ZWQsIGFlcyh4ID0gQ2x1c3RlciwgeSA9IHZhbHVlLCBmaWxsID0gQ2x1c3RlcikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoKSkgKw0KICBmYWNldF93cmFwKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDMpICsNCiAgbGFicyh5ID0gIk1lYW4gVmFsdWUiLCB4ID0gIkNsdXN0ZXIiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpgYGANCg==