Related
When running my sudoku backtracking algorithm using random ordering for finding new available locations, it takes way longer than when finding the available locations, left to right, top to bottom. Why? How should I change the code to have quick random ordering?
//Taking forever
let posOrder = [[4, 4], [4, 0], [7, 0], [4, 8], [2, 3], [0, 8], [6, 0], [0, 6], [0, 5], [5, 4], [8, 2], [7, 7], [5, 1], [6, 3], [3, 2], [3, 3], [1, 2], [6, 2], [0, 7], [4, 2], [1, 4], [0, 1], [1, 1], [7, 1], [5, 2], [0, 2], [3, 7], [1, 6], [0, 4], [8, 1], [5, 6], [2, 1], [8, 3], [6, 4], [8, 6], [2, 7], [6, 6], [8, 7], [1, 8], [7, 4], [4, 7], [4, 5], [8, 4], [6, 1], [2, 2], [1, 5], [7, 6], [3, 6], [5, 0], [4, 1], [2, 8], [6, 8], [3, 1], [5, 3], [3, 4], [7, 2], [2, 5], [8, 5], [5, 5], [7, 8], [8, 8], [6, 5], [6, 7], [4, 6], [2, 6], [3, 5], [2, 0], [5, 7], [1, 0], [0, 3], [2, 4], [7, 5], [8, 0], [7, 3], [0, 0], [3, 8], [5, 8], [3, 0], [1, 7], [1, 3], [4, 3]]
//Goes quickly
//let posOrder = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7], [3, 8], [4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [4, 7], [4, 8], [5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [5, 8], [6, 0], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6], [6, 7], [6, 8], [7, 0], [7, 1], [7, 2], [7, 3], [7, 4], [7, 5], [7, 6], [7, 7], [7, 8], [8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 6], [8, 7], [8, 8]]
const backtrack = (board) => {
const pos = posOrder.find(p => board[p[0]][p[1]] === 0)
if(!pos)
return true
const [row, col] = pos
return (shuffleArray([1, 2, 3, 4, 5, 6, 7, 8, 9]).some(number => {
if (!numberExists(board, number, row, col)) {
board[row][col] = number
if (backtrack(board))
return true
else {
board[row][col] = 0
return false
}
}
}))
}
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // This ; is necessary... apparently
[array[i], array[j]] = [array[j], array[i]]
}
return array
}
const numberInRow = (board, number, row) => board[row].some(col => col === number)
const numberInCol = (board, number, col) => board.some(row => row[col] === number)
const numberInRegion = (board, number, row, col) => {
const r = 3*Math.floor(row/3)
const c = 3*Math.floor(col/3)
return [board[r], board[r+1], board[r+2]].some(arr=> [arr[c], arr[c+1], arr[c+2]].some(nbr => nbr === number))
}
const numberExists = (board, number, row, col) => (
numberInRow(board, number, row) ||
numberInCol(board, number, col) ||
numberInRegion(board, number, row, col)
)
const sudokuBoard = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
]
backtrack(sudokuBoard)
Note that I'd prefer to randomize the order every time, just kept one randomized order to rerun the example.
In the non-random order, after 9 placements all combinations for the first row that would have duplicates, have already been eliminated. After 27 placements, there are already 3 lines and three 3x3 boxes completed. That means already some alternatives had to be considered at an early stage to make it work. For instance, there was only one possible digit to place at move 9.
The random order will in a first phase allow the placement of digits with much more liberty, as a lot of initial coordinates are not directly related, and so fewer choices have to be redone at an early stage. This means more wrong moves are left on the board for a longer time and will therefore take much more time before they are eventually undone and replaced by a different choice.
To get the better performance it is of utmost importance that early choices are good and don't have to be redone. So that means that the sudoku constraints have to be eagerly sought.
I would therefore say that the best order is to fill lines, columns and boxes as soon as possible. For instance, this seems to be a promising order:
The first line
The top-left box (so completing it with 6 more positions)
The second line (completing the 6 remaining positions)
The top-center box (completing 3 remaining positions)
The third line & top-right box (completing 3 remaining positions)
The first column (completing 6 remaining positions)
The second column (completing 6 remaining positions)
... etc
I have an object like this:
const arrays = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
As you can see each first number is equal:
1 for each first inner array
2 for each second inner array
3 for each third inner array
etc...
I want to filter based on the first number of each array
and some comparator number e.g. [3]
If we have a filter number [3] (smaller or equal to 3),
the wanted result would be:
const arrays = {
one: [[1,33,41], [2,0,27], [3,7,9]],
two: [[1,77,2], [2,6,3], [3,0,0]],
three: [[1,4,6], [2,0,0], [3,5,6]],
};
Since all first numbers of inner arrays are smaller than or equal to 3.
The arrays starting with 4,5... are ignored.
What would be the ramda's way to have this functionality?
I like Ramda's map function because it can iterate over the properties of an object (and so avoid Object.fromEntries & Object.entries) and apply a function to each one of them. That function is filter which will take as argument the inner arrays. The function given to filter is itself a composition of gte and head; takes the first element of an array and compare it with 3:
const arrays =
{ one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]]
, two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]]
, three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]] };
map(filter(compose(gte(3), head)), arrays);
// ^ ^ ^ ^ ^
// A B C D E
//=> { one: [[ 1, 33, 41], [2, 0, 27], [3, 7, 9]]
//=> , two: [[ 1, 77, 2], [2, 6, 3], [3, 0, 0]]
//=> , three: [[ 1, 4, 6], [2, 0, 0], [3, 5, 6]] }
map over each property (A); each array is passed to filter (B)
Each inner array is passed to compose (C)
Take the head of each inner array (E) and compare with 3 (D)
Scott Christopher rightly pointed out in the comments that gte can be confusing when partially applied. In fact the whole composition can be replaced with this simple lambda: ([x]) => x <= 3.
Alternative solution which I like too:
map(filter(([x]) => x <= 3), arrays);
I'd totally subscribe for #customcommander's approach,
just wanted to add that you can also pass numerical indexes to R.propSatisfies.
const headIs3OrBelow = R.propSatisfies(R.gte(3), 0);
const fn = R.map(R.filter(headIs3OrBelow));
// ===
const data = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
console.log(
fn(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Also agree that gte and other similar methods are very difficult to read, because they kind of read backwards as is 3 gte than x... in Haskell you could do something like:
3 `gte` x
Vanilla approach:
const headIs3OrBelow = ([head]) => head <= 3;
const fn = (data) => Object.entries(data).reduce(
(res, [k, lists]) => ({ ...res, [k]: lists.filter(headIs3OrBelow) }),
{},
);
// ===
const data = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
console.log(
fn(data),
);
I understand you would like to use Ramda, this is not a solution using the library but accomplishes the same. You could create an object from entries that are filtered by comparing the first array value a[0] to the maxVal passed to the function.
const arrays = {
one: [[1, 33, 41], [2, 0, 27], [3, 7, 9], [4, 1, 3]],
two: [[1, 77, 2], [2, 6, 3], [3, 0, 0], [4, 55, 3]],
three: [[1, 4, 6], [2, 0, 0], [3, 5, 6], [4, 0, 0]],
};
const filterArrays = (arsObj, maxVal) => {
return Object.fromEntries(Object.entries(arsObj).map(([k, v]) => {
return [k, v.filter((a) => a[0] <= maxVal)];
}));
}
const result = filterArrays(arrays, 3);
console.log(result);
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I am looking for an algorithm that takes 2 inputs (#1: a number, #2: an array of numbers [that may be duplicated]):
Output should be the desired groups (with the individual numbers from the original list in them). The groups should be as close in length/size. Note that if a number is in one group, all other items in the list with the same number would be put in that same group. (e.g. so you would not have the number 3 say in multiple output groups).
NOTE that elements in returned groups MUST not overlap in their range of numbers
So you cannot have two output groups like this [[1,1,1,2,2,4,4,4], [3,3,5,5,6,7,8,16]] because the range of numbers in each subgroup is [1-4] and [3-16], which have an overlap. You can only have groups like [1-3][4-16] (note there is no overlap in this case).
Sample #1 Input/Output
1) 3 Desired Groups
2) List: [1,1,2,2,3,3]
Output:
[[1,1],[2,2],[3,3]]
Sample #2 Input/Output
Input 2 desired groups/subarrays to be output, and the following list of numbers:
[1,1,1,2,2,3,3,4,4,4,5,5,6,7,8,16]
Output are two subarrays that contain the following:
[1,1,1,2,2,3,3,4,4,4]
[5,5,6,7,8,16]
Note #1: Output two subarrays/groups of [[1,1,1,2,2,3,3] [4,4,4,5,5,6,7,8,16]] would also be valid given that there is no way to output equal groups.
Note #2: While subgroups of: [[1,1,1,2,2,4,4,4], [3,3,5,5,6,7,8,16]] would be equal in length, it violates the rule of "no overlapping ranges between returned subgroups", meaning the range of numbers in each subgroup cannot overlap with each other. (e.g. In this note, the ranges of the subgroups returned are [1-4] and [3-16], if you took a ruler and drew a line from the numbers 1-4 and draw another line from 3-16, you would see that 3-4 would have overlapping lines, which is not what we want in this case).
In the sample output of this example, the ranges of the two groups are [1-4] and [5-16], which if you took a ruler/tape measure and drew lines where the numbers are, the lines would not overlap with each other, which is what we want.
Sample #3 Input/Output
Input 3 desired groups/subarrays to be output, and the following list of numbers:
[1,1,1,2,2,3,3,4,4,4,5,5,6,7,8,16]
Output:
[1,1,1,2,2]
[3,3,4,4,4]
[5,5,6,7,8,16]
Note in this case, since there is no way to achieve the same # of items, algorithm outputs the best it can do where one group is only 1 bigger than the others.
Sample #4 Input/Output
Input: "4 desired groups", and the following list:
[1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 7, 8, 16]
Possible Output:
[1,1,1,2,2]
[3,3,4,4,4]
[5,5,6]
[7,8,16]
Note: Preferrably, the output should contain more than 1 unique number when possible. While an output of [[1, 1, 1], [2, 2, 3, 3], [4, 4, 4, 5, 5], [6, 7, 8, 16]] does provide approximately similar groupings, it is preferred that there is more than 1 unique number in a single subgroup. In this "Note", 1 is the only number in group 1, while in the sample output of this example, group 1 contains unique numbers 1 and 2, which is preferred.
What is a good way to perform this?
Grouping common numbers then using a recursive function
My last solution didn't give the right results, so this is a different algorithm. It follows:
group the list into sub-lists of common numbers
check if this list has the required number of groups (sub-lists)
if it does then add it to a list of possible solutions and end
else, create a list of all the different pairs of common numbers in the current list merged together
for each list in (4), go back to (2)
If you followed the algorithm, you can see that it will work by branching off down the differently merged lists until it reaches one of the required length where that branch will end. This is a perfect opportunity to write a recursive function.
But before that, we needed a small method for grouping common numbers from the original list. To do this, a small for-loop goes through each number and check if it belongs as part of the previous common numbers. If it does, add it to them, or else it creates its own common numbers.
This may look something like:
l = [1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6]
l.sort()
groups = []
for i in l:
if groups and i in groups[-1]:
groups[-1].append(i)
else:
groups.append([i])
now groups is:
[[1], [2, 2], [3, 3, 3], [4, 4], [5, 5], [6]]
so we are ready for the recursive funtion:
def merge(grps, numGrps):
global solutions
if len(grps) <= numGrps:
solutions.append(grps)
return
merges = [grps[:i] + [grps[i] + grps[i+1]] + grps[i+2:] for i in range(len(grps)-1)]
for m in merges:
merge(m, numGrps)
the function is self-explanatory, but the list-comprehension is the important part which controls the branching:
[grps[:i] + [grps[i] + grps[i+1]] + grps[i+2:] for i in range(len(grps)-1)]
It is essentially saying: for each number up to the length of the current list, take the common number groups before it grps[:i], add them onto its common numbers grps[i] merged with the next common numbers grps[i+1] and then add that onto the rest of the common numbers grps[i+2:].
From there, we just set the function going with each of the different merged common number combinations along with its target number of groups.
The final code
The final code put together would be:
l = [1,2,2,3,3,3,4,4,5,5,6]
l.sort()
groups = []
for i in l:
if groups and i in groups[-1]:
groups[-1].append(i)
else:
groups.append([i])
print("original list:")
print(l, "\n")
print("common numbers grouping:")
print(groups)
print()
def merge(grps, numGrps):
global solutions
if len(grps) <= numGrps:
solutions.append(grps)
return
merges = [grps[:i] + [grps[i] + grps[i+1]] + grps[i+2:] for i in range(len(grps)-1)]
for m in merges:
merge(m, numGrps)
solutions = []
merge(groups, 3)
print("possible solutions:\n")
for s in solutions:
print(s)
which outputs:
original list:
[1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6]
common numbers grouping:
[[1], [2, 2], [3, 3, 3], [4, 4], [5, 5], [6]]
possible solutions:
[[1, 2, 2, 3, 3, 3, 4, 4], [5, 5], [6]]
[[1, 2, 2, 3, 3, 3], [4, 4, 5, 5], [6]]
[[1, 2, 2, 3, 3, 3], [4, 4], [5, 5, 6]]
[[1, 2, 2, 3, 3, 3, 4, 4], [5, 5], [6]]
[[1, 2, 2], [3, 3, 3, 4, 4, 5, 5], [6]]
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4, 5, 5], [6]]
[[1, 2, 2], [3, 3, 3, 4, 4, 5, 5], [6]]
[[1, 2, 2], [3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4], [5, 5, 6]]
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
[[1, 2, 2], [3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3, 4, 4], [5, 5], [6]]
[[1, 2, 2, 3, 3, 3], [4, 4, 5, 5], [6]]
[[1, 2, 2, 3, 3, 3], [4, 4], [5, 5, 6]]
[[1, 2, 2, 3, 3, 3, 4, 4], [5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4], [5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4], [5, 5, 6]]
[[1], [2, 2, 3, 3, 3, 4, 4], [5, 5, 6]]
[[1], [2, 2, 3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3, 4, 4], [5, 5], [6]]
[[1, 2, 2], [3, 3, 3, 4, 4, 5, 5], [6]]
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
[[1, 2, 2, 3, 3, 3, 4, 4], [5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4], [5, 5, 6]]
[[1, 2, 2], [3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2], [3, 3, 3, 4, 4, 5, 5, 6]]
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
[[1], [2, 2, 3, 3, 3, 4, 4], [5, 5, 6]]
[[1], [2, 2], [3, 3, 3, 4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4, 5, 5], [6]]
[[1, 2, 2], [3, 3, 3, 4, 4, 5, 5], [6]]
[[1, 2, 2], [3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2], [3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2, 3, 3, 3, 4, 4, 5, 5], [6]]
[[1], [2, 2], [3, 3, 3, 4, 4, 5, 5, 6]]
[[1, 2, 2], [3, 3, 3], [4, 4, 5, 5, 6]]
[[1], [2, 2, 3, 3, 3], [4, 4, 5, 5, 6]]
[[1], [2, 2], [3, 3, 3, 4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4], [5, 5, 6]]
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
[[1, 2, 2], [3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2, 3, 3, 3], [4, 4], [5, 5, 6]]
[[1], [2, 2, 3, 3, 3, 4, 4], [5, 5, 6]]
[[1], [2, 2, 3, 3, 3], [4, 4, 5, 5, 6]]
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
[[1], [2, 2, 3, 3, 3, 4, 4], [5, 5, 6]]
[[1], [2, 2], [3, 3, 3, 4, 4, 5, 5, 6]]
[[1, 2, 2], [3, 3, 3], [4, 4, 5, 5, 6]]
[[1], [2, 2, 3, 3, 3], [4, 4, 5, 5, 6]]
[[1], [2, 2], [3, 3, 3, 4, 4, 5, 5, 6]]
Sorting
Now that you have ALL the possible solutions, you can sort them any way you want. So for example, if you wanted to select the one with the most even spread in length in the group, you would calculate each groups length and the difference between smallest and biggest lengths would be the score on which you rank them.
Even though there are other ways of sorting them, as we have seen in the comment section, the one described above seems to be what most people want so here it is:
smallestDiff = 9999
for s in solutions:
lenDiff = max([len(a) - len(b) for a in s for b in s])
if lenDiff < smallestDiff:
smallestDiff = lenDiff
sol = s
and for my example with list:
[1, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6]
we have the result:
[[1, 2, 2], [3, 3, 3, 4, 4], [5, 5, 6]]
which I would consider to be the best solution in this case. And finally, to check with the examples given in the question:
Sample 1
groups = 3
l = [1, 1, 2, 2, 3, 3]
gives output of:
[[1, 1], [2, 2], [3, 3]]
Sample 2
groups = 2
l = [1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 7, 8, 16]
gives output of:
[[1, 1, 1, 2, 2, 3, 3], [4, 4, 4, 5, 5, 6, 7, 8, 16]]
So it is clear this algorithm works and I hope this helps.
You can use .map() and .filter() to create an array of arrays containing the same elements, collect values of arrays where there is single element to a single array, if they exist, .splice() expected number of arrays (groups) from collected array, reinsert original single values into to groups, return result
let arr = [1,1,1,2,2,3,3,4,4,4,5,5,6,7,8,16]
const group = (arr, groups) => {
// if input array `.length` is `0`
// or `groups` is less than 2, throw `TypeError`
if (!arr.length) {
throw new TypeError("input array does not have valid `.length`")
}
// declare resulting array `res`,
// `singles` array to group values of arrays having `.length` of `1`
// to single array
// `s` array of groups of same element from input array
// `len` whole number to `.splice()` `s` into `groups` by
// `t` array of single elements from `s`, if they exist
let [res, singles] = [[], []];
const [s
, len = Math.floor(s.length / groups -1)
, t = s.filter(el => el.length === 1)] = [
[...new Set(arr)].map(el => arr.filter(n => n === el))
];
if (t.length) {
for (const g of t) {
// collect our single element arrays to `singles` array
singles = [...singles, ...g];
// remove singles element arrays from `s`
s.splice(s.indexOf(g), 1);
}
// `.push()` `singles` to `s`
s.push(singles);
}
do {
// `.splice()` our groups
const curr = s.splice(0, len);
// `.push()` current group to `res`
if (res.length < groups) {
res.push([])
};
// push elements of arrays to current array in `res`
for (const g of curr) {
res[res.length - 1] = [...res[res.length - 1], ...g]
}
} while (s.length);
// return result `res` array
return res;
}
let g2 = group(arr, 2);
let g3 = group(arr, 3);
let g4 = group(arr, 4);
console.log(g2, g3, g4);
We can use itertools.groupby to group the duplicated items together, and then use a simple "greedy" algorithm to assign each group to a sublist. If we have any leftover items at the end of the main loop, we put them in a new sublist unless we've already reached the desired number of sublists, in which case we simply add the leftovers to the last existing sublist.
The result isn't perfect: depending on the data, it may not even create enough sublists, but with well-behaved data the results are reasonable, IMHO. ;)
from itertools import groupby
def equal_groups(seq, num):
grouplen = len(seq) // num
result, current = [], []
for _, g in groupby(seq):
g = list(g)
# If this group is too big to go in current, flush current
if current and len(current) + len(g) > grouplen:
result.append(current)
current = []
current.extend(g)
# Deal with any leftovers
if current:
if len(result) < num:
result.append(current)
else:
result[-1].extend(current)
return result
# Test
data = [1,1,1,2,2,3,3,4,4,4,5,5,6,7,8,16]
for i in range(1, 8):
print(i, equal_groups(data, i))
output
1 [[1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 7, 8, 16]]
2 [[1, 1, 1, 2, 2, 3, 3], [4, 4, 4, 5, 5, 6, 7, 8, 16]]
3 [[1, 1, 1, 2, 2], [3, 3, 4, 4, 4], [5, 5, 6, 7, 8, 16]]
4 [[1, 1, 1], [2, 2, 3, 3], [4, 4, 4], [5, 5, 6, 7, 8, 16]]
5 [[1, 1, 1], [2, 2], [3, 3], [4, 4, 4], [5, 5, 6, 7, 8, 16]]
6 [[1, 1, 1], [2, 2], [3, 3], [4, 4, 4], [5, 5], [6, 7, 8, 16]]
7 [[1, 1, 1], [2, 2], [3, 3], [4, 4, 4], [5, 5], [6, 7], [8, 16]]
need some help with a script completely on array that I'm doing
skill = [
//[ID, "NAME", TMLEVEL, Learn, Mastery, Prerequisite, PrerequisiteLvl],
//Schoolgirl, Fighter
[0, "Steel Punch", 0, 0, null, null],
[1, "Shockwave", 1, 1, 2, null],
[2, "Bull's Eye", 10, 2, 2, null],
[3, "Burning Rave", 20, 2, 2, null],
[4, "Shockvibe", 20, 1, 2, null],
[5, "Sense Breaker", 20, 1, 2, null],
[6, "Luck Breaker", 20, 1, 2, null],
[7, "Pumping Heart", 25, 3, 3, skill[3], 1],
[8, "Armor Breaker", 30, 2, 2, skill[1], 10],
[9, "Upper Smash", 40, 2, 2, skill[2], 10],
[10, "Hyper Beat", 45, 4, 3, [skill[2],skill[3]], [10,10]],
[11, "Tornado Bomb", 50, 3, 3, skill[8], 1]
];
I need that inside the array, in certain points, to call th array again to put the array value in there, like i have here.
In theory this works fine, without any error, but when i call the array inside it,it says that it's "undefined".
Any one knows how can i do this without rewrite everything on it? (because i use this in +- 300 code lines).
After a long process, I have come up with a solution that will replace all prerequisites, even if they are several levels deep (e.g. skill_3 requires skill_2 which requires skill_1...).
This will require your skill variable to be correctly declared (in your question, not all of the skills had 7 variables).
Here is an example of what the variable will look like:
var skill = [
//[ID, "NAME", TMLEVEL, Learn, Mastery, Prerequisite, PrerequisiteLvl],
[0, "Steel Punch", 0, 0, 0, null, null],
[1, "Shockwave", 1, 1, 2, null, null],
[2, "Bull's Eye", 10, 2, 2, 7, null],
[3, "Burning Rave", 20, 2, 2, null, null],
[4, "Shockvibe", 20, 1, 2, null, null],
[5, "Sense Breaker",20, 1, 2, null, null],
[6, "Luck Breaker", 20, 1, 2, null, null],
[7, "Pumping Heart",25, 3, 3, 3, 1],
[8, "Armor Breaker",30, 2, 2, 7, 10],
[9, "Upper Smash", 40, 2, 2, 2, 10],
[10,"Hyper Beat", 45, 4, 3, [2,3], [10,10]],
[11,"Tornado Bomb", 50, 3, 3, 8, 1]
];
Now, I thought of a function setPrerequisites() that will, for 1 skill, recursively set it's prerequisites:
Array.prototype.setPrerequisites = function(){
if (typeof this[5] === "number")
{
this[5]=skill[getPosOfSkill(this[5])];
this[5].setPrerequisites();
}
else if (this[5] instanceof Array)
{
if (this[5].isSkill()) this[5].setPrerequisites();
else
{
for(var i = 0; i < this[5].length; i++)
{
this[5][i] = skill[getPosOfSkill(this[5][i])];
this[5][i].setPrerequisites();
}
}
}
}
This function uses isSkill() to determine whether an array is a skill, or an array of skill IDs:
Array.prototype.isSkill = function(){
return this.length==7 && typeof this[1]==="string";
}
It also uses getPosOfSkill(id) to look for the right skill in case your skills were listed in no particular order, or if ID's are missing:
function getPosOfSkill(id){
for(var i=0; i<skill.length; i++) if (skill[i][0]==id) return i;
return false;
}
All you have to do is declare your skill variable, and then fill it:
for (var i = 0; i < skill.length; i++) skill[i].setPrerequisites();
// if you want to see the results
console.log(skill);
Here is a jsFiddle Demo
You'll have to either rethink your whole approach here (recommended), or set those items to null at first, then rerun the declaration:
skill = [
//[ID, "NAME", TMLEVEL, Learn, Mastery, Prerequisite, PrerequisiteLvl],
//Schoolgirl, Fighter
[0, "Steel Punch", 0, 0, null, null],
[1, "Shockwave", 1, 1, 2, null],
[2, "Bull's Eye", 10, 2, 2, null],
[3, "Burning Rave", 20, 2, 2, null],
[4, "Shockvibe", 20, 1, 2, null],
[5, "Sense Breaker", 20, 1, 2, null],
[6, "Luck Breaker", 20, 1, 2, null],
[7, "Pumping Heart", 25, 3, 3, null, 1],
[8, "Armor Breaker", 30, 2, 2, null, 10],
[9, "Upper Smash", 40, 2, 2, null, 10],
[10, "Hyper Beat", 45, 4, 3, null, null],
[11, "Tornado Bomb", 50, 3, 3, null, 1]
];
skill = [
//[ID, "NAME", TMLEVEL, Learn, Mastery, Prerequisite, PrerequisiteLvl],
//Schoolgirl, Fighter
[0, "Steel Punch", 0, 0, null, null],
[1, "Shockwave", 1, 1, 2, null],
[2, "Bull's Eye", 10, 2, 2, null],
[3, "Burning Rave", 20, 2, 2, null],
[4, "Shockvibe", 20, 1, 2, null],
[5, "Sense Breaker", 20, 1, 2, null],
[6, "Luck Breaker", 20, 1, 2, null],
[7, "Pumping Heart", 25, 3, 3, skill[3], 1],
[8, "Armor Breaker", 30, 2, 2, skill[1], 10],
[9, "Upper Smash", 40, 2, 2, skill[2], 10],
[10, "Hyper Beat", 45, 4, 3, [skill[2],skill[3]], [10,10]],
[11, "Tornado Bomb", 50, 3, 3, skill[8], 1]
];
That way, the array elements you are trying to access already exist, now you are just overwriting them.
I think i found a easy way, but still has the problem if it's defined after, but since i only call the values defined before there is no problem for now.
But if anyone knows some way better let me know please.
Here is what i do still using only arrays:
var skill = [];
skill[0] = [0, "Steel Punch", 0, 0, null, null];
skill[1] = [1, "Shockwave", 1, 1, 2, null];
skill[2] = [2, "Bull's Eye", 10, 2, 2, null];
skill[3] = [3, "Burning Rave", 20, 2, 2, null];
skill[4] = [4, "Shockvibe", 20, 1, 2, null];
skill[5] = [5, "Sense Breaker", 20, 1, 2, null];
skill[6] = [6, "Luck Breaker", 20, 1, 2, null];
skill[7] = [7, "Pumping Heart", 25, 3, 3, skill[3], 1];
skill[8] = [8, "Armor Breaker", 30, 2, 2, skill[1], 10];
skill[9] = [9, "Upper Smash", 40, 2, 2, skill[2], 10];
skill[10] = [10, "Hyper Beat", 45, 4, 3, [skill[2],skill[3]], [10,10]];
skill[11] = [11, "Tornado Bomb", 50, 3, 3, skill[8], 1];
This way i define them and i'm still using the arrays has i wanted (1 night of sleep makes me think much better :P)
Let's say I have the follow:
var test_data = {
'numGroup1': [[(1, 2, 3, 4, 5), (5, 6, 7, 8, 9)]],
'numGroup2': [[(10, 11, 12, 13, 14), (15, 16, 17, 18, 19)]],
};
How would I go about iterating through it using JavaScript?
var test_data = {
'numGroup1': [[1, 2, 3, 4, 5], [5, 6, 7, 8, 9]],
'numGroup2': [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19]],
};
for(var key in test_data){
group = test_data[key];
for(var num in group){
console.log(group[num]);
}
}
#Ian is right... using () will not do anything but enter the last digit of each group. You should use a multidimensional array
'numGroup1': [[1, 2, 3, 4, 5], [5, 6, 7, 8, 9]],
'numGroup2': [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19]],
You can use underscorejs to iterate over it
var test_data = {
'numGroup1': [[1, 2, 3, 4, 5], [5, 6, 7, 8, 9]],
'numGroup2': [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19]],
};
_.chain(test_data).map(function(value, key) {
return value;
}).flatten().each(alert);