How would one organize a dynamic matrix for best fit? So, let say you are attempting to always display the best fit for a display, and need to organize all cells so that there are no gaps between each item. Each item can either have a size from 1 - 12, and the max width of each row is 12. Using the example dataset, how can will dynamic sort and generate a new array that best fits the display?
let matrixExample = [{
size: 10,
type: 'card'
}, {
size: 4,
type: 'card'
}, {
size: 2,
type: 'card'
}, {
size: 11,
type: 'card'
}, {
size: 6,
type: 'card'
}];
let endingResult = [
[{
size: 10,
type: 'card'
}, {
size: 2,
type: 'card'
}],
[{
size: 4,
type: 'card'
}, {
size: 6,
type: 'card'
}],
[{
size: 11,
type: 'card'
}]
];
The user purpose of this?
When generating dynamic data to a UI, and the UI needs to optimize for component space.
This appears to be an example of the bin packing problem.
This isn't a particularly easy problem to solve and more precise fits are likely to be more complicated.
Below is a greedy algorithm that should solve your problem with a rough estimate. It's possible to get better matches but, as you do, you make things more complicated and computationally expensive.
This solution happens to be recursive and somewhat functional, but that's only my preference; it's probably possible to make a neater and less expensive algorithm if you're not interested in making the code functional or recursive.
const matrixExample = [{
size: 10,
type: 'card'
}, {
size: 4,
type: 'card'
}, {
size: 2,
type: 'card'
}, {
size: 11,
type: 'card'
}, {
size: 6,
type: 'card'
}];
const sumCardList = cardList => cardList.reduce((prev, curr) => prev + curr.size, 0);
const packNextToBin = (cards, bins, max) => {
if (cards.length === 0) {
// there are no more cards to pack, use bins as is
return bins;
}
// get the next card to pack into the bins
const cardToPack = cards[0];
// get the indices of bins which can still be filled
const availableBinIndices = bins
.map((bin, i) => ({sum: sumCardList(bin), index: i}))
.filter(binData => binData.sum + cardToPack.size < max)
.map(binData => binData.index);
// if there are no more bins which can fit this card, makea new bin
if (availableBinIndices.length === 0) {
const updatedBins = [
...bins,
[
cardToPack
]
];
return packNextToBin(
cards.slice(1),
updatedBins,
max
);
}
// get the first available bin which can accept the card
const binToPack = availableBinIndices[0];
// get a version of the matched bin with the new card added
const binWithInsertion = [
...bins[binToPack],
cardToPack,
];
// get the bins with the updated bin updated
const updatedBins = bins
.map((bin, i) => i === binToPack ?
binWithInsertion :
bin
);
// pack the next card into the bins
return packNextToBin(
cards.slice(1),
updatedBins,
max
);
}
const results = packNextToBin(matrixExample, [[]], 12)
console.dir(results)
Related
I am using MongoDB with a Node API and one route needs to return a summary count of each type in a collection.
I am not using the MongoDB Aggregate pipelines because the data I need has already been sent to the API for other summary statistics in the same route.
Note: I have put the _id's below in single quotes for ease of use but they are mongoose.Schema.Types.ObjectId's.
So, given that I have an array of mongo objects like this:
const allObs = [
{
_id: '60d5f37fd93fb82ebe84d920',
type: '60d5f1d4cdc8942dc5b6b12e',
otherFields: 'about 10 - removed for clarity'
},
{
_id: '60d5f389d93fb82ebe84d926',
type: '60d5f1d4cdc8942dc5b6b12e',
otherFields: 'ditto'
},
{
_id: '60d5f39bd93fb82ebe84d92c',
type: '60d5f1e3cdc8942dc5b6b138',
otherFields: 'foobarbarfoo'
}
]
and I have a lookup table like this...
const lookupTable = [
{ _id: '60d5f1d4cdc8942dc5b6b12e', type: 'duck' },
{ _id: '60d5f1decdc8942dc5b6b133', type: 'goose' },
{ _id: '60d5f1e3cdc8942dc5b6b138', type: 'crane' },
{ _id: '60d5f1e9cdc8942dc5b6b13d', type: 'heron' }
]
How can I go about creating a summary table like this?
[
{ name: 'duck', data: [2] },
{ name: 'crane', data: [1] }
]
The resulting table structure is a bit odd (data with single value arrays) but we need this structure for Apex Charts.
Any help would be great, thank you.
There are multiple ways to do this, but the basic logic is doing a groupBy and match with lookup table. It would be easier to do with lodash or a helper library. But also without using JS it can be done pretty easily.
For a quick solution u can use this:
//Group by type and then storing the count
const grouped = allObs.reduce((p, c) => {
p[c.type] = p[c.type] || 0;
p[c.type] += 1;
return p;
}, {});
// putting that into a result array.
const result = lookupTable
.filter(entry=>grouped[entry._id]) //filtering whatever is not there
.map(entry => {
return { name: entry.type, data: [grouped[entry._id]] }
});
You can do it in single pass using a good old for loop.
Output:
[ { name: 'duck', data: [ 2 ] }, { name: 'crane', data: [ 1 ] } ]
Here is the code (it fails to compile at the sentence that builds the state2, i.e. at the second spread):
let line_id = 6;
let state = {
invoice: {
id: 1015,
description: 'web order',
},
lines: [
{id: 5, description: 'phone', color: 'black'},
{id: 6, description: 'tablet', color: 'blue'},
{id: 7, description: 'computer', color: 'gray'},
]
};
//this alert and this access pattern works, so, I would like to use
//.find... to access element in spread... structure as well
//alert(state['lines'].find(line=>line['id']==line_id)['description']);
let state2 = {
...state,
['lines']: { ...state['lines'],
find(line=>line['id']==line_id): { ...state['lines'].find(line=>line['id']==line_id),
['description']: 'TV',
},
},
};
alert(state2['lines'].find(line=>line['id']==line_id)['description']);
I have state structure, I access lines array, I access the specific line by name-value pair id=6 and I would like to change the value of the field description. This effort is the continuation of https://stackoverflow.com/a/64116308/1375882 in which I am trying to create the general procedure, that use the spread... syntax and the access-by-name strategy for updating the complex object/array tree. In fact - this complex tree is the state of the Redux reducer and that update happend in the action that process the valueSetter function of the AgGrid. But - this is generally the interesting exercise by itself to better understand spread... and JavaScript and JSON structure in JavaScript.
So - the only question is: how to write line
find(line=>line['id']==line_id): { ...state['lines'].find(line=>line['id']==line_id),
so that the code compiles? How can I access the certain element of the array by name-value pair in this setting:
Note, that I am trying to build general code:
find(line=>line[keyFieldName]==keyFieldValue): { ...state['lines'].find(line=>line[keyFieldName]==keyFieldValue),
that uses arbitrary field names and field values - so that such handler can update the any field of the any record of arbitrary 2D AgGrid in React/Redux setting.
The desired result of my code: 1) it should compile; 2) the second alert should return 'TV'.
If I understood correctly what you want to achieve, this should work:
let line_id = 6;
let state = {
invoice: {
id: 1015,
description: 'web order',
},
lines: [{
id: 5,
description: 'phone',
color: 'black'
},
{
id: 6,
description: 'tablet',
color: 'blue'
},
{
id: 7,
description: 'computer',
color: 'gray'
},
]
};
const stateKeyId = 'lines';
const itemKeyId = 'id';
const itemAttr = 'description'
let state2 = {
...state,
[stateKeyId]: state[stateKeyId].map(item => {
if (item[itemKeyId] == line_id) {
return ({
...item,
[itemAttr]: 'TV'
});
}
return item
})
}
console.log(state2);
find(line=>line['id']==line_id) should become [find(line=>line['id']==line_id)], since just like the string it must be between square brackets for js to work properly.
Also, if you are using find from lodash, it will return the object, therefore if you need to use the id as key you can do something like:
[get(find(line => line['id'] === line_id]), 'id')]: whatever
a few observations though:
always please always use === over == in js
avoid snake_case, use camelCase with js, since it's standard
your code is not actually handling missing items correclty, if you need to do so split it in multiple lines since it would be more comprehensible
You can use the map method from arrays to return different elements based on the original one.
Here's how you could use it:
line_id = 6;
state = {
invoice: {
id: 1015,
description: 'web order',
},
lines: [
{id: 5, description: 'phone', color: 'black'},
{id: 6, description: 'tablet', color: 'blue'},
{id: 7, description: 'computer', color: 'gray'},
]
};
state2 = {
...state,
lines: state.lines.map(line => {
if (line.id === line_id)
return { ...line, description: 'YT' }
return { ...line }
})
};
alert(state2['lines'].find(line=>line['id']==line_id)['description']);
with my current project, I am dealing with large streams of numerical data and transformations that have to take place on them in a data-flow-programmable fashion.
I stumbled upon the idea of transducers, which promised to solve the difficulties to handle multiple transformations on large arrays. It seems that transducers don't suit exactly for what I'm trying to solve here.
I am looking for a pattern / concept for transducers which only collect a needed amount of lookback to then process out a result. Similar to the browser version of tensorflow, reaktor, max-msp (input outputs, flow-graphs, node-based, visual-programming)
Most of these modules, should be connected to a source, but should also be able to act as a source to chain those to other modules
source ( a stream ) =[new-value]|=> module1 => module2 => ...
|=> module3 => module4 // branch off here to a new chain
From my understanding, the transducers as explained in most blogs takes the whole array, and feeds each individual values trough chosen transformers.
Yet my modules/transformers don't require so much data to work, say the example of a simple moving average with a look back of 4 steps.
I imagine that module to collect enough data until it starts it's output.
I also don't need to hold the whole array in memory, I should only deal with the exact amounts needed. Results/Outputs would be optionally stored in a database.
stream =[sends-1-value]=> module[collects-values-until-processing-starts] =[sends-one-value]=>...
It should also be possible to connect multiple sources into a module (which transducers didn't seem to provide.
Would the transducer pattern here still apply or is something else out there?
To be honest, every programmer would have an idea to make this work, yet I am asking for some established way of doing it, just like transducers came to be.
The transducer pattern certainly applies here. You can create a floating point processor with transducers paired with the right data structure. I'll give you a baseline example, with one assumption:
the stream you are working with implements Symbol.asyncIterator
Consider a simple queue
function SimpleQueue({ size }) {
this.size = size
this.buffer = []
}
SimpleQueue.prototype.push = function(item) {
this.buffer.push(item)
if (this.buffer.length > this.size) {
this.buffer.shift()
}
return this
}
SimpleQueue.prototype[Symbol.iterator] = function*() {
for (const item of this.buffer) {
yield item
}
}
Our simple queue has one method push that pushes an item into its internal buffer (an array). The simple queue is also iterable, so you could do for (const x of simpleQueue) {/* stuff */}
We'll now use our SimpleQueue in our floating point processor.
const average = iterable => {
let sum = 0, count = 0
for (const item of iterable) {
sum += item
count += 1
}
return sum / count
}
const floatingPointAverage = ({ historySize }) => {
const queue = new SimpleQueue({ size: historySize })
return item => {
queue.push(item)
const avg = average(queue)
console.log(queue, avg) // this shows the average as the process runs
return avg
}
}
floatingPointAverage takes an item, pushes it into our SimpleQueue, and returns the current average of items in the queue.
Finally, we can implement and consume our transducer
const { pipe, map, transform } = require('rubico')
const numbersStream = {
[Symbol.asyncIterator]: async function*() {
for (let i = 0; i < 1000; i++) yield i
},
}
transform(
pipe([
map(floatingPointAverage({ historySize: 4 })),
/* transducers that do stuff with floating point average here */
]),
null,
)(numbersStream)
The transducer in this case is map(floatingPointAverage({ historySize: 4 })). This transducer is courtesy of rubico, a library I wrote to solve my own async problems. I write about transducers in the context of rubico here
Your output should look like this
SimpleQueue { size: 4, buffer: [ 0 ] } 0
SimpleQueue { size: 4, buffer: [ 0, 1 ] } 0.5
SimpleQueue { size: 4, buffer: [ 0, 1, 2 ] } 1
SimpleQueue { size: 4, buffer: [ 0, 1, 2, 3 ] } 1.5
SimpleQueue { size: 4, buffer: [ 1, 2, 3, 4 ] } 2.5
SimpleQueue { size: 4, buffer: [ 2, 3, 4, 5 ] } 3.5
SimpleQueue { size: 4, buffer: [ 3, 4, 5, 6 ] } 4.5
SimpleQueue { size: 4, buffer: [ 4, 5, 6, 7 ] } 5.5
SimpleQueue { size: 4, buffer: [ 5, 6, 7, 8 ] } 6.5
SimpleQueue { size: 4, buffer: [ 6, 7, 8, 9 ] } 7.5
Question: How can I reformat this JSON array by "grouping" via different keys, using ReactJS?
I have a JSON array as :
[
{Product: "Shoes", Sold: 5, Bought : 0, Reversed : 2} ,
{Product: "Table", Sold: 2, Bought : 0, Reserved : 4}
]
The reason for this is the data type I'm working with, and on realizing I need to visualize this data in a different way (due to one of the graph packages I am using) I need to structure this data as:
[
{
Status: "Sold",
Shoes : 5,
Table : 2
} ,
{
Status: "Bought",
Shoes : 0,
Table : 0
} ,
{
Status: "Reserved",
Shoes : 2,
Table : 4
}
]
So I'm grouping the data into the keys other than Product, and then the keys after this are Product with the Value being the Product and it's "status".
Frankly, I am at a complete loss as to what to do, as I'm thinking the code required to generate this would be quite convoluted, so I'm very open to know if this just is too much work.
const data = [
{
Product: "Shoes",
Sold: 5,
Bought : 0,
Reserved : 2
} , {
Product: "Table",
Sold: 2,
Bought : 0,
Reserved : 4
}
];
let resultData = [];
Object.keys(data[0]).forEach((key, idx) => {
if (idx !== 0) {
let resultUnit = {
Status: key,
};
data.forEach(item => {
return resultUnit = {
...resultUnit,
[item.Product]: item[key],
}
})
resultData.push(resultUnit);
}
})
console.log(resultData);
// 0: {Status: "Sold", Shoes: 5, Table: 2}
// 1: {Status: "Bought", Shoes: 0, Table: 0}
// 2: {Status: "Reserved", Shoes: 2, Table: 4}
You can do this using the Array.reduce function. (Actually, two reduce functions).
Here's an extensible solution that allows for other statuses.
Note that I changed everything to lowercase, as is standard convention.
const items = [
{product: "Shoes", sold: 5, bought : 0, reserved : 2} ,
{product: "Table", sold: 2, bought : 0, reserved : 4}
]
//We declare the status types here.
const keys = ["sold", "bought", "reserved"];
// Just create the initial 'statuses' array.
function initAcc(keys) {
return keys.map((key) => {
return {
status: key
}
});
}
//Here we are iterating over each item, getting it to return a single accumulator array each time.
const newItems = items.reduce((acc, cur) => {
return addItemToAccumulator(acc, cur);
}, initAcc(keys));
console.log(newItems);
// This function maps of the accumulator array (ie. over each status).
function addItemToAccumulator(acc, item) {
return acc.reduce((acc, statusLine) => {
//Find the count from the existing status if it exists,
//Add the current items count for that status to it.
const itemCount = item[statusLine.status] + (statusLine[item.product] || 0);
//Return a modified status, with the new count for that product
return [
...acc,
{
...statusLine,
[item.product]: itemCount
}
];
}, []);
}
Lets just do a simple loop function and create a couple objects to clearly solve the problem here:
const data = [YOUR_INITIAL_ARRAY];
let Sold, Bought, Reserved = {};
data.forEach(({Product, Sold, Bought, Reserved})=> {
Sold[Product] = Sold;
Bought[Product] = Bought;
Reservered[Product] = Reserved;
});
let newArray = [Sold, Bought, Reserved];
I think you can see where this is going ^ I see a few others have given complete answers, but try and go for the clear understandable route so it makes sense.
All you have to do after this is set the status which i'd do off an enum and you are good
I have five articles with these initial properties:
const articles = [
{ id: 1, views: 92, likes: 0, shares: 2, trendingValue: ? },
{ id: 2, views: 14, likes: 2, shares: 1, trendingValue: ? },
{ id: 3, views: 39, likes: 3, shares: 1, trendingValue: ? },
{ id: 4, views: 87, likes: 0, shares: 1, trendingValue: ? },
{ id: 5, views: 8, likes: 1, shares: 0, trendingValue: ? }
];
I also have a global stats object that should be automatically updated once an article gets new views, likes or shares (or once a week):
const stats = {
totalArticles: 5,
totalViews: 240,
totalLikes: 6,
totalShares: 5,
trendingCriteria: 0
};
So far, I believe there is some sort of formula that can be done with articles' trendingValue and stats' trendingCriteria. Basically the articles that are "trending" need to have an equal or higher number than the criteria. Meaning that whenever an article gets a new view, share or like, trendingValue has to be updated in regards to it's percentage of views, likes and shares, by the global stats' counterparts.
An example (which doesn't exactly work) is:
A user views article 1.
This formula runs for the article to create its trendingValue:
const article = articles[0]; // Article with id 1
article.views++; // Increment the views count
stats.totalViews++ // Increment the total views count
let percentSum = (
(article.views / stats.totalViews) + // = 0.3833
(article.likes / stats.totalLikes) + // = 0
(article.shares / stats.totalShares) // = 0.4
); // = 0.7833
// The trendingValue needs to be a higher value of trendingCriteria
// before refreshing trendingCriteria.
article.trendingValue = (stats.trendingCriteria +
(percentSum / stats.trendingCriteria)
);
Next, trendingCriteria should be refreshed in regards to the updated article. The underlying logic is; if the new trendingCriteria is higher than the article's trendingValue, the article should no longer be "trending".
The third step is where I'm stuck. How do I create this value? Can this value be update for every single new view, like and share? Or do I have to update the value once a week or so?
Update
Thanks for all responses. Unfortunately I could not make any use of them since I'm yet confused what to do with the proposed solutions.
Anyhow, I tried another solution that makes use of an epoch timestamp and the average views, likes and shares. Not sure if it works in practice, so if anyone can confirm I'd be grateful.
function refreshArticleAtIndex(index, props) {
const data = articles[index];
// Increment props
if(props.view) { data.views++; stats.views++; }
else if(props.like) { data.likes++; stats.likes++; }
else if(props.share) { data.shares++; stats.shares++; }
// Refresh trendingRate
data.trendingRate = (() => {
const calcViews = data.views / stats.views;
const calcLikes = data.likes / stats.likes;
const calcShares = data.shares / stats.shares;
let value = Date.now() * (
(isFinite(calcViews) ? calcViews : 0) +
(isFinite(calcLikes) ? calcLikes : 0) +
(isFinite(calcShares) ? calcShares : 0)
);
return Math.round(value);
})();
}
function logArticles() {
const arr = articles.map(article => article);
arr.sort((a, b) => a.trendingRate > b.trendingRate ? -1 : 1);
arr.forEach(a => console.log(a.id +" |", a.trendingRate));
console.log("----------");
}
const stats = { views: 239, likes: 6, shares: 5 };
const articles = [
{ id: 1, views: 91, likes: 0, shares: 2, trendingRate: 0 },
{ id: 2, views: 14, likes: 2, shares: 1, trendingRate: 0 },
{ id: 3, views: 39, likes: 3, shares: 1, trendingRate: 0 },
{ id: 4, views: 87, likes: 0, shares: 1, trendingRate: 0 },
{ id: 5, views: 8, likes: 1, shares: 0, trendingRate: 0 }
];
console.log("ID | trendingRate");
// ================================================
// Add 1 view to article 1
refreshArticleAtIndex(0, { view: true });
// Add nothing to below articles, but refresh their trendingRate
refreshArticleAtIndex(1, {});
refreshArticleAtIndex(2, {});
refreshArticleAtIndex(3, {});
refreshArticleAtIndex(4, {});
logArticles();
// Add 1 like to article 1
refreshArticleAtIndex(0, { like: true });
logArticles();
The first log will show the correct order based on a combination of views, likes and shares for each article. Next, article 1 gets a like, which bumps it to the most trending article.
Question is if this is a working solution?
Trending probably should mean something like "top n% of activity" or "top n articles by activity".
Towards this end, you could simply save the trendingValue to an array like: { article: articleId, trendingValue: trendingValue}
Then when you go to find the most trending articles you would:
let cnt = trendingValues.length * myLimit
let i = 0
let trending = trendingValues.sort((a,b) => a.trendingValue > b.trendingValue).filter(() => i++ < cnt)
To use your language, trendingCriteria could be set to some minimum based on the set of articles. From there it could be fine tuned to ensure a certain minimum activity (as the average article age starts to grow).