Mix two asymmetrical list in python - python

>>> import itertools
>>> a = ['1', '2', '3', '4', '5']
>>> b = ['a', 'b', 'c', 'd', 'e', 'f']
>>> list(itertools.chain.from_iterable(zip(a,b)))
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e']
As you can see, I have two asymmetrical list and I want to mix them like above. The problem is it ignore the last item.
Expected:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', 'f']
Actual:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e']

Since you're using itertools in the first place, I assume you want this to work on any iterables, not just lists, and ideally without eagerly listifying them first. Otherwise, just do this:
list(itertools.chain.from_iterable(zip(a,b))) + a[len(b):] + b[len(a):]
The zip_longest function almost does what you want out of the box, but it inserts a fillvalue (default None) for each slot once the shorter iterable runs out. If your values are all truthy, you can just filter those out with if i as in Ajax1234's answer, or filter with None as the predicate, but if your values can be anything in Python, even None, the only way to do it gets pretty clunky:
_sentinel = object()
[elem for elem in itertools.chain.from_iterable(itertools.zip_longest(a, b, fillvalue=_sentinel))
if elem is not _sentinel]
But you can look at how zip_longest works and do the same thing yourself, only generating "incomplete" tuples instead of "filled-in" tuples, and then call it like this:
list(itertools.chain.from_iterable(zip_longest_nofill(a, b)))
Although making a variant of the zip_longest code from the docs that's easy enough to explain in an SO answer is a bit challenging, so maybe it's better to use an explicit loop:
def zip_longest_nofill(*args):
empty = object()
its = [iter(arg) for arg in args]
while True:
vals = (next(it, empty) for it in its)
tup = tuple(val for val in vals if val is not empty)
if not tup:
return
yield tup
I think this version is a lot easier to understand (although it was actually a bit harder to write…)
Of course if the only thing you're ever going to use zip_longest_nofill for is to implement your flattened_zip_nofill, it's even easier to just inline it into the flattening part, at which point you end up with basically the two-liner in the last section.

Instead of chain, use zip_longest:
import itertools
a = ['1', '2', '3', '4', '5']
b = ['a', 'b', 'c', 'd', 'e', 'f']
new_results = [i for b in itertools.zip_longest(a, b) for i in b if i is not None]
Output:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', 'f']

Just manually append the remaining:
def mix(a, b):
c = list(itertools.chain.from_iterable(zip(a,b)))
c += a[len(b)] + b[len(a):]
return c
One liner:
mix = lambda a, b: list(itertools.chain.from_iterable(zip(a,b))) + a[len(b)] + b[len(a):]

This should work but it's not very elegant
lst = []
for i in range(temp = max(len(a), len(b))):
if i < len(a): lst.append(a[i])
if i < len(b): lst.append(b[i])
lst

You can try itertools zip_longest:
a = ['1', '2', '3', '4', '5']
b = ['a', 'b', 'c', 'd', 'e', 'f']
import itertools
output=[]
for i in itertools.zip_longest(a,b):
if i[0]==None:
output.append(i[1])
else:
output.extend([i[0],i[1]])
print(output)
output:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', 'f']

Related

Split a list before a slash appears in an element for Python

I have a list
list = ['09/30', '16:30','A','B','5','100','10/01','16:30','C','D','4',
'10/02','16:30','E','F']
I want it split into a new list every time before the elements with the /.
Looking to have something like this:
list = ['09/30', '16:30','A','B','5','100'], ['10/01','16:30','C','D','4'], ['10/02','16:30','E','F']
I would then like to fill in the shorter lists with NAs
list = [['09/30', '16:30','A','B','5','100'], ['10/01','16:30','C','D','4', 'NA'], ['10/02','16:30','E','F', 'NA', 'NA]]
I'm sure this is simple and I'm just missing it.
You can easily use itertools.groupby to group consecutive elements under the same "slash" element. Then get the maximum length of a group and append "NA" to the others less than that.
from itertools import groupby, zip_longest
class SlashGrouper:
def __init__(self):
self.group_id = 0
def __call__(self, text):
if "/" in text:
self.group_id += 1
return self.group_id
my_list = ['09/30', '16:30','A','B','5','100','10/01','16:30','C','D','4', '10/02','16:30','E','F']
my_list_grouped = [list(group) for _, group in groupby(my_list, key=SlashGrouper())]
print(my_list_grouped)
max_len = len(max(my_list_grouped, key=lambda val: len(val)))
for group in my_list_grouped:
group.extend(["NA"] * (max_len - len(group)))
print(my_list_grouped)
Output
[['09/30', '16:30', 'A', 'B', '5', '100'], ['10/01', '16:30', 'C', 'D', '4'], ['10/02', '16:30', 'E', 'F']]
[['09/30', '16:30', 'A', 'B', '5', '100'], ['10/01', '16:30', 'C', 'D', '4', 'NA'], ['10/02', '16:30', 'E', 'F', 'NA', 'NA']]

How to convert string elements in a list into integers

I have this list:
new_list = ['a', '1', '--', '2', 'c', '3', 'd', '4', 'e' '5', 'f', '6', 'g', '7', 'h', '8', 'i']
It contains both numbers and words, however, the numbers are seen as strings and not integers.
I want to convert the numbers from strings to integers.
I tried with this myself:
for number in new_list:
if number.isalpha():
continue
else:
int(number)
It looks through the list and if it's something with letters it continues, however, it doesn't work when it seems "special characters" such as the two lines on the third element. I get an error message there.
I also tried this:
for number, element in enumerate(lista_lista):
if number.isalpha() == False:
int(number)
This only looks at every other element, which is a number, and uses isalpha(), and if that's False (which it should be), then I convert, but this doesn't work either.
Use number.isdigit() to recognize the numeric elements. Just because it's not alphabetic, it doesn't mean it's a number.
list_with_numers = [int(x) if x.isdigit() else x for x in new_list]
You can try this:
new_list = ['a', '1', '--', '2', 'c', '3', 'd', '4', 'e' '5', 'f', '6', 'g', '7', 'h', '8', 'i']
def to_int(x):
try:
return int(x)
except:
return x
[to_int(x) for x in new_list]
# Out[4]: ['a', 1, '--', 2, 'c', 3, 'd', 4, 'e5', 'f', 6, 'g', 7, 'h', 8, 'i']
This solution should be more performant than approaches like: int(x) if x.isdigit(), because you do not have to have call 2 different operations, like check if value is a digit and then apply the int conversion.
he below is a simple code without any error that you can use to fulfill your requirement, which is also easy to understand. Please have a check,
new_list = ['a','1','-','2','c','3','d','4','e','5','f','6','g','7','h','8','i']
for ind in range(len(new_list)):
if new_list[ind].isdigit():
new_list[ind]=int(new_list[ind])
else:
continue
print(new_list)
You can use str.isdigit and use list comprehension to modify like:
new_list = ['a', '1', '--', '2', 'c', '3', 'd', '4', 'e' '5', 'f', '6', 'g', '7', 'h', '8', 'i']
modified_list = [int(el) if el.isdigit() else el for el in new_list]
But it won't work for floats or negative integers in string form e.g. '-9', '11.5', if you need that you could do:
def convert_to_number(s):
try:
return int(s)
except:
try:
return float(s)
except:
return s
new_list = ['a', '1', '--', '2', 'c', '3', 'd', '4', 'e' '5', 'f', '6', 'g', '7', 'h', '8', 'i' ,'-9', '11.5']
print([convert_to_number(el) for el in new_list])
Output:
['a', 1, '--', 2, 'c', 3, 'd', 4, 'e5', 'f', 6, 'g', 7, 'h', 8, 'i', -9, 11.5]
Go for something like this, using list comprehension.
old_list = ['a', '1', '--', '2', 'c', '3', 'd', '4', 'e' '5', 'f', '6', 'g', '7', 'h', '8', 'i']
new_list = [int(character) if character.isdigit() else character for character in old_list]
output
['a', 1, '--', 2, 'c', 3, 'd', 4, 'e5', 'f', 6, 'g', 7, 'h', 8, 'i']
Let's analyze your code.
for number in new_list:
if number.isalpha():
continue
else:
int(number)
First of all, you iterate through new_list correctly. Now you also check if number is an alphabet that is correct. But you need to take action on that. Instead, you use continue. I suggest appending number to a list. Say it is not an alphabet, we try and turn number into an int. Sure, this works out. But your new_list will not change. Instead, you'd probably want to append number to a list. One problem I've spotted is, what if a character in the list is --. This is not an alphabet and not an integer. So by default, we will move to the else and try and perform int('--') which will return an error. So using .isdigit() is the best bet.
Use isdigit instead:
[int(x) if x.isdigit() else x for x in new_list]

How to generate an array of strings based on a certain set of parameters?

I'm trying to generate an array of strings (or any other data structure that might be more useful for my task, but I can't think of anything else) in Python.
The program I'm working on has several sets of radio buttons. For example a set of "Block"/"Alternate" and "Single"/"Duplicate".
Examples on how the array of strings should look when they are activated:
Block, Single:
list = ['A', 'B', 'C', '1', '2', '3']
Alternating, Single:
list = ['A', '1', 'B', '2', 'C', '3']
Alternating, Duplicate:
list = ['A', 'A', '1', '1', 'B', 'B', '2', '2', 'C', 'C', '3', '3']
Those are only several examples, the program has way more, but the concept is the same.
I need to read this array of strings and use it as a schema of sorts to further select some data from my Pandas Dataframe.
How would I go about generating this array without writing an if clause for every single possible combination?
I tried to solve this using Jupyter Notebook and some widgets for the user interaction. I don' know how you get this informations but to replicate the same behaviour I am using this.
In [1]:
import ipywidgets as widgets # Widgets for user interactions
## Change the list as you want
my_list = ['A', 'B', 'C', '1', '2', '3']
single_duplicate = widgets.ToggleButtons(
description='Do you want to Duplicate the list ?',
options=['Single', 'Duplicate'],
value='Single',
style={'description_width': 'initial'}
)
single_duplicate
Out [1]:
In [2]:
block_alter = widgets.ToggleButtons(
description='Do you want to Alternate the list?',
options=['Block', 'Alternate'],
value='Block',
style={'description_width': 'initial'}
)
block_alter
Out [2]:
Then define the function to manipulate the list with the user input
In [3]:
def get_array(single_duplicate, block_alter, my_list):
temporary_list = []
## Answer to Single or Duplicate
if single_duplicate == 'Duplicate':
for elem in my_list:
temporary_list.append(elem)
temporary_list.append(elem)
else:
temporary_list = my_list
## Answer to Block or Aternate ?
new_list = []
if block_alter == 'Alternate':
half = int(len(temporary_list)/2)
for i in range(half):
new_list.append(temporary_list[i])
new_list.append(temporary_list[i + half])
else:
new_list = temporary_list
return new_list
Use get_array(single_duplicate.value, block_alter.value, my_list) to see the output with the user's choses
Here some exemples
In [3]:
print(get_array('Single', 'Block', my_list))
print(get_array('Duplicate', 'Block', my_list))
print(get_array('Single', 'Alternate', my_list))
print(get_array('Duplicate', 'Alternate', my_list))
Out [3]:
['A', 'B', 'C', '1', '2', '3']
['A', 'A', 'B', 'B', 'C', 'C', '1', '1', '2', '2', '3', '3']
['A', '1', 'B', '2', 'C', '3']
['A', '1', 'A', '1', 'B', '2', 'B', '2', 'C', '3', 'C', '3']

Python: Act upon (sub)list depending on elements

[I'm starting out with python; sorry if the following is dumb, but I've wrapped my head around this all day long and feel that I won't be able to solve this myself.]
I've got a list like:
list = [['a', 'A', '10.0.0.2'], ['a', 'TXT', '1'], ['a', 'TXT', '2'], ['b', 'A', '10.10.10.10'], ['c', 'A', '10.0.0.3'], ['c', 'TXT', '3'], ['c', 'TXT', '4']]
This example shows the list with seven sublists, but there could be n.
What I would like to achieve: Each unique list[i][0] (in this case, 'a', 'b', 'c') should have 'A' and 'TXT' in the corresponding sublists, so in this example, keep all list[i][0] == 'a' and list[i][0] == 'c', delete all occurrences of list[i][0] == 'b'.
I've tried various stuff to accomplish this, but nothing to show actually, because I'm missing an idea, how to do this.
What I've did:
Get unique list[i][0]:
names = [list[i][0] for i in range(len(list))]
names_unique = list(set(names))
But then...how to proceed? I guess, something like for ... in ... should do the trick? Could anybody shed some light on this? Would be greatly appreciated!
Edit 1: Sorry for not being clear: 'a', 'b' and 'c' are just arbitrary values. I don't know these in advance, it's output of a dns zone transfer. I would like to keep all hostnames (here: 'a', 'b', 'c') which got associated A and TXT records, and drop these without TXT. I can't hardcode any names like 'a', b' or 'c', because these change; instead, I'm trying to come up with code which does what I've described here.
Desired output:
list = [['a', 'A', '10.0.0.2'], ['a', 'TXT', '1'], ['a', 'TXT', '2'], ['c', 'A', '10.0.0.3'], ['c', 'TXT', '3'], ['c', 'TXT', '4']]
You could do this:
'define a function that checks if a key is valid or not'
def isValid(key, l):
return all(_ in (x[1] for x in filter(lambda x:x[0] == key,l)) for _ in ['TXT','A'])
keys = list(set(x[0] for x in l))
keysValid = []
for key in keys:
if isValid(key, l): keysValid.append(key)
filtered_list = list(filter(lambda x: x[0] in keysValid, l))
all this does is get all possible keys, then add all valid keys into a new list. It then uses filter on the original list to check if the key is in the valid key list.
This is an ugly one liner that you could also use:
>>> l = [['a', 'A', '10.0.0.2'], ['a', 'TXT', '1'], ['a', 'TXT', '2'], ['b', 'A', '10.10.10.10'], ['c', 'A', '10.0.0.3'], ['c', 'TXT', '3'], ['c', 'TXT', '4']]
>>> #this monstrosity right here
>>> u = list(filter(lambda x: x[0] in filter(lambda key: all(_ in (x[1] for x in filter(lambda x:x[0] == key,l)) for _ in ['TXT','A']),set(li[0] for li in l)),l))
>>> u
[['a', 'A', '10.0.0.2'], ['a', 'TXT', '1'], ['a', 'TXT', '2'], ['c', 'A', '10.0.0.3'], ['c', 'TXT', '3'], ['c', 'TXT', '4']]
MAJOR NOTE: this is sort of unrelated to your original question but do not use list as a variable name. This will disallow any call to python's cast to list function list() which is useful when you are using filter()/set() because they return filter/set objects rather than list.
You could try using filter and a lambda expression. For example:
acceptable_elements = ['a','b']
filtered_list = filter(lambda sublist: (sublist[0] in acceptable_elements), my_list)
This will check all elements of the list, and won't alter your original list. I can't precisely tell from your example whether or not you want to check just in the first position or in the entire list, if you want to check for the existence of elements within the entire list:
acceptable_elements = ['a','b']
filtered_list = filter(lambda sublist: any([c in acceptable_elements for c in sublist]), my_list)

Python: merge first list with each list within list

I have the list:
list_mix = [['1','2','3'],['a','b','c'], ['d','e','f'], ['g','h','i']]
The first list must be merged with other lists in the list_mis. The result should be:
['1','2','3','a','b','c']
['1','2','3','d','e','f']
['1','2','3','g','h','i']
The following code gives me "TypeError: list indices must be integers, not list":
for item in list_mix[1:]:
print (list_mix[0] + list_mix[item])
Any solution without external libraries would be appreciated.
item is the sublist already, not an index. Just use it directly:
for item in list_mix[1:]:
print (list_mix[0] + item)
The Python for statement is a Foreach loop construct, assigning each element from list_mix[1:] to item in turn.
Demo:
>>> list_mix = [['1','2','3'],['a','b','c'], ['d','e','f'], ['g','h','i']]
>>> for item in list_mix[1:]:
... print (list_mix[0] + item)
...
['1', '2', '3', 'a', 'b', 'c']
['1', '2', '3', 'd', 'e', 'f']
['1', '2', '3', 'g', 'h', 'i']
Use a list comprehension add every sublist to sublist 0 of list_mix, use list_mix[1:] to start at the element after ['1','2','3'].
[list_mix[0] + x for x in list_mix[1:]]
[['1', '2', '3', 'a', 'b', 'c'], ['1', '2', '3', 'd', 'e', 'f'], ['1', '2', '3', 'g', 'h', 'i']]

Categories

Resources