Deleted list entries returning? - python-3.x

tl;dr: how can i delete duplicates from one list, while deleting corresponding entries in a different list?
(note that "class" here is not a class in the programming sense, but in the Dungeons and Dragons sense)
I'm trying to make a function that takes in a list of weights and returns a list of associated strings a number of times equal to the weight. I use random.choice to then select one of these items at random. Here I am removing weights due to multiple possible ways to qualify for each class (multiclassing rules in D&D).
However, I'm running into an error (IndexError: list index out of range) when I try to run this part of the code:
def makeRollable(weights, classNames):
output = []
classNames, weights2 = removeDuplicates(classNames, weights)
for i in range(0, len(weights2)):
weight = weights2[i]
for j in range(0, weight):
output.append(classNames[i])
return output
def removeDuplicates(names, weights):
for i in range(len(names)-1, 0, -1):
for j in range(len(names)-1, i, -1):
if names[i] == names[j]:
weights[i] = max(weights[i], weights[j])
del names[j]
del weights[j]
names2, weights2 = names, weights
return names2, weights2
When I run in debugger, I see that even though the names2 and weights2 have fewer entries in the removeDuplicates fuction, the weights2 list returned to the original size of weights in the makeRollable function.
while the names list has had 5 entries deleted (it was supposed to), the weights list did not. Given that I'm deleting names and weights entries at the same time, I don't see why this should be.
I thought it might be something to do with returning a variable of the same name as one passed to the function, so I made the names2 and weights2 variables to try to fix that, but it didn't seem to help. I was getting the same error with them removed.
if it's helpful, here is a sample data set (there are duplicates for the fighter, magus, and shaman classes because you can qualify for them based on different criteria, and the easiest way I could think of to check for that was to add them multiple times, and then just keep the maximum weight):
classNames = ['Barbarian', 'Bard', 'Cleric', 'Druid', 'Fighter', 'Fighter', 'Mage', 'Magus', 'Magus', 'Magus', 'Monk', 'Paladin', 'Ranger', 'Rogue', 'Shaman', 'Shaman', 'Shaman', 'Sorcerer', 'Warlock', 'Wizard']
weights = [4, 2, 0, 0, 4, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 2, 2, 0]

Since you've validated that your function is returning the right value, and the other function is not receiving the right input, I think the issue is on how you're dealing with the lists between these functions.
Specifically, you're probably returning a value at some point, but not overwriting the two lists. Basically something like:
a = []
b = []
a = populate_a(a)
b = populate_b(b)
removeDupes(a, b)
# should be
# a, b = removeDupes(a,b)
makeRollable(a, b)
A better long term solution would be to use a dictionary, or if you absolutely need two lists write a class to complete your required functionalities with the two lists. Then just pass this object around and manipulate in the functions using its basic modifiers.

Related

How can I count all deleted elements when I remade the list into a set(Python)

I need to make a function, which will take a list or tuple and remade it into a set. As there are no duplicate elements in the set, I need to write the number of all deleted elements. This is my code,
def find_type(arg):
if isinstance(arg, list):
arg = set(arg)
return arg
elif isinstance(arg, tuple):
a = list(arg)
return set(a)
print(find_type((1, 2, 2, 3)))
and answer
{1, 2, 3}
The function works, so i just do not know how to count and write the number of deleted elements
You can use the following relation:
number1 = len(arg)
number2 = len(find_type(arg))
number = number1-number2
Number of deleted elements is simply the difference in length of list/tuple and set.
Also, you don't need to check if arg is instance of list or tuple (and even if you do, do it in one conditional). set accepts all iterable types (like lists, tuples, strings, etc.).

List index out of range with one some data sets?

I am trying to code up a numerical clustering tool. Basically, I have a list (here called 'product') that should be transformed from an ascending list to a list that indicates linkage between numbers in the data set. Reading in the data set, removing carriage returns and hyphens works okay, but manipulating the list based on the data set is giving me a problem.
# opening file and returning raw data
file = input('Data file: ')
with open(file) as t:
nums = t.readlines()
t.close()
print(f'Raw data: {nums}')
# counting pairs in raw data
count = 0
for i in nums:
count += 1
print(f'Count of number pairs: {count}')
# removing carriage returns and hyphens
one = []
for i in nums:
one.append(i.rsplit())
new = []
for i in one:
for a in i:
new.append(a.split('-'))
print(f'Data sets: {new}')
# finding the range of the final list
my_list = []
for i in new:
for e in i:
my_list.append(int(e))
ran = max(my_list) + 1
print(f'Range of final list: {ran}')
# setting up the product list
rcount = count-1
product = list(range(ran))
print(f'Unchanged product: {product}')
for i in product:
for e in range(rcount):
if product[int(new[e][0])] < product[int(new[e][1])]:
product[int(new[e][1])] = product[int(new[e][0])]
else:
product[int(new[e][0])] = product[int(new[e][1])]
print(f'Resulting product: {product}')
I expect the result to be [0, 1, 1, 1, 1, 5, 5, 7, 7, 9, 1, 5, 5], but am met with a 'list index out of range' when using a different data set.
the data set used to give the above desired product is as follows: '1-2\n', '2-3\n', '3-4\n', '5-6\n', '7-8\n', '2-10\n', '11-12\n', '5-12\n', '\n'
However, the biggest issue I am facing is using other data sets. If there is not an additional carriage return, as it turns out, I will have the list index out of range error.
I can't quite figure out what you're actually trying to do here. What does "indicates linkages" mean, and how does the final output do so? Also, can you show an example of a dataset where it actually fails? And provide the actual exception that you get?
Regardless, your code is massively over-complicated, and cleaning it up a little may also fix your index issue. Using nums as from your sample above:
# Drop empty elements, split on hyphen, and convert to integers
pairs = [list(map(int, item.split('-'))) for item in nums if item.strip()]
# You don't need a for loop to count a list
count = len(pairs)
# You can get the maximum element with a nested generator expression
largest = max(item for p in pairs for item in p)
Also, in your final loop you're iterating over product while also modifying it in-place, which tends to not be a good idea. If I had more understanding of what you're trying to achieve I might be able to suggest a better approach.

What is the empty dictionary used for in the code?

I'm doing practice problems in python on Leetcode (still learning). This is the problem:
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
my code is
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
dict = {}
for counter, i in enumerate(nums):
a = target- i
if a in dict:
return (dict[a], counter)
dict[i] = counter
It runs fine and passes all the tests however I found a common reason this works is for the dict = {}
What is the reason for this dictionary and how does this code recognize cases for (3,3) target = 6 where there are duplicates and index matters. A basic run down of why the code works would be great!
The dictionary stores as keys the numbers in the list with their index as a value.
For example:
[2, 7, 11, 15] -> {'2':0, '7':1, '11':2, '15':3}
There is never a duplicate inserted, if the same number appears twice, the index will be replaced with the new index where it appears.
In the case of duplicate, it is important to test all value on the first list, and to store index on a separated dict in order to be sur that you will never test in dictionnary the actually tested value.
By using a dictionnary in order to find the index of the right number, you can't store duplicate.
Since in dictionnary you can't have 2 values with the same key, if duplicate, you just change the old index with the new one.
For example, if dict == {'3': 0, '2':1} and the tested value is 2, the dict == {'3': 0, '2':2}.
And if the target is reach by duplicate number (2+2 for target 4 for example), nothing is stored cause of the return in the if a in dict: return (dict[a], counter)

python3 functional programming: Accumulating items from a different list into an initial value

I have some code that performs the following operation, however I was wondering if there was a more efficient and understandable way to do this. I am thinking that there might be something in itertools or such that might be designed to perform this type of operation.
So I have a list of integers the represents changes in the number of items from one period to the next.
x = [0, 1, 2, 1, 3, 1, 1]
Then I need a function to create a second list that accumulates the total number of items from one period to the next. This is like an accumulate function, but with elements from another list instead of from the same list.
So I can start off with an initial value y = 3.
The first value in the list y = [3]. The I would take the second
element in x and add it to the list, so that means 3+1 = 4. Note that I take the second element because we already know the first element of y. So the updated value of y is [3, 4]. Then the next iteration is 4+2 = 6. And so forth.
The code that I have looks like this:
def func():
x = [0, 1, 2, 1, 3, 1, 1]
y = [3]
for k,v in enumerate(x):
y.append(y[i] + x[i])
return y
Any ideas?
If I understand you correctly, you do what what itertools.accumulate does, but you want to add an initial value too. You can do that pretty easily in a couple ways.
The easiest might be to simply write a list comprehension around the accumulate call, adding the initial value to each output item:
y = [3 + val for val in itertools.accumulate(x)]
Another option would be to prefix the x list with the initial value, then skip it when accumulate includes it as the first value in the output:
acc = itertools.accumulate([3] + x)
next(acc) # discard the extra 3 at the start of the output.
y = list(acc)
Two things I think that need to be fixed:
1st the condition for the for loop. I'm not sure where you are getting the k,v from, maybe you got an example using zip (which allows you to iterate through 2 lists at once), but in any case, you want to iterate through lists x and y using their index, one approach is:
for i in range(len(x)):
2nd, using the first append as an example, since you are adding the 2nd element (index 1) of x to the 1st element (index 0) of y, you want to use a staggered approach with your indices. This will also lead to revising the for loop condition above (I'm trying to go through this step by step) since the first element of x (0) will not be getting used:
for i in range(1, len(x)):
That change will keep you from getting an index out of range error. Next for the staggered add:
for i in range(1, len(x)):
y.append(y[i-1] + x[i])
return y
So going back to the first append example. The for loop starts at index 1 where x = 1, and y has no value. To create a value for y[1] you append the sum of y at index 0 to x at index 1 giving you 4. The loop continues until you've exhausted the values in x, returning accumulated values in list y.

python3 list creation from class makes a global list rather than a series iterated ones

So here is the problem I am having. I am trying to iterate the makeAThing class, and then create a list for the iteration using the makeAList class. Instead of making seperate lists for each iteration of makeAThing, it is making one big global list and adding the different values to it. Is there something I am missing/don't know yet, or is this just how python behaves?
class ListMaker(object):
def __init__(self,bigList = []):
self.bigList = bigList
class makeAThing(object):
def __init__(self,name = 0, aList = []):
self.name = name
self.aList = aList
def makeAList(self):
self.aList = ListMaker()
k = []
x = 0
while x < 3:
k.append(makeAThing())
k[x].name = x
k[x].makeAList()
k[x].aList.bigList.append(x)
x += 1
for e in k:
print(e.name, e.aList.bigList)
output:
0 [0, 1, 2]
1 [0, 1, 2]
2 [0, 1, 2]
the output I am trying to achieve:
0 [0]
1 [1]
2 [2]
After which I want to be able to edit the individual lists and keep them assigned to their iterations
Your init functions are using mutable default arguments.
From the Python documentation:
Default parameter values are evaluated from left to right when the
function definition is executed. This means that the expression is
evaluated once, when the function is defined, and that the same
“pre-computed” value is used for each call. This is especially
important to understand when a default parameter is a mutable object,
such as a list or a dictionary: if the function modifies the object
(e.g. by appending an item to a list), the default value is in effect
modified. This is generally not what was intended. A way around this
is to use None as the default, and explicitly test for it in the body
of the function, e.g.:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
In your code, the default argument bigList = [] is evaluated once - when the function is defined - the empty list is created once. Every time the function is called, the same list is used - even though it is no longer empty.
The default argument aList = [] has the same problem, but you immediately overwrite self.aList with a call to makeAList, so it doesn't cause any problems.
To verify this with your code, try the following after your code executes:
print(k[0].aList.bigList is k[1].aList.bigList)
The objects are the same.
There are instances where this behavior can be useful (Memoization comes to mind - although there are other/better ways of doing that). Generally, avoid mutable default arguments. The empty string is fine (and frequently used) because strings are immutable. For lists, dictionaries and the sort, you'll have to add a bit of logic inside the function.

Resources