I have implemented the Steinhaus-Johnson-Trotter Algorithm using JavaScript, this implementation only calculates all permutations for arrays of numbers.
When the size of the array is >= 4 the algorithm is producing duplicate results. I see why it produces the results, but I am not sure how to avoid this, as creating these duplicates satisfies the algorithms principles.
class Direction {
constructor(dir) {
if(dir === 'LEFT' || dir === 'RIGHT') {
this.dir = dir
}
}
setDir(dir) {
if(dir === 'LEFT' || dir === 'RIGHT') {
this.dir = dir
}
}
switchDir() {
switch(this.dir) {
case 'LEFT':
this.dir = 'RIGHT'
break
case 'RIGHT':
this.dir = 'LEFT'
break
}
}
}
var permute = function(nums) {
if(nums.length === 1) return [nums]
if(nums.length === 2) return [nums, [nums[1], nums[0]]]
// I'm only worried about arrays up to length 6
const facts = [1, 2, 6, 24, 120, 720]
const dirs = {}
const max = Math.max(...nums)
nums.forEach(v => {
dirs[v] = new Direction('LEFT')
})
const res = []
const move = (n) => {
const i = nums.indexOf(n)
const ele = dirs[n]
switch(ele.dir) {
case 'LEFT':
[nums[i], nums[i - 1]] = [nums[i - 1], nums[i]]
break
case 'RIGHT':
[nums[i], nums[i + 1]] = [nums[i + 1], nums[i]]
break
}
if(n === max) {
return
}
nums.forEach(v => {
if(v > n) dirs[v].switchDir()
})
}
// Number is said to mobile if it can move to its direction
const isMobile = (n) => {
const d = dirs[n].dir
if(d === 'LEFT' && nums.indexOf(n) !== 0) {
return true
}
if(d === 'RIGHT' && nums.indexOf(n) !== nums.length - 1) {
return true
}
return false
}
// Finding mobiles means finding the largest number and checking if it is mobile
const findMobile = () => {
// If not max then lets find the next largest mobile
var num = Number.MIN_VALUE
nums.forEach(v => {
if(isMobile(v) && v > num) {
num = v
}
})
return num
}
// Loop through the max length factorial, included up to only 6 as req
while(res.length < facts[nums.length - 1]) {
const next = findMobile()
move(next)
res.push([...nums])
console.log(res)
}
return res
};
Test Cases:
Test 1:
Input: [1,2,3]
Result: [[1,3,2],[3,1,2],[3,2,1],[2,3,1],[2,1,3],[1,2,3]], Passed
Test 2:
Input: [5,4,6,2]
Result: [
[ 5, 6, 4, 2 ], [ 6, 5, 4, 2 ],
[ 5, 6, 4, 2 ], [ 5, 4, 6, 2 ],
[ 5, 4, 2, 6 ], [ 4, 5, 2, 6 ],
[ 4, 5, 6, 2 ], [ 4, 6, 5, 2 ],
[ 6, 4, 5, 2 ], [ 6, 4, 2, 5 ],
[ 4, 6, 2, 5 ], [ 4, 2, 6, 5 ],
[ 4, 2, 5, 6 ], [ 4, 2, 6, 5 ],
[ 4, 6, 2, 5 ], [ 6, 4, 2, 5 ],
[ 4, 6, 2, 5 ], [ 4, 2, 6, 5 ],
[ 4, 2, 5, 6 ], [ 4, 5, 2, 6 ],
[ 4, 5, 6, 2 ], [ 4, 6, 5, 2 ],
[ 6, 4, 5, 2 ], [ 6, 5, 4, 2 ]
], Failed
As shown in the results, the algorithm is producing duplicates, however as mentioned earlier, the steps between the duplicates satisfy the algorithm.
My Understanding of the Algorithm:
All elements start out facing right to left:
ie. <1<2<3<4
We find the next largest "mobile" number, in this case 4. Then we shift it towards its direction.
ie. <1<2<4<3
Repeat the process.
If a mobile number is moved that is less than another number, the larger number has its direction swapped.
EDIT
I have solved the problem, it involved in not checking the size of the mobile number and number to be swapped.
Compare steps with this simple Python implementation (ideone link to look at results, order is similar to wiki example ).
I don't see direction items swapping together with elements in your code
def SJTperms(a, dirs):
n = len(a)
id = -1
for i in range(n):
# can check mobility mobile largest mobile
if (0<=i+dirs[i]<n) and (a[i] > a[i+dirs[i]]) and ((id == -1) or (a[i] > a[id])):
id = i
if (id == -1): #last permutation
return False
for i in range(n):
if a[i] > a[id]:
dirs[i] = - dirs[i]
#swap elements AND their directions
a[id], a[id + dirs[id]] = a[id + dirs[id]], a[id]
t = dirs[id]
dirs[id], dirs[id + t] = dirs[id + t], dirs[id]
return True
a = [1,2,3,4]
d = [-1]*len(a)
cont = True
while cont:
print(a)
#print(d)
cont = SJTperms(a, d)
Related
I am practising on code wars and am currently stuck on a kyu 8 question, all the tests seem to pass bar the last one. I will add my code and the tests below plus the output I get below.
function sumArray(array) {
if (array == null || array.length <= 2) {
return 0
} else {
let largestInt = Math.max.apply(null, array)
let smallestInt = Math.min.apply(null, array)
let indexSmallest = array.indexOf(largestInt)
let indexLargest = array.indexOf(smallestInt)
array.splice(indexSmallest, 1)
array.splice(indexLargest, 1)
let sum = 0
for (let i = 0; i < array.length; i++) {
sum += array[I]
}
return sum
}
}
The tests:
const {
assert
} = require("chai");
it("example tests", () => {
assert.strictEqual(sumArray(null), 0);
assert.strictEqual(sumArray([]), 0);
assert.strictEqual(sumArray([3]), 0);
assert.strictEqual(sumArray([3, 5]), 0);
assert.strictEqual(sumArray([6, 2, 1, 8, 10]), 16);
assert.strictEqual(sumArray([0, 1, 6, 10, 10]), 17);
assert.strictEqual(sumArray([-6, -20, -1, -10, -12]), -28);
assert.strictEqual(sumArray([-6, 20, -1, 10, -13]), 3);
});
The output:
Test Results:
example tests
expected -10 to equal 3
function total(array) {
// always assure at least an empty array.
array = Array.from(array ?? []);
// sort array values ascending.
array.sort((a, b) => a - b);
array.pop(); // remove last/higest value.
array.shift(); // remove first/lowest value.
// for any to be reduced/summed-up (empty) array
// the initial value of zero always assures the
// minimum expected result of zero.
return array
.reduce((total, value) => total + value, 0);
}
const testEntries = [
[ null, 0 ],
[ [ ], 0 ],
[ [ 3 ], 0 ],
[ [ 3, 5 ], 0 ],
[ [ 6, 2, 1, 8, 10 ], 16 ],
[ [ 0, 1, 6, 10, 10 ], 17 ],
[ [ -6, -20, -1, -10, -12 ], -28 ],
[ [ -6, 20, -1, 10, -13 ], 3 ],
];
console.log(
testEntries
.map(([value, result]) =>
`(total([${ value }]) === ${ result }) ... ${ total(value) === result }`
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
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)
);
I've been trying to make a function that generates all the permutations of the numbers from 0 to num and stores them in a multidimensional array. I want to store in the combinations variable something like:
[ [ 1, 2, 3 ],
[ 1, 3, 2 ],
[ 2, 1, 3 ],
[ 3, 1, 2 ],
wrongly [ 2, 3, 1 ],
[ 3, 2, 1 ] ]
but instead I get:
[ [ 3, 2, 1 ],
[ 3, 2, 1 ],
[ 3, 2, 1 ],
[ 3, 2, 1 ],
[ 3, 2, 1 ],
[ 3, 2, 1 ] ]
My function is:
var combinations = [];
function comb(num, index, list, used) {
if (num == index)
combinations.push(list);
else {
for (var i = 0; i < num; ++i) {
if (!used[i]) {
list[i] = index + 1;
used[i] = true;
comb(num, index + 1, list, used);
used[i] = false;
}
}
}
}
I usually program with C++, so I think that I am using the arrays wrongly.
Your issue is that the list array you pass into your recursive call is going to be changed as it is essentially passed by reference, so list will lose its previous combinations. Instead, you can make a copy of the array before you pass it into your recursive call using the spread syntax (...).
See working example below:
var combinations = [];
function comb(num, index, list, used) {
if (num == index)
combinations.push(list);
else {
for (var i = 0; i < num; ++i) {
if (!used[i]) {
list[i] = index + 1;
used[i] = true;
comb(num, index + 1, [...list], used);
used[i] = false;
}
}
}
}
comb(3, 0, [], []);
console.log(combinations); // [[1,2,3],[1,3,2],[2,1,3],[3,1,2],[2,3,1],[3,2,1]]
.as-console-wrapper { max-height: 100% !important; top: 0;}
Just wondering if a function like this can be done tail-recursively. I find it quite difficult because it calls itself twice.
Here is my non-tail-recursive implementation in javascript. (Yes I know most javascript engine doesn't support TCO, but this is just for theory.) The goal is to find all sublists of a certain length(size) of a given array(arr). Ex: getSublistsWithFixedSize([1,2,3] ,2) returns [[1,2], [1,3], [2,3]]
function getSublistsWithFixedSize(arr, size) {
if(size === 0) {
return [[]];
}
if(arr.length === 0 ) {
return [];
}
let [head, ...tail] = arr;
let sublists0 = getSublistsWithFixedSize(tail, size - 1);
let sublists1 = getSublistsWithFixedSize(tail, size);
let sublists2 = sublists0.map(x => {
let y = x.slice();
y.unshift(head);
return y;
});
return sublists1.concat(sublists2);
}
One such way is to use continuation-passing style. In this technique, an additional parameter is added to your function to specify how to continue the computation
Below we emphasize each tail call with /**/
function identity(x) {
/**/return x;
}
function getSublistsWithFixedSize(arr, size, cont = identity) {
if(size === 0) {
/**/ return cont([[]]);
}
if(arr.length === 0 ) {
/**/ return cont([]);
}
let [head, ...tail] = arr;
/**/return getSublistsWithFixedSize(tail, size - 1, function (sublists0) {
/**/ return getSublistsWithFixedSize(tail, size, function (sublists1) {
let sublists2 = sublists0.map(x => {
let y = x.slice();
y.unshift(head);
return y;
});
/**/ return cont(sublists1.concat(sublists2));
});
});
}
console.log(getSublistsWithFixedSize([1,2,3,4], 2))
// [ [ 3, 4 ], [ 2, 4 ], [ 2, 3 ], [ 1, 4 ], [ 1, 3 ], [ 1, 2 ] ]
You can think of the continuation almost like we invent our own return mechanism; only it's a function here, not a special syntax.
This is perhaps more apparent if we specify our own continuation at the call site
getSublistsWithFixedSize([1,2,3,4], 2, console.log)
// [ [ 3, 4 ], [ 2, 4 ], [ 2, 3 ], [ 1, 4 ], [ 1, 3 ], [ 1, 2 ] ]
Or even
getSublistsWithFixedSize([1,2,3,4], 2, sublists => sublists.length)
// 6
The pattern might be easier to see with a simpler function. Consider the famous fib
const fib = n =>
n < 2
? n
: fib (n - 1) + fib (n - 2)
console.log (fib (10))
// 55
Below we convert it to continuation-passing style
const identity = x =>
x
const fib = (n, _return = identity) =>
n < 2
? _return (n)
: fib (n - 1, x =>
fib (n - 2, y =>
_return (x + y)))
fib (10, console.log)
// 55
console.log (fib (10))
// 55
I want to remark that the use of .slice and .unshift is unnecessary for this particular problem. I'll give you an opportunity to come up with some other solutions before sharing an alternative.
Edit
You did a good job rewriting your program, but as you identified, there are still areas which it can be improved. One area I think you're struggling the most is by use of array mutation operations like arr[0] = x or arr.push(x), arr.pop(), and arr.unshift(x). Of course you can use these operations to arrive at the intended result, but in a functional program, we think about things in a different way. Instead of destroying an old value by overwriting it, we only read values and construct new ones.
We'll also avoid high level operations like Array.fill or uniq (unsure which implementation you chose) as we can build the result naturally using recursion.
The inductive reasoning for your recursive function is perfect, so we don't need to adjust that
if the size is zero, return the empty result [[]]
if the input array is empty, return an empty set, []
otherwise the size is at least one and we have at least one element x - get the sublists of one size smaller r1, get the sublists of the same size r2, return the combined result of r1 and r2 prepending x to each result in r1
We can encode this in a straightforward way. Notice the similarity in structure compared to your original program.
const sublists = (size, [ x = None, ...rest ], _return = identity) =>
size === 0
? _return ([[]])
: x === None
? _return ([])
: sublists // get sublists of 1 size smaller, r1
( size - 1
, rest
, r1 =>
sublists // get sublists of same size, r2
( size
, rest
, r2 =>
_return // return the combined result
( concat
( r1 .map (r => prepend (x, r)) // prepend x to each r1
, r2
)
)
)
)
We call it with a size and an input array
console.log (sublists (2, [1,2,3,4,5]))
// [ [ 1, 2 ]
// , [ 1, 3 ]
// , [ 1, 4 ]
// , [ 1, 5 ]
// , [ 2, 3 ]
// , [ 2, 4 ]
// , [ 2, 5 ]
// , [ 3, 4 ]
// , [ 3, 5 ]
// , [ 4, 5 ]
// ]
Lastly, we provide the dependencies identity, None, concat, and prepend - Below concat is an example of providing a functional interface to an object's method. This is one of the many techniques used to increase reuse of functions in your programs and help readability at the same time
const identity = x =>
x
const None =
{}
const concat = (xs, ys) =>
xs .concat (ys)
const prepend = (value, arr) =>
concat ([ value ], arr)
You can run the full program in your browser below
const identity = x =>
x
const None =
{}
const concat = (xs, ys) =>
xs .concat (ys)
const prepend = (value, arr) =>
concat ([ value ], arr)
const sublists = (size, [ x = None, ...rest ], _return = identity) =>
size === 0
? _return ([[]])
: x === None
? _return ([])
: sublists // get sublists of 1 size smaller, r1
( size - 1
, rest
, r1 =>
sublists // get sublists of same size, r2
( size
, rest
, r2 =>
_return // return the combined result
( concat
( r1 .map (r => prepend (x, r)) // prepend x to each r1
, r2
)
)
)
)
console.log (sublists (3, [1,2,3,4,5,6,7]))
// [ [ 1, 2, 3 ]
// , [ 1, 2, 4 ]
// , [ 1, 2, 5 ]
// , [ 1, 2, 6 ]
// , [ 1, 2, 7 ]
// , [ 1, 3, 4 ]
// , [ 1, 3, 5 ]
// , [ 1, 3, 6 ]
// , [ 1, 3, 7 ]
// , [ 1, 4, 5 ]
// , [ 1, 4, 6 ]
// , [ 1, 4, 7 ]
// , [ 1, 5, 6 ]
// , [ 1, 5, 7 ]
// , [ 1, 6, 7 ]
// , [ 2, 3, 4 ]
// , [ 2, 3, 5 ]
// , [ 2, 3, 6 ]
// , [ 2, 3, 7 ]
// , [ 2, 4, 5 ]
// , [ 2, 4, 6 ]
// , [ 2, 4, 7 ]
// , [ 2, 5, 6 ]
// , [ 2, 5, 7 ]
// , [ 2, 6, 7 ]
// , [ 3, 4, 5 ]
// , [ 3, 4, 6 ]
// , [ 3, 4, 7 ]
// , [ 3, 5, 6 ]
// , [ 3, 5, 7 ]
// , [ 3, 6, 7 ]
// , [ 4, 5, 6 ]
// , [ 4, 5, 7 ]
// , [ 4, 6, 7 ]
// , [ 5, 6, 7 ]
// ]
Here is my solution with the help of an accumulator. It's far from perfect but it works.
function getSublistsWithFixedSizeTailRecRun(arr, size) {
let acc= new Array(size + 1).fill([]);
acc[0] = [[]];
return getSublistsWithFixedSizeTailRec(arr, acc);
}
function getSublistsWithFixedSizeTailRec(arr, acc) {
if(arr.length === 0 ) {
return acc[acc.length -1];
}
let [head, ...tail] = arr;
//add head to acc
let accWithHead = acc.map(
x => x.map(
y => {
let z = y.slice()
z.push(head);
return z;
}
)
);
accWithHead.pop();
accWithHead.unshift([[]]);
//zip accWithHead and acc
acc = zipMerge(acc, accWithHead);
return getSublistsWithFixedSizeTailRec(tail, acc);
}
function zipMerge(arr1, arr2) {
let result = arr1.map(function(e, i) {
return uniq(e.concat(arr2[i]));
});
return result;
}
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);