Breaking a list into smaller lists at a point - python

I've written a function that takes for example [["a",1],["b",2],["a",2],["b",3]], where each small list has a letter and a number, and returns, [["a",1,2,"b",2,3]].
There is a lot more to this problem, but to make things simple, the next step is to turn this into a form [["a",3],["b",5]]. The second item of each smaller list, is the sum of the numbers between the letters ie 1,2 are associated with "a", 2,3 are associated with "b", as seen in the original list. The number of occurrences of a letter is unlimited.
Another example To summarize: function([["a",1,3,4,"b",2,2,"c",4,5]]) => [["a",8],["b",4],["c",9]]
Nothing I've written has come close to accomplishing this. This is a kind of bare-bones challenge, no list comprehension and nothing can be imported

This code can help you:
# Assuming a random initial list:
data = [["a",1,3,4,4,2,"b",2,2,3,5,2,3,"c",4,3,5,5]]
# An empty list where it will be added the result:
new_data = []
# Variable to accumulate the sum of every letter:
sume = 0
# FOR loop to scan the "data" variable:
for i in data[0]:
# If type of the i variable is string, we assume it's a letter:
if type(i) == str:
# Add accumulated sum
new_data.append(sume)
# We restart *sume* variable:
sume = 0
# We add a new letter read:
new_data.append(i)
else:
# We accumulate the sum of each letter:
sume += i
# We extract the 0 added initially and added the last sum:
new_data = new_data[1::]+[sume]
# Finally, separate values in pairs with a FOR loop and add it to "new_data2":
new_data2 = []
for i in range(len(new_data)//2):
pos1 = i*2
pos2 = pos1+1
new_data2.append([new_data[pos1],new_data[pos2]])
# Print data and new_data2 to verify results:
print (data)
print (new_data2)
# Pause the script:
input()
This code can work once by script, but it can convert in a nested function to use it in the way you are looking for.

It’s normally expected you post your solution first, but it seems that you have tried some things and need help. For future questions make sure you include your attempt, since it helps us provide more help as to why your solution doesn't work, and what additional steps you can take to improve your solution.
Assuming that your list always starts with a letter or str, and all numbers are of type int, you could use a dictionary to do the counting. I have added comments to explain the logic.
def group_consecutive(lst):
groups = {}
key = None
for item in lst:
# If we found a string, set the key and continue to next iteration immediately
if isinstance(item, str):
key = item
continue
# Add item to counts
# Using dict.get() to initialize to 0 if ket doesn't exist
groups[key] = groups.get(key, 0) + item
# Replacing list comprehension: [[k, v] for k, v in groups.items()]
result = []
for k, v in groups.items():
result.append([k, v])
return result
Then you could call the function like this:
>>> group_consecutive(["a",1,3,4,"b",2,2,"c",4,5])
[['a', 8], ['b', 4], ['c', 9]]
A better solution would probably use collections.Counter or collections.defaultdict to do the counting, but since you mentioned no imports then the above solution adheres to that.

Related

Display only 1 element when it's a repetition

I would like print a result without duplicate with my multiplication
Here an example :
5*3*2=30
2*3*5=30
5*2*3=30
3*2*5=30
.....
All these element are from my list that I browse and you can see it's always =30
So I would like display only the first element (5*3*2) and not others because they are the same.
To be more accurate, here an example what I have :
list = ['5*3*2','5*2*3','2*3*5','2*5*3']
for i in list:
if eval(i) == eval(i)+1 ??? (I dont know how to say the next element)
print(eval(i))
Thanks for reading
Something like this with not in will help you.
#python 3.5.2
list = ['5*3*2','5*2*3','6*9','2*3*5','2*5*3','8*3','9*6']
elm = []
for i in list:
elm_value = eval(i)
if elm_value not in elm:
elm.append(elm_value)
print(elm)
DEMO: https://rextester.com/QKV22875
The comparison:
eval(i) == eval(i)+1
Will compare if the the number i is equal to i + 1, which will always return False. I'm sure you mean to use i as an index and simply wanted to compare if the current element is equal to the next element in the list. However, doing this doesn't really keep track of duplicates, since you have to consider everything else in the list.
Its also not a good idea to use list as a variable name, since it shadows the builtin function list. Plenty of other suitable names you can use.
One way is to use a set to keep track of what items you have seen, and only print items that you have seen for the first time:
lst = ["5*3*2","5*2*3","2*3*5","2*5*3"]
seen = set()
for exp in lst:
calc = eval(exp)
if calc not in seen:
print(calc)
seen.add(calc)
If you are always dealing with simple multiplying expressions with the * operator(no brackets), you could also use functools.reduce and operator.mul instead to multiply the numbers instead of eval here. This will first split the numbers by *, map each number string to an integer, then multiply every element with each other.
from operator import mul
from functools import reduce
lst = ["5*3*2","5*2*3","2*3*5","2*5*3"]
seen = set()
for exp in lst:
numbers = map(int, exp.split("*"))
calc = reduce(mul, numbers)
if calc not in seen:
print(calc)
seen.add(calc)
Output:
30
With the following list:
l = ['5*3*2','5*2*3','2*3*5','2*5*3', '2*2']
(Note that list is already something in python so I wouldn't recommend using that as a variable name)
I would first create a list of unique values:
unique_vals = set(map(eval, list))
set([4, 30])
Then for each unique values get the first match in l:
[next(x for x in l if eval(x) == i) for i in unique_vals]
I get:
['2*2', '5*3*2']
Is that what you want?

Number Filtration Algorithm bug

So I wrote this algorithm where given a set of integers it will remove all integers except 0 and 7 and then it will check if the remaining integers are in a certain order and then will return a boolean. Code below:
def spy_game(nums):
for i in nums:
if i != 0:
if i == 7:
continue
else:
nums.remove(i)
else:
continue
stringlist = [str(o) for o in nums]
mystring = ''.join(stringlist)
return '007' in mystring
spy_game([1,0,2,4,0,7,5])
Now the problem is that if I run
(for example) spy_game([1,0,2,4,0,7,5]) it will not return True regardless of the fact that the sequence of interest is present. After I decided to return the list per se after the filtration process, I found that all numbers except the ones in the middle got filtered out. So in this example, if I return nums it will return [0, 4, 0, 7] although the 4 should've been removed. I am aware that there are more optimal alternatives to this algorithm but I just want to understand why it doesn't work. Thank you.
Instead of modifying the list, use another list to keep track of the wanted numbers.
You should not modify the list while iterating on it.
Here's a cleaned up version
def spy_game(nums):
ans = []
for i in nums:
if i == 0 or i == 7:
ans.append(i)
stringlist = [str(o) for o in ans]
mystring = ''.join(stringlist)
return '007' in mystring
zenwraight's comment says what the problem is: in Python, you can't modify a list while iterating over it.
As for why, the Python documentation discusses this in a note on the for statement's section:
An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. … This means that if the [loop body] deletes the current … item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated).
The documentation also describes what happens when you insert an element during a loop, and suggests one possible solution (using a slice to copy the list: for i in nums[:]: ...). In your use case, that solution is likely to work fine, but it is considerably less efficient than options that don't copy the entire list.
A better solution might be to use another list comprehension:
nums = [i for i in nums if i == 0 or i == 7]

How to reference the next item in a list in Python?

I'm fairly new to Python, and am trying to put together a Markov chain generator. The bit that's giving me problems is focused on adding each word in a list to a dictionary, associated with the word immediately following.
def trainMarkovChain():
"""Trains the Markov chain on the list of words, returning a dictionary."""
words = wordList()
Markov_dict = dict()
for i in words:
if i in Markov_dict:
Markov_dict[i].append(words.index(i+1))
else:
Markov_dict[i] = [words.index(i+1)]
print Markov_dict
wordList() is a previous function that turns a text file into a list of words. Just what it sounds like. I'm getting an error saying that I can't concatenate strings and integers, referring to words.index(i+1), but if that's not how to refer to the next item then how is it done?
You can also do it as:
for a,b in zip(words, words[1:]):
This will assign a as an element in the list and b as the next element.
The following code, simplified a bit, should produce what you require. I'll elaborate more if something needs explaining.
words = 'Trains the Markov chain on the list of words, returning a dictionary'.split()
chain = {}
for i, word in enumerate(words):
# ensure there's a record
next_words = chain.setdefault(word, [])
# break on the last word
if i + 1 == len(words):
break
# append the next word
next_words.append(words[i + 1])
print(words)
print(chain)
assert len(chain) == 11
assert chain['the'] == ['Markov', 'list']
assert chain['dictionary'] == []
def markov_chain(list):
markov = {}
for index, i in enumerate(list):
if index<len(list)-1:
markov[i]=list[index+1]
return (markov)
The code above takes a list as an input and returns the corresponding markov chain as a dictionary.
You can use loops to get that, but it's actually a waste to have to put the rest of your code in a loop when you only need the next element.
There are two nice options to avoid this:
Option 1 - if you know the next index, just call it:
my_list[my_index]
Although most of the times you won't know the index, but still you might want to avoid the for loop.
Option 2 - use iterators
& check this tutorial
my_iterator = iter(my_list)
next(my_iterator) # no loop required

Python number to word converter needs a space detector

I have been working on a sort of encryption tool in python. This bit of code is for the decryption feature.
The point is to take the given numbers and insert them into a list from where they will be divided by the given keys.
My idea for code is below but I keep getting the out of list index range whenever I try it out. Any suggestions? Keep in mind I'm a beginner:
need = []
detr = raw_input('What would you like decrypted?')
count = 0
for d in detr:
if (d == '.' or d == '!') or (d.isalpha() or d== " "):
count +=1
else:
need[count].append(d)
The problem is you are attempting to overwrite list values that don't exist.
list.append(item) adds item to the end of list. list[index] = item inserts item into list at position index.
list = [0,0,0]
list.append(0) # = [0,0,0,0]
list[0] = 1 # [1,0,0,0]
list[99] = 1 # ERROR: out of list index range
You should get rid of the count variable entirely. You could append None in the case of d==' ' etc. or just ignore them.
The way I understood your description you want to extract the numbers in a string and append them to a list using a for-loop to iterate over each character.
I think it would be easier doing it with regular expressions (something like r'([\d]+)').
But the way joconner said: "get rid of the count variable":
need = []
detr = input('What would you like decrypted?\n')
i = iter(detr) # get an iterator
# iterate over the input-string
for d in i:
numberstr = ""
try:
# as long as there are digits
while d.isdigit():
# append them to a cache-string
numberstr+= d
d = next(i)
except StopIteration:
# occurs when there are no more characters in detr
pass
if numberstr != "":
# convert the cache-string to an int
# and append the int to the need-array
need.append( int(numberstr) )
# print the need-array to see what is inside
print(need)

A double preceding function with bug

It is a homework question that I am stuck on:
Your classmate claims to have written a function that replaces each value in a list with twice the preceding value (and the first value with 0). For example, if the list [1, 3, 7, 11] is passed as a parameter, the function is supposed to return [0, 2, 6, 14] -- Note: 22 is not part of the output. Here's the code:
def double_preceding(values):
if (values != []):
temp = values[0]
values[0] = 0
for i in range(1, len(values)):
values[i] = 2 * temp
temp = values[i]
Analyse this function and rewrite it so that it works as intended.
I couldn't even follow what the code was doing, can someone explain to me please
Here's an explanation of the given code, and the errors I see within it:
def double_preceding(values):
if (values != []): # this if checks for an empty list, to avoid exceptions
temp = values[0]
values[0] = 0 # but this line is not indented, so you may get an IndexError anyway
for i in range(1, len(values)): # this loops over all indexes but the first (0)
values[i] = 2 * temp # this replaces the current value with double temp
temp = values[i] # this line however saves the same doubled value as the new temp
So, the two errors I see is incorrect handling of empty lists, and a logic error in the assignment code that will cause the loop to replace values after the first with the list's original first value times successive powers of two.
A good way to solve the second issue is to do both of the assignments in the loop with a single statement. This is a neat thing that Python can do that many other languages cannot. Here's what a basic version would look like:
values[i], temp = temp*2, values[i]
The commas are the key things to pay attention to. The one on the right side of the assignment makes a tuple out of temp*2 and values[i]. The comma on the left hand side tells Python to unpack the tuple being assigned into the variables values[i] and temp. And the two parts are evaluated in that order (first the expression on the right side, then the unpacking and assignments). This means that the "old" values of temp and values[i] are used to build the tuple and it doesn't matter that they're both reassigned later.
If we're doing the assignments that way, we can solve the empty list situation elegantly too. Rather than treating the first value specially and needing a check to make sure values[0] is a valid expression, why not just set temp to 0 at the start and let the loop handle the first value as well as the later ones? Here's a fully fixed function:
def double_preceeding(values):
temp = 0
for i in range(len(values)): # loop over all indexes, not skipping the first
values[i], temp = temp*2, values[i]
The loop will do nothing if values is an empty list, since len([]) is 0 and range(0) is empty itself.
Example output:
>>> L=[]
>>> double_preceeding(L)
>>> L
[]
>>> L=[1, 3, 7, 11]
>>> double_preceeding(L)
>>> L
[0, 2, 6, 14]
If I guessed the indentation of the program correctly. See comments below:
Code:
def double_preceding(v):
values = v[:] # Make a copy of the list passed as argument
if (values != []): # If list is not empty, store the first value in 'temp'
temp = values[0]
else:
return
v[0] = 0 # Set the first value of the list as '0' (as the problem says it)
for i in range(1, len(values)): # Iterate 'n - 1' times, where n is the length of the list
v[i] = 2 * temp # Set the corresponding value to twice the precedent (the precedent is stored in 'temp')
temp = values[i]
Test:
v = [1, 3, 7, 11]
double_preceding(v)
print v
Output:
[0, 2, 6, 14, 22]

Categories