Dictionaries

a_dict = {'color': 'blue', 'fruit': 'apple', 'pet': 'dog'}
for key in a_dict:
    print(key)
## color
## fruit
## pet
for key in a_dict:
    print(key, '->', a_dict[key])
## color -> blue
## fruit -> apple
## pet -> dog

The preceding code allowed you to get access to the keys (key) and the values (a_dict[key]) of a_dict at the same time. This way, you can do any operation with both the keys and the values.

Iterating Through .items()

When you’re working with dictionaries, it’s likely that you’ll want to work with both the keys and the values. One of the most useful ways to iterate through a dictionary in Python is by using .items(), which is a method that returns a new view of the dictionary’s items:

a_dict = {'color': 'blue', 'fruit': 'apple', 'pet': 'dog'}
d_items = a_dict.items()
d_items  # Here d_items is a view of items
## dict_items([('color', 'blue'), ('fruit', 'apple'), ('pet', 'dog')])

Dictionary views like d_items provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the views reflect these changes.

Views can be iterated over to yield their respective data, so you can iterate through a dictionary in Python by using the view object returned by .items():

for item in a_dict.items():
    print(item)
## ('color', 'blue')
## ('fruit', 'apple')
## ('pet', 'dog')

The view object returned by .items() yields the key-value pairs one at a time and allows you to iterate through a dictionary in Python, but in such a way that you get access to the keys and values at the same time.

If you take a closer look at the individual items yielded by .items(), you’ll notice that they’re really tuple objects. Let’s take a look:

for item in a_dict.items():
    print(item)
    print(type(item))
## ('color', 'blue')
## <class 'tuple'>
## ('fruit', 'apple')
## <class 'tuple'>
## ('pet', 'dog')
## <class 'tuple'>

Once you know this, you can use tuple unpacking to iterate through the keys and values of the dictionary you are working with. To achieve this, you just need to unpack the elements of every item into two different variables representing the key and the value:

for key, value in a_dict.items():
    print(key, '->', value)
## color -> blue
## fruit -> apple
## pet -> dog

Here, the variables key and value in the header of your for loop do the unpacking. Every time the loop runs, key will store the key, and value will store the value of the item that is been processed. This way, you’ll have more control over the items of the dictionary, and you’ll be able to process the keys and values separately and in a way that is more readable and Pythonic.

Note: Notice that .values() and .keys() return view objects just like .items(), as you’ll see in the next two sections.

You can also use .keys() and .values() methods to return the called items.

It’s worth noting that they also support membership tests (in), which is an important feature if you’re trying to know if a specific element is in a dictionary or not:

a_dict = {'color': 'blue', 'fruit': 'apple', 'pet': 'dog'}
'pet' in a_dict.keys()
## True
'apple' in a_dict.values()
## True
'onion' in a_dict.values()
## False
'color' in a_dict.values()
## False

Modifying values and keys

The values, for example, can be modified whenever you need, but you’ll need to use the original dictionary and the key that maps the value you want to modify:

prices = {'apple': 0.40, 'orange': 0.35, 'banana': 0.25}
for k, v in prices.items():
    prices[k] = round(v * 0.9, 2)  # Apply a 10% discount

prices
## {'apple': 0.36, 'orange': 0.32, 'banana': 0.23}

Dictionary comprehension

Suppose, for example, that you have two lists of data, and you need to create a new dictionary from them. In this case, you can use Python’s zip(*iterables) to loop over the elements of both lists in pairs:

objects = ['blue', 'apple', 'dog']
categories = ['color', 'fruit', 'pet']
a_dict = {key: value for key, value in zip(categories, objects)}
a_dict
## {'color': 'blue', 'fruit': 'apple', 'pet': 'dog'}

Here, zip() receives two iterables (categories and objects) as arguments and makes an iterator that aggregates elements from each iterable. The tuple objects generated by zip() are then unpacked into key and value, which are finally used to create the new dictionary.

Turning keys into values and vice versa

a_dict = {'one': 1, 'two': 2, 'thee': 3, 'four': 4}
new_dict = {value: key for key, value in a_dict.items()}
new_dict
## {1: 'one', 2: 'two', 3: 'thee', 4: 'four'}

Filtering items

To filter the items in a dictionary with a comprehension, you just need to add an if clause that defines the condition you want to meet.

a_dict = {'one': 1, 'two': 2, 'thee': 3, 'four': 4}
new_dict = {k: v for k, v in a_dict.items() if v <= 2}
new_dict
## {'one': 1, 'two': 2}

Doing some calculations

If you use a list comprehension to iterate through the dictionary’s values, then you’ll get code that is more compact, fast, and Pythonic:

incomes = {'apple': 5600.00, 'orange': 3500.00, 'banana': 5000.00}
total_income = sum([value for value in incomes.values()])
total_income
## 14100.0

The list comprehension created a list object containing the values of incomes, and then you summed up all of them by using sum() and stored the result in total_income.

If you’re working with a really large dictionary, and memory usage is a problem for you, then you can use a generator expression instead of a list comprehension. A generator expression is an expression that returns an iterator. It looks like a list comprehension, but instead of brackets you need to use parentheses to define it:

total_income = sum(value for value in incomes.values())
total_income
## 14100.0

If you change the square brackets for a pair of parentheses (the parentheses of sum() here), you’ll be turning the list comprehension into a generator expression, and your code will be memory efficient, because generator expressions yield elements on demand. Instead of creating and storing the whole list in memory, you’ll only have to store one element at a time.

Finally, there is a simpler way to solve this problem by just using incomes.values() directly as an argument to sum():

total_income = sum(incomes.values())
total_income
## 14100.0

Removing specific items

Now, suppose you have a dictionary and need to create a new one with selected keys removed. Remember how key-view objects are like sets? Well, these similarities go beyond just being collections of hashable and unique objects. Key-view objects also support common set operations. Let’s see how you can take advantage of this to remove specific items in a dictionary:

incomes = {'apple': 5600.00, 'orange': 3500.00, 'banana': 5000.00}
non_citric = {k: incomes[k] for k in incomes.keys() - {'orange'}}
non_citric
## {'apple': 5600.0, 'banana': 5000.0}

This code works because key-view objects support set operations like unions, intersections, and differences. When you wrote incomes.keys() - {‘orange’} inside the dictionary comprehension, you were really doing a set difference operation. If you need to perform any set operations with the keys of a dictionary, then you can just use the key-view object directly without first converting it into a set. This is a little-known feature of key-view objects that can be useful in some situations.

Sorting a Dictionary

It’s often necessary to sort the elements of a collection. Since Python 3.6, dictionaries are ordered data structures, so if you use Python 3.6 (and beyond), you’ll be able to sort the items of any dictionary by using sorted() and with the help of a dictionary comprehension:

incomes = {'apple': 5600.00, 'orange': 3500.00, 'banana': 5000.00}
sorted_income = {k: incomes[k] for k in sorted(incomes)}
sorted_income
## {'apple': 5600.0, 'banana': 5000.0, 'orange': 3500.0}

This code allows you to create a new dictionary with its keys in sorted order. This is possible because sorted(incomes) returns a list of sorted keys that you can use to generate the new dictionary sorted_dict.

Sorted by values

for value in sorted(incomes.values()):
    print(value)
## 3500.0
## 5000.0
## 5600.0

Using Some of Python’s Built-In Functions

map()

Python’s map() is defined as map(function, iterable, …) and returns an iterator that applies function to every item of iterable, yielding the results on demand. So, map() could be viewed as an iteration tool that you can use to iterate through a dictionary in Python.

Suppose you have a dictionary containing the prices of a bunch of products, and you need to apply a discount to them. In this case, you can define a function that manages the discount and then uses it as the first argument to map(). The second argument can be prices.items():

prices = {'apple': 0.40, 'orange': 0.35, 'banana': 0.25}
def discount(current_price):
    return (current_price[0], round(current_price[1] * 0.95, 2))

new_prices = dict(map(discount, prices.items()))
new_prices
## {'apple': 0.38, 'orange': 0.33, 'banana': 0.24}

filter()

filter() is another built-in function that you can use to iterate through a dictionary in Python and filter out some of its items. This function is defined as filter(function, iterable) and returns an iterator from those elements of iterable for which function returns True.

Suppose you want to know the products with a price lower than 0.40. You need to define a function to determine if the price satisfies that condition and pass it as first argument to filter(). The second argument can be prices.keys():

prices = {'apple': 0.40, 'orange': 0.35, 'banana': 0.25}
def has_low_price(price):
    return prices[price] < 0.4

low_price = list(filter(has_low_price, prices.keys()))
low_price
## ['orange', 'banana']

Using collections.ChainMap

collections is a useful module from the Python Standard Library that provides specialized container data types. One of these data types is ChainMap, which is a dictionary-like class for creating a single view of multiple mappings (like dictionaries). With ChainMap, you can group multiple dictionaries together to create a single, updateable view.

Now, suppose you have two (or more) dictionaries, and you need to iterate through them together as one. To achieve this, you can create a ChainMap object and initialize it with your dictionaries:

from collections import ChainMap
fruit_prices = {'apple': 0.40, 'orange': 0.35}
vegetable_prices = {'pepper': 0.20, 'onion': 0.55}
chained_dict = ChainMap(fruit_prices, vegetable_prices)
chained_dict  # A ChainMap object
## ChainMap({'apple': 0.4, 'orange': 0.35}, {'pepper': 0.2, 'onion': 0.55})
for key in chained_dict:
    print(key, '->', chained_dict[key])
## pepper -> 0.2
## onion -> 0.55
## apple -> 0.4
## orange -> 0.35

After importing ChainMap from collections, you need to create a ChainMap object with the dictionaries you want to chain, and then you can freely iterate through the resulting object as you would do with a regular dictionary.

ChainMap objects also implement .keys(), values(), and .items() as a standard dictionary does, so you can use these methods to iterate through the dictionary-like object generated by ChainMap, just like you would do with a regular dictionary:

for key, value in chained_dict.items():
    print(key, '->', value)
## pepper -> 0.2
## onion -> 0.55
## apple -> 0.4
## orange -> 0.35

Using itertools

Chained interation with chain()

Chained Iteration With chain() itertools also provides chain(*iterables), which gets some iterables as arguments and makes an iterator that yields elements from the first iterable until it’s exhausted, then iterates over the next iterable and so on, until all of them are exhausted.

This allows you to iterate through multiple dictionaries in a chain, like to what you did with collections.ChainMap:

from itertools import chain
fruit_prices = {'apple': 0.40, 'orange': 0.35, 'banana': 0.25}
vegetable_prices = {'pepper': 0.20, 'onion': 0.55, 'tomato': 0.42}
for item in chain(fruit_prices.items(), vegetable_prices.items()):
    print(item)
## ('apple', 0.4)
## ('orange', 0.35)
## ('banana', 0.25)
## ('pepper', 0.2)
## ('onion', 0.55)
## ('tomato', 0.42)

Using the Dictionary Unpacking Operator (**)

Suppose you have two (or more) dictionaries, and you need to iterate through them together, without using collections.ChainMap or itertools.chain(), as you’ve seen in the previous sections. In this case, you can use the dictionary unpacking operator (**) to merge the two dictionaries into a new one and then iterate through it:

fruit_prices = {'apple': 0.40, 'orange': 0.35}
vegetable_prices = {'pepper': 0.20, 'onion': 0.55}
# How to use the unpacking operator **
{**vegetable_prices, **fruit_prices}

# You can use this feature to iterate through multiple dictionaries
## {'pepper': 0.2, 'onion': 0.55, 'apple': 0.4, 'orange': 0.35}
for k, v in {**vegetable_prices, **fruit_prices}.items():
    print(k, '->', v)
## pepper -> 0.2
## onion -> 0.55
## apple -> 0.4
## orange -> 0.35

It’s important to note that if the dictionaries you’re trying to merge have repeated or common keys, then the values of the right-most dictionary will prevail

Handling Missing Keys in Dictionaries

a_dict = {}

a_dict.setdefault('missing_key', 'default value')
## 'default value'
a_dict['missing_key']
## 'default value'
a_dict.setdefault('missing_key', 'another default value')
## 'default value'
a_dict
## {'missing_key': 'default value'}

In the above code, you use .setdefault() to generate a default value for missing_key. Notice that your dictionary, a_dict, now has a new key called missing_key whose value is ‘default value’. This key didn’t exist before you called .setdefault(). Finally, if you call .setdefault() on an existing key, then the call won’t have any effect on the dictionary. Your key will hold the original value instead of the new default value.

defaultdict Type for Handling Missing Keys

The Python standard library provides collections, which is a module that implements specialized container types. One of those is the Python defaultdict type, which is an alternative to dict that’s specifically designed to help you out with missing keys. defaultdict is a Python type that inherits from dict:hug

The Python defaultdict type behaves almost exactly like a regular Python dictionary, but if you try to access or modify a missing key, then defaultdict will automatically create the key and generate a default value for it. This makes defaultdict a valuable option for handling missing keys in dictionaries.

a_dict = {}
a_dict['missing_key']

Sometimes, you’ll use a mutable built-in collection (a list, dict, or set) as values in your Python dictionaries. In these cases, you’ll need to initialize the keys before first use, or you’ll get a KeyError. You can either do this process manually or automate it using a Python defaultdict. In this section, you’ll learn how to use the Python defaultdict type for solving some common programming problems:

  • Grouping the items in a collection
  • Counting the items in a collection
  • Accumulating the values in a collection

You’ll be covering some examples that use list, set, int, and float to perform grouping, counting, and accumulating operations in a user-friendly and efficient way.

Grouping items

Grouping Items A typical use of the Python defaultdict type is to set .default_factory to list and then build a dictionary that maps keys to lists of values. With this defaultdict, if you try to get access to any missing key, then the dictionary runs the following steps:

Call list() to create a new empty list Insert the empty list into the dictionary using the missing key as key Return a reference to that list This allows you to write code like this:

from collections import defaultdict
dd = defaultdict(list)
dd['key'].append(1)
dd
## defaultdict(<class 'list'>, {'key': [1]})
dd['key'].append(2)
dd
## defaultdict(<class 'list'>, {'key': [1, 2]})
dd['key'].append(3)
dd
## defaultdict(<class 'list'>, {'key': [1, 2, 3]})

You can use defaultdict along with list to group the items in a sequence or a collection. Suppose that you’ve retrieved the following data from your company’s database:


import pandas as pd

d = {'Department': ['Sales', 'Sales', 'Accounting', 'Marketing', 'Marketing'], 'Employee Name': ['John Doe', 'Martin Smith', 'Jane Doe', 'Elizabeth Smith', 'Adam Doe']}

df = pd.DataFrame(data=d)

df
##    Department    Employee Name
## 0       Sales         John Doe
## 1       Sales     Martin Smith
## 2  Accounting         Jane Doe
## 3   Marketing  Elizabeth Smith
## 4   Marketing         Adam Doe

With this data, you create an initial list of tuple objects like the following:

dep = [('Sales', 'John Doe'),
       ('Sales', 'Martin Smith'),
       ('Accounting', 'Jane Doe'),
       ('Marketing', 'Elizabeth Smith'),
       ('Marketing', 'Adam Doe')]

Now, you need to create a dictionary that groups the employees by department. To do this, you can use a defaultdict as follows:

from collections import defaultdict

dep_dd = defaultdict(list)
for department, employee in dep:
    dep_dd[department].append(employee)

dep_dd
## defaultdict(<class 'list'>, {'Sales': ['John Doe', 'Martin Smith'], 'Accounting': ['Jane Doe'], 'Marketing': ['Elizabeth Smith', 'Adam Doe']})

Here, you create a defaultdict called dep_dd and use a for loop to iterate through your dep list. The statement dep_dd[department].append(employee) creates the keys for the departments, initializes them to an empty list, and then appends the employees to each department. Once you run this code, your dep_dd will look something like this:

In this example, you group the employees by their department using a defaultdict with .default_factory set to list. To do this with a regular dictionary, you can use dict.setdefault() as follows:

dep_d = dict()
for department, employee in dep:
    dep_d.setdefault(department, []).append(employee)

This code is straightforward, and you’ll find similar code quite often in your work as a Python coder. However, the defaultdict version is arguably more readable, and for large datasets, it can also be a lot faster and more efficient. So, if speed is a concern for you, then you should consider using a defaultdict instead of a standard dict.

More info on defaultdict can be found here

LS0tDQp0aXRsZTogIlN0dWR5aW5nIFB5dGhvbiBQdCAyIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMg0KICAgIHRvY19mbG9hdDogeWVzDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoZWFkZXIuaHRtbA0KLS0tDQoNCiMgRGljdGlvbmFyaWVzDQoNCmBgYHtweXRob259DQphX2RpY3QgPSB7J2NvbG9yJzogJ2JsdWUnLCAnZnJ1aXQnOiAnYXBwbGUnLCAncGV0JzogJ2RvZyd9DQpmb3Iga2V5IGluIGFfZGljdDoNCiAgICBwcmludChrZXkpDQoNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZm9yIGtleSBpbiBhX2RpY3Q6DQogICAgcHJpbnQoa2V5LCAnLT4nLCBhX2RpY3Rba2V5XSkNCg0KYGBgDQpUaGUgcHJlY2VkaW5nIGNvZGUgYWxsb3dlZCB5b3UgdG8gZ2V0IGFjY2VzcyB0byB0aGUga2V5cyAoa2V5KSBhbmQgdGhlIHZhbHVlcyAoYV9kaWN0W2tleV0pIG9mIGFfZGljdCBhdCB0aGUgc2FtZSB0aW1lLiBUaGlzIHdheSwgeW91IGNhbiBkbyBhbnkgb3BlcmF0aW9uIHdpdGggYm90aCB0aGUga2V5cyBhbmQgdGhlIHZhbHVlcy4NCg0KIyMgSXRlcmF0aW5nIFRocm91Z2ggLml0ZW1zKCkNCg0KV2hlbiB5b3XigJlyZSB3b3JraW5nIHdpdGggZGljdGlvbmFyaWVzLCBpdOKAmXMgbGlrZWx5IHRoYXQgeW914oCZbGwgd2FudCB0byB3b3JrIHdpdGggYm90aCB0aGUga2V5cyBhbmQgdGhlIHZhbHVlcy4gT25lIG9mIHRoZSBtb3N0IHVzZWZ1bCB3YXlzIHRvIGl0ZXJhdGUgdGhyb3VnaCBhIGRpY3Rpb25hcnkgaW4gUHl0aG9uIGlzIGJ5IHVzaW5nIC5pdGVtcygpLCB3aGljaCBpcyBhIG1ldGhvZCB0aGF0IHJldHVybnMgYSBuZXcgdmlldyBvZiB0aGUgZGljdGlvbmFyeeKAmXMgaXRlbXM6DQoNCmBgYHtweXRob259DQphX2RpY3QgPSB7J2NvbG9yJzogJ2JsdWUnLCAnZnJ1aXQnOiAnYXBwbGUnLCAncGV0JzogJ2RvZyd9DQpkX2l0ZW1zID0gYV9kaWN0Lml0ZW1zKCkNCmRfaXRlbXMgICMgSGVyZSBkX2l0ZW1zIGlzIGEgdmlldyBvZiBpdGVtcw0KYGBgDQoNCkRpY3Rpb25hcnkgdmlld3MgbGlrZSBkX2l0ZW1zIHByb3ZpZGUgYSBkeW5hbWljIHZpZXcgb24gdGhlIGRpY3Rpb25hcnnigJlzIGVudHJpZXMsIHdoaWNoIG1lYW5zIHRoYXQgd2hlbiB0aGUgZGljdGlvbmFyeSBjaGFuZ2VzLCB0aGUgdmlld3MgcmVmbGVjdCB0aGVzZSBjaGFuZ2VzLg0KDQpWaWV3cyBjYW4gYmUgaXRlcmF0ZWQgb3ZlciB0byB5aWVsZCB0aGVpciByZXNwZWN0aXZlIGRhdGEsIHNvIHlvdSBjYW4gaXRlcmF0ZSB0aHJvdWdoIGEgZGljdGlvbmFyeSBpbiBQeXRob24gYnkgdXNpbmcgdGhlIHZpZXcgb2JqZWN0IHJldHVybmVkIGJ5IC5pdGVtcygpOg0KDQpgYGB7cHl0aG9ufQ0KZm9yIGl0ZW0gaW4gYV9kaWN0Lml0ZW1zKCk6DQogICAgcHJpbnQoaXRlbSkNCg0KYGBgDQoNClRoZSB2aWV3IG9iamVjdCByZXR1cm5lZCBieSAuaXRlbXMoKSB5aWVsZHMgdGhlIGtleS12YWx1ZSBwYWlycyBvbmUgYXQgYSB0aW1lIGFuZCBhbGxvd3MgeW91IHRvIGl0ZXJhdGUgdGhyb3VnaCBhIGRpY3Rpb25hcnkgaW4gUHl0aG9uLCBidXQgaW4gc3VjaCBhIHdheSB0aGF0IHlvdSBnZXQgYWNjZXNzIHRvIHRoZSBrZXlzIGFuZCB2YWx1ZXMgYXQgdGhlIHNhbWUgdGltZS4NCg0KSWYgeW91IHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgaW5kaXZpZHVhbCBpdGVtcyB5aWVsZGVkIGJ5IC5pdGVtcygpLCB5b3XigJlsbCBub3RpY2UgdGhhdCB0aGV54oCZcmUgcmVhbGx5IHR1cGxlIG9iamVjdHMuIExldOKAmXMgdGFrZSBhIGxvb2s6DQoNCmBgYHtweXRob259DQpmb3IgaXRlbSBpbiBhX2RpY3QuaXRlbXMoKToNCiAgICBwcmludChpdGVtKQ0KICAgIHByaW50KHR5cGUoaXRlbSkpDQoNCmBgYA0KDQpPbmNlIHlvdSBrbm93IHRoaXMsIHlvdSBjYW4gdXNlIHR1cGxlIHVucGFja2luZyB0byBpdGVyYXRlIHRocm91Z2ggdGhlIGtleXMgYW5kIHZhbHVlcyBvZiB0aGUgZGljdGlvbmFyeSB5b3UgYXJlIHdvcmtpbmcgd2l0aC4gVG8gYWNoaWV2ZSB0aGlzLCB5b3UganVzdCBuZWVkIHRvIHVucGFjayB0aGUgZWxlbWVudHMgb2YgZXZlcnkgaXRlbSBpbnRvIHR3byBkaWZmZXJlbnQgdmFyaWFibGVzIHJlcHJlc2VudGluZyB0aGUga2V5IGFuZCB0aGUgdmFsdWU6DQoNCmBgYHtweXRob259DQpmb3Iga2V5LCB2YWx1ZSBpbiBhX2RpY3QuaXRlbXMoKToNCiAgICBwcmludChrZXksICctPicsIHZhbHVlKQ0KDQpgYGANCg0KSGVyZSwgdGhlIHZhcmlhYmxlcyBrZXkgYW5kIHZhbHVlIGluIHRoZSBoZWFkZXIgb2YgeW91ciBmb3IgbG9vcCBkbyB0aGUgdW5wYWNraW5nLiBFdmVyeSB0aW1lIHRoZSBsb29wIHJ1bnMsIGtleSB3aWxsIHN0b3JlIHRoZSBrZXksIGFuZCB2YWx1ZSB3aWxsIHN0b3JlIHRoZSB2YWx1ZSBvZiB0aGUgaXRlbSB0aGF0IGlzIGJlZW4gcHJvY2Vzc2VkLiBUaGlzIHdheSwgeW914oCZbGwgaGF2ZSBtb3JlIGNvbnRyb2wgb3ZlciB0aGUgaXRlbXMgb2YgdGhlIGRpY3Rpb25hcnksIGFuZCB5b3XigJlsbCBiZSBhYmxlIHRvIHByb2Nlc3MgdGhlIGtleXMgYW5kIHZhbHVlcyBzZXBhcmF0ZWx5IGFuZCBpbiBhIHdheSB0aGF0IGlzIG1vcmUgcmVhZGFibGUgYW5kIFB5dGhvbmljLg0KDQpOb3RlOiBOb3RpY2UgdGhhdCAudmFsdWVzKCkgYW5kIC5rZXlzKCkgcmV0dXJuIHZpZXcgb2JqZWN0cyBqdXN0IGxpa2UgLml0ZW1zKCksIGFzIHlvdeKAmWxsIHNlZSBpbiB0aGUgbmV4dCB0d28gc2VjdGlvbnMuDQoNCllvdSBjYW4gYWxzbyB1c2UgLmtleXMoKSBhbmQgLnZhbHVlcygpIG1ldGhvZHMgdG8gcmV0dXJuIHRoZSBjYWxsZWQgaXRlbXMuDQoNCkl04oCZcyB3b3J0aCBub3RpbmcgdGhhdCB0aGV5IGFsc28gc3VwcG9ydCBtZW1iZXJzaGlwIHRlc3RzIChpbiksIHdoaWNoIGlzIGFuIGltcG9ydGFudCBmZWF0dXJlIGlmIHlvdeKAmXJlIHRyeWluZyB0byBrbm93IGlmIGEgc3BlY2lmaWMgZWxlbWVudCBpcyBpbiBhIGRpY3Rpb25hcnkgb3Igbm90Og0KDQpgYGB7cHl0aG9ufQ0KYV9kaWN0ID0geydjb2xvcic6ICdibHVlJywgJ2ZydWl0JzogJ2FwcGxlJywgJ3BldCc6ICdkb2cnfQ0KJ3BldCcgaW4gYV9kaWN0LmtleXMoKQ0KDQonYXBwbGUnIGluIGFfZGljdC52YWx1ZXMoKQ0KDQonb25pb24nIGluIGFfZGljdC52YWx1ZXMoKQ0KDQonY29sb3InIGluIGFfZGljdC52YWx1ZXMoKQ0KYGBgDQojIyBNb2RpZnlpbmcgdmFsdWVzIGFuZCBrZXlzDQoNClRoZSB2YWx1ZXMsIGZvciBleGFtcGxlLCBjYW4gYmUgbW9kaWZpZWQgd2hlbmV2ZXIgeW91IG5lZWQsIGJ1dCB5b3XigJlsbCBuZWVkIHRvIHVzZSB0aGUgb3JpZ2luYWwgZGljdGlvbmFyeSBhbmQgdGhlIGtleSB0aGF0IG1hcHMgdGhlIHZhbHVlIHlvdSB3YW50IHRvIG1vZGlmeToNCg0KYGBge3B5dGhvbn0NCnByaWNlcyA9IHsnYXBwbGUnOiAwLjQwLCAnb3JhbmdlJzogMC4zNSwgJ2JhbmFuYSc6IDAuMjV9DQpmb3IgaywgdiBpbiBwcmljZXMuaXRlbXMoKToNCiAgICBwcmljZXNba10gPSByb3VuZCh2ICogMC45LCAyKSAgIyBBcHBseSBhIDEwJSBkaXNjb3VudA0KDQpwcmljZXMNCmBgYA0KDQojIyBEaWN0aW9uYXJ5IGNvbXByZWhlbnNpb24NCg0KU3VwcG9zZSwgZm9yIGV4YW1wbGUsIHRoYXQgeW91IGhhdmUgdHdvIGxpc3RzIG9mIGRhdGEsIGFuZCB5b3UgbmVlZCB0byBjcmVhdGUgYSBuZXcgZGljdGlvbmFyeSBmcm9tIHRoZW0uIEluIHRoaXMgY2FzZSwgeW91IGNhbiB1c2UgUHl0aG9u4oCZcyB6aXAoKml0ZXJhYmxlcykgdG8gbG9vcCBvdmVyIHRoZSBlbGVtZW50cyBvZiBib3RoIGxpc3RzIGluIHBhaXJzOg0KDQpgYGB7cHl0aG9ufQ0Kb2JqZWN0cyA9IFsnYmx1ZScsICdhcHBsZScsICdkb2cnXQ0KY2F0ZWdvcmllcyA9IFsnY29sb3InLCAnZnJ1aXQnLCAncGV0J10NCmFfZGljdCA9IHtrZXk6IHZhbHVlIGZvciBrZXksIHZhbHVlIGluIHppcChjYXRlZ29yaWVzLCBvYmplY3RzKX0NCmFfZGljdA0KYGBgDQoNCkhlcmUsIHppcCgpIHJlY2VpdmVzIHR3byBpdGVyYWJsZXMgKGNhdGVnb3JpZXMgYW5kIG9iamVjdHMpIGFzIGFyZ3VtZW50cyBhbmQgbWFrZXMgYW4gaXRlcmF0b3IgdGhhdCBhZ2dyZWdhdGVzIGVsZW1lbnRzIGZyb20gZWFjaCBpdGVyYWJsZS4gVGhlIHR1cGxlIG9iamVjdHMgZ2VuZXJhdGVkIGJ5IHppcCgpIGFyZSB0aGVuIHVucGFja2VkIGludG8ga2V5IGFuZCB2YWx1ZSwgd2hpY2ggYXJlIGZpbmFsbHkgdXNlZCB0byBjcmVhdGUgdGhlIG5ldyBkaWN0aW9uYXJ5Lg0KDQojIyBUdXJuaW5nIGtleXMgaW50byB2YWx1ZXMgYW5kIHZpY2UgdmVyc2ENCg0KYGBge3B5dGhvbn0NCmFfZGljdCA9IHsnb25lJzogMSwgJ3R3byc6IDIsICd0aGVlJzogMywgJ2ZvdXInOiA0fQ0KbmV3X2RpY3QgPSB7dmFsdWU6IGtleSBmb3Iga2V5LCB2YWx1ZSBpbiBhX2RpY3QuaXRlbXMoKX0NCm5ld19kaWN0DQoNCmBgYA0KDQojIyBGaWx0ZXJpbmcgaXRlbXMNCg0KVG8gZmlsdGVyIHRoZSBpdGVtcyBpbiBhIGRpY3Rpb25hcnkgd2l0aCBhIGNvbXByZWhlbnNpb24sIHlvdSBqdXN0IG5lZWQgdG8gYWRkIGFuIGlmIGNsYXVzZSB0aGF0IGRlZmluZXMgdGhlIGNvbmRpdGlvbiB5b3Ugd2FudCB0byBtZWV0LiANCg0KYGBge3B5dGhvbn0NCmFfZGljdCA9IHsnb25lJzogMSwgJ3R3byc6IDIsICd0aGVlJzogMywgJ2ZvdXInOiA0fQ0KbmV3X2RpY3QgPSB7azogdiBmb3IgaywgdiBpbiBhX2RpY3QuaXRlbXMoKSBpZiB2IDw9IDJ9DQpuZXdfZGljdA0KYGBgDQoNCiMjIERvaW5nIHNvbWUgY2FsY3VsYXRpb25zDQoNCklmIHlvdSB1c2UgYSBsaXN0IGNvbXByZWhlbnNpb24gdG8gaXRlcmF0ZSB0aHJvdWdoIHRoZSBkaWN0aW9uYXJ54oCZcyB2YWx1ZXMsIHRoZW4geW914oCZbGwgZ2V0IGNvZGUgdGhhdCBpcyBtb3JlIGNvbXBhY3QsIGZhc3QsIGFuZCBQeXRob25pYzoNCiANCmBgYHtweXRob259DQppbmNvbWVzID0geydhcHBsZSc6IDU2MDAuMDAsICdvcmFuZ2UnOiAzNTAwLjAwLCAnYmFuYW5hJzogNTAwMC4wMH0NCnRvdGFsX2luY29tZSA9IHN1bShbdmFsdWUgZm9yIHZhbHVlIGluIGluY29tZXMudmFsdWVzKCldKQ0KdG90YWxfaW5jb21lDQpgYGANCg0KVGhlIGxpc3QgY29tcHJlaGVuc2lvbiBjcmVhdGVkIGEgbGlzdCBvYmplY3QgY29udGFpbmluZyB0aGUgdmFsdWVzIG9mIGluY29tZXMsIGFuZCB0aGVuIHlvdSBzdW1tZWQgdXAgYWxsIG9mIHRoZW0gYnkgdXNpbmcgc3VtKCkgYW5kIHN0b3JlZCB0aGUgcmVzdWx0IGluIHRvdGFsX2luY29tZS4NCg0KSWYgeW914oCZcmUgd29ya2luZyB3aXRoIGEgcmVhbGx5IGxhcmdlIGRpY3Rpb25hcnksIGFuZCBtZW1vcnkgdXNhZ2UgaXMgYSBwcm9ibGVtIGZvciB5b3UsIHRoZW4geW91IGNhbiB1c2UgYSBnZW5lcmF0b3IgZXhwcmVzc2lvbiBpbnN0ZWFkIG9mIGEgbGlzdCBjb21wcmVoZW5zaW9uLiBBIGdlbmVyYXRvciBleHByZXNzaW9uIGlzIGFuIGV4cHJlc3Npb24gdGhhdCByZXR1cm5zIGFuIGl0ZXJhdG9yLiBJdCBsb29rcyBsaWtlIGEgbGlzdCBjb21wcmVoZW5zaW9uLCBidXQgaW5zdGVhZCBvZiBicmFja2V0cyB5b3UgbmVlZCB0byB1c2UgcGFyZW50aGVzZXMgdG8gZGVmaW5lIGl0Og0KDQpgYGB7cHl0aG9ufQ0KdG90YWxfaW5jb21lID0gc3VtKHZhbHVlIGZvciB2YWx1ZSBpbiBpbmNvbWVzLnZhbHVlcygpKQ0KdG90YWxfaW5jb21lDQpgYGANCg0KSWYgeW91IGNoYW5nZSB0aGUgc3F1YXJlIGJyYWNrZXRzIGZvciBhIHBhaXIgb2YgcGFyZW50aGVzZXMgKHRoZSBwYXJlbnRoZXNlcyBvZiBzdW0oKSBoZXJlKSwgeW914oCZbGwgYmUgdHVybmluZyB0aGUgbGlzdCBjb21wcmVoZW5zaW9uIGludG8gYSBnZW5lcmF0b3IgZXhwcmVzc2lvbiwgYW5kIHlvdXIgY29kZSB3aWxsIGJlIG1lbW9yeSBlZmZpY2llbnQsIGJlY2F1c2UgZ2VuZXJhdG9yIGV4cHJlc3Npb25zIHlpZWxkIGVsZW1lbnRzIG9uIGRlbWFuZC4gSW5zdGVhZCBvZiBjcmVhdGluZyBhbmQgc3RvcmluZyB0aGUgd2hvbGUgbGlzdCBpbiBtZW1vcnksIHlvdeKAmWxsIG9ubHkgaGF2ZSB0byBzdG9yZSBvbmUgZWxlbWVudCBhdCBhIHRpbWUuDQoNCkZpbmFsbHksIHRoZXJlIGlzIGEgc2ltcGxlciB3YXkgdG8gc29sdmUgdGhpcyBwcm9ibGVtIGJ5IGp1c3QgdXNpbmcgaW5jb21lcy52YWx1ZXMoKSBkaXJlY3RseSBhcyBhbiBhcmd1bWVudCB0byBzdW0oKToNCg0KYGBge3B5dGhvbn0NCnRvdGFsX2luY29tZSA9IHN1bShpbmNvbWVzLnZhbHVlcygpKQ0KdG90YWxfaW5jb21lDQpgYGANCg0KIyMgUmVtb3Zpbmcgc3BlY2lmaWMgaXRlbXMNCg0KTm93LCBzdXBwb3NlIHlvdSBoYXZlIGEgZGljdGlvbmFyeSBhbmQgbmVlZCB0byBjcmVhdGUgYSBuZXcgb25lIHdpdGggc2VsZWN0ZWQga2V5cyByZW1vdmVkLiBSZW1lbWJlciBob3cga2V5LXZpZXcgb2JqZWN0cyBhcmUgbGlrZSBzZXRzPyBXZWxsLCB0aGVzZSBzaW1pbGFyaXRpZXMgZ28gYmV5b25kIGp1c3QgYmVpbmcgY29sbGVjdGlvbnMgb2YgaGFzaGFibGUgYW5kIHVuaXF1ZSBvYmplY3RzLiBLZXktdmlldyBvYmplY3RzIGFsc28gc3VwcG9ydCBjb21tb24gc2V0IG9wZXJhdGlvbnMuIExldOKAmXMgc2VlIGhvdyB5b3UgY2FuIHRha2UgYWR2YW50YWdlIG9mIHRoaXMgdG8gcmVtb3ZlIHNwZWNpZmljIGl0ZW1zIGluIGEgZGljdGlvbmFyeToNCg0KYGBge3B5dGhvbn0NCmluY29tZXMgPSB7J2FwcGxlJzogNTYwMC4wMCwgJ29yYW5nZSc6IDM1MDAuMDAsICdiYW5hbmEnOiA1MDAwLjAwfQ0Kbm9uX2NpdHJpYyA9IHtrOiBpbmNvbWVzW2tdIGZvciBrIGluIGluY29tZXMua2V5cygpIC0geydvcmFuZ2UnfX0NCm5vbl9jaXRyaWMNCmBgYA0KDQpUaGlzIGNvZGUgd29ya3MgYmVjYXVzZSBrZXktdmlldyBvYmplY3RzIHN1cHBvcnQgc2V0IG9wZXJhdGlvbnMgbGlrZSB1bmlvbnMsIGludGVyc2VjdGlvbnMsIGFuZCBkaWZmZXJlbmNlcy4gV2hlbiB5b3Ugd3JvdGUgaW5jb21lcy5rZXlzKCkgLSB7J29yYW5nZSd9IGluc2lkZSB0aGUgZGljdGlvbmFyeSBjb21wcmVoZW5zaW9uLCB5b3Ugd2VyZSByZWFsbHkgZG9pbmcgYSBzZXQgZGlmZmVyZW5jZSBvcGVyYXRpb24uIElmIHlvdSBuZWVkIHRvIHBlcmZvcm0gYW55IHNldCBvcGVyYXRpb25zIHdpdGggdGhlIGtleXMgb2YgYSBkaWN0aW9uYXJ5LCB0aGVuIHlvdSBjYW4ganVzdCB1c2UgdGhlIGtleS12aWV3IG9iamVjdCBkaXJlY3RseSB3aXRob3V0IGZpcnN0IGNvbnZlcnRpbmcgaXQgaW50byBhIHNldC4gVGhpcyBpcyBhIGxpdHRsZS1rbm93biBmZWF0dXJlIG9mIGtleS12aWV3IG9iamVjdHMgdGhhdCBjYW4gYmUgdXNlZnVsIGluIHNvbWUgc2l0dWF0aW9ucy4NCg0KIyMgU29ydGluZyBhIERpY3Rpb25hcnkNCg0KSXTigJlzIG9mdGVuIG5lY2Vzc2FyeSB0byBzb3J0IHRoZSBlbGVtZW50cyBvZiBhIGNvbGxlY3Rpb24uIFNpbmNlIFB5dGhvbiAzLjYsIGRpY3Rpb25hcmllcyBhcmUgb3JkZXJlZCBkYXRhIHN0cnVjdHVyZXMsIHNvIGlmIHlvdSB1c2UgUHl0aG9uIDMuNiAoYW5kIGJleW9uZCksIHlvdeKAmWxsIGJlIGFibGUgdG8gc29ydCB0aGUgaXRlbXMgb2YgYW55IGRpY3Rpb25hcnkgYnkgdXNpbmcgc29ydGVkKCkgYW5kIHdpdGggdGhlIGhlbHAgb2YgYSBkaWN0aW9uYXJ5IGNvbXByZWhlbnNpb246DQoNCmBgYHtweXRob259DQppbmNvbWVzID0geydhcHBsZSc6IDU2MDAuMDAsICdvcmFuZ2UnOiAzNTAwLjAwLCAnYmFuYW5hJzogNTAwMC4wMH0NCnNvcnRlZF9pbmNvbWUgPSB7azogaW5jb21lc1trXSBmb3IgayBpbiBzb3J0ZWQoaW5jb21lcyl9DQpzb3J0ZWRfaW5jb21lDQpgYGANCg0KVGhpcyBjb2RlIGFsbG93cyB5b3UgdG8gY3JlYXRlIGEgbmV3IGRpY3Rpb25hcnkgd2l0aCBpdHMga2V5cyBpbiBzb3J0ZWQgb3JkZXIuIFRoaXMgaXMgcG9zc2libGUgYmVjYXVzZSBzb3J0ZWQoaW5jb21lcykgcmV0dXJucyBhIGxpc3Qgb2Ygc29ydGVkIGtleXMgdGhhdCB5b3UgY2FuIHVzZSB0byBnZW5lcmF0ZSB0aGUgbmV3IGRpY3Rpb25hcnkgc29ydGVkX2RpY3QuDQoNCiMjIFNvcnRlZCBieSB2YWx1ZXMNCg0KYGBge3B5dGhvbn0NCmZvciB2YWx1ZSBpbiBzb3J0ZWQoaW5jb21lcy52YWx1ZXMoKSk6DQogICAgcHJpbnQodmFsdWUpDQoNCmBgYA0KDQojIyBVc2luZyBTb21lIG9mIFB5dGhvbuKAmXMgQnVpbHQtSW4gRnVuY3Rpb25zDQoNCiMjIyBtYXAoKQ0KDQpQeXRob27igJlzIG1hcCgpIGlzIGRlZmluZWQgYXMgbWFwKGZ1bmN0aW9uLCBpdGVyYWJsZSwgLi4uKSBhbmQgcmV0dXJucyBhbiBpdGVyYXRvciB0aGF0IGFwcGxpZXMgZnVuY3Rpb24gdG8gZXZlcnkgaXRlbSBvZiBpdGVyYWJsZSwgeWllbGRpbmcgdGhlIHJlc3VsdHMgb24gZGVtYW5kLiBTbywgbWFwKCkgY291bGQgYmUgdmlld2VkIGFzIGFuIGl0ZXJhdGlvbiB0b29sIHRoYXQgeW91IGNhbiB1c2UgdG8gaXRlcmF0ZSB0aHJvdWdoIGEgZGljdGlvbmFyeSBpbiBQeXRob24uDQoNClN1cHBvc2UgeW91IGhhdmUgYSBkaWN0aW9uYXJ5IGNvbnRhaW5pbmcgdGhlIHByaWNlcyBvZiBhIGJ1bmNoIG9mIHByb2R1Y3RzLCBhbmQgeW91IG5lZWQgdG8gYXBwbHkgYSBkaXNjb3VudCB0byB0aGVtLiBJbiB0aGlzIGNhc2UsIHlvdSBjYW4gZGVmaW5lIGEgZnVuY3Rpb24gdGhhdCBtYW5hZ2VzIHRoZSBkaXNjb3VudCBhbmQgdGhlbiB1c2VzIGl0IGFzIHRoZSBmaXJzdCBhcmd1bWVudCB0byBtYXAoKS4gVGhlIHNlY29uZCBhcmd1bWVudCBjYW4gYmUgcHJpY2VzLml0ZW1zKCk6DQoNCmBgYHtweXRob259DQpwcmljZXMgPSB7J2FwcGxlJzogMC40MCwgJ29yYW5nZSc6IDAuMzUsICdiYW5hbmEnOiAwLjI1fQ0KZGVmIGRpc2NvdW50KGN1cnJlbnRfcHJpY2UpOg0KICAgIHJldHVybiAoY3VycmVudF9wcmljZVswXSwgcm91bmQoY3VycmVudF9wcmljZVsxXSAqIDAuOTUsIDIpKQ0KDQpuZXdfcHJpY2VzID0gZGljdChtYXAoZGlzY291bnQsIHByaWNlcy5pdGVtcygpKSkNCm5ld19wcmljZXMNCg0KYGBgDQoNCiMjIyBmaWx0ZXIoKQ0KDQpmaWx0ZXIoKSBpcyBhbm90aGVyIGJ1aWx0LWluIGZ1bmN0aW9uIHRoYXQgeW91IGNhbiB1c2UgdG8gaXRlcmF0ZSB0aHJvdWdoIGEgZGljdGlvbmFyeSBpbiBQeXRob24gYW5kIGZpbHRlciBvdXQgc29tZSBvZiBpdHMgaXRlbXMuIFRoaXMgZnVuY3Rpb24gaXMgZGVmaW5lZCBhcyBmaWx0ZXIoZnVuY3Rpb24sIGl0ZXJhYmxlKSBhbmQgcmV0dXJucyBhbiBpdGVyYXRvciBmcm9tIHRob3NlIGVsZW1lbnRzIG9mIGl0ZXJhYmxlIGZvciB3aGljaCBmdW5jdGlvbiByZXR1cm5zIFRydWUuDQoNClN1cHBvc2UgeW91IHdhbnQgdG8ga25vdyB0aGUgcHJvZHVjdHMgd2l0aCBhIHByaWNlIGxvd2VyIHRoYW4gMC40MC4gWW91IG5lZWQgdG8gZGVmaW5lIGEgZnVuY3Rpb24gdG8gZGV0ZXJtaW5lIGlmIHRoZSBwcmljZSBzYXRpc2ZpZXMgdGhhdCBjb25kaXRpb24gYW5kIHBhc3MgaXQgYXMgZmlyc3QgYXJndW1lbnQgdG8gZmlsdGVyKCkuIFRoZSBzZWNvbmQgYXJndW1lbnQgY2FuIGJlIHByaWNlcy5rZXlzKCk6DQoNCmBgYHtweXRob259DQpwcmljZXMgPSB7J2FwcGxlJzogMC40MCwgJ29yYW5nZSc6IDAuMzUsICdiYW5hbmEnOiAwLjI1fQ0KZGVmIGhhc19sb3dfcHJpY2UocHJpY2UpOg0KICAgIHJldHVybiBwcmljZXNbcHJpY2VdIDwgMC40DQoNCmxvd19wcmljZSA9IGxpc3QoZmlsdGVyKGhhc19sb3dfcHJpY2UsIHByaWNlcy5rZXlzKCkpKQ0KbG93X3ByaWNlDQpgYGANCg0KIyMjIFVzaW5nIGNvbGxlY3Rpb25zLkNoYWluTWFwDQoNCmNvbGxlY3Rpb25zIGlzIGEgdXNlZnVsIG1vZHVsZSBmcm9tIHRoZSBQeXRob24gU3RhbmRhcmQgTGlicmFyeSB0aGF0IHByb3ZpZGVzIHNwZWNpYWxpemVkIGNvbnRhaW5lciBkYXRhIHR5cGVzLiBPbmUgb2YgdGhlc2UgZGF0YSB0eXBlcyBpcyBDaGFpbk1hcCwgd2hpY2ggaXMgYSBkaWN0aW9uYXJ5LWxpa2UgY2xhc3MgZm9yIGNyZWF0aW5nIGEgc2luZ2xlIHZpZXcgb2YgbXVsdGlwbGUgbWFwcGluZ3MgKGxpa2UgZGljdGlvbmFyaWVzKS4gV2l0aCBDaGFpbk1hcCwgeW91IGNhbiBncm91cCBtdWx0aXBsZSBkaWN0aW9uYXJpZXMgdG9nZXRoZXIgdG8gY3JlYXRlIGEgc2luZ2xlLCB1cGRhdGVhYmxlIHZpZXcuDQoNCk5vdywgc3VwcG9zZSB5b3UgaGF2ZSB0d28gKG9yIG1vcmUpIGRpY3Rpb25hcmllcywgYW5kIHlvdSBuZWVkIHRvIGl0ZXJhdGUgdGhyb3VnaCB0aGVtIHRvZ2V0aGVyIGFzIG9uZS4gVG8gYWNoaWV2ZSB0aGlzLCB5b3UgY2FuIGNyZWF0ZSBhIENoYWluTWFwIG9iamVjdCBhbmQgaW5pdGlhbGl6ZSBpdCB3aXRoIHlvdXIgZGljdGlvbmFyaWVzOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ2hhaW5NYXANCmZydWl0X3ByaWNlcyA9IHsnYXBwbGUnOiAwLjQwLCAnb3JhbmdlJzogMC4zNX0NCnZlZ2V0YWJsZV9wcmljZXMgPSB7J3BlcHBlcic6IDAuMjAsICdvbmlvbic6IDAuNTV9DQpjaGFpbmVkX2RpY3QgPSBDaGFpbk1hcChmcnVpdF9wcmljZXMsIHZlZ2V0YWJsZV9wcmljZXMpDQpjaGFpbmVkX2RpY3QgICMgQSBDaGFpbk1hcCBvYmplY3QNCg0KZm9yIGtleSBpbiBjaGFpbmVkX2RpY3Q6DQogICAgcHJpbnQoa2V5LCAnLT4nLCBjaGFpbmVkX2RpY3Rba2V5XSkNCg0KYGBgDQoNCkFmdGVyIGltcG9ydGluZyBDaGFpbk1hcCBmcm9tIGNvbGxlY3Rpb25zLCB5b3UgbmVlZCB0byBjcmVhdGUgYSBDaGFpbk1hcCBvYmplY3Qgd2l0aCB0aGUgZGljdGlvbmFyaWVzIHlvdSB3YW50IHRvIGNoYWluLCBhbmQgdGhlbiB5b3UgY2FuIGZyZWVseSBpdGVyYXRlIHRocm91Z2ggdGhlIHJlc3VsdGluZyBvYmplY3QgYXMgeW91IHdvdWxkIGRvIHdpdGggYSByZWd1bGFyIGRpY3Rpb25hcnkuDQoNCkNoYWluTWFwIG9iamVjdHMgYWxzbyBpbXBsZW1lbnQgLmtleXMoKSwgdmFsdWVzKCksIGFuZCAuaXRlbXMoKSBhcyBhIHN0YW5kYXJkIGRpY3Rpb25hcnkgZG9lcywgc28geW91IGNhbiB1c2UgdGhlc2UgbWV0aG9kcyB0byBpdGVyYXRlIHRocm91Z2ggdGhlIGRpY3Rpb25hcnktbGlrZSBvYmplY3QgZ2VuZXJhdGVkIGJ5IENoYWluTWFwLCBqdXN0IGxpa2UgeW91IHdvdWxkIGRvIHdpdGggYSByZWd1bGFyIGRpY3Rpb25hcnk6DQoNCmBgYHtweXRob259DQpmb3Iga2V5LCB2YWx1ZSBpbiBjaGFpbmVkX2RpY3QuaXRlbXMoKToNCiAgICBwcmludChrZXksICctPicsIHZhbHVlKQ0KDQpgYGANCg0KIyMgVXNpbmcgaXRlcnRvb2xzDQoNCiMjIyBDaGFpbmVkIGludGVyYXRpb24gd2l0aCBjaGFpbigpDQoNCkNoYWluZWQgSXRlcmF0aW9uIFdpdGggY2hhaW4oKQ0KaXRlcnRvb2xzIGFsc28gcHJvdmlkZXMgY2hhaW4oKml0ZXJhYmxlcyksIHdoaWNoIGdldHMgc29tZSBpdGVyYWJsZXMgYXMgYXJndW1lbnRzIGFuZCBtYWtlcyBhbiBpdGVyYXRvciB0aGF0IHlpZWxkcyBlbGVtZW50cyBmcm9tIHRoZSBmaXJzdCBpdGVyYWJsZSB1bnRpbCBpdOKAmXMgZXhoYXVzdGVkLCB0aGVuIGl0ZXJhdGVzIG92ZXIgdGhlIG5leHQgaXRlcmFibGUgYW5kIHNvIG9uLCB1bnRpbCBhbGwgb2YgdGhlbSBhcmUgZXhoYXVzdGVkLg0KDQpUaGlzIGFsbG93cyB5b3UgdG8gaXRlcmF0ZSB0aHJvdWdoIG11bHRpcGxlIGRpY3Rpb25hcmllcyBpbiBhIGNoYWluLCBsaWtlIHRvIHdoYXQgeW91IGRpZCB3aXRoIGNvbGxlY3Rpb25zLkNoYWluTWFwOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBpdGVydG9vbHMgaW1wb3J0IGNoYWluDQpmcnVpdF9wcmljZXMgPSB7J2FwcGxlJzogMC40MCwgJ29yYW5nZSc6IDAuMzUsICdiYW5hbmEnOiAwLjI1fQ0KdmVnZXRhYmxlX3ByaWNlcyA9IHsncGVwcGVyJzogMC4yMCwgJ29uaW9uJzogMC41NSwgJ3RvbWF0byc6IDAuNDJ9DQpmb3IgaXRlbSBpbiBjaGFpbihmcnVpdF9wcmljZXMuaXRlbXMoKSwgdmVnZXRhYmxlX3ByaWNlcy5pdGVtcygpKToNCiAgICBwcmludChpdGVtKQ0KDQpgYGANCg0KIyMjIFVzaW5nIHRoZSBEaWN0aW9uYXJ5IFVucGFja2luZyBPcGVyYXRvciAoKiopDQoNClN1cHBvc2UgeW91IGhhdmUgdHdvIChvciBtb3JlKSBkaWN0aW9uYXJpZXMsIGFuZCB5b3UgbmVlZCB0byBpdGVyYXRlIHRocm91Z2ggdGhlbSB0b2dldGhlciwgd2l0aG91dCB1c2luZyBjb2xsZWN0aW9ucy5DaGFpbk1hcCBvciBpdGVydG9vbHMuY2hhaW4oKSwgYXMgeW914oCZdmUgc2VlbiBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbnMuIEluIHRoaXMgY2FzZSwgeW91IGNhbiB1c2UgdGhlIGRpY3Rpb25hcnkgdW5wYWNraW5nIG9wZXJhdG9yICgqKikgdG8gbWVyZ2UgdGhlIHR3byBkaWN0aW9uYXJpZXMgaW50byBhIG5ldyBvbmUgYW5kIHRoZW4gaXRlcmF0ZSB0aHJvdWdoIGl0Og0KDQpgYGB7cHl0aG9ufQ0KZnJ1aXRfcHJpY2VzID0geydhcHBsZSc6IDAuNDAsICdvcmFuZ2UnOiAwLjM1fQ0KdmVnZXRhYmxlX3ByaWNlcyA9IHsncGVwcGVyJzogMC4yMCwgJ29uaW9uJzogMC41NX0NCiMgSG93IHRvIHVzZSB0aGUgdW5wYWNraW5nIG9wZXJhdG9yICoqDQp7Kip2ZWdldGFibGVfcHJpY2VzLCAqKmZydWl0X3ByaWNlc30NCg0KIyBZb3UgY2FuIHVzZSB0aGlzIGZlYXR1cmUgdG8gaXRlcmF0ZSB0aHJvdWdoIG11bHRpcGxlIGRpY3Rpb25hcmllcw0KZm9yIGssIHYgaW4geyoqdmVnZXRhYmxlX3ByaWNlcywgKipmcnVpdF9wcmljZXN9Lml0ZW1zKCk6DQogICAgcHJpbnQoaywgJy0+JywgdikNCg0KYGBgDQoNCkl04oCZcyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IGlmIHRoZSBkaWN0aW9uYXJpZXMgeW914oCZcmUgdHJ5aW5nIHRvIG1lcmdlIGhhdmUgcmVwZWF0ZWQgb3IgY29tbW9uIGtleXMsIHRoZW4gdGhlIHZhbHVlcyBvZiB0aGUgcmlnaHQtbW9zdCBkaWN0aW9uYXJ5IHdpbGwgcHJldmFpbA0KDQoNCiMjIEhhbmRsaW5nIE1pc3NpbmcgS2V5cyBpbiBEaWN0aW9uYXJpZXMNCg0KYGBge3B5dGhvbn0NCmFfZGljdCA9IHt9DQoNCmFfZGljdC5zZXRkZWZhdWx0KCdtaXNzaW5nX2tleScsICdkZWZhdWx0IHZhbHVlJykNCg0KYV9kaWN0WydtaXNzaW5nX2tleSddDQoNCmFfZGljdC5zZXRkZWZhdWx0KCdtaXNzaW5nX2tleScsICdhbm90aGVyIGRlZmF1bHQgdmFsdWUnKQ0KDQphX2RpY3QNCmBgYA0KDQpJbiB0aGUgYWJvdmUgY29kZSwgeW91IHVzZSAuc2V0ZGVmYXVsdCgpIHRvIGdlbmVyYXRlIGEgZGVmYXVsdCB2YWx1ZSBmb3IgbWlzc2luZ19rZXkuIE5vdGljZSB0aGF0IHlvdXIgZGljdGlvbmFyeSwgYV9kaWN0LCBub3cgaGFzIGEgbmV3IGtleSBjYWxsZWQgbWlzc2luZ19rZXkgd2hvc2UgdmFsdWUgaXMgJ2RlZmF1bHQgdmFsdWUnLiBUaGlzIGtleSBkaWRu4oCZdCBleGlzdCBiZWZvcmUgeW91IGNhbGxlZCAuc2V0ZGVmYXVsdCgpLiBGaW5hbGx5LCBpZiB5b3UgY2FsbCAuc2V0ZGVmYXVsdCgpIG9uIGFuIGV4aXN0aW5nIGtleSwgdGhlbiB0aGUgY2FsbCB3b27igJl0IGhhdmUgYW55IGVmZmVjdCBvbiB0aGUgZGljdGlvbmFyeS4gWW91ciBrZXkgd2lsbCBob2xkIHRoZSBvcmlnaW5hbCB2YWx1ZSBpbnN0ZWFkIG9mIHRoZSBuZXcgZGVmYXVsdCB2YWx1ZS4NCg0KIyMgKmRlZmF1bHRkaWN0KiBUeXBlIGZvciBIYW5kbGluZyBNaXNzaW5nIEtleXMNCg0KVGhlIFB5dGhvbiBzdGFuZGFyZCBsaWJyYXJ5IHByb3ZpZGVzIGNvbGxlY3Rpb25zLCB3aGljaCBpcyBhIG1vZHVsZSB0aGF0IGltcGxlbWVudHMgc3BlY2lhbGl6ZWQgY29udGFpbmVyIHR5cGVzLiBPbmUgb2YgdGhvc2UgaXMgdGhlIFB5dGhvbiBkZWZhdWx0ZGljdCB0eXBlLCB3aGljaCBpcyBhbiBhbHRlcm5hdGl2ZSB0byBkaWN0IHRoYXTigJlzIHNwZWNpZmljYWxseSBkZXNpZ25lZCB0byBoZWxwIHlvdSBvdXQgd2l0aCBtaXNzaW5nIGtleXMuIGRlZmF1bHRkaWN0IGlzIGEgUHl0aG9uIHR5cGUgdGhhdCBpbmhlcml0cyBmcm9tIGRpY3Q6aHVnDQoNClRoZSBQeXRob24gZGVmYXVsdGRpY3QgdHlwZSBiZWhhdmVzIGFsbW9zdCBleGFjdGx5IGxpa2UgYSByZWd1bGFyIFB5dGhvbiBkaWN0aW9uYXJ5LCBidXQgaWYgeW91IHRyeSB0byBhY2Nlc3Mgb3IgbW9kaWZ5IGEgbWlzc2luZyBrZXksIHRoZW4gZGVmYXVsdGRpY3Qgd2lsbCBhdXRvbWF0aWNhbGx5IGNyZWF0ZSB0aGUga2V5IGFuZCBnZW5lcmF0ZSBhIGRlZmF1bHQgdmFsdWUgZm9yIGl0LiBUaGlzIG1ha2VzIGRlZmF1bHRkaWN0IGEgdmFsdWFibGUgb3B0aW9uIGZvciBoYW5kbGluZyBtaXNzaW5nIGtleXMgaW4gZGljdGlvbmFyaWVzLg0KDQpgYGB7cHl0aG9uIGV2YWw9RkFMU0V9DQphX2RpY3QgPSB7fQ0KYV9kaWN0WydtaXNzaW5nX2tleSddDQpgYGANCg0KU29tZXRpbWVzLCB5b3XigJlsbCB1c2UgYSBtdXRhYmxlIGJ1aWx0LWluIGNvbGxlY3Rpb24gKGEgbGlzdCwgZGljdCwgb3Igc2V0KSBhcyB2YWx1ZXMgaW4geW91ciBQeXRob24gZGljdGlvbmFyaWVzLiBJbiB0aGVzZSBjYXNlcywgeW914oCZbGwgbmVlZCB0byBpbml0aWFsaXplIHRoZSBrZXlzIGJlZm9yZSBmaXJzdCB1c2UsIG9yIHlvdeKAmWxsIGdldCBhIEtleUVycm9yLiBZb3UgY2FuIGVpdGhlciBkbyB0aGlzIHByb2Nlc3MgbWFudWFsbHkgb3IgYXV0b21hdGUgaXQgdXNpbmcgYSBQeXRob24gZGVmYXVsdGRpY3QuIEluIHRoaXMgc2VjdGlvbiwgeW914oCZbGwgbGVhcm4gaG93IHRvIHVzZSB0aGUgUHl0aG9uIGRlZmF1bHRkaWN0IHR5cGUgZm9yIHNvbHZpbmcgc29tZSBjb21tb24gcHJvZ3JhbW1pbmcgcHJvYmxlbXM6DQoNCiogR3JvdXBpbmcgdGhlIGl0ZW1zIGluIGEgY29sbGVjdGlvbg0KKiBDb3VudGluZyB0aGUgaXRlbXMgaW4gYSBjb2xsZWN0aW9uDQoqIEFjY3VtdWxhdGluZyB0aGUgdmFsdWVzIGluIGEgY29sbGVjdGlvbg0KDQpZb3XigJlsbCBiZSBjb3ZlcmluZyBzb21lIGV4YW1wbGVzIHRoYXQgdXNlIGxpc3QsIHNldCwgaW50LCBhbmQgZmxvYXQgdG8gcGVyZm9ybSBncm91cGluZywgY291bnRpbmcsIGFuZCBhY2N1bXVsYXRpbmcgb3BlcmF0aW9ucyBpbiBhIHVzZXItZnJpZW5kbHkgYW5kIGVmZmljaWVudCB3YXkuDQoNCiMjIyBHcm91cGluZyBpdGVtcw0KDQpHcm91cGluZyBJdGVtcw0KQSB0eXBpY2FsIHVzZSBvZiB0aGUgUHl0aG9uIGRlZmF1bHRkaWN0IHR5cGUgaXMgdG8gc2V0IC5kZWZhdWx0X2ZhY3RvcnkgdG8gbGlzdCBhbmQgdGhlbiBidWlsZCBhIGRpY3Rpb25hcnkgdGhhdCBtYXBzIGtleXMgdG8gbGlzdHMgb2YgdmFsdWVzLiBXaXRoIHRoaXMgZGVmYXVsdGRpY3QsIGlmIHlvdSB0cnkgdG8gZ2V0IGFjY2VzcyB0byBhbnkgbWlzc2luZyBrZXksIHRoZW4gdGhlIGRpY3Rpb25hcnkgcnVucyB0aGUgZm9sbG93aW5nIHN0ZXBzOg0KDQpDYWxsIGxpc3QoKSB0byBjcmVhdGUgYSBuZXcgZW1wdHkgbGlzdA0KSW5zZXJ0IHRoZSBlbXB0eSBsaXN0IGludG8gdGhlIGRpY3Rpb25hcnkgdXNpbmcgdGhlIG1pc3Npbmcga2V5IGFzIGtleQ0KUmV0dXJuIGEgcmVmZXJlbmNlIHRvIHRoYXQgbGlzdA0KVGhpcyBhbGxvd3MgeW91IHRvIHdyaXRlIGNvZGUgbGlrZSB0aGlzOg0KDQpgYGB7cHl0aG9ufQ0KZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgZGVmYXVsdGRpY3QNCmRkID0gZGVmYXVsdGRpY3QobGlzdCkNCmRkWydrZXknXS5hcHBlbmQoMSkNCmRkDQoNCmRkWydrZXknXS5hcHBlbmQoMikNCmRkDQoNCmRkWydrZXknXS5hcHBlbmQoMykNCmRkDQoNCmBgYA0KDQpZb3UgY2FuIHVzZSBkZWZhdWx0ZGljdCBhbG9uZyB3aXRoIGxpc3QgdG8gZ3JvdXAgdGhlIGl0ZW1zIGluIGEgc2VxdWVuY2Ugb3IgYSBjb2xsZWN0aW9uLiBTdXBwb3NlIHRoYXQgeW914oCZdmUgcmV0cmlldmVkIHRoZSBmb2xsb3dpbmcgZGF0YSBmcm9tIHlvdXIgY29tcGFueeKAmXMgZGF0YWJhc2U6DQoNCmBgYHtweXRob259DQoNCmltcG9ydCBwYW5kYXMgYXMgcGQNCg0KZCA9IHsnRGVwYXJ0bWVudCc6IFsnU2FsZXMnLCAnU2FsZXMnLCAnQWNjb3VudGluZycsICdNYXJrZXRpbmcnLCAnTWFya2V0aW5nJ10sICdFbXBsb3llZSBOYW1lJzogWydKb2huIERvZScsICdNYXJ0aW4gU21pdGgnLCAnSmFuZSBEb2UnLCAnRWxpemFiZXRoIFNtaXRoJywgJ0FkYW0gRG9lJ119DQoNCmRmID0gcGQuRGF0YUZyYW1lKGRhdGE9ZCkNCg0KZGYNCmBgYA0KDQpXaXRoIHRoaXMgZGF0YSwgeW91IGNyZWF0ZSBhbiBpbml0aWFsIGxpc3Qgb2YgdHVwbGUgb2JqZWN0cyBsaWtlIHRoZSBmb2xsb3dpbmc6DQoNCmBgYHtweXRob259DQpkZXAgPSBbKCdTYWxlcycsICdKb2huIERvZScpLA0KICAgICAgICgnU2FsZXMnLCAnTWFydGluIFNtaXRoJyksDQogICAgICAgKCdBY2NvdW50aW5nJywgJ0phbmUgRG9lJyksDQogICAgICAgKCdNYXJrZXRpbmcnLCAnRWxpemFiZXRoIFNtaXRoJyksDQogICAgICAgKCdNYXJrZXRpbmcnLCAnQWRhbSBEb2UnKV0NCmBgYA0KDQpOb3csIHlvdSBuZWVkIHRvIGNyZWF0ZSBhIGRpY3Rpb25hcnkgdGhhdCBncm91cHMgdGhlIGVtcGxveWVlcyBieSBkZXBhcnRtZW50LiBUbyBkbyB0aGlzLCB5b3UgY2FuIHVzZSBhIGRlZmF1bHRkaWN0IGFzIGZvbGxvd3M6DQoNCmBgYHtweXRob259DQpmcm9tIGNvbGxlY3Rpb25zIGltcG9ydCBkZWZhdWx0ZGljdA0KDQpkZXBfZGQgPSBkZWZhdWx0ZGljdChsaXN0KQ0KZm9yIGRlcGFydG1lbnQsIGVtcGxveWVlIGluIGRlcDoNCiAgICBkZXBfZGRbZGVwYXJ0bWVudF0uYXBwZW5kKGVtcGxveWVlKQ0KDQpkZXBfZGQNCmBgYA0KSGVyZSwgeW91IGNyZWF0ZSBhIGRlZmF1bHRkaWN0IGNhbGxlZCBkZXBfZGQgYW5kIHVzZSBhIGZvciBsb29wIHRvIGl0ZXJhdGUgdGhyb3VnaCB5b3VyIGRlcCBsaXN0LiBUaGUgc3RhdGVtZW50IGRlcF9kZFtkZXBhcnRtZW50XS5hcHBlbmQoZW1wbG95ZWUpIGNyZWF0ZXMgdGhlIGtleXMgZm9yIHRoZSBkZXBhcnRtZW50cywgaW5pdGlhbGl6ZXMgdGhlbSB0byBhbiBlbXB0eSBsaXN0LCBhbmQgdGhlbiBhcHBlbmRzIHRoZSBlbXBsb3llZXMgdG8gZWFjaCBkZXBhcnRtZW50LiBPbmNlIHlvdSBydW4gdGhpcyBjb2RlLCB5b3VyIGRlcF9kZCB3aWxsIGxvb2sgc29tZXRoaW5nIGxpa2UgdGhpczoNCg0KSW4gdGhpcyBleGFtcGxlLCB5b3UgZ3JvdXAgdGhlIGVtcGxveWVlcyBieSB0aGVpciBkZXBhcnRtZW50IHVzaW5nIGEgZGVmYXVsdGRpY3Qgd2l0aCAuZGVmYXVsdF9mYWN0b3J5IHNldCB0byBsaXN0LiBUbyBkbyB0aGlzIHdpdGggYSByZWd1bGFyIGRpY3Rpb25hcnksIHlvdSBjYW4gdXNlIGRpY3Quc2V0ZGVmYXVsdCgpIGFzIGZvbGxvd3M6DQoNCmBgYHtweXRob259DQpkZXBfZCA9IGRpY3QoKQ0KZm9yIGRlcGFydG1lbnQsIGVtcGxveWVlIGluIGRlcDoNCiAgICBkZXBfZC5zZXRkZWZhdWx0KGRlcGFydG1lbnQsIFtdKS5hcHBlbmQoZW1wbG95ZWUpDQoNCmBgYA0KDQpUaGlzIGNvZGUgaXMgc3RyYWlnaHRmb3J3YXJkLCBhbmQgeW914oCZbGwgZmluZCBzaW1pbGFyIGNvZGUgcXVpdGUgb2Z0ZW4gaW4geW91ciB3b3JrIGFzIGEgUHl0aG9uIGNvZGVyLiBIb3dldmVyLCB0aGUgZGVmYXVsdGRpY3QgdmVyc2lvbiBpcyBhcmd1YWJseSBtb3JlIHJlYWRhYmxlLCBhbmQgZm9yIGxhcmdlIGRhdGFzZXRzLCBpdCBjYW4gYWxzbyBiZSBhIGxvdCBmYXN0ZXIgYW5kIG1vcmUgZWZmaWNpZW50LiBTbywgaWYgc3BlZWQgaXMgYSBjb25jZXJuIGZvciB5b3UsIHRoZW4geW91IHNob3VsZCBjb25zaWRlciB1c2luZyBhIGRlZmF1bHRkaWN0IGluc3RlYWQgb2YgYSBzdGFuZGFyZCBkaWN0Lg0KDQpNb3JlIGluZm8gb24gZGVmYXVsdGRpY3QgY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL3JlYWxweXRob24uY29tL3B5dGhvbi1kZWZhdWx0ZGljdC8jZ3JvdXBpbmctdW5pcXVlLWl0ZW1zKQ0KDQoNCg==