0

For example,

const target = [1,2,3,4,5,"abc", "def", [10,1000]]

test(target, [2,3,4]) === true
test(target, [4,5,"abc"]) === true
test(target, ["def", [10, 1000]) === true
test(target, [0,1,2]) === false
test(target, ["abc",5,4]) === false
test(target, [2,4]) === false
test(target, ["def", [1000, 10]]) === false

Currently I'm using JSON.stringify to accomplish this (and with replacer function if the elements contain things like BigInt values):

`,${JSON.stringify(target).slice(1, -1)},`.includes(`,${JSON.stringify(arr).slice(1, -1)},`)

to check if target contains arr with the same consecutive ordering of elements, however this seems a bit too hacky so I wonder if there are better ways to accomplish it?

EDIT:

I'm not sure why this question is marked as a duplicate of Check if an array includes an array in javascript since it's a completely different question?

EDIT 2:

I have edited the title to be more precise, to match what I really meant and what the examples here have shown. And I still don't think it's a duplicate of Javascript array contains/includes sub array since "sub array" doesn't sound like the same thing and it says The order of the subarray is important but the actual offset it not important which again not sure if it's about the same question.

Also I'm aware that this can be done by brute force loop/recursion kind of solutions instead of the JSON.stringify "hack" I use, I'm more interested in whether there are more performant, or safer, or more idiomatic ways to do it than this "hackish" solution (And I know JSON.stringify cannot serialize things like BigInt, which needs a replacer function to make it work)

EDIT 3:

Add more examples to illustrate what I try to accomplish better.

hellopeach
  • 924
  • 8
  • 15
  • 2
    Does this answer your question? [Javascript array contains/includes sub array](https://stackoverflow.com/questions/34151834/javascript-array-contains-includes-sub-array) – Raymond Chen Oct 15 '22 at 14:22
  • @Raymond Chen, not sure the "sub array" thing is the same meaning here, I want the exact same ordering of elements, that `test(target, [2,4]) === false` – hellopeach Oct 16 '22 at 05:04
  • @hellopeach Strange, seems like that was the wrong target. This one should be correct. I have posted [an answer that addresses all other answers’ flaws](/a/74081381/4642212). Please note that `[ 2, 4 ]` _is_ the same ordering of elements. What you’re after is a _consecutive_ run of elements. – Sebastian Simon Oct 16 '22 at 06:28
  • @Sebastian Simon, again not sure the "sub array" thing is the same meaning here, and your answer was not even there when I posted this question, it was not the accepted answer, nor was it the most voted answer, so I don't think it's a good reason to mark this question as a duplicate of that. – hellopeach Oct 16 '22 at 09:31
  • @hellopeach To me, it _does_ look like the same problem. The fact that I _just_ wrote the answer doesn’t actually matter for the duplicate system. You’ve added arrays as elements of the target array; this is actually a separate problem: the equality semantics. There are different equality semantics, e.g. SameValue, SameValueZero, etc. I chose IsStrictlyEqual (i.e. `===`). Object comparison is a different semantic and can be implemented with one of the examples at [How to determine equality for two JavaScript objects?](/q/201183/4642212). – Sebastian Simon Oct 16 '22 at 11:17
  • @Sebastian Simon, I don't think the "sub array" of that question has the same meaning here, and that OP's `The order of the subarray is important but the actual offset it not important` does not seem to have the `consecutive` meaning that you have asked me to explicitly add (which I have added) here. Also reading the OP question and the top voted answers there does not give one the feeling that it's the same question as mine here. – hellopeach Oct 16 '22 at 11:39
  • @Sebastian Simon, the arrays as elements of target array thing is always part of my use case, I just forgot to add it in the examples until I realized it when reading the answers posted here. So nope I don't think it's a different question, it's always part of my question as it's part of my real world use case, and what my `JSON.stringify` based "hackish" solution has handled well all this time. – hellopeach Oct 16 '22 at 11:44

3 Answers3

2

To check for an ordered sub-array, including rejecting non-consecutive elements, we can use recursion: Array (A) is ordered inside another array (B) if first element of A is found, and if the rest of A, is inside the rest of B immediately after the found element.

function test(b, a, bIndex) {
  if (!a.length) return true;
  bIndex ??= b.findIndex(el => el === a[0]);
  return a[0] !== b[bIndex] ? false : test(b.slice(bIndex+1), a.slice(1), 0);
}

const target = [1,2,3,4,5,"abc", "def"]

console.log(test(target, [2,3,4]) === true)
console.log(test(target, [4,5,"abc"]) === true)
console.log(test(target, [0,1,2]) === false)
console.log(test(target, ["abc",5,4]) === false)
console.log(test(target, [2,4]) === false)

The original version, that isn't sensitive to non-consecutive elements, relaxing the immediately after constraint.

function isOrderedSubArray(b, a) {
  if (!a.length) return true;
  let index = b.findIndex(el => el === a[0]);
  return index === -1 ? false : isOrderedSubArray(b.slice(index + 1), a.slice(1));
}

// testing it...

let bigArray = [0, 1, 2, 3, 4, 5, 6];
let smallA = [2, 5, 6];
let smallB = [2, 5, 12];
let smallC = [4, 3, 5];
let smallD = [6, undefined];

console.log(isOrderedSubArray(bigArray, smallA)===true)
console.log(isOrderedSubArray(bigArray, bigArray)===true)
console.log(isOrderedSubArray(bigArray, smallB)===false)
console.log(isOrderedSubArray(bigArray, smallC)===false)
console.log(isOrderedSubArray(bigArray, smallD)===false)
danh
  • 62,181
  • 10
  • 95
  • 136
  • Smart catch, @SebastianSimon. The entry condition was too lax. Only [] is a subset of []. Edited – danh Oct 15 '22 at 14:53
  • I just noticed, this fails the `test(target, [2, 4])` test. `index` is just the first occurrence within the big array of the next element within the small array, which doesn’t guarantee that all elements must be consecutive. – Sebastian Simon Oct 15 '22 at 14:59
  • @SebastianSimon, What result do you expect for test(target, [2, 4]) ? – danh Oct 15 '22 at 15:06
  • The same as mentioned in the question: `false`. – Sebastian Simon Oct 15 '22 at 15:10
  • I see it now. I read "contains with the same ordering", literally, and I missed that the OP expects false. I'll make an edit to offer an alternative. (an alternative in case the OP mis-stated their expectation). – danh Oct 15 '22 at 15:20
  • your solution fails when it's testing something like `test([1, 2, 3, [10, 1000]], [3, [10, 1000]])` where the `JSON.stringify` hack works fine. – hellopeach Oct 16 '22 at 09:59
1

You could get the start index and check every item of the second array.

const
    test = (a, b) => {
        let offset = a.indexOf(b[0]);
        while (offset !== -1) {
            if (b.every((v, i) => (i + offset) in a && v === a[i + offset])) return true;
            offset = a.indexOf(b[0], offset + 1);
        }
        return false;
    },
    target = [1, 2, 3, 4, 5, "abc", "def"];

console.log(test(target, [2, 3, 4]));                  //  true
console.log(test(target, [4, 5, "abc"]));              //  true
console.log(test(target, [0, 1, 2]));                  // false
console.log(test(target, ["abc", 5, 4]));              // false
console.log(test(target, [2, 4]));                     // false
console.log(test(target, []));                         // false
console.log(test(target, [ "abc", "def", undefined])); // false
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Shouldn’t `test(target, [])` be `true`? The empty subarray is always contained in every array, just like the empty substring is always contained in every string. – Sebastian Simon Oct 15 '22 at 14:36
  • @SebastianSimon, for me, it should be false. but op may want a differnt result. – Nina Scholz Oct 15 '22 at 14:37
  • 2
    I think math sets is the right way to think of it. [] is a subset of every set, and its only subset is []. – danh Oct 15 '22 at 14:56
  • @danh, sets have no order. – Nina Scholz Oct 15 '22 at 15:00
  • I understand, @NinaScholz. I'm suggesting an analogy for conditions relating to emptiness. – danh Oct 15 '22 at 15:05
  • 1
    @NinaScholz Then it’s not a set, but a sequence. [The empty sequence is a subsequence of every sequence](//en.wikipedia.org/wiki/Subsequence); more specifically for this problem, the same applies to [substrings](//en.wikipedia.org/wiki/Substring). Subsequences are n-tuples, which can be [generalized to sets](//en.wikipedia.org/wiki/Tuple#Tuples_as_nested_sets). – Sebastian Simon Oct 15 '22 at 15:08
1

This could be tidied up but I think it will be quite performant.

  1. If the first element of the sub array does not appear in the target return false
  2. iterate over the target for the length of sub, from the starting position x
  3. If the position of the sub element is not equal to its relative position in the target element, or we go beyond the length of the target array, return false
  4. If all these checks pass return true

const target = [1,2,3,4,5,"abc", "def"]

function test(arr, sub) {
    x = arr.indexOf(sub[0])
    i = 0
    while(i < sub.length ) { 
    if(i != sub.indexOf(arr[x],i) || x >= arr.length) return false;
        x+=1
        i+=1
    }
    return true;
}

console.log(test(target, [2,3,4]))// === true
console.log(test(target, [4,5,"abc"]))// === true
console.log(test(target, [0,1,2]))// === false
console.log(test(target, ["abc",5,4]))// === false
console.log(test(target, [2,4]))// === false
console.log(test(target, [])) // === true
console.log(test(target, [ "abc", "def", undefined ])) // === false
console.log(test([ 4, 5, 5, 6 ], [ 5, 5 ])) /// === true
Simeon
  • 797
  • 5
  • 14