for loops and lists assignment - python

I've been given the assignment as follows:
Write a function called insert that takes 3 parameters, listA, listB and an index, then returns a copy of listA with the elements of listB inserted at the index. Your code should work with both strings and lists.
examples should look give this:
insert([1,2,3], ['a', 'b', 'c'], 3) should give [1, 2, 'a', 'b', 'c', 3]
AND:
insert('dog', 'cat', 1) should give 'dcatog'
I want to complete this first part both with and without loops. So far I have gotten:
def insert (listA, listB, index):
return listA[0:index] + listB + listA[index:len(listA)]
and this works out correctly, giving the correct example shown above. I don't know how to do this with loops, though. I've been trying to use for loops as follows:
def insert (listA, listB, index):
for nextchar in listA:
if nextchar == index:
listA.insert(index, listB)
return listA
but that's not correct. It's the closest I've gotten though, giving
[1, 2, ['a', 'b', 'c'], 3]
AND
'dog'
for the examples above.
but that's a nested list, yes? I don't want that. and the second example is completely wrong.

For the "dog" example, remember that strings in Python are immutable... that is, they can't be changed. So if you are trying to insert some characters into a string "dog", it won't be changed.
Strings don't have the "insert" method at all, so you will get an error in the "dog" example.
You will need to create a new string, and NOT use the insert method, if it's a string being passed in.

Your example is a bit off I believe.
insert([1,2,3], ['a', 'b', 'c'], 3)
should in fact return
[1, 2, 3, 'a', 'b', 'c']
Anyhow, here's my fix:
def insert (listA, listB, index):
if index == len(listA):
listA.extend(listB)
return listA
for i in range(len(listA)):
print i
if i == index:
for j, b_elem in enumerate(listB):
listA.insert(i+j, b_elem)
return listA
A bug with your given code is that you are inserting a list into that index of listA, as opposed to inserting each element of listB STARTING from that index.

Related

Sequence using recursion

I am trying to write a function which outputs all possible combinations of char list with length and without any repeats like aa, bb etc.
I am now on this stage:
def sequences(char_list, n, lst = []):
if len(lst) == n:
print(lst)
else:
for i in range(len(char_list)):
temp_list = [char_list[j] for j in range(len(char_list)) if i != j]
sequences(temp_list, n, lst + [char_list[i]])
print(sequences(["a", "b", "c"], 2))
Output is correct but I have None at the end. I actually have no idea why.
['a', 'b']
['a', 'c']
['b', 'a']
['b', 'c']
['c', 'a']
['c', 'b']
None
And what is the best way to get strings in the output and not lists?
The function sequences doesn't return anything (there's no return statement anywhere in the code), so it'll automatically return None. print(sequences(["a", "b", "c"], 2)) will execute this function and print its return value, outputting None.
To get strings instead of lists, concatenate all the strings in the list like this:
print(''.join(lst))
Every function has an implicit return None at the end of it. On the last line of your code, you asked Python the print the output of sequences, which is None since no return value was specified.
Well the problem was is that I printed it at the end once again.

Alternatives to using in-place list methods within a list comprehension?

I understand that in-place list methods return None instead of the mutated list. As far as I can see, this makes it impossible to use these methods as part of the internal logic of a list comprehension.
What is the most pythonic way to create a list comprehension whose members result from mutating other lists? In other words: what is the best alternative to this (non-functioning) line:
new_list = [old_list.insert(0, "X") for old_list in list_of_old_lists]
Which results in a list of Nones because list.insert() returns None.
Is it simply not possible to do this in an elegant single-line of code without a lot of slicing and concatenating?
The example above is trivial for the sake of illustrating my question but in reality I'd like to do this in more complex situations in lieu of multiple nested 'for' loops.
Here's a simplified sample of what I'm trying to do:
word = 'abcdefg'
variations_list = []
characters_to_insert = ['X', 'Y', 'Z']
for character in characters_to_insert:
for position in range(len(word) + 1):
w = list(word)
w.insert(position, character)
this_variation = ''.join(w)
variations_list.append(this_variation)
for v in variations_list:
print(v)
This works fine using nested 'for' loops, like this (my real application is much more complex/verbose than this sample).
But I cannot do the same thing using list comprehension because the 'insert' method returns None:
variations_list_comprehension = [list(word).insert(position, character) for position in range(len(word) +1) for character in ['X', 'Y', 'Z']]
for v in variations_list_comprehension:
print(v)
Results in a list of None values because the in-place mutations return "None".
If you don't care about the results of mutating the other list, then you don't need to use an interim list:
variations_list = [word[:i] + char + word[i:]
for char in characters_to_insert
for i in range(len(word) + 1)]
['Xabcdefg', 'aXbcdefg', 'abXcdefg', 'abcXdefg', 'abcdXefg', 'abcdeXfg', 'abcdefXg', 'abcdefgX',
'Yabcdefg', 'aYbcdefg', 'abYcdefg', 'abcYdefg', 'abcdYefg', 'abcdeYfg', 'abcdefYg', 'abcdefgY',
'Zabcdefg', 'aZbcdefg', 'abZcdefg', 'abcZdefg', 'abcdZefg', 'abcdeZfg', 'abcdefZg', 'abcdefgZ']
I would still say this is at best a borderline comprehension: it's much easier to follow as a for loop.
Not everything should or needs to be solved by a comprehension. for-loops aren't bad - sometimes they are better than comprehensions, especially because one tends to avoid doing too much in one line automatically.
But if you really want a list-comprehension solution I would use a helper function that wraps the in-place operation:
def insert(it, index, value):
lst = list(it)
lst.insert(index, value)
return lst
[''.join(insert(word, position, character)) for position in range(len(word) +1) for character in ['X', 'Y', 'Z']]
However to be really equivalent to your loopy solution you need to swap the loops in the comprehension:
[''.join(insert(word, position, character)) for character in ['X', 'Y', 'Z'] for position in range(len(word) +1)]
The advantage here is that wrapping the in-place method can be applied in a lot of cases, it doesn't just work in this case (you can wrap any in-place function that way). It's verbose, but it's very readable and re-useable!
Personally I would use a generator function with loops, because you can use it to create a list but you could also create the items on demand (without needing the list):
def insert_characters_everywhere(word, chars):
for character in characters_to_insert:
for position in range(len(word) + 1):
lst = list(word)
lst.insert(position, character)
yield ''.join(lst)
list(insert_characters_everywhere('abcdefg', ['X', 'Y', 'Z']))
I think it is important to understand what are you actually trying to achieve here.
new_list = [old_list.insert(0, "X") for old_list in list_of_old_lists]
Case 1: In your code above, are you trying to create a new new_list containing old(!!!) lists updated to include 'X' character as their first element? If that is the case, then list_of_old_lists will be a list of "new lists".
For example,
list_of_old_lists = [['A'], ['B']]
new_list = []
for old_list in list_of_old_lists:
old_list.insert(0, 'X')
new_list.append(old_list)
print(list_of_old_lists)
print(new_list)
print(list_of_old_lists == new_list)
will print:
[['X', 'A'], ['X', 'B']]
[['X', 'A'], ['X', 'B']]
True
That is, new_list is a shallow copy of list_of_old_lists containing "updated" lists. If this is what you want, then you can do something like this using list comprehension:
[old_list.insert(0, "X") for old_list in list_of_old_lists]
new_list = list_of_old_lists[:]
instead of the for-loop in my example above.
Case 2: Or, are you trying to create a new list containing updated lists while having list_of_old_lists hold the original lists? In this case, you can use list comprehension in the following way:
new_list = [['X'] + old_list for old_list in list_of_old_lists]
Then:
In [14]: list_of_old_lists = [['A'], ['B']]
...: new_list = [['X'] + old_list for old_list in list_of_old_lists]
...: print(list_of_old_lists)
...: print(new_list)
...: print(new_list == list_of_old_lists)
...:
[['A'], ['B']]
[['X', 'A'], ['X', 'B']]
False

I don't understand that part of code about recursive (python)

I don't understand recursive part.
def length(list1):
if list1 == [] or list1 == "":
return 0
else:
return 1+length(list1[1:])
a = [1,2,3,4,5]
print "Length of list is: ",length(a)
This is code. and that code's role is like len function.
and output is for a; 5
but return 1+length(list1[1:]) that part is not understandable for me.
why there is "1"? and why it starts with 1? why not "0"?
I thought length(list1[0:]) but it doesn't run.
actually i think but, ı understand nothing about that part.
can anyone explain?
The length of a list can be defined recursively as:
0 if the list is empty (equal to the empty list [])
1 + the length of the list deprived of its first element. (e.g. if you have a list with 3 elements, the length is the same as 1 + the length of the same list without one of its element (2))
Here, list1[1:] is a new list similar to list1 but without the first element.
If we take a concrete example with the list ['a', 'b', 'c']:
['a', 'b', 'c'] is different than the empty list [], then we return 1 + length(['b', 'c']). ['b', 'c'] is different than the empty list [], then length(['b', 'c']) returns 1 + length(['c']); and so on until length([]) is called.
In python: a[1:] means "take all values of a starting from 1, so excluding 0", for instance:
"abc"[1:] == "bc"
[2, 3, 4][1:] == [3, 4]
The function you are looking at takes either a list or a string, then remove an item and counts the remaining until it find an empty one, in this case returns its length (0).

How to force Python to correctly identify list element index in FOR loop

There is a simple list, for example,
my_list = ['a', 'b', 'b', 'c', 'c']
I want to run through my_list[1:] with for loop to get previous element value for each iteration:
for i in my_list[1:]:
print(my_list[my_list.index(i)-1])
I expected to see a b b c on output, but get a a b b instead.
I guess this is because index() method search only for first i value, ignoring the fact that there are two elements "b" as well as two "c"...
How to fix this code to get expected output?
The list.index() method will return the index of first occurrence of its argument. And since you have multiple duplicate items in your list it doesn't give you the expected result. You can use a simple slicing to get your expected output:
>>> my_list = ['a', 'b', 'b', 'c', 'c']
>>>
>>> my_list[:-1]
['a', 'b', 'b', 'c']
Or if you want to access these items through a loop you can use zip() function like following:
>>> for i, j in zip(my_list,my_list[1:]):
... print(i, j)
...
a b
b b
b c
c c
Matching elements with their predecessors or sucessors is a common use case for zip:
In [13]: for i,prior in zip(my_list[1:], my_list[0:]):
print (prior)
....:
a
b
b
c
You can always emulate the behaviour of C/Pascal/Perl/whatever 'for' instead of Python 'for' (which is actually more like foreach). Note that the range starts with 1 to avoid returning the last element on the first iteration.
for i in range(1, len(my_list)):
print(my_list[i], my_list[i-1])
Not very Pythonic, but this approach is sometimes more intuitive for people with background in other languages.
As you noticed, using index does not work here, as it always finds the first position of the given element. Also, it is pretty wasteful, as in the worst case you have to search the entire list each time.
You could use enumerate with start parameter to get the element along with its index:
start = 1
for i, x in enumerate(my_list[start:], start):
print(i, x, my_list[i-1]) # index, current, last
This will do the trick:
for i in range(len(my_list)+1):
try: print(my_list[i-1])
except: print 'it is 1st iteration'

having trouble with lists in python

The function satisfiesF() takes a list L of strings as a paramenter. function f takes a string as a parameter returns true or false. Function satisfiesF() modifies L to contain only those strings,s for which f(s) returns true.
I have two different programs aimed to produce the same output. But I am getting different outputs.
First program:
def f(s):
return 'a' in s
def satisfiesF(L):
k=[]
for i in L:
if f(i)==True:
k.append(i)
L=k
print L
print
return len(L)
L = ['a', 'b', 'a']
print satisfiesF(L)
print L
Output:
['a', 'a']
2
['a', 'b', 'a']
Second program:
def f(s):
return 'a' in s
def satisfiesF(L):
for i in L:
if f(i)==False:
L.remove(i)
print L
print
return len(L)
L = ['a', 'b', 'a']
print satisfiesF(L)
print L
output:
['a', 'a']
2
['a', 'a']
Please explain why these are giving differnt outputs.
In your second function you are seeing 2 as the length and all the elements in L outside the function because you are setting a local variableL which is a reference to k, your L created outside the function is not affected. To see the change in L you would need to use L[:] = k, then printing L will give you ['a', 'a'] outside the function as you are changing the original list object L list passed in to the function.
In the first you are directly modifying L so you see the changes in L outside the function.
Also never iterate over a list you are removing element from, if you make
L = ['a', 'b', 'a','a','d','e','a'], you will get behaviour you won't expect. Either make a copy for i in L[:] or use reversed for i in reversed(L):
In the first function, you assign over L in satisfiesF(), but you never modify the original list. When you write L=k, that makes the reference L now refer to the same list as k. It doesn't assign to the original list.
In contrast, in the second function you modify L without reassigning to it.
Also, as a side note, you shouldn't modify a list while you iterate over it.
As a second side note, you can rewrite satisfiesF as a one-liner:
L = [item for item in L if f(item)]
This was down voted mistakenly. The question was changed. So, the answer got outdated. Following is the answer for changed question:
L=k
Above would mean that we lost the reference to L.
So, Try this:
To the 1st program, comment the above assignment, do below, to retain reference to L:
# L=k
del L[:]
L[:] = k
Now both programs will output same, below one:
['a', 'a']
2
['a', 'a']
Best of luck.
In the question, there are two Ls. A global one and a local one. The
print L
statement prints the GLOBAL L, which you did not mutate in the programme.
Therefore, in order to let the programme knows that you want to mutate the global L, instead of the local L, you can add the line
globals()['L'] = L
to your first programme. I hope this can help!
In the first program, if you want to mutate the original list L and see the change made by your function, you should replace L = K in your code with L[:] = k:
def satisfiesF(L):
k=[]
for i in L:
if f(i)==True:
k.append(i)
# new code --------
L[:] = k # before: L = K
# -----------------
print L
print
return len(L)
This will give you ['a', 'a'] outside the function.
About mutating a list within a loop in the second program...
Just to remember that during a "for" loop, python keeps track of where it is in the list using an internal counter that is incremented at the end of each iteration.
When the value of the counter reaches the current length of the list, the loop terminates. This means that if you are mutating the list within the loop you can have surprising consequence.
For example, look at the for loop below:
my_list = ['a', 'b', 'c', 'd']
>>>print "my_list - before loop: ", my_list
my_list - before loop: ['a', 'b', 'c', 'd']
for char in my_list:
if char == 'a' or char == 'b':
my_list.remove(char)
>>>print "my_list - after loop: ", my_list
my_list - after loop: ['b', 'c', 'd']
Here, the hidden counter starts out at the index 0, discovers that "a" (in my_list[0]) is in the list, and remove it, reducing the length of my_list to 3 (from the initial 4). The counter is then incremented to 1, and the code proceeds to check the "if" condition, at the position my_list[1], in the mutated list (now of len == 3). This means that you will skip the "b" character (present now at the index 0) even if it had to be remove it.
One solution for this is to use slicing to clone and create a new list where you can remove items from it:
cloneList = my_list[:]

Categories

Resources