MODULOS PARA INTELIGENCIA ARTIFICIAL - NUMPY

1. Concepto

NumPy es un paquete de Python que significa “Numerical Python”, es la librería principal para la informática científica, proporciona potentes estructuras de datos, implementando matrices y matrices multidimensionales. Estas estructuras de datos garantizan cálculos eficientes con matrices.

2. Proporciona

* Paquetes de procesamiento de matrices de uso general.

* Proporciona un objeto de matriz multidimensional de alto rendimiento, y
  herramientas para trabajar con estas matrices.
  
* Paquete fundamental para la computación científica con Python.

* Puede ser usado como un eficiente contenedor multidimensional de datos
  genéricos.
       
* Enriquece el lenguaje de programación de Python con potentes
  estructuras de datos, implementando matrices y arreglos  
  multidimensional.
       
* Estas estructuras de datos garantizan cálculos eficientes con matrices.
     
* La implementación apunta incluso a matrices enormes, mejor conocidas bajo el título de “grandes datos”.
     
* El módulo suministra funciones matemáticas de alto nivel para operar en estas matrices.

3. Array NumPy

NumPy array es un potente objeto de matriz N-dimensional que tiene forma de filas y columnas, en la que
tenemos varios elementos que están almacenados en sus respectivas ubicaciones de memoria. 

3.1. Array Unidimensional

Todos los elementos que se almacenan en el array deben ser del mismo tipo. Esto implica que el array e un
bloque de datos homogéneos.
import numpy as np
a = np.array([1,2,3])
print(a)
--------
[1 2 3]

3.2. Array multidimensional

Tiene más de una columna. Podemos considerar un arreglo multidisciplinario como una hoja de cálculo de
Excel, tiene columnas y filas. Cada columna puede ser considerada como una dimensión.
import numpy as np
a = np.array([[1,2],[10,20]])
print(a)
--------
[[ 1  2]
 [10 20]]

4. Crear Arrays en MunPy

4.1 Array con ceros

Para crear un array unidimensional con 2 elementos, ambos ceros utilizamos el siguiente código:
import numpy as np
a = np.zeros(2)
print(a)
--------
[0. 0.]
Por su parte para crear un array bidimensional debes indicar el número de files y columnas que tendrá el
array.
import numpy as np
a = np.zeros([2,3])
print(a)
--------
[[0. 0. 0.]
 [0. 0. 0.]]

4.2 Array con unos

Para crear un array unidimensional con 2 elementos, ambos unos utilizamos el siguiente código:
import numpy as np
a = np.ones(2)
print(a)
--------
[1. 1.]
Por su parte para crear un array bidimensional debes indicar el número de files y columnas que tendrá el
array.
import numpy as np
a = np.ones([2,3])
print(a)
--------
[[1. 1. 1.]
 [1. 1. 1.]]

4.3 Array con rango de elementos

Para crear un array con rango de elementos, por ejemplo 3, escribimos el siguiente código:
import numpy as np
a = np.arange(3)
print(a)
--------
[0 1 2]
Si quieres crear un array con un rango de elementos, con números entre 2 y 7, por ejemplo, escribimos el
siguiente código:
import numpy as np
a = np.arange(3,7)
print(a)
--------
[3 4 5 6]

4.4 Array con elementos aleatorios

Para crear un array con elementos aleatorios debemos utilizar la siguiente instrucción e indicar el número
de filas y columnas que tendrá el array.
import numpy as np
a = np.random.rand(3,2)
print(a)
--------
[[0.51800425 0.13125493]
 [0.15577916 0.1678749 ]
 [0.12214038 0.09268094]]

5. Conceptos básicos de la estadística descriptiva

En estadística descriptiva se utilizan distintas medidas para intentar describir las propiedades de
nuestros datos, algunos de los conceptos básicos, son:

\[ \mu=\frac{1}{n} \sum_{i} x_{i} \]

# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Media Aritmética por columna:\n")
datos.mean(axis=0) # media aritmetica de cada columna

--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Media Aritmética por columna:

array([ 0.0791985 , -0.61180267,  0.23061642, -0.2894644 ])

\[ \sigma^{2}=\frac{\sum_{i=1}^{n}\left(x_{i}-\mu\right)^{2}}{n} \]

# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Varianza por columna:\n")
np.var(datos, 0) # varianza de cada columna
--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Varianza por columna:

array([0.337064  , 0.6139171 , 0.76359823, 0.03126703])

\[ \sigma=\sqrt{\frac{\sum_{i=1}^{n}\left(x_{i}-\mu\right)^{2}}{n}} \]

# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Desviación típica por columna:\n")
np.std(datos, 0) # Desviación típica de cada columna
--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Desviación típica por columna:

array([0.58057213, 0.78352862, 0.87384108, 0.17682485])
# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Moda por columna:\n")
# moda
stats.mode(datos) # Calcula la moda de cada columna
# el 2do array devuelve la frecuencia.
--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Moda por columna:

ModeResult(mode=array([[-0.76959435, -1.37414587, -0.62681496, -0.63329028]]), count=array([[1, 1, 1, 1]]))
# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")
print("Mediana por columna:\n")
np.median(datos, 0) # media aritmetica de cada columna
--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Mediana por columna:

array([-0.1074033 , -0.88138082, -0.34466623, -0.18148302])
# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")

# correlacion
print("Crea matriz de correlación:\n")
print(np.corrcoef(datos)) # Crea matriz de correlación.
print("\n")

# calculando la correlación entre dos vectores.
print("calculando la correlación entre dos vectores.:\n")
np.corrcoef(datos[0], datos[1])
--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Crea matriz de correlación:

[[ 1.          0.82333743  0.15257202  0.78798675 -0.02292073]
 [ 0.82333743  1.         -0.13709662  0.86873632  0.41234875]
 [ 0.15257202 -0.13709662  1.         -0.47691376  0.21216856]
 [ 0.78798675  0.86873632 -0.47691376  1.         -0.03445705]
 [-0.02292073  0.41234875  0.21216856 -0.03445705  1.        ]]


calculando la correlación entre dos vectores.:

array([[1.        , 0.82333743],
       [0.82333743, 1.        ]])
# Ejemplos de estadistica descriptiva con python

import numpy as np # importando numpy
from scipy import stats # importando scipy.stats
import pandas as pd # importando pandas

np.random.seed(2131982) # para poder replicar el random

datos = np.random.randn(5, 4) # datos normalmente distribuidos
print("Datos:\n")
print(datos)
print("\n")


# Covarianza de dos vectores
print("Covarianza de dos vectores:\n")
np.cov(datos[0], datos[1])
--------
Datos:

[[ 0.46038022 -1.08942528 -0.62681496 -0.63329028]
 [-0.1074033  -0.88138082 -0.34466623 -0.28320214]
 [ 0.94051171  0.86693793  1.20947882 -0.16894118]
 [-0.12790177 -0.58099931 -0.46188426 -0.18148302]
 [-0.76959435 -1.37414587  1.37696874 -0.18040537]]


Covarianza de dos vectores:

array([[0.43350958, 0.18087281],
       [0.18087281, 0.11132485]])
LS0tDQp0aXRsZTogIlNpbnRheGlzIELDoXNpY2EgTWFya2Rvd24iDQphdXRob3I6ICJKZWFuIFAuIE0uIEh1YW1hbiBRdWlzcGUiDQpkYXRlOiAiMjAyMi8wOC8wMSINCnN1YnRpdGxlOiBBcGxpY8OhbmRvIE1hcmtkb3duIA0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQo8IS0tIEHDsWFkaXIgY29tZW50YXJpb3MgYSBudWVzdHJvIGRvY3VtZW50byBNYXJrZG93biAtLT4NCg0KPGNlbnRlcj4NCiFbXShpbWcvbnVtcHkuanBnKXt3aWR0aD00MDB9DQoNCiMgTU9EVUxPUyBQQVJBIElOVEVMSUdFTkNJQSBBUlRJRklDSUFMIC0gTlVNUFkNCjwvY2VudGVyPg0KDQojIyMgMS4gQ29uY2VwdG8gDQogICAgTnVtUHkgZXMgdW4gcGFxdWV0ZSBkZSBQeXRob24gcXVlIHNpZ25pZmljYSDigJxOdW1lcmljYWwgUHl0aG9u4oCdLCBlcyBsYSBsaWJyZXLDrWEgcHJpbmNpcGFsIHBhcmEgbGEgaW5mb3Jtw6F0aWNhIGNpZW50w61maWNhLCBwcm9wb3JjaW9uYSBwb3RlbnRlcyBlc3RydWN0dXJhcyBkZSBkYXRvcywgaW1wbGVtZW50YW5kbyBtYXRyaWNlcyB5IG1hdHJpY2VzIG11bHRpZGltZW5zaW9uYWxlcy4gRXN0YXMgZXN0cnVjdHVyYXMgZGUgZGF0b3MgZ2FyYW50aXphbiBjw6FsY3Vsb3MgZWZpY2llbnRlcyBjb24gbWF0cmljZXMuDQoNCiMjIyAyLiBQcm9wb3JjaW9uYQ0KDQogICAgKiBQYXF1ZXRlcyBkZSBwcm9jZXNhbWllbnRvIGRlIG1hdHJpY2VzIGRlIHVzbyBnZW5lcmFsLg0KICAgIA0KICAgICogUHJvcG9yY2lvbmEgdW4gb2JqZXRvIGRlIG1hdHJpeiBtdWx0aWRpbWVuc2lvbmFsIGRlIGFsdG8gcmVuZGltaWVudG8sIHkNCiAgICAgIGhlcnJhbWllbnRhcyBwYXJhIHRyYWJhamFyIGNvbiBlc3RhcyBtYXRyaWNlcy4NCiAgICAgIA0KICAgICogUGFxdWV0ZSBmdW5kYW1lbnRhbCBwYXJhIGxhIGNvbXB1dGFjacOzbiBjaWVudMOtZmljYSBjb24gUHl0aG9uLg0KICAgIA0KICAgICogUHVlZGUgc2VyIHVzYWRvIGNvbW8gdW4gZWZpY2llbnRlIGNvbnRlbmVkb3IgbXVsdGlkaW1lbnNpb25hbCBkZSBkYXRvcw0KICAgICAgZ2Vuw6lyaWNvcy4NCiAgICAgICAgICAgDQogICAgKiBFbnJpcXVlY2UgZWwgbGVuZ3VhamUgZGUgcHJvZ3JhbWFjacOzbiBkZSBQeXRob24gY29uIHBvdGVudGVzDQogICAgICBlc3RydWN0dXJhcyBkZSBkYXRvcywgaW1wbGVtZW50YW5kbyBtYXRyaWNlcyB5IGFycmVnbG9zICANCiAgICAgIG11bHRpZGltZW5zaW9uYWwuDQogICAgICAgICAgIA0KICAgICogRXN0YXMgZXN0cnVjdHVyYXMgZGUgZGF0b3MgZ2FyYW50aXphbiBjw6FsY3Vsb3MgZWZpY2llbnRlcyBjb24gbWF0cmljZXMuDQogICAgICAgICANCiAgICAqIExhIGltcGxlbWVudGFjacOzbiBhcHVudGEgaW5jbHVzbyBhIG1hdHJpY2VzIGVub3JtZXMsIG1lam9yIGNvbm9jaWRhcyBiYWpvIGVsIHTDrXR1bG8gZGUg4oCcZ3JhbmRlcyBkYXRvc+KAnS4NCiAgICAgICAgIA0KICAgICogRWwgbcOzZHVsbyBzdW1pbmlzdHJhIGZ1bmNpb25lcyBtYXRlbcOhdGljYXMgZGUgYWx0byBuaXZlbCBwYXJhIG9wZXJhciBlbiBlc3RhcyBtYXRyaWNlcy4NCg0KIyMjIDMuIEFycmF5IE51bVB5DQogICAgTnVtUHkgYXJyYXkgZXMgdW4gcG90ZW50ZSBvYmpldG8gZGUgbWF0cml6IE4tZGltZW5zaW9uYWwgcXVlIHRpZW5lIGZvcm1hIGRlIGZpbGFzIHkgY29sdW1uYXMsIGVuIGxhIHF1ZQ0KICAgIHRlbmVtb3MgdmFyaW9zIGVsZW1lbnRvcyBxdWUgZXN0w6FuIGFsbWFjZW5hZG9zIGVuIHN1cyByZXNwZWN0aXZhcyB1YmljYWNpb25lcyBkZSBtZW1vcmlhLiANCg0KIyMjIDMuMS4gQXJyYXkgVW5pZGltZW5zaW9uYWwNCiAgICBUb2RvcyBsb3MgZWxlbWVudG9zIHF1ZSBzZSBhbG1hY2VuYW4gZW4gZWwgYXJyYXkgZGViZW4gc2VyIGRlbCBtaXNtbyB0aXBvLiBFc3RvIGltcGxpY2EgcXVlIGVsIGFycmF5IGUgdW4NCiAgICBibG9xdWUgZGUgZGF0b3MgaG9tb2fDqW5lb3MuDQoNCjxjZW50ZXI+ICAgIA0KIVtdKGltZy91bmlkaW1lbnNpb25hbC5qcGcpe3dpZHRoPTQwMH0NCjwvY2VudGVyPg0KDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLmFycmF5KFsxLDIsM10pDQpwcmludChhKQ0KLS0tLS0tLS0NClsxIDIgM10NCmBgYA0KDQojIyMgMy4yLiBBcnJheSBtdWx0aWRpbWVuc2lvbmFsDQogICAgVGllbmUgbcOhcyBkZSB1bmEgY29sdW1uYS4gUG9kZW1vcyBjb25zaWRlcmFyIHVuIGFycmVnbG8gbXVsdGlkaXNjaXBsaW5hcmlvIGNvbW8gdW5hIGhvamEgZGUgY8OhbGN1bG8gZGUNCiAgICBFeGNlbCwgdGllbmUgY29sdW1uYXMgeSBmaWxhcy4gQ2FkYSBjb2x1bW5hIHB1ZWRlIHNlciBjb25zaWRlcmFkYSBjb21vIHVuYSBkaW1lbnNpw7NuLg0KDQo8Y2VudGVyPiAgICANCiFbXShpbWcvbXVsdGlkaW1lbnNpb25hbC5qcGcpe3dpZHRoPTQwMH0NCjwvY2VudGVyPg0KDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLmFycmF5KFtbMSwyXSxbMTAsMjBdXSkNCnByaW50KGEpDQotLS0tLS0tLQ0KW1sgMSAgMl0NCiBbMTAgMjBdXQ0KYGBgDQoNCiMjIyA0LiBDcmVhciBBcnJheXMgZW4gTXVuUHkNCiMjIyA0LjEgQXJyYXkgY29uIGNlcm9zDQogICAgUGFyYSBjcmVhciB1biBhcnJheSB1bmlkaW1lbnNpb25hbCBjb24gMiBlbGVtZW50b3MsIGFtYm9zIGNlcm9zIHV0aWxpemFtb3MgZWwgc2lndWllbnRlIGPDs2RpZ286DQoNCmBgYFB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQphID0gbnAuemVyb3MoMikNCnByaW50KGEpDQotLS0tLS0tLQ0KWzAuIDAuXQ0KYGBgDQogICAgUG9yIHN1IHBhcnRlIHBhcmEgY3JlYXIgdW4gYXJyYXkgYmlkaW1lbnNpb25hbCBkZWJlcyBpbmRpY2FyIGVsIG7Dum1lcm8gZGUgZmlsZXMgeSBjb2x1bW5hcyBxdWUgdGVuZHLDoSBlbA0KICAgIGFycmF5Lg0KDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLnplcm9zKFsyLDNdKQ0KcHJpbnQoYSkNCi0tLS0tLS0tDQpbWzAuIDAuIDAuXQ0KIFswLiAwLiAwLl1dDQpgYGANCg0KIyMjIDQuMiBBcnJheSBjb24gdW5vcw0KICAgIFBhcmEgY3JlYXIgdW4gYXJyYXkgdW5pZGltZW5zaW9uYWwgY29uIDIgZWxlbWVudG9zLCBhbWJvcyB1bm9zIHV0aWxpemFtb3MgZWwgc2lndWllbnRlIGPDs2RpZ286DQoNCmBgYFB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQphID0gbnAub25lcygyKQ0KcHJpbnQoYSkNCi0tLS0tLS0tDQpbMS4gMS5dDQpgYGANCiAgICBQb3Igc3UgcGFydGUgcGFyYSBjcmVhciB1biBhcnJheSBiaWRpbWVuc2lvbmFsIGRlYmVzIGluZGljYXIgZWwgbsO6bWVybyBkZSBmaWxlcyB5IGNvbHVtbmFzIHF1ZSB0ZW5kcsOhIGVsDQogICAgYXJyYXkuDQogICAgDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLm9uZXMoWzIsM10pDQpwcmludChhKQ0KLS0tLS0tLS0NCltbMS4gMS4gMS5dDQogWzEuIDEuIDEuXV0NCmBgYA0KICAgIA0KIyMjIDQuMyBBcnJheSBjb24gcmFuZ28gZGUgZWxlbWVudG9zDQogICAgUGFyYSBjcmVhciB1biBhcnJheSBjb24gcmFuZ28gZGUgZWxlbWVudG9zLCBwb3IgZWplbXBsbyAzLCBlc2NyaWJpbW9zIGVsIHNpZ3VpZW50ZSBjw7NkaWdvOg0KICAgIA0KYGBgUHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmEgPSBucC5hcmFuZ2UoMykNCnByaW50KGEpDQotLS0tLS0tLQ0KWzAgMSAyXQ0KYGBgDQogICAgU2kgcXVpZXJlcyBjcmVhciB1biBhcnJheSBjb24gdW4gcmFuZ28gZGUgZWxlbWVudG9zLCBjb24gbsO6bWVyb3MgZW50cmUgMiB5IDcsIHBvciBlamVtcGxvLCBlc2NyaWJpbW9zIGVsDQogICAgc2lndWllbnRlIGPDs2RpZ286DQoNCmBgYFB5dGhvbg0KaW1wb3J0IG51bXB5IGFzIG5wDQphID0gbnAuYXJhbmdlKDMsNykNCnByaW50KGEpDQotLS0tLS0tLQ0KWzMgNCA1IDZdDQpgYGAgICAgDQogICAgDQojIyMgNC40IEFycmF5IGNvbiBlbGVtZW50b3MgYWxlYXRvcmlvcw0KICAgIFBhcmEgY3JlYXIgdW4gYXJyYXkgY29uIGVsZW1lbnRvcyBhbGVhdG9yaW9zIGRlYmVtb3MgdXRpbGl6YXIgbGEgc2lndWllbnRlIGluc3RydWNjacOzbiBlIGluZGljYXIgZWwgbsO6bWVybw0KICAgIGRlIGZpbGFzIHkgY29sdW1uYXMgcXVlIHRlbmRyw6EgZWwgYXJyYXkuDQogICAgDQpgYGBQeXRob24NCmltcG9ydCBudW1weSBhcyBucA0KYSA9IG5wLnJhbmRvbS5yYW5kKDMsMikNCnByaW50KGEpDQotLS0tLS0tLQ0KW1swLjUxODAwNDI1IDAuMTMxMjU0OTNdDQogWzAuMTU1Nzc5MTYgMC4xNjc4NzQ5IF0NCiBbMC4xMjIxNDAzOCAwLjA5MjY4MDk0XV0NCmBgYCAgICAgICANCiAgICANCiMjIyA1LiBDb25jZXB0b3MgYsOhc2ljb3MgZGUgbGEgZXN0YWTDrXN0aWNhIGRlc2NyaXB0aXZhDQogICAgRW4gZXN0YWTDrXN0aWNhIGRlc2NyaXB0aXZhIHNlIHV0aWxpemFuIGRpc3RpbnRhcyBtZWRpZGFzIHBhcmEgaW50ZW50YXIgZGVzY3JpYmlyIGxhcyBwcm9waWVkYWRlcyBkZQ0KICAgIG51ZXN0cm9zIGRhdG9zLCBhbGd1bm9zIGRlIGxvcyBjb25jZXB0b3MgYsOhc2ljb3MsIHNvbjoNCg0KKiBNZWRpYSBhcml0bcOpdGljYTogIExhIG1lZGlhIGFyaXRtw6l0aWNhIGVzIGVsIHZhbG9yIG9idGVuaWRvIGFsIHN1bWFyIHRvZG9zIGxvcyBkYXRvcyB5IGRpdmlkaXIgZWwgcmVzdWx0YWRvIGVudHJlIGVsIG7Dum1lcm8gdG90YWwgZWxlbWVudG9zLiBTZSBzdWVsZSByZXByZXNlbnRhciBjb24gbGEgbGV0cmEgZ3JpZWdhIM68LiBTaSB0ZW5lbW9zIHVuYSBtdWVzdHJhIGRlIG4gdmFsb3JlcywgeGksIGxhIG1lZGlhIGFyaXRtw6l0aWNhLCDOvCwgZXMgbGEgc3VtYSBkZSBsb3MgdmFsb3JlcyBkaXZpZGlkb3MgcG9yIGVsIG51bWVybyBkZSBlbGVtZW50b3M7IGVuIG90cmFzIHBhbGFicmFzOg0KDQokJA0KXG11PVxmcmFjezF9e259IFxzdW1fe2l9IHhfe2l9DQokJA0KDQpgYGBQeXRob24NCiMgRWplbXBsb3MgZGUgZXN0YWRpc3RpY2EgZGVzY3JpcHRpdmEgY29uIHB5dGhvbg0KDQppbXBvcnQgbnVtcHkgYXMgbnAgIyBpbXBvcnRhbmRvIG51bXB5DQpmcm9tIHNjaXB5IGltcG9ydCBzdGF0cyAjIGltcG9ydGFuZG8gc2NpcHkuc3RhdHMNCmltcG9ydCBwYW5kYXMgYXMgcGQgIyBpbXBvcnRhbmRvIHBhbmRhcw0KDQpucC5yYW5kb20uc2VlZCgyMTMxOTgyKSAjIHBhcmEgcG9kZXIgcmVwbGljYXIgZWwgcmFuZG9tDQoNCmRhdG9zID0gbnAucmFuZG9tLnJhbmRuKDUsIDQpICMgZGF0b3Mgbm9ybWFsbWVudGUgZGlzdHJpYnVpZG9zDQpwcmludCgiRGF0b3M6XG4iKQ0KcHJpbnQoZGF0b3MpDQpwcmludCgiXG4iKQ0KcHJpbnQoIk1lZGlhIEFyaXRtw6l0aWNhIHBvciBjb2x1bW5hOlxuIikNCmRhdG9zLm1lYW4oYXhpcz0wKSAjIG1lZGlhIGFyaXRtZXRpY2EgZGUgY2FkYSBjb2x1bW5hDQoNCi0tLS0tLS0tDQpEYXRvczoNCg0KW1sgMC40NjAzODAyMiAtMS4wODk0MjUyOCAtMC42MjY4MTQ5NiAtMC42MzMyOTAyOF0NCiBbLTAuMTA3NDAzMyAgLTAuODgxMzgwODIgLTAuMzQ0NjY2MjMgLTAuMjgzMjAyMTRdDQogWyAwLjk0MDUxMTcxICAwLjg2NjkzNzkzICAxLjIwOTQ3ODgyIC0wLjE2ODk0MTE4XQ0KIFstMC4xMjc5MDE3NyAtMC41ODA5OTkzMSAtMC40NjE4ODQyNiAtMC4xODE0ODMwMl0NCiBbLTAuNzY5NTk0MzUgLTEuMzc0MTQ1ODcgIDEuMzc2OTY4NzQgLTAuMTgwNDA1MzddXQ0KDQoNCk1lZGlhIEFyaXRtw6l0aWNhIHBvciBjb2x1bW5hOg0KDQphcnJheShbIDAuMDc5MTk4NSAsIC0wLjYxMTgwMjY3LCAgMC4yMzA2MTY0MiwgLTAuMjg5NDY0NCBdKQ0KYGBgIA0KDQoqIFZhcmlhbnphOiBMYSB2YXJpYW56YSBlcyBsYSBtZWRpYSBhcml0bcOpdGljYSBkZWwgY3VhZHJhZG8gZGUgbGFzIGRlc3ZpYWNpb25lcyByZXNwZWN0byBhIGxhIG1lZGlhIGRlIHVuYSBkaXN0cmlidWNpw7NuIGVzdGFkw61zdGljYS4gTGEgdmFyaWFuemEgaW50ZW50YSBkZXNjcmliaXIgbGEgZGlzcGVyc2nDs24gZGUgbG9zIGRhdG9zLiBTZSByZXByZXNlbnRhIGNvbW8gz4MyLg0KDQokJA0KXHNpZ21hXnsyfT1cZnJhY3tcc3VtX3tpPTF9XntufVxsZWZ0KHhfe2l9LVxtdVxyaWdodCleezJ9fXtufQ0KJCQNCg0KYGBgUHl0aG9uDQojIEVqZW1wbG9zIGRlIGVzdGFkaXN0aWNhIGRlc2NyaXB0aXZhIGNvbiBweXRob24NCg0KaW1wb3J0IG51bXB5IGFzIG5wICMgaW1wb3J0YW5kbyBudW1weQ0KZnJvbSBzY2lweSBpbXBvcnQgc3RhdHMgIyBpbXBvcnRhbmRvIHNjaXB5LnN0YXRzDQppbXBvcnQgcGFuZGFzIGFzIHBkICMgaW1wb3J0YW5kbyBwYW5kYXMNCg0KbnAucmFuZG9tLnNlZWQoMjEzMTk4MikgIyBwYXJhIHBvZGVyIHJlcGxpY2FyIGVsIHJhbmRvbQ0KDQpkYXRvcyA9IG5wLnJhbmRvbS5yYW5kbig1LCA0KSAjIGRhdG9zIG5vcm1hbG1lbnRlIGRpc3RyaWJ1aWRvcw0KcHJpbnQoIkRhdG9zOlxuIikNCnByaW50KGRhdG9zKQ0KcHJpbnQoIlxuIikNCnByaW50KCJWYXJpYW56YSBwb3IgY29sdW1uYTpcbiIpDQpucC52YXIoZGF0b3MsIDApICMgdmFyaWFuemEgZGUgY2FkYSBjb2x1bW5hDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpWYXJpYW56YSBwb3IgY29sdW1uYToNCg0KYXJyYXkoWzAuMzM3MDY0ICAsIDAuNjEzOTE3MSAsIDAuNzYzNTk4MjMsIDAuMDMxMjY3MDNdKQ0KYGBgIA0KDQoqIERlc3ZpYWNpw7NuIHTDrXBpY2E6IExhIGRlc3ZpYWNpw7NuIHTDrXBpY2EgZXMgbGEgcmHDrXogY3VhZHJhZGEgZGUgbGEgdmFyaWFuemEuIFNlIHJlcHJlc2VudGEgY29uIGxhIGxldHJhIGdyaWVnYSDPgy4NCg0KJCQNClxzaWdtYT1cc3FydHtcZnJhY3tcc3VtX3tpPTF9XntufVxsZWZ0KHhfe2l9LVxtdVxyaWdodCleezJ9fXtufX0NCiQkDQoNCmBgYFB5dGhvbg0KIyBFamVtcGxvcyBkZSBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YSBjb24gcHl0aG9uDQoNCmltcG9ydCBudW1weSBhcyBucCAjIGltcG9ydGFuZG8gbnVtcHkNCmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzICMgaW1wb3J0YW5kbyBzY2lweS5zdGF0cw0KaW1wb3J0IHBhbmRhcyBhcyBwZCAjIGltcG9ydGFuZG8gcGFuZGFzDQoNCm5wLnJhbmRvbS5zZWVkKDIxMzE5ODIpICMgcGFyYSBwb2RlciByZXBsaWNhciBlbCByYW5kb20NCg0KZGF0b3MgPSBucC5yYW5kb20ucmFuZG4oNSwgNCkgIyBkYXRvcyBub3JtYWxtZW50ZSBkaXN0cmlidWlkb3MNCnByaW50KCJEYXRvczpcbiIpDQpwcmludChkYXRvcykNCnByaW50KCJcbiIpDQpwcmludCgiRGVzdmlhY2nDs24gdMOtcGljYSBwb3IgY29sdW1uYTpcbiIpDQpucC5zdGQoZGF0b3MsIDApICMgRGVzdmlhY2nDs24gdMOtcGljYSBkZSBjYWRhIGNvbHVtbmENCi0tLS0tLS0tDQpEYXRvczoNCg0KW1sgMC40NjAzODAyMiAtMS4wODk0MjUyOCAtMC42MjY4MTQ5NiAtMC42MzMyOTAyOF0NCiBbLTAuMTA3NDAzMyAgLTAuODgxMzgwODIgLTAuMzQ0NjY2MjMgLTAuMjgzMjAyMTRdDQogWyAwLjk0MDUxMTcxICAwLjg2NjkzNzkzICAxLjIwOTQ3ODgyIC0wLjE2ODk0MTE4XQ0KIFstMC4xMjc5MDE3NyAtMC41ODA5OTkzMSAtMC40NjE4ODQyNiAtMC4xODE0ODMwMl0NCiBbLTAuNzY5NTk0MzUgLTEuMzc0MTQ1ODcgIDEuMzc2OTY4NzQgLTAuMTgwNDA1MzddXQ0KDQoNCkRlc3ZpYWNpw7NuIHTDrXBpY2EgcG9yIGNvbHVtbmE6DQoNCmFycmF5KFswLjU4MDU3MjEzLCAwLjc4MzUyODYyLCAwLjg3Mzg0MTA4LCAwLjE3NjgyNDg1XSkNCmBgYCANCg0KKiBNb2RhOiBMYSBtb2RhIGVzIGVsIHZhbG9yIHF1ZSB0aWVuZSBtYXlvciBmcmVjdWVuY2lhIGFic29sdXRhLiBTZSByZXByZXNlbnRhIGNvbiBNMA0KDQpgYGBQeXRob24NCiMgRWplbXBsb3MgZGUgZXN0YWRpc3RpY2EgZGVzY3JpcHRpdmEgY29uIHB5dGhvbg0KDQppbXBvcnQgbnVtcHkgYXMgbnAgIyBpbXBvcnRhbmRvIG51bXB5DQpmcm9tIHNjaXB5IGltcG9ydCBzdGF0cyAjIGltcG9ydGFuZG8gc2NpcHkuc3RhdHMNCmltcG9ydCBwYW5kYXMgYXMgcGQgIyBpbXBvcnRhbmRvIHBhbmRhcw0KDQpucC5yYW5kb20uc2VlZCgyMTMxOTgyKSAjIHBhcmEgcG9kZXIgcmVwbGljYXIgZWwgcmFuZG9tDQoNCmRhdG9zID0gbnAucmFuZG9tLnJhbmRuKDUsIDQpICMgZGF0b3Mgbm9ybWFsbWVudGUgZGlzdHJpYnVpZG9zDQpwcmludCgiRGF0b3M6XG4iKQ0KcHJpbnQoZGF0b3MpDQpwcmludCgiXG4iKQ0KcHJpbnQoIk1vZGEgcG9yIGNvbHVtbmE6XG4iKQ0KIyBtb2RhDQpzdGF0cy5tb2RlKGRhdG9zKSAjIENhbGN1bGEgbGEgbW9kYSBkZSBjYWRhIGNvbHVtbmENCiMgZWwgMmRvIGFycmF5IGRldnVlbHZlIGxhIGZyZWN1ZW5jaWEuDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpNb2RhIHBvciBjb2x1bW5hOg0KDQpNb2RlUmVzdWx0KG1vZGU9YXJyYXkoW1stMC43Njk1OTQzNSwgLTEuMzc0MTQ1ODcsIC0wLjYyNjgxNDk2LCAtMC42MzMyOTAyOF1dKSwgY291bnQ9YXJyYXkoW1sxLCAxLCAxLCAxXV0pKQ0KYGBgIA0KDQoqIE1lZGlhbmE6IExhIG1lZGlhbmEgZXMgZWwgdmFsb3IgcXVlIG9jdXBhIGVsIGx1Z2FyIGNlbnRyYWwgZGUgdG9kb3MgbG9zIGRhdG9zIGN1YW5kbyDDqXN0b3MgZXN0w6FuIG9yZGVuYWRvcyBkZSBtZW5vciBhIG1heW9yLiBTZSByZXByZXNlbnRhIGNvbiB4y5wuDQoNCmBgYFB5dGhvbg0KIyBFamVtcGxvcyBkZSBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YSBjb24gcHl0aG9uDQoNCmltcG9ydCBudW1weSBhcyBucCAjIGltcG9ydGFuZG8gbnVtcHkNCmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzICMgaW1wb3J0YW5kbyBzY2lweS5zdGF0cw0KaW1wb3J0IHBhbmRhcyBhcyBwZCAjIGltcG9ydGFuZG8gcGFuZGFzDQoNCm5wLnJhbmRvbS5zZWVkKDIxMzE5ODIpICMgcGFyYSBwb2RlciByZXBsaWNhciBlbCByYW5kb20NCg0KZGF0b3MgPSBucC5yYW5kb20ucmFuZG4oNSwgNCkgIyBkYXRvcyBub3JtYWxtZW50ZSBkaXN0cmlidWlkb3MNCnByaW50KCJEYXRvczpcbiIpDQpwcmludChkYXRvcykNCnByaW50KCJcbiIpDQpwcmludCgiTWVkaWFuYSBwb3IgY29sdW1uYTpcbiIpDQpucC5tZWRpYW4oZGF0b3MsIDApICMgbWVkaWEgYXJpdG1ldGljYSBkZSBjYWRhIGNvbHVtbmENCi0tLS0tLS0tDQpEYXRvczoNCg0KW1sgMC40NjAzODAyMiAtMS4wODk0MjUyOCAtMC42MjY4MTQ5NiAtMC42MzMyOTAyOF0NCiBbLTAuMTA3NDAzMyAgLTAuODgxMzgwODIgLTAuMzQ0NjY2MjMgLTAuMjgzMjAyMTRdDQogWyAwLjk0MDUxMTcxICAwLjg2NjkzNzkzICAxLjIwOTQ3ODgyIC0wLjE2ODk0MTE4XQ0KIFstMC4xMjc5MDE3NyAtMC41ODA5OTkzMSAtMC40NjE4ODQyNiAtMC4xODE0ODMwMl0NCiBbLTAuNzY5NTk0MzUgLTEuMzc0MTQ1ODcgIDEuMzc2OTY4NzQgLTAuMTgwNDA1MzddXQ0KDQoNCk1lZGlhbmEgcG9yIGNvbHVtbmE6DQoNCmFycmF5KFstMC4xMDc0MDMzICwgLTAuODgxMzgwODIsIC0wLjM0NDY2NjIzLCAtMC4xODE0ODMwMl0pDQpgYGAgDQoNCiogQ29ycmVsYWNpw7NuOiBMYSBjb3JyZWxhY2nDs24gdHJhdGEgZGUgZXN0YWJsZWNlciBsYSByZWxhY2nDs24gbyBkZXBlbmRlbmNpYSBxdWUgZXhpc3RlIGVudHJlIGxhcyBkb3MgdmFyaWFibGVzIHF1ZSBpbnRlcnZpZW5lbiBlbiB1bmEgZGlzdHJpYnVjacOzbiBiaWRpbWVuc2lvbmFsLiBFcyBkZWNpciwgZGV0ZXJtaW5hciBzaSBsb3MgY2FtYmlvcyBlbiB1bmEgZGUgbGFzIHZhcmlhYmxlcyBpbmZsdXllbiBlbiBsb3MgY2FtYmlvcyBkZSBsYSBvdHJhLiBFbiBjYXNvIGRlIHF1ZSBzdWNlZGEsIGRpcmVtb3MgcXVlIGxhcyB2YXJpYWJsZXMgZXN0w6FuIGNvcnJlbGFjaW9uYWRhcyBvIHF1ZSBoYXkgY29ycmVsYWNpw7NuIGVudHJlIGVsbGFzLiBMYSBjb3JyZWxhY2nDs24gZXMgcG9zaXRpdmEgY3VhbmRvIGxvcyB2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgYXVtZW50YSBqdW50b3M7IHkgZXMgbmVnYXRpdmEgY3VhbmRvIHVuIHZhbG9yIGRlIHVuYSB2YXJpYWJsZSBzZSByZWR1Y2UgY3VhbmRvIGVsIHZhbG9yIGRlIGxhIG90cmEgdmFyaWFibGUgYXVtZW50YS4NCg0KYGBgUHl0aG9uDQojIEVqZW1wbG9zIGRlIGVzdGFkaXN0aWNhIGRlc2NyaXB0aXZhIGNvbiBweXRob24NCg0KaW1wb3J0IG51bXB5IGFzIG5wICMgaW1wb3J0YW5kbyBudW1weQ0KZnJvbSBzY2lweSBpbXBvcnQgc3RhdHMgIyBpbXBvcnRhbmRvIHNjaXB5LnN0YXRzDQppbXBvcnQgcGFuZGFzIGFzIHBkICMgaW1wb3J0YW5kbyBwYW5kYXMNCg0KbnAucmFuZG9tLnNlZWQoMjEzMTk4MikgIyBwYXJhIHBvZGVyIHJlcGxpY2FyIGVsIHJhbmRvbQ0KDQpkYXRvcyA9IG5wLnJhbmRvbS5yYW5kbig1LCA0KSAjIGRhdG9zIG5vcm1hbG1lbnRlIGRpc3RyaWJ1aWRvcw0KcHJpbnQoIkRhdG9zOlxuIikNCnByaW50KGRhdG9zKQ0KcHJpbnQoIlxuIikNCg0KIyBjb3JyZWxhY2lvbg0KcHJpbnQoIkNyZWEgbWF0cml6IGRlIGNvcnJlbGFjacOzbjpcbiIpDQpwcmludChucC5jb3JyY29lZihkYXRvcykpICMgQ3JlYSBtYXRyaXogZGUgY29ycmVsYWNpw7NuLg0KcHJpbnQoIlxuIikNCg0KIyBjYWxjdWxhbmRvIGxhIGNvcnJlbGFjacOzbiBlbnRyZSBkb3MgdmVjdG9yZXMuDQpwcmludCgiY2FsY3VsYW5kbyBsYSBjb3JyZWxhY2nDs24gZW50cmUgZG9zIHZlY3RvcmVzLjpcbiIpDQpucC5jb3JyY29lZihkYXRvc1swXSwgZGF0b3NbMV0pDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpDcmVhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs246DQoNCltbIDEuICAgICAgICAgIDAuODIzMzM3NDMgIDAuMTUyNTcyMDIgIDAuNzg3OTg2NzUgLTAuMDIyOTIwNzNdDQogWyAwLjgyMzMzNzQzICAxLiAgICAgICAgIC0wLjEzNzA5NjYyICAwLjg2ODczNjMyICAwLjQxMjM0ODc1XQ0KIFsgMC4xNTI1NzIwMiAtMC4xMzcwOTY2MiAgMS4gICAgICAgICAtMC40NzY5MTM3NiAgMC4yMTIxNjg1Nl0NCiBbIDAuNzg3OTg2NzUgIDAuODY4NzM2MzIgLTAuNDc2OTEzNzYgIDEuICAgICAgICAgLTAuMDM0NDU3MDVdDQogWy0wLjAyMjkyMDczICAwLjQxMjM0ODc1ICAwLjIxMjE2ODU2IC0wLjAzNDQ1NzA1ICAxLiAgICAgICAgXV0NCg0KDQpjYWxjdWxhbmRvIGxhIGNvcnJlbGFjacOzbiBlbnRyZSBkb3MgdmVjdG9yZXMuOg0KDQphcnJheShbWzEuICAgICAgICAsIDAuODIzMzM3NDNdLA0KICAgICAgIFswLjgyMzMzNzQzLCAxLiAgICAgICAgXV0pDQpgYGAgDQoNCiogQ292YXJpYW56YTogTGEgY292YXJpYW56YSBlcyBlbCBlcXVpdmFsZW50ZSBkZSBsYSB2YXJpYW56YSBhcGxpY2FkbyBhIHVuYSB2YXJpYWJsZSBiaWRpbWVuc2lvbmFsLiBFcyBsYSBtZWRpYSBhcml0bcOpdGljYSBkZSBsb3MgcHJvZHVjdG9zIGRlIGxhcyBkZXN2aWFjaW9uZXMgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyByZXNwZWN0byBhIHN1cyBtZWRpYXMgcmVzcGVjdGl2YXMuTGEgY292YXJpYW56YSBpbmRpY2EgZWwgc2VudGlkbyBkZSBsYSBjb3JyZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlczsgU2kgz4N4eT4wIGxhIGNvcnJlbGFjacOzbiBlcyBkaXJlY3RhOyBTaSDPg3h5PDAgbGEgY29ycmVsYWNpw7NuIGVzIGludmVyc2EuDQoNCmBgYFB5dGhvbg0KIyBFamVtcGxvcyBkZSBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YSBjb24gcHl0aG9uDQoNCmltcG9ydCBudW1weSBhcyBucCAjIGltcG9ydGFuZG8gbnVtcHkNCmZyb20gc2NpcHkgaW1wb3J0IHN0YXRzICMgaW1wb3J0YW5kbyBzY2lweS5zdGF0cw0KaW1wb3J0IHBhbmRhcyBhcyBwZCAjIGltcG9ydGFuZG8gcGFuZGFzDQoNCm5wLnJhbmRvbS5zZWVkKDIxMzE5ODIpICMgcGFyYSBwb2RlciByZXBsaWNhciBlbCByYW5kb20NCg0KZGF0b3MgPSBucC5yYW5kb20ucmFuZG4oNSwgNCkgIyBkYXRvcyBub3JtYWxtZW50ZSBkaXN0cmlidWlkb3MNCnByaW50KCJEYXRvczpcbiIpDQpwcmludChkYXRvcykNCnByaW50KCJcbiIpDQoNCg0KIyBDb3ZhcmlhbnphIGRlIGRvcyB2ZWN0b3Jlcw0KcHJpbnQoIkNvdmFyaWFuemEgZGUgZG9zIHZlY3RvcmVzOlxuIikNCm5wLmNvdihkYXRvc1swXSwgZGF0b3NbMV0pDQotLS0tLS0tLQ0KRGF0b3M6DQoNCltbIDAuNDYwMzgwMjIgLTEuMDg5NDI1MjggLTAuNjI2ODE0OTYgLTAuNjMzMjkwMjhdDQogWy0wLjEwNzQwMzMgIC0wLjg4MTM4MDgyIC0wLjM0NDY2NjIzIC0wLjI4MzIwMjE0XQ0KIFsgMC45NDA1MTE3MSAgMC44NjY5Mzc5MyAgMS4yMDk0Nzg4MiAtMC4xNjg5NDExOF0NCiBbLTAuMTI3OTAxNzcgLTAuNTgwOTk5MzEgLTAuNDYxODg0MjYgLTAuMTgxNDgzMDJdDQogWy0wLjc2OTU5NDM1IC0xLjM3NDE0NTg3ICAxLjM3Njk2ODc0IC0wLjE4MDQwNTM3XV0NCg0KDQpDb3ZhcmlhbnphIGRlIGRvcyB2ZWN0b3JlczoNCg0KYXJyYXkoW1swLjQzMzUwOTU4LCAwLjE4MDg3MjgxXSwNCiAgICAgICBbMC4xODA4NzI4MSwgMC4xMTEzMjQ4NV1dKQ0KYGBgIA0KDQoNCg0KICAgIA0KDQoNCg0KIA0K