Note: Since my time has passed for this challenge, I do not remember exactly the stipulations but will attempt to recapitulate them to the best of my knowledge.
Essentially, the challenge was this:
Given a list (from 1 to 2000 elements) of random integers (from 1 to 999999) write a function,
answer(l)
that accepts a list as input and returns the number of "lucky triples" present in the list.For this purpose, a lucky triple is defined as a list of three numbers \$(x, y, z)\$ such that \$x\$ divides \$y\$, \$y\$ divides \$z\$, and \$x \le y \le z\$. So, for instance, \$(2, 4, 8)\$ is a lucky triple and so is \$(1, 1, 1)\$.
Test cases:
input: [1, 1, 1] ouput: 1
input: [1, 2, 3, 4, 5, 6] output: 3
Theory
First, a brief explanation about the theory behind my answer. I first noticed that for any list of multiples where each element \$n\$ is a factor of element \$n + 1\$, for example 1, 2, 4, 8, 16, the number of lucky triples in the list is equal to the summation from x = 0 to x = length - 2. So, for the previous example, the number of lucky triples in the list is equal to 3 + 2 + 1 or 6:
- 1, 2, 4
- 1, 2, 8
- 1, 2, 16
- 2, 4, 8
- 2, 8, 16
- 4, 8, 16
My idea, then, was to construct a tree of from the list, each branch representing a list of factors, and record the depth of each branch in the tree. From there, the number of lucky triples in each branch could be readily calculated with sum()
. Unfortunately, I was a little too late and can therefore no longer validate my code. I've tested it on rather trivial cases, but have no way of knowing:
- If my code is optimized;
- If it returns the correct answer in more intricate examples, say a list from 1 to 2000.
I preemptively apologize for any ambiguity in my explanation - feel free to ask for clarification - (this is the first time I've submitted code for review) as well as for the formatting of my code (if it is not readily comprehensible/Pythonic). But feedback would be much appreciated!
Code
# {{{ Imports
from collections import Counter
from itertools import imap, izip
# }}}
# {{{ LuckyTriples(object)
class LuckyTriples(object):
# {{{ __init__(self, list_of_random_int)
def __init__(self, list_of_random_int):
self._node_pool = set(sorted(list_of_random_int))
self._tree_space_parents = {}
self._tree_space_children = {}
self._roots = []
self._depths = []
map(self.build_tree_space, self._node_pool)
map(self.get_all_roots, self._node_pool)
for root in self._roots:
self.embark(root, [1])
self._lucky_triple_count = 0
self.enumerate_lucky_triples(list_of_random_int)
# }}}
# {{{ build_tree_space(self, node_pool)
def build_tree_space(self, node):
parents = self.get_all_parents(node, self._node_pool)
self._tree_space_parents[node] = parents
children = self.get_all_children(node, self._node_pool)
self._tree_space_children[node] = children
return
# }}}
# {{{ get_all_parents(self, node, node_pool)
def get_all_parents(self, node, node_pool):
parents = [
parent for parent in node_pool
if parent < node and node % parent == 0
]
return parents
# }}}
# {{{ get_all_roots(self, node_pool)
def get_all_roots(self, node):
parents = self.get_all_parents(node, self._node_pool)
if not parents:
self._roots.append(node)
return
# }}}
# {{{ get_all_children(self, node, node_pool)
def get_all_children(self, node, node_pool):
children = [
child for child in node_pool
if child > node and child % node == 0
]
return children
# }}}
# {{{ get_children_to_visit(self, nodes)
def get_children_to_visit(self, nodes):
nodes = [
subbranch for branches in nodes for subbranch in
branches
]
children_to_visit = []
for node in nodes:
immediate_children = set(
self._tree_space_children.get(node)
)
descendants = set()
nodes_to_visit = list(immediate_children)
while nodes_to_visit:
node = nodes_to_visit.pop()
children = self._tree_space_children.get(node)
descendants.update(children)
children_to_visit.append(list(
immediate_children - descendants)
)
return children_to_visit
# }}}
# {{{ branch_off(self, branches, depths)
def branch_off(self, branches, depths):
for subbranch in branches:
depths.extend(depths[0] for i in
range(len(subbranch) - 1)
)
return depths
# }}}
# {{{ explore_deeper(self, depths)
def explore_deeper(self, level, depths):
branches = [node for branch in level for node in branch]
explore_deeper = [1] * len(branches)
explore_deeper += [0 for i in
range(len(depths) - len(explore_deeper))
]
new_depths = map(sum, izip(depths, explore_deeper))
return new_depths
# }}}
# {{{ def embark(self, node, depths)
def embark(self, node, depths):
nodes_to_visit = [[node]]
while any(nodes_to_visit):
children = self.get_children_to_visit(nodes_to_visit)
depths = self.branch_off(children, depths)
depths = self.explore_deeper(children, depths)
nodes_to_visit = children
self._depths.extend(depths)
return
# }}}
# {{{ enumerate_lucky_triples(self, list_of_random_int)
def enumerate_lucky_triples(self, list_of_random_int):
for depth in self._depths:
tmp = range(1, depth - 1)
self._lucky_triple_count += sum(tmp)
multiples = [
node for node in list_of_random_int
if list_of_random_int.count(node) > 1
]
multiples = Counter(multiples)
for multiple in multiples:
parents = self._tree_space_parents[multiple]
children = self._tree_space_children[multiple]
nodes_in_branch = len(parents) + len(children)
if multiples[multiple] == 2:
self._lucky_triple_count += nodes_in_branch
elif multiples[multiple] >= 3:
self._lucky_triple_count += nodes_in_branch + 1
# }}}
# }}}
# {{{ answer(l)
def answer(l):
lock_codes = LuckyTriples(l)
return lock_codes._lucky_triple_count
# }}}
# {{{
and# }}}
comments a habit of yours? Where does it come from? \$\endgroup\$k
th element of the list? reduces to How many lucky doubles end with thek
th element of the list?. Should be about 10 or 20 lines, I'd say, and it would run in O(n^2) time. \$\endgroup\$