Appending to lists within loops in Python3 (again) - python

I'm having difficulty adding to a list iteratively.
Here's a MWE:
# Given a nested list of values, or sets
sets = [[1, 2, 3], [1, 2, 4], [1, 2, 5]]
# add a value to each sublist giving the number of that set in the list.
n_sets = len(sets)
for s in range(n_sets):
(sets[s]).insert(0, s)
# Now repeat those sets reps times
reps = 4
expanded_sets = [item for item in sets for i in range(reps)]
# then assign a repetition number to each occurance of a set.
rep_list = list(range(reps)) * n_sets
for i in range(n_sets * reps):
(expanded_sets[i]).insert(0, rep_list[i])
expanded_sets
which returns
[[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 0, 1, 2, 3],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 1, 1, 2, 4],
[3, 2, 1, 0, 2, 1, 2, 5],
[3, 2, 1, 0, 2, 1, 2, 5],
[3, 2, 1, 0, 2, 1, 2, 5],
[3, 2, 1, 0, 2, 1, 2, 5]]
instead of the desired
[[0, 0, 1, 2, 3],
[1, 0, 1, 2, 3],
[2, 0, 1, 2, 3],
[3, 0, 1, 2, 3],
[0, 1, 1, 2, 4],
[1, 1, 1, 2, 4],
[2, 1, 1, 2, 4],
[3, 1, 1, 2, 4],
[0, 2, 1, 2, 5],
[1, 2, 1, 2, 5],
[2, 2, 1, 2, 5],
[3, 2, 1, 2, 5]]
Just for fun, the first loop returns an expected value of sets
[[0, 1, 2, 3], [1, 1, 2, 4], [2, 1, 2, 5]]
but after the second loop sets changed to
[[3, 2, 1, 0, 0, 1, 2, 3], [3, 2, 1, 0, 1, 1, 2, 4], [3, 2, 1, 0, 2, 1, 2, 5]]
I suspect the issue has something to do with copies and references. I've tried adding .copy() and slices in various places, but with the indexed sublists I haven't come across a combo that works. I'm running Python 3.10.6.
Thanks for looking!
Per suggested solution, [list(range(reps)) for _ in range(n_sets)] doesn't correctly replace the list(range(reps)) * n_sets, since it gives [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]] instead of
the desired [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]. Do I need to flatten, or is there a syntax with the _ notation that gives me a single list?
Further update . . .
replacing
rep_list = list(range(reps)) * n_sets
with
rep_list_nest = [list(range(reps)) for _ in range(n_sets)]
rep_list = [i for sublist in rep_list_nest for i in sublist]
gives the same undesired result for expanded_sets.

The problem is here:
expanded_sets = [item
for item in sets
for i in range(reps)]
This list now contains the same element of sets four times in a row, followed by the next element repeated four times, and so on.
Creating copies of item fixes the issue:
expanded_sets = [item.copy()
for item in sets
for i in range(reps)]
Try it online
If you want a more pythonic approach, then recognize that the result is a product of two ranges, and your original sets all concatenated together:
from itertools import product
sets = [[1, 2, 3], [1, 2, 4], [1, 2, 5]]
expanded_sets = [[inner_counter, outer_counter] + sets_elem
for sets_elem, outer_counter, inner_counter in product(sets, range(len(sets)), range(4))]
Try it online

Related

Find most common value in numpy 2d array rows, otherwise return maximum

I have an array like this
Nbank = np.array([[2, 3, 1],
[1, 2, 2],
[3, 2, 1],
[3, 2, 1],
[2, 3, 2],
[2, 2, 3],
[1, 1, 3],
[2, 1, 1],
[2, 2, 3],
[1, 1, 1],
[2, 1, 1],
[2, 3, 1],
[1, 2, 1]])
I want to return an array with only one column. The condition is to return the most common value in each row; if multiple values have the same number of occurrences, just return the maximum of them.
I used this code
most_f = np.array([np.bincount(row).argmax() for row in Nbank])
if multiple values have the same number of occurrences, it returns the first item instead of the maximum. how can I work this around?
You could use a Counter after sorting in descending order by row. There's a most_common that will return what you want. Since it's sorted already, the first element is always either the largest or the most frequent.
import numpy as np
from collections import Counter
Nbank = np.array([[2, 3, 1],
[1, 2, 2],
[3, 2, 1],
[3, 2, 1],
[2, 3, 2],
[2, 2, 3],
[1, 1, 3],
[2, 1, 1],
[2, 2, 3],
[1, 1, 1],
[2, 1, 1],
[2, 3, 1],
[1, 2, 1]])
np.array([Counter(sorted(row, reverse=True)).most_common(1)[0][0] for row in Nbank])
Output
array([3, 2, 3, 3, 2, 2, 1, 1, 2, 1, 1, 3, 1])
I believe this will solve the problem. You could probable make it into a one liner with some fancy list comprehension, but I don't think that would be worth while.
most_f = []
for n in Nbank: #iterate over elements
counts = np.bincount(n) #count the number of elements of each value
most_f.append(np.argwhere(counts == np.max(counts))[-1][0]) #append the last and highest
You can cheat a little bit and reverse each row in order to make np.argmax return indice of the rightmost occurence which corresponds to the largest item:
N = np.max(arr)
>>> [N - np.argmax(np.bincount(row, minlength=N+1)[::-1]) for row in Nbank]
[3, 2, 3, 3, 2, 2, 1, 1, 2, 1, 1, 3, 1]
You might also like to avoid loops which is definitely adviseable if you want to take full advantages of numpy. Unfortunately np.bincount is not supported for 2D arrays but you can do it manually:
N, M = arr.shape[0], np.max(arr)+1
bincount_2D = np.zeros(shape=(N, M), dtype=int)
advanced_indexing = np.repeat(np.arange(N), arr.shape[1]), arr.ravel()
np.add.at(bincount_2D, advanced_indexing, 1)
>>> bincount_2D
array([[0, 1, 1, 1],
[0, 1, 2, 0],
[0, 1, 1, 1],
[0, 1, 1, 1],
[0, 0, 2, 1],
[0, 0, 2, 1],
[0, 2, 0, 1],
[0, 2, 1, 0],
[0, 0, 2, 1],
[0, 3, 0, 0],
[0, 2, 1, 0],
[0, 1, 1, 1],
[0, 2, 1, 0]])
And then repeat the process for all the rows simultaneously:
>>> M -1 - np.argmax(bincount_2D[:,::-1], axis=1)
array([3, 2, 3, 3, 2, 2, 1, 1, 2, 1, 1, 3, 1], dtype=int64)

The original list is mutated after applying GA operators

The original list is mutated after applying GA operators
I am using the function below where I copy part of rankedChromos to newpop (list of lists) and then use a while loop to add remaining lists to it until the length of newpop is equal to the predetermined popSize. But for some reason, the newpop list is mutated after the while loop is implemented. In the while loop, I am trying to implement some GA operators and all I do is append the new individuals to the newpop list, which should not mutate the newpop but it does. Not just that, even the rankedChromos is mutated for some reason.
def nextGenPopulation (population, rankedPop, num_robots, crossover_rate):
newpop = 0
fitnessScores = [item[-1] for item in rankedPop ] # extract fitness scores
rankedChromos = [item[0] for item in rankedPop ] # extract chromosomes
popSize = len(population)
newpop = []
print("\n fitness", fitnessScores)
newpop.extend(rankedChromos[:int(popSize*0.4)]) # elitism
print("newpop before operations", newpop)
while len(newpop) < popSize:
ind1, ind2 = selectFittest (fitnessScores, rankedChromos)
ind1, ind2 = breed (ind1, ind2, num_robots, crossover_rate)
newpop.append(ind1)
newpop.append(ind2)
print("\n newpop after operation", newpop)
return newpop
For example if I am using 5 for popSize, the first 3 lists of newpop should remain the same before and after the implementation of the loop but it's not the same for some reason (example below) and I cannot understand why. I would very much appreciate any help. Thank you!
newpop before operation = [[0, 3, 0, 3, 2, 0, 3, 3, 1, 1, 1, 1, 2, 0, 2, 0, 2, 3, 1, 1], [1, 3, 2, 3, 1, 1, 2, 0, 1, 3, 3, 1, 2, 2, 3, 2, 2, 2, 0, 2]]
newpop after operation = [[0, 3, 0, 3, 2, 0, 3, 3, 1, 1, 1, 1, 1, 1, 2, 0, 0, 3, 1, 1], [1, 3, 2, 3, 1, 1, 2, 0, 1, 3, 3, 1, 2, 2, 3, 2, 2, 2, 0, 2], [0, 3, 0, 3, 2, 0, 3, 3, 1, 1, 1, 1, 1, 1, 2, 0, 0, 3, 1, 1], [2, 0, 0, 1, 0, 1, 2, 2, 1, 0, 0, 2, 2, 0, 3, 3, 2, 0, 0, 0], [2, 0, 0, 1, 0, 1, 2, 2, 1, 0, 0, 2, 2, 0, 3, 3, 2, 0, 0, 0], [0, 3, 0, 3, 2, 0, 3, 3, 1, 1, 1, 1, 1, 1, 2, 0, 0, 3, 1, 1]]
newpop.extend(rankedChromos[:int(popSize*0.4)]) it is actually copying the references for the lists from rankedChromos for the first int(popSize*0.4) - 1 lists into newpop, this means that any modification for rankedChromos for those lists will be reflected in newpop, most probably the function selectFittest it is mutating rankedChromos and as a result, you see the mutation in your variable newpop, you could use copy.deepcopy to eliminate this behaviour
from copy import deepcopy
newpop = deepcopy(rankedChromos[:int(popSize*0.4)])
here is an example to understand your issue:
list_1 = [[1, 1], [2, 2], [3, 3], [4, 4]] # rankedChromos in your example
list_2 = [] # newpop in your example
list_2.extend(list_1[1: 3])
print('before operation',list_2)
# the function that mutates, selectFittest in your example
def some_fumction(l):
for i in l:
i[1:] = [-9]
some_fumction(list_1)
print('after operation',list_2)
output:
before operation [[2, 2], [3, 3]]
after operation [[2, -9], [3, -9]]

Generate the initial game board of a Candy-Crush-like game

I need to implement a function,
which returns a 6 by 6 matrix that fulfills the following requirements:
The 36 numbers on the board must be 9 ones, 9 twos, 9 threes and 9 fours
Any row or column must not contain 3 or more direct neighbours that are the same number
The function return value must not be a constant
Obviously it’s not allowed to use pre-calculated answers
correct answer:
[[3, 2, 4, 1, 3, 2],
[2, 2, 1, 1, 4, 4],
[4, 4, 1, 3, 3, 2],
[4, 1, 3, 2, 2, 4],
[3, 1, 2, 4, 3, 1],
[3, 3, 1, 1, 2, 4]]
[[3, 3, 1, 2, 2, 4],
[1, 1, 3, 3, 2, 4],
[4, 4, 2, 1, 1, 3],
[2, 2, 3, 4, 4, 1],
[4, 4, 1, 1, 2, 2],
[3, 1, 2, 3, 3, 4]]
wrong answer:
[[3, 3, 3, 2, 2, 4],
[1, 1, 1, 3, 2, 4],
[4, 4, 2, 1, 1, 3],
[2, 2, 3, 4, 4, 1],
[4, 4, 1, 1, 2, 2],
[3, 1, 2, 3, 3, 4]]
[[3, 3, 1, 2, 2, 4],
[1, 1, 2, 3, 2, 4],
[4, 4, 1, 1, 2, 3],
[2, 2, 3, 4, 4, 1],
[4, 4, 1, 1, 2, 2],
[3, 1, 2, 3, 3, 4]]
Don’t need to worry too much about the academic time/space complexity. Focus more on the engineering point of view. Is there any good idea?
This should work. Note that this solution just generates a random board, checks if the conditions hold, and if not, generates another, so is not the most elegant solution.
Code:
from random import shuffle
def check_board(board):
for row in board:
if check_list(row):
return False
for i in range(len(board[0])):
col = [row[i] for row in board]
if check_list(col):
return False
return True
def check_list(lst):
return any(lst[i]==lst[i+1] and lst[i]==lst[i+2] for i in range(len(lst)-2))
board = [[]]
while check_board(board):
board = [1,2,3,4]*9
shuffle(board)
board = [board[i:i + 6] for i in range(0, len(board), 6)]
print(board)
Example boards generated:
[[3, 2, 4, 3, 3, 2],
[1, 1, 2, 3, 1, 3],
[1, 3, 3, 2, 2, 2],
[4, 4, 1, 4, 1, 2],
[1, 1, 4, 4, 2, 4],
[2, 4, 4, 3, 3, 1]]
[[2, 3, 4, 1, 4, 1],
[3, 4, 1, 1, 3, 4],
[3, 1, 4, 1, 3, 4],
[3, 4, 2, 4, 2, 1],
[2, 1, 4, 2, 3, 2],
[2, 2, 1, 3, 3, 2]]
Create an array of size 36 and fill it with your desired values => [1,1,1....4,4,4]
Apply Fisher-Yates shuffle to create a permutation of that array in O(n)
Check for the "3 in a row" rule and swap a random value if nesseccary, check again until the grid is free of that.

Creating a dictionary given values from a list and dictionary

So, I am given a list
a =[[[0, 0, 3, 3, 3, 3], [0, 0, 1, 3, 3, 3, 3]], [[0, 1]], [[2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3]], [[0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3]]]
and a dictionary d.
d = {0:2,1:1,2:3,3:4}
For the output, I want a dictionary:
output = {0:[0,3],1;[1],2:[2,3],3:[0,2]}
This output is formed by passing through each sublist of a and checking the number of times each element appears in d.
Let's look at index 0 of a. Now we look at a[0][0]and
a[0][1] and since 0 appears twice in both and 3 appears 4 times (comparing it to d), [0,3] are added to index 0. Similarly, at index 1, 0 appears just once and is not added to the dictionary at index 1.
What I tried so far:
def example(a,d):
for i in range(len(a)):
count = 0
for j in range(len(a[i])):
if j in (a[i][j]):
count+=1
if count == d[i]:
print(i,j)
Edit: A version that work
from collections import Counter
a = [[[0, 0, 3, 3, 3, 3], [0, 0, 1, 3, 3, 3, 3]], [[0, 1]],
[[2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3]],
[[0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3], [0, 0, 2, 2, 2, 3, 3, 3, 3]]]
d = {0: 2, 1: 1, 2: 3, 3: 4}
output = {i: [] for i in range(len(a))}
for j, sublist in enumerate(a):
counts = [Counter(i) for i in sublist]
for k,v in d.items():
try:
if all(counts[i][k] == v for i in range(len(counts))):
output[j].append(k)
except: continue
print(output)
output:
{0: [0, 3], 1: [1], 2: [2, 3], 3: [0, 2]}
The try except block is merely for convenience, If you insist you can if your way around this by checking if a key is in all counters (which is a requirement for it to be add)

Iterating through list of lists of lists and list of tuples

I'm trying to iterate over a list of lists of lists and list of tuples at the same time, replacing certain values within the list of lists of lists with values from the list of tuples.
Here is my code so far:
tups = [(1,2,3),(4,5,6),(9,8,7)]
lsts = [[[1, 0, 1], [2, 3, 1, 0], [1, 1, 1, 1, 10, 0]], \
[[1, 0, 1], [2, 3, 1, 0], [1, 1, 1, 1, 10, 0]], \
[[1, 0, 1], [2, 3, 1, 0], [1, 1, 1, 1, 10, 0]]]
for index1, aitem in enumerate(tups):
for index2, a in enumerate(aitem):
for index3, mega_item in enumerate(lsts):
for index4, bitem in enumerate(mega_item):
for index5, b in enumerate(item):
if i == 0:
lsts[index3][index4][index5] = a
break
else:
continue
break
else:
continue
break
else:
continue
break
I want my solution to produce lsts with the 0s replaced by the values in tups in sequential order like this:
lsts = [[[1, 1, 1], [2, 3, 1, 2], [1, 1, 1, 1, 10, 3]], \
[[1, 4, 1], [2, 3, 1, 5], [1, 1, 1, 1, 10, 6]], \
[[1, 9, 1], [2, 3, 1, 8], [1, 1, 1, 1, 10, 7]]]
However, the my result at the moment is:
lsts = [[[1, 1, 1], [2, 3, 1, 2], [1, 1, 1, 1, 10, 3]], \
[[1, 1, 1], [2, 3, 1, 2], [1, 1, 1, 1, 10, 3]], \
[[1, 1, 1], [2, 3, 1, 2], [1, 1, 1, 1, 10, 3]]]
I believe my loop may only be iterating over the first item in my list of tuples.
How can I solve this problem?
This should work:
for inner_lst,inner_tup in zip(lsts,tups):
for ind,depth in enumerate(inner_lst):
depth[depth.index(0)]= inner_tup[ind]
print lsts
Output:
[[[1, 1, 1], [2, 3, 1, 2], [1, 1, 1, 1, 10, 3]],
[[1, 4, 1], [2, 3, 1, 5], [1, 1, 1, 1, 10, 6]],
[[1, 9, 1], [2, 3, 1, 8], [1, 1, 1, 1, 10, 7]]]
Simply nested for loops are working well.
for i in xrange(len(tups)):
for j in xrange(len(tups[i])):
index = lsts[i][j].index(0)
lsts[i][j][index] = tups[i][j]
The output for:
for s in lsts:
print s
is:
[[1, 1, 1], [2, 3, 1, 2], [1, 1, 1, 1, 10, 3]]
[[1, 4, 1], [2, 3, 1, 5], [1, 1, 1, 1, 10, 6]]
[[1, 9, 1], [2, 3, 1, 8], [1, 1, 1, 1, 10, 7]]

Categories

Resources