1

I have a list of strings which looks like this:

['(num1, num2):1', '(num3, num4):1', '(num5, num6):1', '(num7, num8):1']

What I try to achieve is to reduce this list and combine every two elements and I want to do this until there is only one big string element left. So the intermediate list would look like this:

['((num1, num2):1,(num3, num4):1)', '((num5, num6):1,(num7, num8):1)']

The complicated thing is (as you can see in the intermediate list), that two strings need to be wrapped in paranthesis. So for the above mentioned starting point the final result should look like this:

(((num_1,num_2):1,(num_3,num_4):1),((num_5,num_6):1,(num_7,num_8):1))

Of course this should work in a generic way also for 8, 16 or more string elements in the starting list. Or to be more precise it should work for an=2(n+1).

Just to be very specific how the result should look with 8 elements:

'((((num_1,num_2):1,(num_3,num_4):1),((num_5,num_6):1,(num_7,num_8):1)),(((num_9,num_10):1,(num_11,num_12):1),((num_13,num_14):1,(num_15,num_16):1)))'

I already solved the problem using nested for loops but I thought there should be a more functional or short-cut solution.

I also found this solution on stackoverflow:

import itertools as it
l = [map( ",".join ,list(it.combinations(my_list, l))) for l in range(1,len(my_list)+1)]

Although, the join isn't bad, I still need the paranthesis. I tried to use:

"{},{}".format

instead of .join but this seems to be to easy to work :).

I also thought to use reduce but obviously this is not the right function. Maybe one can implement an own reduce function or so?

I hope some advanced pythonics can help me.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Tobias
  • 564
  • 3
  • 13
  • 1
    Your intermediate list has some extra quotes in it... – Mad Physicist Aug 01 '18 at 14:31
  • 1
    Also, could you show your nested loops solution? – Mad Physicist Aug 01 '18 at 14:36
  • 1
    Also, what happens if your list does not have a power of two number of elements? – Mad Physicist Aug 01 '18 at 14:37
  • 2
    Nobody else thinks this is an extremely weird request, an inefficient way to store data and probaly an XY problem? No? Ok... (For starters, why is the initial list not an ordered dict?) – timgeb Aug 01 '18 at 14:42
  • @MadPhysicist Sorry for the extra quotes. I edited my post. Furthermore, I will add the nested for-loop code tomorrow (I'm in a hurry). The use-case is only for the definded number of elements. Since I create a class, I will give an error if the number is not a correct input. – Tobias Aug 01 '18 at 14:43
  • Your itertools example does not do what you claim it does at all. Have you tried running it? Or does it do something different on Python 2 than it does on Python 3? – Mad Physicist Aug 01 '18 at 14:44
  • @timgeb I know this is a very specific use case and definitely not a common way to store data. However, the resulting string is also only an intermediate which will be used for further processing. – Tobias Aug 01 '18 at 14:45
  • @MadPhysicist If I use the itertools code it works not as I need it. As I mentioned I need the paranthesis around the intermediate products which is not the case while using `join` – Tobias Aug 01 '18 at 14:48
  • @Tobias. Running the example in Py3 gives a list containing four map objects. – Mad Physicist Aug 01 '18 at 14:49

2 Answers2

4

Sounds like a job for the zip clustering idiom: zip(*[iter(x)]*n) where you want to break iterable x into size n chunks. This will discard "leftover" elements that don't make up a full chunk. For x=[1, 2, 3], n=2 this would yield (1, 2)

def reducer(l):
    while len(l) > 1:
        l = ['({},{})'.format(x, y) for x, y in zip(*[iter(l)]*2)]
    return l

reducer(['(num1, num2):1', '(num3, num4):1', '(num5, num6):1', '(num7, num8):1'])
# ['(((num1, num2):1,(num3, num4):1),((num5, num6):1,(num7, num8):1))']
Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • Thanks, this is exactly what I need. This code is awesome. Just to understand the code, let me recap what is happening. I will do this in another answer. It would be great if you can agree/disagree on my explanantion and update it, if it is missing a detail. – Tobias Aug 02 '18 at 06:33
0

This is an explanation of what is happening in zip(*[iter(l)]*2)

[iter(l)*2] This creates an list of length 2 with two times the same iterable element or to be more precise with two references to the same iter-object.

zip(*...) does the extracting. It pulls:

  1. Loop

    • the first element from the first reference of the iter-object
    • the second element from the second reference of the iter-object
  2. Loop

    • the third element from the first reference of the iter-object
    • the fourth element from the second reference of the iter object
  3. Loop

    • the fifth element from the first reference of the iter-object
    • the sixth element from the second reference of the iter-object

and so on...

Therefore we have the extracted elements available in the for-loop and can use them as x and y for further processing.

This is really handy.

I also want to point to this thread since it helped me to understand the concept.

Tobias
  • 564
  • 3
  • 13