pytransitions is there a simple way to get the history of triggered events - depth-first-search

class Matter(object):
def __init__(self, states, transitions):
self.states = states
self.transitions = transitions
self.machine = Machine(model=self, states=self.states, transitions=transitions, initial='liquid')
def get_triggered_events(self, source, dest):
self.machine.set_state(source)
eval("self.to_{}()".format(dest))
return
states=['solid', 'liquid', 'gas', 'plasma']
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
{ 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
{ 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
{ 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]
matter=Matter(states,transitions)
matter.get_triggered_events("solid","plasma")
I want to get the history of triggered events from source to destination in get_triggered_events method. E.g. runing matter.get_triggered_events("solid","plasma") will get [["melt","evaporate","ionize"],["sublimate","ionize"]]. Is there a simple way to achieve it?

I guess what you intend to do is to get all possible paths from one state to another.
You are interested in the names of the events that must be emitted/triggered to reach your target.
A solution is to traverse through all events that can be triggered in a state. Since one event can result in multiple transitions we need to loop over a list of transitions as well. With Machine.get_triggers(<state_name>) we'll get a list of all events that can be triggered from <state_name>.
With Machine.get_transitions(<event_name>, source=<state_name>) we will get a list of all transitions that are associated with <event_name> and can be triggered from <state_name>.
We basically feed the result from Machine.get_triggers to Machine.get_transitions and loop over the list of transitions while keeping track of the events that have been processed so far.
Furthermore, to prevent cyclic traversal, we also keep track of the transition entities we have already visited:
from transitions import Machine
states = ['solid', 'liquid', 'gas', 'plasma']
transitions = [
{'trigger': 'melt', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
{'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}
]
class TraverseMachine(Machine):
def traverse(self, current_state, target_state, seen=None, events=None):
seen = seen or []
events = events or []
# we reached our destination and return the list of events that brought us here
if current_state == target_state:
return events
paths = [self.traverse(t.dest, target_state, seen + [t], events + [e])
for e in self.get_triggers(current_state)
for t in self.get_transitions(e, source=current_state)
if t not in seen]
# the len check is meant to prevent deep nested return values
# if you just return path the result will be:
# [[[['melt', 'evaporate', 'ionize']]], [['sublimate', 'ionize']]]
return paths[0] if len(paths) == 1 and isinstance(paths[0], list) else paths
# disable auto transitions!
# otherwise virtually every path ending with `to_<target_state>` is a valid solution
m = TraverseMachine(states=states, transitions=transitions, initial='solid', auto_transitions=False)
print(m.traverse("solid", "plasma"))
# [['melt', 'evaporate', 'ionize'], ['sublimate', 'ionize']]
You might want to have a look at the discussion in transitions issue tracker about automatic traversal as it contains some insights about conditional traversal.

Related

Breadth-first algorithm implementation

I am trying to implement a "Breadth-First" Algorithm as a variation of something I've seen in a book.
My issue is that the algorithm is not adding the elements of every node into the queue.
For instance, if I search for "black lab" under the name 'mariela' in the "search()" function, I will get the correct output: "simon is a black lab"
However, I ought to be able to look for "black lab" in "walter", which is connected to "mariela", which is connected to "simon", who is a "black lab'. This is not working.
Have I made a rookie mistake in my implementation of this algorithm, or have I set up my graph wrong?
As always, any/all help is much appreciated!
from collections import deque
# TEST GRAPH -------------
graph = {}
graph['walter'] = ['luci', 'kaiser', 'andrea', 'mariela']
graph['andrea'] = ['echo', 'dante', 'walter', 'mariela']
graph['mariela'] = ['ginger', 'simon', 'walter', 'andrea']
graph['kaiser'] = 'german shepherd'
graph['luci'] = 'black cat'
graph['echo'] = 'pitbull'
graph['dante'] = 'pitbull'
graph['ginger'] = 'orange cat'
graph['simon'] = 'black lab'
def condition_met(name):
if graph[name] == 'black lab':
return name
def search(name):
search_queue = deque()
search_queue += graph[name] # add all elements of "name" to queue
searchedAlready = [] # holding array for people already searched through
while search_queue: # while queue not empty...
person = search_queue.popleft() # pull 1st person from queue
if person not in searchedAlready: # if person hasn't been searched through yet...
if condition_met(person):
print person + ' is a black labrador'
return True
else:
search_queue += graph[person]
searchedAlready.append(person)
return False
search('walter')
#search('mariela')
You have lots of problems in your implementation - both Python and Algorithm wise.
Rewrite as:
# #param graph graph to search
# #param start the node to start at
# #param value the value to search for
def search(graph, start, value):
explored = []
queue = [start]
while len(queue) > 0:
# next node to explore
node = queue.pop()
# only explore if not already explored
if node not in explored:
# node found, search complete
if node == value:
return True
# add children of node to queue
else:
explored.append(node)
queue.extend(graph[node]) # extend is faster than concat (+=)
return False
graph = {}
graph['walter'] = ['luci', 'kaiser', 'andrea', 'mariela']
graph['andrea'] = ['echo', 'dante', 'walter', 'mariela']
graph['mariela'] = ['ginger', 'simon', 'walter', 'andrea']
# children should be a list
graph['kaiser'] = ['german shepherd']
graph['luci'] = ['black cat']
graph['echo'] = ['pitbull']
graph['dante'] = ['pitbull']
graph['ginger'] = ['orange cat']
graph['simon'] = ['black lab']
print search(graph, 'mariela', 'walter')
Here is a demo https://repl.it/IkRA/0

GTK how to create 3 different filter for 1 Liststore

In GTK/Python, I'm trying to build an interface with nodes.
This is photo of my interface
I create one liststore and I want to filter different things. The user has to do two actions, first, he has to choose in the combobox the type of filter that he wants to use, if he wants to filter by node's type, node's beginning name or others.
Then in the text entry, he decides what information that he wants to see. Take the exemple of nodes type. There are like 3 different types of nodes, node type 1, node type 2 and node type 3. As an user I want to see only node type 1, so I write 1 in the node entry. Actually I have a problem, it is my filter does not work.
I first create 2 liststores:
def create_liststore(self):
if len(self.FdessinCarto.pos) != 0:
for i,node in enumerate(self.FdessinCarto.pos):
self.node_liststore.append([str(node.title),self.controller.model.string_from_numdate(int(node.start_time)),self.controller.model.string_from_numdate(int(node.end_time)),str(node.node_group),str(node.description),str(node.attachment_list)])
self.edgelist = self.controller.get_edge_list()
if len(self.edgelist) !=0:
for i in self.edgelist:
edge_prop=self.controller.edge_data(i[0],i[1])
self.edge_liststore.append([edge_prop['label'],str(i[0].title),str(i[1].title),edge_prop['description'],edge_prop['attachment_list']])
#creating the treeview for Node, making it use the filter as a model, and adding the columns
self.treeviewNode = Gtk.TreeView.new_with_model(self.node_liststore)
for i, column_title in enumerate(["Name", "Beginning date", "End date", "Type of node", "Description of node","files"]):
self.Noderenderer = Gtk.CellRendererText()
self.Noderenderer.set_property("editable", True)
column = Gtk.TreeViewColumn(column_title, self.Noderenderer, text=i)
column.set_sort_column_id(0)
self.treeviewNode.append_column(column)
#creating the treeview for edge
self.treeviewEdge = Gtk.TreeView.new_with_model(self.edge_liststore)
for i, column_title in enumerate(["Name", "Node 1", "Node 2", "Description of edge","file"]):
self.Edgerenderer = Gtk.CellRendererText()
self.Edgerenderer.set_property("editable", True)
column = Gtk.TreeViewColumn(column_title, self.Edgerenderer, text=i)
column.set_sort_column_id(0)
self.treeviewEdge.append_column(column)
self.SWViewListStore.add(self.treeviewNode)
self.SWEdgeStore.add(self.treeviewEdge)
self.SWViewListStore.show_all()
self.SWEdgeStore.show_all()
There are my 3 different filters:
#creating the filtre
self.node_beginning_date_filter = self.node_liststore.filter_new()
self.node_end_date_filter = self.node_liststore.filter_new()
self.node_type_filter = self.node_liststore.filter_new()
#setting the filter function, note that we're not using the
self.node_end_date_filter.set_visible_func(self.node_end_date_filter_func)
self.node_beginning_date_filter.set_visible_func(self.node_beginning_date_filter_func)
self.node_type_filter.set_visible_func(self.node_type_filter_func)
Once I change my combo-box, it would activate my function, it took the type of combofilter then also the text of combobox :
def on_entryComboBox_changed(self,widget):
textComboFilter = self.View.combo_filter.get_active_text()
print("textComboFilter %s" %textComboFilter)
if textComboFilter == "Filter by node's beginning date":
#print("%s language selected!" % textComboFilter)
self.View.current_filter = textComboFilter
self.View.node_beginning_date_filter.refilter()
if textComboFilter == "Filter by node's end date":
#print("%s language selected!" % textComboFilter)
self.View.current_filter = textComboFilter
self.View.node_end_date_filter.refilter()
if textComboFilter == "Filter by type of node":
#print("%s language selected!" % textComboFilter)
self.View.current_filter = textComboFilter
self.View.node_type_filter.refilter()
And it does not work.
Finally,
I manage to answer it on my own.
You should build one filter rather than three. For people who have the same problem, this is a very good example which helps me to solve my problem.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
#list of tuples for each software, containing the software name, initial release, and main programming languages used
software_list = [("Firefox", 2002, "C++"),
("Eclipse", 2004, "Java" ),
("Pitivi", 2004, "Python"),
("Netbeans", 1996, "Java"),
("Chrome", 2008, "C++"),
("Filezilla", 2001, "C++"),
("Bazaar", 2005, "Python"),
("Git", 2005, "C"),
("Linux Kernel", 1991, "C"),
("GCC", 1987, "C"),
("Frostwire", 2004, "Java")]
class TreeViewFilterWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Treeview Filter Demo")
self.set_border_width(10)
#Setting up the self.grid in which the elements are to be positionned
self.grid = Gtk.Grid()
self.grid.set_column_homogeneous(True)
self.grid.set_row_homogeneous(True)
self.add(self.grid)
#Creating the ListStore model
self.software_liststore = Gtk.ListStore(str, int, str)
for software_ref in software_list:
self.software_liststore.append(list(software_ref))
self.current_filter_language = None
#Creating the filter, feeding it with the liststore model
self.language_filter = self.software_liststore.filter_new()
#setting the filter function, note that we're not using the
self.language_filter.set_visible_func(self.language_filter_func)
#creating the treeview, making it use the filter as a model, and adding the columns
self.treeview = Gtk.TreeView.new_with_model(self.language_filter)
for i, column_title in enumerate(["Software", "Release Year", "Programming Language"]):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
self.treeview.append_column(column)
#creating buttons to filter by programming language, and setting up their events
self.buttons = list()
for prog_language in ["Java", "C", "C++", "Python", "None"]:
button = Gtk.Button(prog_language)
self.buttons.append(button)
button.connect("clicked", self.on_selection_button_clicked)
#setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row
self.scrollable_treelist = Gtk.ScrolledWindow()
self.scrollable_treelist.set_vexpand(True)
self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10)
self.grid.attach_next_to(self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1)
for i, button in enumerate(self.buttons[1:]):
self.grid.attach_next_to(button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1)
self.scrollable_treelist.add(self.treeview)
self.show_all()
def language_filter_func(self, model, iter, data):
"""Tests if the language in the row is the one in the filter"""
if self.current_filter_language is None or self.current_filter_language == "None":
return True
else:
return model[iter][2] == self.current_filter_language
def on_selection_button_clicked(self, widget):
"""Called on any of the button clicks"""
#we set the current language filter to the button's label
self.current_filter_language = widget.get_label()
print("%s language selected!" % self.current_filter_language)
#we update the filter, which updates in turn the view
self.language_filter.refilter()
win = TreeViewFilterWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

creating nested dictionary from flat list with python

i have a list of file in this form:
base/images/graphs/one.png
base/images/tikz/two.png
base/refs/images/three.png
base/one.txt
base/chapters/two.txt
i would like to convert them to a nested dictionary of this sort:
{ "name": "base" , "contents":
[{"name": "images" , "contents":
[{"name": "graphs", "contents":[{"name":"one.png"}] },
{"name":"tikz", "contents":[{"name":"two.png"}]}
]
},
{"name": "refs", "contents":
[{"name":"images", "contents": [{"name":"three.png"}]}]
},
{"name":"one.txt", },
{"name": "chapters", "contents":[{"name":"two.txt"}]
]
}
trouble is, my attempted solution, given some input like images/datasetone/grapha.png" ,"images/datasetone/graphb.png" each one of them will end up in a different dictionary named "datasetone" however i'd like both to be in the same parent dictionary as they are in the same directory, how do i create this nested structure without duplicating parent dictionaries when there's more than one file in a common path?
here is what i had come up with and failed:
def path_to_tree(params):
start = {}
for item in params:
parts = item.split('/')
depth = len(parts)
if depth > 1:
if "contents" in start.keys():
start["contents"].append(create_base_dir(parts[0],parts[1:]))
else:
start ["contents"] = [create_base_dir(parts[0],parts[1:]) ]
else:
if "contents" in start.keys():
start["contents"].append(create_leaf(parts[0]))
else:
start["contents"] =[ create_leaf(parts[0]) ]
return start
def create_base_dir(base, parts):
l={}
if len(parts) >=1:
l["name"] = base
l["contents"] = [ create_base_dir(parts[0],parts[1:]) ]
elif len(parts)==0:
l = create_leaf(base)
return l
def create_leaf(base):
l={}
l["name"] = base
return l
b=["base/images/graphs/one.png","base/images/graphs/oneb.png","base/images/tikz/two.png","base/refs/images/three.png","base/one.txt","base/chapters/two.txt"]
d =path_to_tree(b)
from pprint import pprint
pprint(d)
In this example you can see we end up with as many dictionaries named "base" as there are files in the list, but only one is necessary, the subdirectories should be listed in the "contents" array.
This does not assume that all paths start with the same thing, so we need a list for it:
from pprint import pprint
def addBits2Tree( bits, tree ):
if len(bits) == 1:
tree.append( {'name':bits[0]} )
else:
for t in tree:
if t['name']==bits[0]:
addBits2Tree( bits[1:], t['contents'] )
return
newTree = []
addBits2Tree( bits[1:], newTree )
t = {'name':bits[0], 'contents':newTree}
tree.append( t )
def addPath2Tree( path, tree ):
bits = path.split("/")
addBits2Tree( bits, tree )
tree = []
for p in b:
print p
addPath2Tree( p, tree )
pprint(tree)
Which produces the following for your example path list:
[{'contents': [{'contents': [{'contents': [{'name': 'one.png'},
{'name': 'oneb.png'}],
'name': 'graphs'},
{'contents': [{'name': 'two.png'}],
'name': 'tikz'}],
'name': 'images'},
{'contents': [{'contents': [{'name': 'three.png'}],
'name': 'images'}],
'name': 'refs'},
{'name': 'one.txt'},
{'contents': [{'name': 'two.txt'}], 'name': 'chapters'}],
'name': 'base'}]
Omitting the redundant name tags, you can go on with :
import json
result = {}
records = ["base/images/graphs/one.png", "base/images/tikz/two.png",
"base/refs/images/three.png", "base/one.txt", "base/chapters/two.txt"]
recordsSplit = map(lambda x: x.split("/"), records)
for record in recordsSplit:
here = result
for item in record[:-1]:
if not item in here:
here[item] = {}
here = here[item]
if "###content###" not in here:
here["###content###"] = []
here["###content###"].append(record[-1])
print json.dumps(result, indent=4)
The # characters are used for uniqueness (there could be a folder which name was content in the hierarchy). Just run it and see the result.
EDIT : Fixed a few typos, added the output.

Carrot2 circle chart

Anyone know how to create circle chart like the one used in carrto2?
The mbostock/d3 gallery has good visualizations for Carrot2 output.
This carrot2-rb ruby client for Carrot2 returns an object with a clusters array. The scores and phrases attributes can be used in a simple doughnut chart.
More dynamic visualizations like expandable dendrograms are possible with tree structures like flare.json.
Here is a zoomable wheel based on Carrot2 results.
This is the coffeescript code I wrote to create flare.json using the documents elements.
clusters = [{"id":0,"size":3,"phrases":["Coupon"],"score":0.06441151442396735,"documents":["0","1","2"],"attributes":{"score":0.06441151442396735}},{"id":1,"size":2,"phrases":["Exclusive"],"score":0.7044284368639101,"documents":["0","1"],"attributes":{"score":0.7044284368639101}},{"id":2,"size":1,"phrases":["Other Topics"],"score":0.0,"documents":["3"],"attributes":{"other-topics":true,"score":0.0}}]
flare = get_flare clusters
get_children = (index, index2, clusters, documents) ->unless index == (clusters.length - 1) # If not last cluster
orphans = {'name': ''}
intr = _.intersection(documents, clusters[index2].documents);
if intr.length > 0 # continue drilling
if index2 < (clusters.length - 1) # Up until last element.
# Get next layer of orphans
orphan_docs = _.difference(intr, clusters[index2 + 1].documents)
if orphan_docs.length > 0
orphans = {'name': orphan_docs, 'size': orphan_docs.length}
if _.intersection(intr, clusters[index2 + 1].documents).length > 0
return [orphans, {'name': clusters[index2+1].phrases[0], 'children': get_children(index, (index2 + 1), clusters, intr)}]
else
return [orphans]
else
# At second to last cluster, so terminate here
return [{'name': inter}]
else # No intersection, so return bundle of current documents.
return [{'name': documents}]
return [{'name': _.intersection(clusters[index].documents, clusters[index2].documents)}]
get_flare = (clusters) ->
# Make root object
flare =
name: "root"
children: []
children = flare.children
_.each(clusters[0..(clusters.length - 2)], (cluster, index) -> # All clusters but the last. (It has already been compared to previous ones)
#All documents for all remaining clusters in array
remaining_documents = _.flatten(_.map clusters[(index + 1)..clusters.length], (c) ->
c.documents
)
root_child = {'name': cluster.phrases[0], 'children': []}
# Get first layer of orphans
orphan_docs = _.difference(cluster.documents, remaining_documents)
if orphan_docs.length > 0
root_child.children.push {'name': orphan_docs, size: orphan_docs.length}
for index2 in [(index + 1)..(clusters.length - 1)] by 1
if _.intersection(cluster.documents, clusters[index2].documents).length > 0
root_child.children.push {'name': clusters[index2].phrases[0], 'children': get_children(index, (index2), clusters, cluster.documents)}
children.push root_child
)
flare
You can buy their Circles Javascript component: http://carrotsearch.com/circles-overview

Multiple events matching algorithm

I have a task to match multiple events(facts) with each other by some their properties.
As a result of events matching some action should be generated. Action can be generated when events of all exists types were matched.
Is there any algorithm which could be used for such task? Or any direction?
Thanks
Example:
We have several events with different types and properties.
Type SEEN is cumulative event (several events could be merged for matching) and type FOUND is not.
Event 1 (SEEN):
DATE="2009-09-30"
EYES_COLOR="BLUE"
LEFT_SOCK_COLOR="RED"
Event 2 (SEEN):
DATE="2009-09-30"
EYES_COLOR="BLUE"
RIGHT_SOCK_COLOR="GREEN"
Event 3 (FOUND):
DATE="2009-09-30"
EYES_COLOR="BLUE"
LEFT_SOCK_COLOR="BLUE"
RIGHT_SOCK_COLOR="GREEN"
PLACE="MARKET"
Event 4 (FOUND):
DATE="2009-09-30"
EYES_COLOR="BLUE"
LEFT_SOCK_COLOR="GREEN"
PLACE="SHOP"
Event 5 (FOUND):
DATE="2009-09-30"
EYES_COLOR="BLUE"
PLACE="AIRPORT"
For above events such actions should be generated (by composing matched events):
Action 1_2_3:
DATE="2009-09-30"
EYES_COLOR="BLUE"
LEFT_SOCK_COLOR="RED"
RIGHT_SOCK_COLOR="GREEN"
PLACE="MARKET"
Action 2_4:
DATE="2009-09-30"
EYES_COLOR="BLUE"
LEFT_SOCK_COLOR="GREEN"
PLACE="SHOP"
Means:
Event 1 + Event 2 + Event 3 => Action 1_2_3
Event 2 + Event 4 => Action 2_4
Event 5 does not match with anything.
in your case every two events are either compatible or not; we can denote this by C(e,e'), meaning that event e is compatible with event e'. You can build a maximal set of compatible events of course iteratively; when you have a set {e1,e2,...,en} of compatible events, you can add e' to the set if and only if e' is compatible with every e1,...,en, i.e. C(ei,e') is true for all 1<=i<=n.
Unfortunately in your case the number of maximal sets of compatible events can be exponential to the number of events, because you can have e.g. events e1, e2, e3 and e4 so that they are all pair-wisely compatible but none of them is compatible with TWO other events; for this set you will already get 6 different "actions", and they overlap each other.
A simple algorithm is to have a recursive search where you add events one by one to the prospectual "action", and when you can't add any more events you register the action; then you backtrack. It's called "backtracking search". You can improve its running time then by proper datastructures for "quickly" looking up the matching events.
As in the comment, the question about SEEN/FOUND is open; I'm assuming here that the fields are merged "as is".
This pseudo-code may help: (C# syntax)
foreach (var found in events.Where(x => x.EventType == "Found"))
{
var matches = events.Where(x => x.EventType == "Seen"
&& x.Whatever == found.Whatever);
if (matches.Count() > 0)
{
// Create an action based on the single "Found" event
// and the multiple matching "Seen" events.
}
}
I'm not sure I understand the question correctly. It seems that for every FOUND event, you want to identify all matching SEEN events and merge them? Python code:
# assume events are dictionaries, and you have 2 lists of them by type:
# (omitting DATE because it's always "2009-09-03" in your example)
seen_events = [
{
"EYES_COLOR": "BLUE",
"LEFT_SOCK_COLOR": "RED",
},
{
"EYES_COLOR": "BLUE",
"RIGHT_SOCK_COLOR": "GREEN",
},
]
found_events = [
{
"EYES_COLOR": "BLUE",
"LEFT_SOCK_COLOR": "BLUE",
"RIGHT_SOCK_COLOR": "GREEN",
"PLACE": "MARKET",
},
{
"EYES_COLOR": "BLUE",
"LEFT_SOCK_COLOR": "GREEN",
"PLACE": "SHOP",
},
{
"EYES_COLOR": "BLUE",
"PLACE": "AIRPORT",
},
]
def do_action(seen_events, found):
"""DUMMY"""
for seen in seen_events:
print seen
print found
print
# brute force
for found in found_events:
matching = []
for seen in seen_events:
for k in found:
if k in seen and seen[k] != found[k]:
break
else: # for ended without break (Python syntax)
matching.append(seen)
if matching:
do_action(matching, found)
which prints:
{'EYES_COLOR': 'BLUE', 'RIGHT_SOCK_COLOR': 'GREEN'}
{'EYES_COLOR': 'BLUE', 'PLACE': 'MARKET', 'LEFT_SOCK_COLOR': 'BLUE', 'RIGHT_SOCK_COLOR': 'GREEN'}
{'EYES_COLOR': 'BLUE', 'RIGHT_SOCK_COLOR': 'GREEN'}
{'EYES_COLOR': 'BLUE', 'PLACE': 'SHOP', 'LEFT_SOCK_COLOR': 'GREEN'}
{'EYES_COLOR': 'BLUE', 'LEFT_SOCK_COLOR': 'RED'}
{'EYES_COLOR': 'BLUE', 'RIGHT_SOCK_COLOR': 'GREEN'}
{'EYES_COLOR': 'BLUE', 'PLACE': 'AIRPORT'}
Right, this is not effecient - O(n*m) - but does this even describe the problem correctly?

Resources