How many possible combinations of numbers make the same binary tree - python

Setup:
I have a list of numbers that represent a Binary tree. The first number is treated differently than some of the rest, it is the root. Out of "the rest" of the numbers, some will be higher than the root, some will be lower. Higher numbers are ordered to go to the left, while younger numbers are ordered to go to the right. Example:
list = [5,7,6,8,9,2,1,3,4]
root = 5
higher = [7,6,8,9] #in order of appearance
root = 7
higher = [8,9]
lower = [6]
lower = [2,1,3,4] #in order of appearance
root = 2
higher = [3,4]
lower = [1]
In this case, the tree would look something like this:
5
-------------| |--------------
| |
7 2
8--------| |-------6 3-----| |-----1
---| ---|
9 4
I am looking to find a way to model the number of possible combinations that the list [5,7,6,8,9,2,1,3,4] can be arranged in so that an identical binary tree is made. The solution will most definitely be recursive, in that out of the list of "higher" and "lower" numbers, they can be broken down even more.
Figuring out how many ways all the numbers can be arranged can be started by breaking down the trees as we did with the lists above.
Parents can be mixed, children can be mixed, but children and parents cannot be mixed
higher = [7,6,8,9]
But the higher list does not need to preserve its order of [7,6,8,9]. Only the items higher the root that are not parents to another tree need to be kept in order of appearance. Since 6, and 8 are both children of 7, they are interchangeable, but 9 must be before 8, since its a child of it. So basically the only rules in rearranging this list are:
Must start with 7
8 must always come before 9
Therefore there are three combinations with this.
[7,6,8,9]
[7,8,6,9]
[7,8,9,6]
All of these can be broken down into identical sub-trees, so our condition is met, now we can look at the list of elements lower than the main root.
lower = [2,1,3,4]
Lower list does not need to preserve its order either, it follows similar rules and can be written three different ways to produce identical trees:
[2,1,3,4]
[2,3,1,4]
[2,3,4,1]
We now have this information:
- The lower can be written 3 ways
- The higher can be written 3 ways
How many different ways can they be combined to produce the same tree? Is this 3^3? Or more?
Looking at the numbers I know this:
list = [5,7,6,8,2,1,3,4]
If I make a list of possible numbers that can go in each spot, here is how far I end up getting:
First element of list must be 5, it is the root. After than it has to be a 2 or a 7, because anything else would break the order of the higher/lower lists. After that, it gets messy.
If second number = 2, the third number can be one of three things, 1, 3, or 7.
If second number = 7, the third number can be one of three things, 6, 8, or 2.
After this it expands much larger and the combinations go up very quick. My question is, is there a way to recursively retrieve the number of total possible combinations in an efficient manner? I will be doing this in python. Thank you.

From what I understand, you want the number of topological orders compatible with the graph where each node has an arc to its parent. I'll sketch a solution in untested Python. First we need a node data type.
import collections
Node = collections.namedtuple('Node', ('left', 'right'))
A tree is either None (the empty tree) or a Node where both fields are trees.
Now, the number of topological orders for the empty tree is 1, i.e., just the empty order. The number of topological orders for a nonempty tree is given by the following counting argument. The root always comes first. After the root, an arbitrary topological order for the left subtree is shuffled arbitrarily with an arbitrary topological order for the right subtree. Thus the formula is the product of three factors.
import math
def num_shuffles(left_size, right_size): # binomial coefficient
return (math.factorial(left_size + right_size) //
(math.factorial(left_size) * math.factorial(right_size)))
def num_orders_and_size(tree):
assert isinstance(tree, (type(None), Node))
if tree is None:
return (1, 0)
else:
left_num_orders, left_size = num_orders_and_size(tree.left)
right_num_orders, right_size = num_orders_and_size(tree.right)
return (num_shuffles(left_size, right_size) *
left_num_orders * right_num_orders,
left_size + 1 + right_size)
To build the tree from the list, we use recursion (there are faster ways, but this one is simplest).
def tree_from_list(lst):
if not lst:
return None
root = lst[0]
left_lst = [x for x in lst if x > root]
right_lst = [x for x in lst if x < root]
return Node(tree_from_list(left_lst), tree_from_list(right_lst))

Building on ideas above....Here is python generator code to produce all equivalent orderings.
import collections
Node = collections.namedtuple('Node', ('root','left', 'right'))
def tree_from_list(lst):
if not lst:
return None
root = lst[0]
left_lst = [x for x in lst if x > root]
right_lst = [x for x in lst if x < root]
return Node(root,tree_from_list(left_lst), tree_from_list(right_lst))
def parent(key, tree):
if tree is None:
return -1
elif (tree.left != None) and (tree.left.root == key):
return tree.root
elif (tree.right != None) and (tree.right.root == key):
return tree.root
elif (key > tree.root):
return parent(key, tree.left)
else: return parent(key, tree.right)
def all_insert_after(key, target, seq):
i = seq.index(key)
for j in range(i,len(seq)):
mylist = seq[:j+1] + [target] + seq[j+1:]
yield mylist
def all_equivalent_orderings(seq):
if (len(seq)==1):
yield seq
else:
z = seq[-1]
p = parent(z, tree_from_list(seq))
for a in all_equivalent_orderings(seq[:-1]):
for b in all_insert_after(p,z,a):
yield b
print "Here are all 630 equivalent orderings of [5,7,6,8,9,2,1,3,4]"
for o in all_equivalent_orderings([5,7,6,8,9,2,1,3,4]):
print o

Related

Is there a way to get from a DFS output to a BFS output?

I have been struggling with the following problem: I have a DFS outputted list:
[0.2500000074505806,
0.6500000059604645,
0.15000000223517418,
0.45000000298023224,
0.45000000298023224,
0.8499999940395355,
0.15000000223517418]
and want to transform this to a BFS ordering without first creating a tree and then applying BFS. For reference, this is a complete binary graph with depth 2.
Thanks in advance for the help.
For general graphs, DFS doesn’t contain enough information to obtain the BFS output. But if you’re sure the graph is a complete binary tree on 7 nodes, and you ran DFS from the root and output is x1,x2,x3,…,x7, then the BFS output would be x1,x2,x5,x3,x4,x6,x7.
More generally, if you have the DFS output for a complete binary tree on n nodes, the permutation that gives the BFS output can be generated by the following algorithm:
k = 3 # number of levels in complete binary tree
n = 2**k #so node labels are 1,2,...,n-1
L = list(range(1, n))
def toBFS(L):
#input: a sequence of node labels, obtained by DFS on a complete binary tree
#output: the sequence of node labels if BFS was performed
#Q = a queue of all sublists to be processed
#each sublist q has length 2^k-2
#process each sublist by printing 1st, and n/2th element
#and partitioning into two subsublists, both added to queue
print(L[0])
Q = []
Q.append(L[1:len(L)])
while len(Q) > 0:
q = Q.pop(0) #removes first element of Q
if len(q) <= 2:
for x in q:
print(x)
else:
print(q[0])
n = len(q)
print(q[n//2])
Q.append(q[1:n//2])
Q.append(q[n//2+1:n])
toBFS(L)
Output:
1
2
5
3
4
6
7
The algorithm takes as input the DFS sequence, and prints the root node, then does the following for each sublist in the FIFO queue: prints the left child, then adds about half of the elements as a sublist to a queue, then prints the right child, then adds about half of the elements as a sublist to a queue.
When the tree is guaranteed to be a perfect binary tree, i.e. where the leaves of the tree are all on the bottom level, then you could use a pragmatic approach where you populate the levels of the level order traversal as separate lists, and then return the concatenation of those values:
def preorder_to_levelorder(seq):
height = int(len(seq) ** 0.5)
levels = [[] for _ in range(height+1)]
it = iter(seq)
def recur(depth):
if depth > height:
return
try:
val = next(it)
except:
return
levels[depth].append(val)
recur(depth + 1)
recur(depth + 1)
recur(0)
return [val for level in levels for val in level]
Example perfect tree:
4
/ \
2 6
/ \ / \
1 3 5 7
Example run for that tree:
preorder = [4, 2, 1, 3, 6, 5, 7]
print(preorder_to_levelorder(preorder)) # [4, 2, 6, 1, 3, 5, 7]

foobar please-pass-the-coded-messages hidden test case not passing

I have been attempting google foobar and in the second level i got the task named please-pass-the-coded-messages. below is the task
==============================
You need to pass a message to the bunny workers, but to avoid detection, the code you agreed to use is... obscure, to say the least. The bunnies are given food on standard-issue plates that are stamped with the numbers 0-9 for easier sorting, and you need to combine sets of plates to create the numbers in the code. The signal that a number is part of the code is that it is divisible by 3. You can do smaller numbers like 15 and 45 easily, but bigger numbers like 144 and 414 are a little trickier. Write a program to help yourself quickly create large numbers for use in the code, given a limited number of plates to work with.
You have L, a list containing some digits (0 to 9). Write a function solution(L) which finds the largest number that can be made from some or all of these digits and is divisible by 3. If it is not possible to make such a number, return 0 as the solution. L will contain anywhere from 1 to 9 digits. The same digit may appear multiple times in the list, but each element in the list may only be used once.
Languages
=========
To provide a Java solution, edit Solution.java
To provide a Python solution, edit solution.py
Test cases
==========
Your code should pass the following test cases.
Note that it may also be run against hidden test cases not shown here.
-- Java cases --
Input:
Solution.solution({3, 1, 4, 1})
Output:
4311
Input:
Solution.solution({3, 1, 4, 1, 5, 9})
Output:
94311
-- Python cases --
Input:
solution.solution([3, 1, 4, 1])
Output:
4311
Input:
solution.solution([3, 1, 4, 1, 5, 9])
Output:
94311
Use verify [file] to test your solution and see how it does. When you are finished editing your code, use submit [file] to submit your answer. If your solution passes the test cases, it will be removed from your home folder.
i have tried a solution which is working very correct in my ide(note i wanted a solution without any library)
def solution(l):
# Your code here
if (len(l) == 1 and l[0] % 3 != 0) or (len(l) == 0):
return 0
number = formGreatestNumber(l)
remainder = number % 3
if remainder == 0:
result = formGreatestNumber(l)
return result
result = removeUnwanted(l, remainder)
return result
def formGreatestNumber(li):
li.sort(reverse=True) # descending order
li = [str(d) for d in li] # each digit in string
number = 0
if len(li) > 0:
number = int("".join(li)) # result
return number
def removeUnwanted(l, remainder):
possibleRemovals = [i for i in l if i % 3 == remainder]
if len(possibleRemovals) > 0:
l.remove(min(possibleRemovals))
result = formGreatestNumber(l)
return result
pairs = checkForTwo(l, remainder)
if len(pairs) > 0:
for ind in pairs:
l.remove(ind)
result = formGreatestNumber(l)
return result
else:
divisibleDigits = [d for d in l if d % 3 == 0]
if len(divisibleDigits) > 0:
result = formGreatestNumber(divisibleDigits)
return result
else:
return 0
def checkForTwo(l, remainder): # check of (sum of any two pairs - remainder) is divisible by 3
result = []
for i in range(len(l)):
for j in range(i+1, len(l)):
if ((l[i]+l[j])-remainder) % 3 == 0:
result.append(l[i])
result.append(l[j])
return result
return []
print(solution([]))
print(solution([1]))
print(solution([9]))
print(solution([3, 1, 4, 1, 9, 2, 5, 7]))
however it is on verifying showing-
Verifying solution...
Test 1 passed!
Test 2 passed!
Test 3 failed [Hidden]
Test 4 passed! [Hidden]
Test 5 passed! [Hidden]
so where is the error i am not noticing and is there any other way without any library like itertools?
I won't give away the code and spoil the fun for you, I'll perhaps try to explain the intuition.
About your code, I think the (2nd part of) the function removeUnwanted() is problematic here.
Let's see.
So first off, you'd arrange the input digits into a single number, in order from largest to smallest, which you've already done.
Then if the number formed isn't divisible by 3, try removing the smallest digit.
If that doesn't work, reinsert the smallest digit and remove the 2nd smallest digit, and so on.
Once you're done with removing all possible digits one at a time, try removing digits two at a time, starting with the two smallest.
If any of these result in a number that is divisible by 3, you're done.
Observe that you'll never need to remove more than 2 digits for this problem. The only way it's impossible to form the required number is if there are 2 or lesser digits and they are both either in the set {1,4,7} or {2,5,8}.
Edit: More about your code -
The initial part of your removeUnwanted() looks okay where you check if there's a single digit in the number which can be removed, removing the minimum from the choice of single digits and getting the answer.
I reckon the problem lies in your function checkForTwo(), which you call subsequently in removeUnwanted.
When you're passing the list to checkForTwo(), observe that the list is actually sorted in the decreasing order. This is because li.sort(reverse=True) in your function formGreatestNumber() sorted the list in place, which means the content of list l was sorted in descending order too.
And then in checkForTwo(), you try to find a pair that satisfies the required condition, but you're looping from the biggest 2 pairs that can possibly be removed. i starts from 0 and j starts from i+1 which is 1, and since your list is in descending order, you're trying to remove the biggest 2 elements possible.
A quick fix would be to sort the list in ascending order and then proceed further iterate through the list in reverse order, because since the list is sorted in descending order already, reverse iteration gives you the list in ascending order and saves us from re-sorting which would normally cost an additional O(NlogN) time.

varying degree of shuffling using random module python

I am using two architecture programs, with visual programming plugins (Grasshopper for Rhino and Dynamo for Revit - for those that know / are interested)
Grasshopper contains a function called 'Jitter' this will shuffle a list, however it has an input from 0.0 to 1.0 which controls the degree of shuffling - 0.0 results in no shuffling 1.0 produces a complete shuffle.
The second of the programs (Dynamo) does not contain this functionality. It contains a shuffle module (which contains a seed value) however it is a complete random shuffle.
Ultimately the goal is to produce a series of solid and glazed panels, but to produce a slight random effect (but avoiding large clumping of solid and glazed elements - hence I want a "light shuffle")
I have written a code which will calculate the number of glazed(True) and solid(False) values required and then evenly distribute True and False values based on the number of items and percent specified.
I have checked out the random module reference however I'm not familiar with the various distributions as described.
Could someone help out or point me in the right direction if an existing function would achieve this.
(I have cheated slightly by adding True False alternately to make up the correct number of items within the list - list3 is the final list, list2 contains the repeated module of true falses)
Many thanks
import math
import random
percent = 30
items = 42
def remainder():
remain = items % len(list2)
list3.append(True)
remain -= 1
while remain > 0 :
list3.append(False)
remain -= 1
return list3
#find module of repeating True and False values
list1 = ([True] + [False] * int((100/percent)-1))
#multiply this list to nearest multiple based on len(items)
list2 = list1 * int(items/(100/percent))
# make a copy of list2
list3 = list2[:]
#add alternating true and false to match len(list3) to len(items)
remainder()
#an example of a completely shuffled list - which is not desired
shuffled = random.sample(list3, k = len(list3))
Here is an approach based on this paper which proves a result about the mixing time needed to scramble a list by using swaps of adjacent items
from random import choice
from math import log
def jitter(items,percent):
n = len(items)
m = (n**2 * log(n))
items = items[:]
indices = list(range(n-1))
for i in range(int(percent*m)):
j = choice(indices)
items[j],items[j+1] = items[j+1],items[j]
return items
A test, each line showing the result of jitter with various percents being applied to the same list:
ls = list(('0'*20 + '1'*20)*2)
for i in range(11):
p = i/10.0
print(''.join(jitter(ls,p)))
Typical output:
00000000000000000000111111111111111111110000000000000000000011111111111111111111
00000000000000111100001101111011011111001010000100010001101000110110111111111111
00000000100100000101111110000110111101000001110001101001010101100011111111111110
00000001010010011011000100111010101100001111011100100000111010110111011001011111
00100001100000001101010000011010011011111011001100000111011011111011010101011101
00000000011101000110000110000010011001010110011111100100111101111011101100111110
00110000000001011001000010110011111101001111001001100101010011010111111011101100
01101100000100100110000011011000001101111111010100000100000110111011110011011111
01100010110100010100010100011000000001000101100011111011111011111011010100011111
10011100101000100010001100100000100111001111011011000100101101101010101101011111
10000000001000111101101011000011010010110011010101110011010100101101011110101110
I'm not sure how principled the above is, but it seems like a reasonable place to start.
There's no clear definition of what "degree of shuffling" (d) means, so you'll need to choose one. One option would be: "the fraction of items remaining unshuffled is (1-d)".
You could implement that as:
Produce a list of indices
Remove (1-d)*N of them
Shuffle the rest
Reinsert the ones removed
Use these to look up values from the original data
def partial_shuffle(x, d):
"""
x: data to shuffle
d: fraction of data to leave unshuffled
"""
n = len(x)
dn = int(d*n)
indices = list(range(n))
random.shuffle(indices)
ind_fixed, ind_shuff = indices[dn:], indices[:dn]
# copy across the fixed values
result = x[:]
# shuffle the shuffled values
for src, dest in zip(ind_shuff, sorted(ind_shuff)):
result[dest] = x[src]
return result
The other algorithms you're referring to are probably using the Fisher-Yates shuffle under the hood.
This O(n) shuffle starts with the first element of an array and swaps it with a random higher element, then swaps the second element with a random higher element, and so on.
Naturally, stopping this shuffle before you reach the last element at some fraction [0,1] would give a partially-randomized array, like you want.
Unfortunately, the effect of the foregoing is that all the "randomness" builds up on one side of the array.
Therefore, make a list of array indices, shuffle these completely, and then use the indices as an input to the Fisher-Yates algorithm to partially sort the original array.
I believe I found a more versatile, robust, and a consistent way to implement this "adjustable shuffling" technique.
import random
import numpy as np
def acc_shuffle(lis, sr, array=False, exc=None): # "sr" = shuffling rate
if type(lis) != list: # Make it compatible with shuffling (mxn) numpy.ndarrays
arr = lis
shape = arr.shape
lis = list(arr.reshape(-1))
lis = lis[:] # Done, such that any changes applied on "lis" wont affect original input list "x"
indices = list(range(len(lis)))
if exc is not None: # Exclude any indices if necessary
for ele in sorted(exc, reverse=True):
del indices[ele]
shuff_range = int(sr * len(lis) / 2) # How much to shuffle (depends on shuffling rate)
if shuff_range < 1:
shuff_range = 1 # "At least one shuffle (swap 2 elements)"
for _ in range(shuff_range):
i = random.choice(indices)
indices.remove(i) # You can opt not to remove the indices for more flexibility
j = random.choice(indices)
indices.remove(j)
temp = lis[i]
lis[i] = lis[j]
lis[j] = temp
if array is True:
return np.array(lis).reshape(shape)
return lis

Cycle detection in a 2-tuple python list

Given a list of edges in 2-tuple, (source, destination), is there any efficient way to determine if a cycle exists? Eg, in the example below, a cycle exists because 1 -> 3 -> 6 -> 4 -> 1. One idea is to calculate the number of occurrence of each integer in the list (again, is there any efficient way to do this?). Is there any better way? I am seeing a problem with 10,000 of 2-tuple edge information.
a = [(1,3), (4,6), (3,6), (1,4)]
I'm assuming you want to find a cycle in the undirected graph represented by your edge list and you don't want to count "trivial" cycles of size 1 or 2.
You can still use a standard depth-first search, but you need to be a bit careful about the node coloring (a simple flag to signal which nodes you have already visited is not sufficient):
from collections import defaultdict
edges = [(1,3), (4,6), (3,6), (1,4)]
adj = defaultdict(set)
for x, y in edges:
adj[x].add(y)
adj[y].add(x)
col = defaultdict(int)
def dfs(x, parent=None):
if col[x] == 1: return True
if col[x] == 2: return False
col[x] = 1
res = False
for y in adj[x]:
if y == parent: continue
if dfs(y, x): res = True
col[x] = 2
return res
for x in adj:
if dfs(x):
print "There's a cycle reachable from %d!" % x
This will detect if there is a back edge in the depth-first forest that spans at least 2 levels. This is exactly the case if there is a simple cycle of size >= 2. By storing parent pointers you can actually print the cycle as well if you found it.
For large graphs you might want to use an explicit stack instead of recursion, as illustrated on Wikipedia.

Finding the maximum height of a tree and returning the sum of that path

1
2 3
returns 1 + 3 = 4
I want to first find the maximum height of a tree and then find the sum of all its nodes.
If two path has the same height, only the path with larger sum will be return.
sorry for my bad examples... all i want to express is that a tree like above
should have the tree list like [1, [2, None, None], [3, None, None]] instead of [1,2,3]
Recursive function as recommended by Egg:
def sum_of_longest_branch(tree):
"""
Parses a tree and returns a tuple containing (depth, sum) of deepest branch.
"""
# stop conditions on leave nodes (can be single [node] or None)
if 1 == len(tree):
return 1, tree[0]
elif None == tree:
return 1, 0
# splitting the branches
else:
# calling the function recursively on all branches branching from current node
branches_sums = [sum_of_longest_branch(branch) for branch in tree[1:]]
# extracting the currently deepest branch
branch, sum = sorted(branches_sums, reverse=True)[0]
# add own node value and one depth before returning
return branch + 1, sum + tree[0]
Example:
tree = [1, [2, [4]], [3, [0]]]
depth, sum = sum_of_longest_branch(tree)
print depth, sum
Gives:
3, 7
Sorry if it's quick & dirty, but it works. The problem is actually not that trivial, especially for a beginner to programming / python. I hope its understandable.
Edit: Now checks first for depth and secondarily for the sum.
def tree_height(tree):
if (isinstance(tree, list)):
tree = tree[1:]
if (tree):
return (1 + max([tree_height(x) for x in tree]))
return 0
def tree_sum(tree):
if tree and (isinstance(tree, list)):
return tree[0] + sum([tree_sum(x) for x in tree[1:]])
return (tree or 0)
Are these values weights? Is the tree sorted in any way (is there a particular order to the tree)?
If so, maybe you can do a modified version of Dijkstra's Algorithm where you take the longest distance at each junction instead of the shortest, and instead of a min-priority queue use a stack so you traverse depth first instead of breadth first.
EDIT:
Now that I think about it, perhaps using a max-priority queue would work better. I'm still not sure what you are trying to accomplish. What I think you are asking for is the path with the largest sum, which doesn't necessarily mean it will be the branch with the most nodes. The path with the most nodes, given that each node appears to have a weight, seems meaningless because there could be shorter paths with greater weight.

Categories

Resources