How to avoid variable scope issues within a map function? - javascript

I have a working solution of this problem but I'm trying to make a cleaner and neat version of it as much as possible. I came up with another solution that uses a function within a map function. Unfortunately, this version has a few issues and I want to just know why the second solution is not working. I'm guessing it's a variable scope issue here. I'm looking forward to know your opinion about it.
I have a simple function that prints calendar days in an array!
So a question is why the first version of my code get the expected results while the second version prints unexpected results.
I tried to change let to var and I also made the counter and startedIndexing outside the function scope.
Solution 1 (works):
const currentFullMonth = {
days_length: 31,
first_day: "Thu",
first_day_index: 4,
last_day: "Sat",
last_day_index: 6,
month: "Aug",
year: 2019
}
const testMonth = [
[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]
];
function printMonthCalender(month) {
let counter = 0;
let startedIdxing = false;
return month.map(week => {
return week.map((day, index) => {
if (index === currentFullMonth.first_day_index && !startedIdxing) {
counter++;
startedIdxing = true;
return counter;
} else if (startedIdxing) {
if (currentFullMonth.days_length === counter) {
counter = 0;
}
counter++;
return counter;
} else {
return 0;
}
});
});
} // end of Solution #1 <-- this works :)
Solution 2 (doesn't work):
// start of Solution #2 <-- does not work :(
// im using two functions to make it look more cleaner
//
function printMonthCalender2(month) {
let counter = 0;
let startedIdxing = false;
return month.map(week => {
return week.map((day, index) =>
indexingMonth(counter, startedIdxing, index)
);
});
}
function indexingMonth(counter, startedIdxing, index) {
if (index === currentFullMonth.first_day_index && !startedIdxing) {
counter++;
startedIdxing = true;
return counter;
} else if (startedIdxing) {
if (currentFullMonth.days_length === counter) {
counter = 0;
}
counter++;
return counter;
} else {
return 0;
}
}// end of Solution #2
console.log(printMonthCalender(testMonth));
console.log(printMonthCalender2(testMonth));
expected result as follows (first version):
[0, 0, 0, 0, 1, 2, 3]
[4, 5, 6, 7, 8, 9, 10]
[11, 12, 13, 14, 15, 16, 17]
[18, 19, 20, 21, 22, 23, 24]
[25, 26, 27, 28, 29, 30, 31]
[1, 2, 3, 4, 5, 6, 7]
unexpected result as follows (second version):
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]

The problem is that when you reassign startedIdxing inside of indexingMonth, it's a local variable, so it doesn't get changed inside of the calling function (printMonthCalender2).
An issue is that .map should not have mutation or reassignment as a side-effect. While you could tweak things so that indexingMonth returned something that you checked and then reassigned startedIdxing to, I'd prefer a different approach: create a flat array, eg
[0, 0, 0, 0, 1, 2, ..., 30, 31, 1, 2, 3]
and then chunk it into pieces of 7 afterwards:
const currentFullMonth = {
days_length: 31,
first_day: "Thu",
first_day_index: 4,
last_day: "Sat",
last_day_index: 6,
month: "Aug",
year: 2019
}
const makeZeroArr = length => new Array(length).fill(0);
const printMonthCalendar = (testMonth) => {
// Create array: [1, 2, 3, ..., 30, 31]
const oneMonth = Array.from(
{ length: currentFullMonth.days_length },
(_, i) => i + 1
);
// Create a flat array with leading zeros and trailing last week:
// [0, 0, 0, 0, 1, 2, 3, ..., 30, 31, 1, 2, 3, 4, 5, 6, 7]
const flatResultArr = [
...makeZeroArr(currentFullMonth.first_day_index),
...oneMonth,
...oneMonth // this includes extra numbers that will be trimmed
].slice(0, 7 * 6); // 7 days/week * 6 weeks
// Chunk the flat array into slices of 7:
const resultArr = [];
for (let i = 0; i < 7; i++) {
resultArr.push(flatResultArr.slice(i * 7, (i + 1) * 7));
}
return resultArr;
};
console.log(printMonthCalendar());

In functions, primitive types like numbers and booleans are passed by value, not by reference. So when you define counter and startedIdxing in printMonthCalender2 and then try to change them in indexingMonth, the changes get lost as soon as you return to printMonthCalender2.
However in JavaScript, objects get passed by reference. So something like this would work:
function printMonthCalender2(month) {
let obj = { counter: 0, startedIdxing = false };
return month.map(week => {
return week.map((day, index) =>
indexingMonth(obj, index)
);
});
}
function indexingMonth(obj, index) {
if (index === currentFullMonth.first_day_index && !obj.startedIdxing) {
obj.counter++;
obj.startedIdxing = true;
return obj.counter;
} else if (obj.startedIdxing) {
if (currentFullMonth.days_length === obj.counter) {
obj.counter = 0;
}
obj.counter++;
return obj.counter;
} else {
return 0;
}
}// end of Solution #2
Things like obj.counter++ will actually keep those changes in your original object defined in printMonthCalender2.
Warning: While you can do this, if you are working with complex code this is often frowned upon. These kinds of mutations can be very difficult to debug if a problem occurs. It's a legitimate programming technique, but shouldn't be abused.
Also if you're working in a team that adheres to the functional programming paradigm, I believe this is a big no-no.
However given the very short duration and limited scope of the obj variable in this example, I would personally feel very comfortable with this. If obj had a much longer lifetime and was used in numerous places in the code then I'd be more wary of it, and would agree with #CertainPerformance's comment that a map statement shouldn't mutate things.

Related

loopin throug object and array using hasOwnProperty

i have this object:
obj = {23-10-2022: 3, 24-10-2022: 1, 28-10-2022: 4, 29-10-2022: 1, 30-10-2022: 1}
and this dates array:
dates = [19-10-2022, 20-10-2022, 21-10-2022, 22-10-2022, 23-10-2022, 24-10-2022, 25-10-2022, 26-10-2022, 27-10-2022, 28-10-2022, 29-10-2022, 30-10-2022, 31-10-2022]
i want to loop throug the obj and array and to check:
if the first key in the obj === dates[i] so return obj[key]=(the value) and move to the next obj key but continue from the next dates[i] and not from the begginnig elese return the 0.
at the end i want to get in return: 0,0,0,0,3,1,0,0,0,4,1,1
here is my code:
for (let key in obj){
for (let i = 0; i < dates.length; i++){
if (obj.hasOwnProperty(key)) {
let value = obj[key];
let date = dates[i];
return key
if(key===date){
return value;
key++ ///gives NaN not correct
}
else{
return 0;
}
}
}
}
what i need to change?
at the moment it return:
[0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4]
becuse it gets back to the firt dates value and re-check.
You can just map() over the dates array. Here using the nullish coalescing operator (??) to assign 0 if retrieving the property returns undefined.
const obj = { '23-10-2022': 3, '24-10-2022': 1, '28-10-2022': 4, '29-10-2022': 1, '30-10-2022': 1 }
const dates = ['19-10-2022', '20-10-2022', '21-10-2022', '22-10-2022', '23-10-2022', '24-10-2022', '25-10-2022', '26-10-2022', '27-10-2022', '28-10-2022', '29-10-2022', '30-10-2022', '31-10-2022']
const result = dates.map(date => obj[date] ?? 0);
console.log(result)

Nice way to create new arrays from provided array with determined length without start with 0 [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 2 years ago.
Improve this question
I think I achieve my goal but I'm sure that it is not the best approach to do it.
I have a function and there is a issue that make it to add some extra [0], I got that is because the while test keep going. I don't need to do it with while+splice. I would like some suggestion to make it easier. My goal is from a provided array, create new arrays always starting from a element different from 0 and the length will be provided as k:
function splitNumber(arrayProvided, k) {
let newArray = [];
while (arrayProvided.length > 0) {
newArray.push(
arrayProvided.splice(
arrayProvided.findIndex(el => el),
k
)
);
}
return newArray;
}
console.log(
splitNumber([1, 0, 4, 6, 0, 7, 8, 4, 2, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 6], 4)
);
The result for this code is:
[
[ 1, 0 ], [ 4, 6 ],
[ 7, 8 ], [ 4, 2 ],
[ 8, 3 ], [ 6 ],
[ 0 ], [ 0 ],
[ 0 ], [ 0 ],
[ 0 ], [ 0 ],
[ 0 ], [ 0 ],
[ 0 ]
]
It's correctly partially because the system is having a extra working after the job done adding extra [0]. The system cannot start with a 0 value in the first array position and no need extra [0] (It's happens because the logic is not totally right), and Yes the lenght of the new arrays is the k value.
Without zeroes, you could add another check and omit unwanted zeroes.
This approach does not mutate the given data.
function splitNumber(array, k) {
let result = [],
i = 0;
while (i < array.length) {
if (array[i] === 0) {
i++;
continue;
}
result.push(array.slice(i, i += k));
}
return result;
}
console.log(splitNumber([1, 0, 4, 6, 0, 7, 8, 4, 2, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 6], 4));
I think its already a pretty smart solution to use findIndex to implicitly check for non-zero indeces. However, you need to handle when it returns -1 as is the case when no non-zero entries was found. So putting a check for that solves your issue.
function splitNumber(arrayProvided, k) {
let newArray = [];
while (arrayProvided.length > 0) {
let nonZeroStartIndex = arrayProvided.findIndex(el => el )
if( nonZeroStartIndex == -1 ){
break;
}
else{
newArray.push(
arrayProvided.splice( nonZeroStartIndex , k )
);
}
}
return newArray;
}
console.log(
splitNumber([1, 0, 4, 6, 0, 7, 8, 4, 2, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 6], 4)
);
Then, the check can be moved up to the while loop to let it gracefully exit when no more non-zero entries can be found
function splitNumber(arrayProvided, k) {
let newArray = [];
let nonZeroStartIndex = arrayProvided.findIndex(el => el )
while (nonZeroStartIndex != -1) {
newArray.push( arrayProvided.splice( nonZeroStartIndex, k ) );
nonZeroStartIndex = arrayProvided.findIndex(el => el )
}
return newArray;
}
console.log(
splitNumber([1, 0, 4, 6, 0, 7, 8, 4, 2, 0, 8, 3, 0, 0, 0, 0, 0, 0, 0, 6], 4)
);

Calculating quarterly and yearly avarage through javascript

I have an array:
const test = [1,2,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
I want to group the elements of the array into chunks of size 3 (quarters) and size 12 (years):
const quarters = [[1,2,2],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18],[19,20]];
const years = [[1,2,2,4,5,6,7,8,9,10,11,12],[13,14,15,16,17,18,19,20]];
I also want to compute the sum of each chunk:
const quarterSums = [5,15,24,33,42,51,39];
const yearSums = [77,132];
How do I do so?
Use a loop that increments by the group size, and use .slice().
EDIT: You added information not in the original question. Since you seem to want the sum of each quarter/year, add this .reduce((s,n)=>s+n, 0) to each subset. This shows a better use of .reduce().
const test = [1, 2, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
console.log(getGroups(test, 3)); // quarters
console.log(getGroups(test, 12)); // years
function getGroups(a, s) {
for (var i = 0, r = []; i < a.length; i += s) {
r.push(a.slice(i, i + s).reduce((s,n)=>s + n, 0));
}
return r;
}
Using something like .reduce() that visits every element makes it more complicated in this case. The traditional for loop provides the benefit of defining how the loop should be incremented.
If you prefer a more function way, I'd still not use .reduce(), but would roll my own tail recursion.
const test = [1, 2, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
console.log(getGroups(test, 3)); // quarters
console.log(getGroups(test, 12)); // years
function getGroups(a, s) {
return function p(a, s, r) {
return !a.length ? r : r.concat(a.slice(0, s).reduce((s,n)=>s + n, 0),
p(a.slice(s), s, r));
}(a, s, []);
}
If you want to group elements into chunks of size n then:
const groupInto = (n, xs) => xs.reduce((xss, x, i) => {
if (i % n === 0) xss.push([]); // create a new group
xss[xss.length - 1].push(x); // push in last group
return xss;
}, []);
const xs = [1,2,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
const quarters = groupInto(3, xs);
const years = groupInto(12, xs);
console.log(JSON.stringify(quarters));
console.log(JSON.stringify(years));
On the other hand, if you want to find the sum of these chunks:
const sumInto = (n, xs) => xs.reduce((ys, x, i) => {
if (i % n === 0) ys.push(0);
ys[ys.length - 1] += x;
return ys;
}, []);
const xs = [1,2,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
const quarters = sumInto(3, xs);
const years = sumInto(12, xs);
console.log(JSON.stringify(quarters));
console.log(JSON.stringify(years));
Hope that helps.
You could use a Array#forEach with an object as temporary variable for collecting the values. Then calculate the average.
var values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
temp = { quarter: { avg: [], items: 3, sum: 0 }, year: { avg: [], items: 12, sum: 0 } }
values.forEach(function (v, i) {
Object.keys(temp).forEach(function (k) {
temp[k].sum += v;
if (i && (i + 1) % temp[k].items === 0) {
temp[k].avg.push(temp[k].sum / temp[k].items);
temp[k].sum = 0;
}
});
});
console.log(temp.quarter.avg);
console.log(temp.year.avg);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript array contains/includes sub array

I need to check if an array contains another array. The order of the subarray is important but the actual offset it not important. It looks something like this:
var master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
var sub = [777, 22, 22];
So I want to know if master contains sub something like:
if(master.arrayContains(sub) > -1){
//Do awesome stuff
}
So how can this be done in an elegant/efficient way?
With a little help from fromIndex parameter
This solution features a closure over the index for starting the position for searching the element if the array. If the element of the sub array is found, the search for the next element starts with an incremented index.
function hasSubArray(master, sub) {
return sub.every((i => v => i = master.indexOf(v, i) + 1)(0));
}
var array = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
console.log(hasSubArray(array, [777, 22, 22]));
console.log(hasSubArray(array, [777, 22, 3]));
console.log(hasSubArray(array, [777, 777, 777]));
console.log(hasSubArray(array, [42]));
var master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
var sub = [777, 22, 22];
console.log(master.join(',').includes(sub.join(',')))
//true
You can do this by simple console.log(master.join(',').includes(sub.join(','))) this line of code using include method
The simplest way to match subset/sub-array
const master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
const sub1 = [777, 44, 222];
const sub2 = [777, 18, 66];
sub1.every(el => master.includes(el)); // reture true
sub2.every(el => master.includes(el)); // return false
Just came up with quick thought , but efficiency depends on size of the array
var master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
var sub = [777, 22, 22];
if ((master.toString()).indexOf(sub.toString()) > -1 ){
//body here
}
It’s surprising how often this is implemented incorrectly.
What we’re looking for is a substring in the mathematical sense.
In mathematics, a sequence is an enumerated collection of objects in which repetitions are allowed and order matters.
In mathematics, a subsequence of a given sequence is a sequence that can be derived from the given sequence by deleting some or no elements without changing the order of the remaining elements.
A subsequence which consists of a consecutive run of elements from the original sequence, such as ⟨ B, C, D ⟩ from ⟨ A, B, C, D, E, F ⟩ is a substring.
Note that a “string”, here, can consist of any element and is not limited to Unicode code-point sequences.
Effectively all previous answers have one of many possible flaws:
The string concatenation approach (array1.toString().includes(array2.toString())) fails when your array elements have commas. (Example: [ "a", "b" ] does not contain [ "a,b" ]).
Some implementations check beyond array bounds. (Example: [ "3" ] does not contain [ "3", undefined ], just because array[1] reports undefined for both).
Some implementations fail to handle repetition correctly.
Some implementations aren’t checking for substrings (in the mathematical sense) correctly, but for subsets or subsequences or something else.
Some implementations don’t account for the empty array. The empty string is the substring of every string.
Check if an array constitutes a “substring” of another array
Right off the bat, this handles the empty array correctly.
Then, it builds a list of candidate starting indexes by matching against the first element of the potential subarray.
Find the first candidate where every element of the slice matches index by index with the full array, offset by the candidate starting index.
The checked index also has to exist within the full array, hence Object.hasOwn.
const isSubArray = (full, slice) => {
if(slice.length === 0){
return true;
}
const candidateIndexes = full
.map((element, fullIndex) => ({
matched: element === slice[0],
fullIndex
}))
.filter(({ matched }) => matched),
found = candidateIndexes
.find(({ fullIndex }) => slice.every((element, sliceIndex) => Object.hasOwn(full, fullIndex + sliceIndex) && element === full[fullIndex + sliceIndex]));
return Boolean(found);
};
console.log(isSubArray([], []) === true);
console.log(isSubArray([ 0 ], []) === true);
console.log(isSubArray([ 0, 1, 2 ], [ 1, 2 ]) === true);
console.log(isSubArray([ 0, 1, 1, 2 ], [ 0, 1, 2 ]) === false);
console.log(isSubArray([ 2, 1 ], [ 1, 2 ]) === false);
console.log(isSubArray([ 1, 2, 3 ], [ 2, 3, undefined ]) === false);
console.log(isSubArray([ 0, 1, 1, 2, 3 ], [ 1, 1, 2 ]) === true);
console.log(isSubArray([ 0, 1, 1, 2, 3 ], [ 1, 2 ]) === true);
console.log(isSubArray([ 0, 1, 1, 2, 3 ], [ 0, 1, 1, 1 ]) === false);
console.log(isSubArray([ "a", "b" ], [ "a,b" ]) === false);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This has quadratic complexity, yes.
There might be more efficient implementations using Trees or Ropes.
You might also want to research some efficient substring search algorithms and try to apply them to this problem.
Get the index of the found “substring”, or -1 if not found
It’s basically the same code, but with return true; replaced by return 0;, and return Boolean(found); replaced by return found?.fullIndex ?? -1;.
const findSubArrayIndex = (full, slice) => {
if(slice.length === 0){
return 0;
}
const candidateIndexes = full
.map((element, fullIndex) => ({
matched: element === slice[0],
fullIndex
}))
.filter(({ matched }) => matched),
found = candidateIndexes
.find(({ fullIndex }) => slice.every((element, sliceIndex) => Object.hasOwn(full, fullIndex + sliceIndex) && element === full[fullIndex + sliceIndex]));
return found?.fullIndex ?? -1;
};
console.log(findSubArrayIndex([], []) === 0);
console.log(findSubArrayIndex([ 0 ], []) === 0);
console.log(findSubArrayIndex([ 0, 1, 2 ], [ 1, 2 ]) === 1);
console.log(findSubArrayIndex([ 0, 1, 1, 2 ], [ 0, 1, 2 ]) === -1);
console.log(findSubArrayIndex([ 2, 1 ], [ 1, 2 ]) === -1);
console.log(findSubArrayIndex([ 1, 2, 3 ], [ 2, 3, undefined ]) === -1);
console.log(findSubArrayIndex([ 0, 1, 1, 2, 3 ], [ 1, 1, 2 ]) === 1);
console.log(findSubArrayIndex([ 0, 1, 1, 2, 3 ], [ 1, 2 ]) === 2);
console.log(findSubArrayIndex([ 0, 1, 1, 2, 3 ], [ 0, 1, 1, 1 ]) === -1);
console.log(findSubArrayIndex([ "a", "b" ], [ "a,b" ]) === -1);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Semi-acceptable alternative: JSON
JSON-encoding both arrays might be a viable strategy as well.
Here, the surrounding […] of the potential subarray need to be removed, then an includes will tell you if the JSON string is included in the other JSON string.
This works — as opposed to the simple string concatenation or join approach — because JSON has delimiters that cannot appear verbatim in the encoded elements; if they do appear in the original elements, they’d be correctly escaped.
The caveat is that this won’t work for values that are not encodable in JSON.
const isSubArray = (full, slice) => JSON.stringify(full)
.includes(JSON.stringify(slice).replaceAll(/^\[|\]$/g, ""));
console.log(isSubArray([], []) === true);
console.log(isSubArray([ 0 ], []) === true);
console.log(isSubArray([ 0, 1, 2 ], [ 1, 2 ]) === true);
console.log(isSubArray([ 0, 1, 1, 2 ], [ 0, 1, 2 ]) === false);
console.log(isSubArray([ 2, 1 ], [ 1, 2 ]) === false);
console.log(isSubArray([ 1, 2, 3 ], [ 2, 3, undefined ]) === false);
console.log(isSubArray([ 0, 1, 1, 2, 3 ], [ 1, 1, 2 ]) === true);
console.log(isSubArray([ 0, 1, 1, 2, 3 ], [ 1, 2 ]) === true);
console.log(isSubArray([ 0, 1, 1, 2, 3 ], [ 0, 1, 1, 1 ]) === false);
console.log(isSubArray([ "a", "b" ], [ "a,b" ]) === false);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If the order is important, it has to be an actually sub-array (and not the subset of array) and if the values are strictly integers then try this
console.log ( master.join(",").indexOf( subarray.join( "," ) ) == -1 )
for checking only values check this fiddle (uses no third party libraries)
var master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
var sub = [777, 22, 22];
function isSubset( arr1, arr2 )
{
for (var i=0; i<arr2.length; i++)
{
if ( arr1.indexOf( arr2[i] ) == -1 )
{
return false;
}
}
return true;
}
console.log( isSubset( master, sub ) );
There are faster options explained here as well.
EDIT
Misunderstood question initially.
function arrayContainsSub(arr, sub) {
var first = sub[0],
i = 0,
starts = [];
while (arr.indexOf(first, i) >= 0) {
starts.push(arr.indexOf(first, i));
i = arr.indexOf(first, i) + 1;
}
return !!starts
.map(function(start) {
for (var i = start, j = 0; j < sub.length; i++, j++) {
if (arr[i] !== sub[j]) {
return false;
}
if (j === sub.length - 1 && arr[i] === sub[j]) {
return true;
}
};
}).filter(function(res) {
return res;
}).length;
}
This solution will recursively check all available start points, so points where the first index of the sub has a match in the array
Old Answer Kept in case useful for someone searching.
if(master.indexOf(sub) > -1){
//Do awesome stuff
}
Important to remember that this will only match of master literally references sub. If it just contains an array with the same contents, but references a different specific object, it will not match.
You can try with filter and indexOf like this:
Note: This code works in case we do not cover the order in sub array.
Array.prototype.arrayContains = function (sub) {
var self = this;
var result = sub.filter(function(item) {
return self.indexOf(item) > -1;
});
return sub.length === result.length;
}
Example here.
UPDATED: Return index of sub array inside master (cover order in sub array)
Array.prototype.arrayContains = function(sub) {
var first;
var prev;
for (var i = 0; i < sub.length; i++) {
var current = this.indexOf(sub[i]);
if (current > -1) {
if (i === 0) {
first = prev = current;
continue;
} else {
if (++prev === current) {
continue;
} else {
return -1;
}
}
} else {
return -1;
}
}
return first;
}
Demo: here
For this answer, I am preserving the order of sub-array. Means, the elements of sub-array should be in Consecutive order. If there is any extra element while comparing with the master, it will be false.
I am doing it in 3 steps:
Find the index of the first element of sub in the master and store it an array matched_index[].
for each entry in matched_index[] check if each element of sub is same as master starting from the s_index. If it doesn't match then return false and break the for loop of sub and start next for-loop for next element in matched_index[]
At any point, if the same sub array is found in master, the loop will break and return true.
function hasSubArray(master,sub){
//collect all master indexes matching first element of sub-array
let matched_index = []
let start_index = master.indexOf(master.find(e=>e==sub[0]))
while(master.indexOf(sub[0], start_index)>0){
matched_index.push(start_index)
let index = master.indexOf(sub[0], start_index)
start_index = index+1
}
let has_array //flag
for(let [i,s_index] of matched_index.entries()){
for(let [j,element] of sub.entries()){
if(element != master[j+s_index]) {
has_array = false
break
}else has_array = true
}
if (has_array) break
}
return has_array
}
var master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
console.log(hasSubArray(master, [777, 22, 22]));
console.log(hasSubArray(master, [777, 22, 3]));
console.log(hasSubArray(master, [777, 777, 777]));
console.log(hasSubArray(master, [44]));
console.log(hasSubArray(master, [22, 66]));
I had a similar problem and resolved it using sets.
function _hasSubArray( mainArray, subArray )
{
mainArray = new Set( mainArray );
subArray = new Set( subArray );
for ( var element of subArray )
{
if ( !mainArray.has( element ) )
{
return false;
}
}
return true;
}
If run this snippet below it should work
x = [34, 2, 4];
y = [2, 4];
y.reduce((included, num) => included && x.includes(num), true);
EDIT:
#AlexanderGromnitsky You are right this code is incorrect and thank you for the catch! The above code doesn't actually do what the op asked for. I didn't read the question close enough and this code ignores order. One year later here is what I came up with and hopefully this may help someone.
var master = [12, 44, 22, 66, 222, 777, 22, 22, 22, 6, 77, 3];
var sub = [777, 22, 22];
var is_ordered_subset = master.join('|').includes(sub.join('|'))
This code is somewhat elegant and does what op asks for. The separator doesn't matter as long as its not an int.
async function findSelector(a: Uint8Array, selector: number[]): Promise<number> {
let i = 0;
let j = 0;
while (i < a.length) {
if (a[i] === selector[j]) {
j++;
if (j === selector.length) {
return i - j + 1;
}
} else {
j = 0;
}
i++;
}
return -1;
}
Try using every and indexOf
var mainArr = [1, 2, 3, 4, 5]
var subArr = [1, 2, 3]
function isSubArray(main, sub) {
return sub.every((eachEle) => {
return (main.indexOf(eachEle) + 1);
});
}
isSubArray(mainArr, subArr);

Grouping consecutive elements together using Javascript

I have an array of elements like so:
messages[i], where messages[i] may only exist for certain values of i. For instance messages[0] and messages[2] may exist but not messages[1].
Now I would like to group together elements with continuous indices, for example if the indices for which messages existed were:
2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20
I would like to group them like so:
2, 3, 4, 5
8, 9
12, 13, 14, 15, 16, 17
20
What would be an effective way to do so using Javascript?
EDIT:
for (i = 0; i < messages.length; i++) {
if (messages[i].from_user_id == current_user_id) {
// group the continuous messages together
} else {
//group these continuous messages together
}
}
You can use a counter variable which has to be incremented and the difference between the index and the consecutive elements are the same, group them in a temporary array. If the difference is varies for two consecutive array elements, the temporary element has to be moved to the result and the temporary array has to be assigned a new array object.
var array = [2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20];
var result = [], temp = [], difference;
for (var i = 0; i < array.length; i += 1) {
if (difference !== (array[i] - i)) {
if (difference !== undefined) {
result.push(temp);
temp = [];
}
difference = array[i] - i;
}
temp.push(array[i]);
}
if (temp.length) {
result.push(temp);
}
console.log(result);
# [ [ 2, 3, 4, 5 ], [ 8, 9 ], [ 12, 13, 14, 15, 16, 17 ], [ 20 ] ]
Given :
var data = [ undefined, undefined, 2, 3, 4, 5,
undefined,undefined, 8, 9,
undefined, undefined, 12, 13, 14, 15, 16, 17,
undefined, undefined, 20];
(or the almost equivalent array where the undefined elements don't exist at all, but where the defined elements have the same indices as above) this reduce call will return a two-dimensional array where each top level element is the contents of the original array, grouped by contiguously defined entries:
var r = data.reduce(function(a, b, i, v) {
if (b !== undefined) { // ignore undefined entries
if (v[i - 1] === undefined) { // if this is the start of a new run
a.push([]); // then create a new subarray
}
a[a.length - 1].push(b); // append current value to subarray
}
return a; // return state for next iteration
}, []); // initial top-level array
i.e. [[ 2, 3, 4, 5], [8, 9], [12, 13, 14, 15, 16, 17], [20]]
NB: this could also be written using a .forEach call, but I like .reduce because it requires no temporary variables - all state is encapsulated in the function parameters.
I would iterate through the list, and if you find an element at messages[i], add i to a list of mins. Then, once you don't find an element at messages[j], and j to a list of maxes.
Then you will have two lists (or one, if you use a container, as I probably would) that contains the start and stop indexes of the groups.
Another approach would be something like this. I'm using a library called lodash for my array manipulation.
Basically I'm sorting the array in ascending order. And then for every increment, I'm storing the current element to a temporary array and comparing the last value of that array to the current element if they are in sequence if not I push the values of the temporary array to my result array and so on. If my loop reaches the end I just push the values of my temporary array to my results array.
var _ = require('lodash');
var arr = [2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20];
arr = _.sortBy(arr, function (o) {
return o;
});
var tmp = [];
var res = [];
for (var i = 0; i < arr.length; i++) {
if (tmp.length === 0) {
tmp.push(arr[i]);
}
else {
var lastEl = _.last(tmp);
if ((lastEl + 1) === arr[i]) {
tmp.push(arr[i]);
}
else {
res.push(tmp);
tmp = [];
tmp.push(arr[i]);
}
if (i === (arr.length - 1)) {
res.push(tmp);
tmp = [];
}
}
}
// Outputs: [ [ 2, 3, 4, 5 ], [ 8, 9 ], [ 12, 13, 14, 15, 16, 17 ], [ 20 ] ]
const cluster = (arr, tmp = [], result = []) =>
(result = arr.reduce((acc, c, i) =>
(!tmp.length || c === (arr[i-1]+1)
? (tmp.push(c), acc)
: (acc.push(tmp), tmp = [c], acc))
, []), tmp.length ? (result.push(tmp), result) : result)
console.log(cluster([2, 3, 4, 5, 8, 9, 12, 13, 14, 15, 16, 17, 20]))

Categories