Reputation: 1648
Let's say I have this array of strings (they're HTML elements, but we can use strings to keep it simple):
["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]
I need a quick way to split this array up by "d"
. Kinda like String.split()
, except for arrays. The end result should be something like this:
[["something", "else", "and"], ["more", "things", "in", "the"], ["array", "etc"]]
Are there any simple one-liners for this? Maybe there's a function built into JS and I'm just missing it?
Upvotes: 1
Views: 168
Reputation: 135197
Here's a functional encoding that works for any iterable input (including arrays)
const None =
Symbol ()
const prepend = (xs, x) =>
[ x ] .concat (xs)
const split = (f, [ x = None, ...xs ], then = prepend) =>
x === None
? then ([], [])
: split
( f
, xs
, (l, r) =>
f (x)
? then (prepend (l, r), [])
: then (l, prepend (r, x))
)
const data =
[ 'something', 'else', 'and', 'd', 'more', 'things', 'in', 'the', 'd', 'array', 'etc' ]
console .log
( split (x => x === 'd', data)
)
// [ [ 'something', 'else', 'and' ]
// , [ 'more', 'things', 'in', 'the' ]
// , [ 'array', 'etc' ]
// ]
And an optimization that works for any array-like input
const prepend = (xs, x) =>
[ x ] .concat (xs)
const split = (f, xs = [], i = 0, then = prepend) =>
i >= xs.length
? then ([], [])
: split
( f
, xs
, i + 1
, (l, r) =>
f (xs[i])
? then (prepend (l, r), [])
: then (l, prepend (r, xs[i]))
)
const data =
[ 'something', 'else', 'and', 'd', 'more', 'things', 'in', 'the', 'd', 'array', 'etc' ]
console .log
( split (x => x === 'd', data)
)
// [ [ 'something', 'else', 'and' ]
// , [ 'more', 'things', 'in', 'the' ]
// , [ 'array', 'etc' ]
// ]
Both implementations are O(n).
Upvotes: 1
Reputation: 92440
You can make a pretty elegant recursive function with something like:
let arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]
const spliton = (v, arr, i = arr.indexOf(v)) => (i < 0)
? [arr]
: [arr.slice(0, i), ...spliton(v, arr.slice(i+1))]
console.log(spliton('d', arr))
Upvotes: 1
Reputation: 18515
If you do not care about mutating the array this is also pretty trivial with while
and Array.shift
:
let r = [[]], data = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]
while(data.length) {
let item = data.shift()
item != 'd' ? r[r.length-1].push(item) : r.push([])
}
console.log(r)
And if you do then even shorter with Array.reduce
:
let arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]
let f = arr.reduce((r,c) => (c!='d' ? r[r.length-1].push(c) : r.push([]),r),[[]])
console.log(f)
The idea in both is to start with [[]]
and then the only check you need is if the current element of the iteration is d
and if so push new array or push to r[r.length-1]
which is the previous sub array
.
Upvotes: 0
Reputation: 8773
Well if it's a one-liner you want, here you go:
var myArray = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
const result = myArray.reduce((a, c) => c === "d" ? (a.arr[++a.i] = []) && a : a.arr[a.i].push(c) && a, {arr: [[]], i: 0}).arr;
console.log(result);
Upvotes: 2
Reputation: 21638
Use reduce starting with an accumulator that has an array containing an empty array. If the current item is the split value add an extra empty array to the end, otherwise spread the last array with the current item.
const arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
const splitArray = (array, val) =>
array && array.length
? array.reduce(
(results, item) =>
item === val
? [...results, []]
: [...results.filter((_, i) => i < results.length - 1), [...results[results.length - 1], item]],
[[]]
)
: array;
console.log(splitArray(arr, 'd'));
Upvotes: 1
Reputation: 42099
To answer your question, there aren't any concise one-liners that come to mind, but you may accomplish what you want with just a few lines of code by iterating over your values and if the word isn't 'd'
store it; if it is, then create a new array to hold the next non-'d' value:
const words = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]
let grouped = words.reduce((response,word)=>{
if (word!=='d')
response[response.length-1].push(word)
else
response[response.length]=[]
return response
},[[]])
console.log(grouped)
Upvotes: 1
Reputation: 8751
A simple forEach
approach is enough.
var arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
var result = [], temp = [];
arr.forEach(function(elem, index){
elem !=='d' ? temp.push(elem) : (result.push(temp), temp = []);
index==arr.length-1 && (result.push(temp));
});
console.log(result)
Upvotes: 1
Reputation: 622
let myArray = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
let splitArray = [],
tempArray = [];
myArray.forEach((ele, index) => {
if(ele !== 'd') {
tempArray.push(ele);
}
if(ele === 'd' || index === myArray.length - 1) {
splitArray.push(tempArray);
tempArray = [];
}
})
console.log(': ', splitArray);
Upvotes: 1
Reputation: 370679
One option would be to join by spaces, then split by ' d '
, then split each subarray by spaces:
const input = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
const output = input
.join(' ')
.split(' d ')
.map(str => str.split(' '));
console.log(output);
Or, without joining, figure out the index of every d
, and slice
every section of the input around the d
s:
const input = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
const dIndicies = input.reduce((a, item, i) => {
if (item === 'd') a.push(i);
return a;
}, []);
const output = dIndicies.reduce((a, dIndex, i, arr) => {
const nextDIndex = arr[i + 1];
a.push(input.slice(dIndex + 1, nextDIndex));
return a;
}, [input.slice(0, dIndicies[0] - 1)]);
console.log(output);
Upvotes: 2