I'm doing Advent of Code and until this point I had no problem to solve issues on my own until day 18 which got me. I want to solve it on my own but I feel like I can't even start however the task itself is not difficult. It's kinda long to elaborate but the point is the following:
I have to implement nested pairs, so every item of a pair can be an another pair and so on, for example:
[[[[[9,8],1],2],3],4]
[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]
I have to do different operations on these pairs, such as deleting pairs on a very deep level and use its values at higher level. I know Python handles tuples really great but I have yet to find a solution for recursive (?) traversal, deletion and "saving" values from the "deep". It's not a wise solution to delete items during iterating. Shall I use a different approach or some custom data structure for a task like this? I don't need an exact solution just some generic guidance.
I haven't read problem 18 from advent of code, but here is an example answer to your question with a list version and a tuple version.
Imagine I have nested pairs, and I want to write a function that delete all the deepest pairs and replaced them with a single number being the sum of the two numbers in the pair. For example:
input -> output
(1, 2) -> 3
((1, 2), 3) -> (3, 3)
(((1,2), (3,4)), ((5,6), (7,8))) -> ((3, 7), (11, 15))
((((((1, 2), 3), 4), 5), 6), 7) -> (((((3, 3), 4), 5), 6), 7)
Here is a version with immutable tuples, that returns a new tuple:
def updated(p):
result, _ = updated_helper(p)
return result
def updated_helper(p):
if isinstance(p, tuple) and len(p) == 2:
a, b = p
new_a, a_is_number = updated_helper(a)
new_b, b_is_number = updated_helper(b)
if a_is_number and b_is_number:
return a+b, False
else:
return (new_a, new_b), False
else:
return p, True
Here is a version with mutable lists, that returns nothing useful but mutates the list:
def update(p):
if isinstance(p, list) and len(p) == 2:
a, b = p
a_is_number = update(a)
b_is_number = update(b)
if isinstance(a, list) and len(a) == 1:
p[0] = a[0]
if isinstance(b, list) and len(b) == 1:
p[1] = b[0]
if a_is_number and b_is_number:
p[:] = [a+b]
return False
else:
return True
Note how I used a substantive, updated, and a verb, update, to highlight the different logic between these two similar functions. Function update performs an action (modifying a list), whereas updated(p) doesn't perform any action, but is the updated pair.
Testing:
print( updated( (((1,2), (3,4)), ((5,6), (7,8))) ) )
# ((3, 7), (11, 15))
l = [[[1,2], [3,4]], [[5,6], [7,8]]]
update(l)
print(l)
# [[3, 7], [11, 15]]
Related
I have a "large" list of tuples:
thelist=[(1,2),(1,3),(2,3)]
I want to check whether any tuple in the list starts with a 1, and if it does, print "aaa":
for i in thelist:
templist.append((i[0],i))
for i in templist:
if i[0]==1:
print("aaa")
break
Which is rather ardurous as I have to create the templist. Is there any way I can do this:
if (1,_) in thelist:
print("aaa")
Where _ is the universal selector. Note that the list would be very large and thus it is very costly to implement another list.
There isn't, although you can just use any
any(i[0] == 1 for i in thelist) --> Returns true if the first element is 1
If you don’t actually need the actual tuple, like you do in your example, then you can actually use tuple unpacking for exactly that purpose:
>>> the_list = [(1, 2), (1, 3), (2, 3)]
>>> for x, y in the_list:
if x == 1:
print('aaa')
break
aaa
If you add a * in front of the y, you can also unpack tuples of different sizes, collecting the remainder of the tuple:
>>> other_list = [(1, 2, 3, 4, 5), (1, 3), (2, 3)]
>>> for x, *y in other_list:
if x == 1:
print(y)
break
[2, 3, 4, 5]
Otherwise, if you just want to filter your list based on some premise and then do something on those filtered items, you can use filter with a custom function:
>>> def startsWithOne(x):
return x[0] == 1
>>> thelist = [(1, 2), (1, 3), (2, 3)]
>>> for x in filter(starts_with_one, the_list):
print(x)
(1, 2)
(1, 3)
This is probably the most flexible way which also avoids creating a separate list in memory, as the elements are filtered lazily when you interate the list with your loop.
Finally, if you just want to figure out if any of your items starts with a 1, like you do in your example code, then you could just do it like this:
>>> if any(filter(starts_with_one, the_list)):
print('aaa')
aaa
But I assume that this was just an oversimplified example.
Is there a faster way to write this, the function takes a list and a value to find the pairs of numeric values in that list that sum to N without duplicates I tried to make it faster by using sets instead of using the list itself (however I used count() which I know is is linear time) any suggestions I know there is probably a way
def pairsum_n(list1, value):
set1 = set(list1)
solution = {(min(i, value - i) , max(i, value - i)) for i in set1 if value - i in set1}
solution.remove((value/2,value/2)) if list1.count(value/2) < 2 else None
return solution
"""
Example: value = 10, list1 = [1,2,3,4,5,6,7,8,9]
pairsum_n = { (1,9), (2,8), (3,7), (4,6) }
Example: value = 10, list2 = [5,6,7,5,7,5,3]
pairsum_n = { (5,5), (3,7) }
"""
Your approach is quite good, it just needs a few tweaks to make it more efficient. itertools is convenient, but it's not really suitable for this task because it produces so many unwanted pairs. It's ok if the input list is small, but it's too slow if the input list is large.
We can avoid producing duplicates by looping over the numbers in order, stopping when i >= value/2, after using a set to get rid of dupes.
def pairsum_n(list1, value):
set1 = set(list1)
list1 = sorted(set1)
solution = []
maxi = value / 2
for i in list1:
if i >= maxi:
break
j = value - i
if j in set1:
solution.append((i, j))
return solution
Note that the original list1 is not modified. The assignment in this function creates a new local list1. If you do actually want (value/2, value/2) in the output, just change the break condition.
Here's a slightly more compact version.
def pairsum_n(list1, value):
set1 = set(list1)
solution = []
for i in sorted(set1):
j = value - i
if i >= j:
break
if j in set1:
solution.append((i, j))
return solution
It's possible to condense this further, eg using itertools.takewhile, but it will be harder to read and there won't be any improvement in efficiency.
Try this, running time O(nlogn):
v = [1, 2, 3, 4, 5, 6, 7, 8, 9]
l = 0
r = len(v)-1
def myFunc(v, value):
ans = []
% this block search for the pair (value//2, value//2)
if value % 2 == 0:
c = [i for i in v if i == value // 2]
if len(c) >= 2:
ans.append((c[0], c[1]))
v = list(set(v))
l = 0
r = len(v)-1
v.sort()
while l<len(v) and r >= 0 and l < r:
if v[l] + v[r] == value:
ans.append((v[l], v[r]))
l += 1
r -= 1
elif v[l] + v[r] < value:
l += 1
else:
r -= 1
return list(set(ans))
It is called the Two pointers technique and it works as follows. First of all, sort the array. This imposes a minimum running time of O(nlogn). Then set two pointers, one pointing at the start of the array l and other pointing at its last element r (pointers name are for left and right).
Now, look at the list. If the sum of the values returned at position l and r is lower than the value we are looking for, then we need to increment l. If it's greater, we need to decrement r.
If v[l] + v[r] == value than we can increment/decrement both l or r since in any case we want to skip the combination of values (v[l], v[r]) as we don't want duplicates.
Timings: this is actually slower then the other 2 solutions. Due to the amount of combinations produced but not actually needed it gets worse the bigger the lists are.
You can use itertools.combinations to produce the 2-tuple-combinations for you.
Put them into a set if they match your value, then return as set/list:
from itertools import combinations
def pairsum_n(list1, value):
"""Returns the unique list of pairs of combinations of numbers from
list1 that sum up `value`. Reorders the values to (min_value,max_value)."""
result = set()
for n in combinations(list1, 2):
if sum(n) == value:
result.add( (min(n),max(n)) )
return list(result)
# more ugly one-liner:
# return list(set(((min(n),max(n)) for n in combinations(list1,2) if sum(n)==value)))
data = [1,2,3,4,5,6,6,5,4,3,2,1]
print(pairsum_n(data,7))
Output:
[(1, 6), (2, 5), (3, 4)]
Fun little thing, with some sorting overhead you can get all at once:
def pairsum_n2(data, count_nums=2):
"""Generate a dict with all count_nums-tuples from data. Key into the
dict is the sum of all tuple-values."""
d = {}
for n in (tuple(sorted(p)) for p in combinations(data,count_nums)):
d.setdefault(sum(n),set()).add(n)
return d
get_all = pairsum_n2(data,2) # 2 == number of numbers to combine
for k in get_all:
print(k," -> ", get_all[k])
Output:
3 -> {(1, 2)}
4 -> {(1, 3), (2, 2)}
5 -> {(2, 3), (1, 4)}
6 -> {(1, 5), (2, 4), (3, 3)}
7 -> {(3, 4), (2, 5), (1, 6)}
2 -> {(1, 1)}
8 -> {(2, 6), (4, 4), (3, 5)}
9 -> {(4, 5), (3, 6)}
10 -> {(5, 5), (4, 6)}
11 -> {(5, 6)}
12 -> {(6, 6)}
And then just access the one you need via:
print(get_all.get(7,"Not possible")) # {(3, 4), (2, 5), (1, 6)}
print(get_all.get(17,"Not possible")) # Not possible
Have another solution, it's alot faster then the one I just wrote, not as fast as #PM 2Ring's answer:
def pairsum_n(list1, value):
set1 = set(list1)
if list1.count(value/2) < 2:
set1.remove(value/2)
return set((min(x, value - x) , max(x, value - x)) for x in filterfalse(lambda x: (value - x) not in set1, set1))
I was trying to write a function that inputs a nested tuple and returns a tuple where all the elements are backwards, including those elements in other tuples (basically mirrors it).
So with this input:
((1, (2, 3)), (4, 5))
It should return:
((5, 4), ((3, 2), 1))
What I tried
def mirror(t):
n = 1
for i in t:
if isinstance(i, tuple):
mirror(i)
if n == len(t):
t = list(t)
t = t[::-1]
t = tuple(t)
n += 1
return t
Maybe I'm missing something, but I think it can be done relatively simply:
def mirror(data):
if not isinstance(data, tuple):
return data
return tuple(map(mirror, reversed(data)))
>>> mirror(((1, (2, 3)), (4, 5)))
((5, 4), ((3, 2), 1))
This applies the mirror function to every element in the tuple, combining them into one new tuple in reverse order.
The trickiness of this problem lies in the fact that tuple objects are immutable. One solution I can think of is recursively building each piece in the final reversed result, and then using itertools to join them together.
from itertools import chain
def mirror(data):
r = []
for t in reversed(data):
if isinstance(t, tuple):
t = mirror(t)
r.append((t, ))
return tuple(chain.from_iterable(r))
>>> mirror(((1, (2, 3)), (4, 5)))
((5, 4), ((3, 2), 1))
Thanks to Chris_Rands for the improvement.
Here's a simpler solution, courtesy PM2 Ring -
def mirror(t):
return tuple(mirror(u) for u in t[::-1]) if isinstance(t, tuple) else t
>>> mirror(((1, (2, 3)), (4, 5)))
((5, 4), ((3, 2), 1))
It builds the result tuple recursively but using a gen comp.
This type of structure, list inside list, is called hierarchical structure, which has property that the whole structure is assembled by small structures which resemble the large structure and are again assembled by even smaller structures.
Imaging a tree with branches resembled the whole tree and leaves at the tips. The first thing is to distinguish branches from leaves. If you see a branch, you treat it as a smaller tree (this naturally forms a recursion). If you see a leave, that means you get to the tip of the structure and you can return it (base case in recursion).
To go from bigger branch to smaller branches (deduction in recursion), there are generally two recursive approaches. The first is as what I did, splitting the branch to left and right and going along each of them. The other way is to map on each branch as what had been done by khelwood.
def mirror(T):
if not isinstance(T, tuple):
return T
elif T == ():
return ()
else:
return mirror(T[1:]) + (mirror(T[0]),)
print(mirror(((1,(2,3)),(4,5))))
Couldn't help myself :)
(This is a joke of course, but has the added benefit of reversing the digits ;)
def rev(s, i, acc):
if i == len(s):
return acc
ps = {'(': ')', ')': '('}
return rev(s, i + 1, s[i] + acc) if not s[i] in ps else rev (s, i + 1, ps[s[i]] + acc)
def funnyMirror(t):
return eval(rev(str(t), 0, ''))
print funnyMirror(((1, (2, 83)), (4, 5))) # ((5, 4), ((38, 2), 1))
I'm trying to write a recursive function that will look for matching items in a list of tuples, and distribute them into groups. An example list could be:
l = [(1,2),(2,3),(4,6),(3,5),(7,9),(6,8)]
And the intent is to end with three sublists in the form of:
l = [(1,2),(2,3),(3,5)],[(4,6),(6,8)],[7,9]]
This appears to be an ideal situation for a recursive function, but I haven't made it work yet. Here is what I have written so far:
count = 0
def network(index_list, tuples_list, groups, count):
if len(index_list) > 0:
i = 0
for j in range (len(index_list)):
match = set(tuples_list[groups[count][i]]) & set(tuples_list[index_list[j]])
if len(match) > 0:
groups[count].append(index_list[j])
index_list.pop(j)
i += 1
else:
count += 1
groups.append([])
groups[count].append(index_list[0])
network(index_list, tuples_list, groups, count)
else:
return groups
Also I'm pretty certain this question is different than the one marked duplicate. I'm looking for a recursive solution that keeps all of the tuples intact, in this case by pushing around their indices with append and pop. I'm positive there's an elegant and recursive solution to this problem.
I'm not sure recursion is helpful here. It was an interesting thought experiment, so I tried it out, but my recursive function only ever needs one run-through.
Modified based on comment to only match the first two data points of a tuple
import copy
#Inital 1-item groups
def groupTuples(tupleList):
groups = []
for t in tupleList:
groups.append([t])
return groups
#Merge two groups
def splice(list1, list2, index):
for item in list2:
list1.insert(index, item)
index += 1
return list1
def network(groups = []):
#Groups get modified by reference, need a shallow copy for reference
oldGroups = copy.copy(groups)
for group in groups:
for index, secondGroup in enumerate(groups):
if group == secondGroup:
#Splice can get caught in a loop if passed the same item twice.
continue
#Last item of First Group matches First item of Second Group
if group[-1][1] == secondGroup[0][0]:
splice(group, secondGroup, len(group))
groups.pop(index)
#First item of First Group matches Last item of Second Group
elif group[0][0] == secondGroup[-1][1]:
splice(group, secondGroup, 0)
groups.pop(index)
if groups == oldGroups:
return groups #No change.
else:
return network(groups)
l = [(1,2,0),(2,3,1),(4,6,2),(3,5,3),(1,7,4),(7,9,5),(6,8,6),(12, 13,7),(5, 12,8)]
result = network(groupTuples(l))
print result
Result: [[(1, 2, 0), (2, 3, 1), (3, 5, 3), (5, 12, 8), (12, 13, 7)], [(1, 7, 4), (7, 9, 5)], [(4, 6, 2), (6, 8, 6)]]
It does run the matching algorithm over the dataset multiple times, but I've yet to see a configuration of data where the second iteration yields any changes, so not sure recursion is necessary here.
Earlier I had a lot of wonderful programmers help me get a function done. however the instructor wanted it in a single loop and all the working solutions used multiple loops.
I wrote an another program that almost solves the problem. Instead of using a loop to compare all the values, you have to use the function has_key to see if that specific key exists. Answer of that will rid you of the need to iter through the dictionary to find matching values because u can just know if they are matching or not.
again, charCount is just a function that enters the constants of itself into a dictionary and returns the dictionary.
def sumPair(theList, n):
for a, b in level5.charCount(theList).iteritems():
x = n - a
if level5.charCount(theList).get(a):
if a == x:
if b > 1: #this checks that the frequency of the number is greater then one so the program wouldn't try to multiply a single possibility by itself and use it (example is 6+6=12. there could be a single 6 but it will return 6+6
return a, x
else:
if level5.charCount(theList).get(a) != x:
return a, x
print sumPair([6,3,8,3,2,8,3,2], 9)
I need to just make this code find the sum without iteration by seeing if the current element exists in the list of elements.
You can use collections.Counter function instead of the level5.charCount
And I don't know why you need to check if level5.charCount(theList).get(a):. I think it is no need. a is the key you get from the level5.charCount(theList)
So I simplify you code:
form collections import Counter
def sumPair(the_list, n):
for a, b in Counter(the_list).iteritems():
x = n - a
if a == x and b >1:
return a, x
if a != x and b != x:
return a, x
print sumPair([6, 3, 8, 3, 2, 8, 3, 2], 9) #output>>> (8, 1)
The also can use List Comprehension like this:
>>>result = [(a, n-a) for a, b in Counter(the_list).iteritems() if a==n-a and b>1 or (a != n-a and b != n-a)]
>>>print result
[(8, 1), (2, 7), (3, 6), (6, 3)]
>>>print result[0] #this is the result you want
(8, 1)