Find if few array items exists in any one of many arrays? - javascript

Have these 2 Javascript arrays
// The player which has a "winning" combination (1, 5, 9)
player1Cells = [ 5, 9, 1, 6]
// Player which doesnt have a "winning" combination
player2Cells = [ 2, 1, 4, 8]
A player has a "winning" combination if 3 of their numbers match one of the arrays in this array:
winningCombinations = [
[1, 2, 3], [4, 5, 6], [7, 8, 9],
[1, 4, 7], [2, 5, 8], [3, 6, 9],
[1, 5, 9], [3, 5, 7]
];
In the example, player1Cells has a winning combination - 1, 5, 9. The other player doesn't
I figured there is a way to loop through the winningCombinations in some way, and compare it against the player compare but I don't know best approach to comparing these in an efficient way.

You can use some method on the winning array and then filter method to check if player has more than 3 matches as current array in some loop.
const w = [[1, 2, 3], [4, 5, 6], [7, 8, 9],[1, 4, 7], [2, 5, 8], [3, 6, 9],[1, 5, 9], [3, 5, 7]];
const player1Cells = [ 5, 9, 1, 6];
const player2Cells = [ 2, 1, 4, 8];
function check(arr, p) {
return arr.some(a => {
const match = p.filter(e => a.includes(e));
return match.length >= 3;
})
}
console.log(check(w, player1Cells));
console.log(check(w, player2Cells));

You may use:
Array.prototype.some()
Array.prototype.every()
Array.prototype.includes()
Working Example:
let player1Cells = [5, 9, 1, 6];
let player2Cells = [2, 1, 4, 8];
let winningCombinations = [
[1, 2, 3], [4, 5, 6], [7, 8, 9],
[1, 4, 7], [2, 5, 8], [3, 6, 9],
[1, 5, 9], [3, 5, 7]
];
let checker = function(w, p) {
return w.some(a => a.every(v => p.includes(v)));
}
console.log(checker(winningCombinations, player1Cells));
console.log(checker(winningCombinations, player2Cells));

Related

Filter each inner property arrays with Ramda

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);

How to return the correct array which include all values inside the second array

Let say there are two array.
let MotherArray = [
[30, 1, 2, 3, 4, 5, 6],
[5, 6, 7, 8, 9],
[7, 8, 9],
];
let arraytoTest = [5,6];
What i want is that i want to return the array if
all the value inside the arraytoTest is included in the MotherArray[i]
I have tried
let MotherArray = [[30, 1, 2, 3, 4, 5, 6],[5, 6, 7, 8, 9],[7, 8, 9],];
let arraytoTest = [5, 6];
let result = MotherArray.includes(arraytoTest)
console.log(result);
But i don't think this is the correct method.
I also find the array.every() but i think my usage is not correct.
What I want is that I want to return MotherArray[0],MotherArray[1] which are [[30, 1, 2, 3, 4, 5, 6],[5, 6, 7, 8, 9]] in this particular example
since 5 and 6 are includes inside these 2 arrays.
You can combine array.filter() with array.every()
let MotherArray = [
[30, 1, 2, 3, 4, 5, 6],
[5, 6, 7, 8, 9],
[7, 8, 9],
];
let arraytoTest = [5,6];
let found = MotherArray.filter(childArray => arraytoTest.every(num => childArray.includes(num)));
console.log(found);
I think this is what you want; A combination of filter on the mother array and every for array you're testing.
let MotherArray = [
[30, 1, 2, 3, 4, 5, 6],
[5, 6, 7, 8, 9],
[7, 8, 9],
];
let arraytoTest = [5,6];
let result = MotherArray.filter(arr => arraytoTest.every(x => arr.indexOf(x)>-1));
console.log(result);
You can use filter and every like below.
Or you can use filter and some with negative condition like filter(x => !arraytoTest.some(y => !x.includes(y))). I think with some it would be efficient because
The some() method executes the callback function once for each element present in the array until it finds the one where callback returns a truthy value.
let MotherArray = [
[30, 1, 2, 3, 4, 5, 6],
[5, 6, 7, 8, 9],
[7, 8, 9],
];
let arraytoTest = [5,6];
let result = MotherArray.filter(x => arraytoTest.every(y => x.includes(y)));
console.log(result);
let result2 = MotherArray.filter(x => !arraytoTest.some(y => !x.includes(y)));
console.log(result2);

Extracting an array with even numbers out a bidimensional set of arrays in javascript

/for the following bidimensional array im trying to write a function that finds the array composed by even numbers and then extract it/
var loggedPasscodes =[
[1, 4, 4, 1],
[1, 2, 3, 1],
[2, 6, 0, 8],
[5, 5, 5, 5],
[4, 3, 4, 3]
];
// I can check if its elements are even so:
if(loggedPasscodes[0][1]%2===0) {
console.log(loggedPasscodes[0])
} else {
console.log('nope')
}
//And I can loop the function to atleast give me the outer tier of the array like this:
function getValidPassword(x){
for(i=0;i<x.length;i++){
console.log(x[i])
}
};
console.log(getValidPassword(loggedPasscodes))
I would like to run the function and return the [2, 6, 0, 8] array.
Thanks in advance for your time.
You could find the array by checking each nested array with Array#every and if all values are even.
var loggedPasscodes = [[1, 4, 4, 1], [1, 2, 3, 1], [2, 6, 0, 8], [5, 5, 5, 5], [4, 3, 4, 3]],
allEven = loggedPasscodes.find(a => a.every(v => v % 2 === 0));
console.log(allEven);
If you want more than the first found, you could filter the array.
var loggedPasscodes = [[1, 4, 4, 1], [1, 2, 3, 1], [2, 6, 0, 8], [5, 5, 5, 5], [4, 3, 4, 3]],
allEven = loggedPasscodes.filter(a => a.every(v => v % 2 === 0));
console.log(allEven);
I kept tinkering with the problem and found and alternative answer that runs 2 cycles on the array and displays every 4 consecutive matches. Nina's answer is better and more elegant, but I found this one interesting and i'll leave it here as an alternative.
let loggedPasscodes = [
[4, 3, 4, 4],
[1, 2, 3, 7],
[4, 6, 0, 8],
[2, 2, 2, 2],
[4, 4, 4, 4],
[2, 2, 2, 2],
[1, 3, 4, 5],
[2, 2, 2, 2]
];
function getValidPassword(x){
var password =[];
//main array cycle:
for(ciclop=0;ciclop<x.length;ciclop++){
//secondary array cycle:
for (ciclos=0;ciclos<=4;ciclos++){
//if it gets 4 matches in a row:
if (password.length===4){
console.log(password);
password=[];
}
// if it is even:
else if (x[ciclop][ciclos]%2===0) {
password.push(x[ciclop][ciclos]);
}
//if it is odd:
else if(x[ciclop][ciclos]%2!==0){
password=[];
}
}
}
}
getValidPassword(loggedPasscodes);

How to return passed present items in an array of javascript?

I have a function and I want to return true if the passed item is present in all the arrays inside arrayOfArrays# check if it appears across all arrays.
function item(allArrays, item) {
}
Run against this code
describe('item', () => {
it('returns true if the passed value is present in all the arrays inside the passed array', () => {
const allArrays= [
[9, 2, 3],
[4, 5, 9],
[-2, 9, -1],
[1, 2, 3, 4, 5, 9]
];
expect(item(allArrays, 9)).to.be.true;
});
it('returns false if the passed value is not present in at least one array inside the passed array', () => {
let allArrays= [[9, 2, 3], [4, 5, 9], [-2, 1, -1]];
expect(item(allArrays, 9)).to.be.false;
allArrays= [[6, 2, 3], [4, 5, 8], [-2, 9, -1]];
expect(item(allArrays, 9)).to.be.false;
allArrays= [[4, 2, 3], [4, 5, 9], [-2, 9, -1]];
expect(item(allArrays, 9)).to.be.false;
});
});
let isCommon = (arrays, v) =>
arrays.every(a => a.includes(v));
const allArrays = [
[9, 2, 3],
[4, 5, 9],
[-2, 9, -1],
[1, 2, 3, 4, 5, 9]
];
console.log(isCommon(allArrays, 5)); // false
console.log(isCommon(allArrays, 9)); // true

Algorithm in javascript or python that takes a list of numbers and "groups" them into approximately similar size? [closed]

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]]

Categories

Resources