Couldn't understand how .reduce works in JavaScript - javascript

I was doing JavaScript exercise and got this question:
Write a JavaScript function that accepts a string as a parameter and find the longest word within the string.
Example string : 'Web Development Tutorial' (Development)
I found a solution and it was like this:
function longest(str) {
let arr = str.split(' ');
return arr.reduce((a,b) => a.length < b.length ? b : a, "");
}
console.log(longest('Web Development Tutorial'));
The code above does work, but I couldn't understand
arr.reduce((a,b) => a.length < b.length ? b : a, "")
part.
Does it mean
reduce(function(a, b) {
if (a.length < b.length) {
b
} else {
a,
"";
}
}
which still doesn't make much sense?

Some very good answers have been given. One way to look at what is happening is to put console.log. Just take a look at this code and you would know what is happening:
function longest(str) {
let arr = str.split(' ');
return arr.reduce((a,b) => {
console.log(`a=${a}, b=${b}`);
return a.length < b.length ? b : a;
}, "");
}
console.log(longest('Web Development Tutorial'));
The output should be self explanatory.
However, if you are not aware of arrow functions or template string literals then you need to learn them first to understand the output.

The first parameter to reduce is the accumulator, which is either the value returned from the last callback, or the initial value (which is either the first argument in the array, or the second argument passed to reduce).
It's equivalent to the following:
function longest(str) {
let arr = str.split(' ');
let a = ''; // initial value
arr.forEach((b) => {
a = a.length < b.length ? b : a;
});
return a;
}
Or, removing the conditional operator:
function longest(str) {
let arr = str.split(' ');
let a = ''; // initial value
arr.forEach((b) => {
if (b.length > a.length) {
a = b;
}
});
return a;
}
Remember that when an arrow function lacks a { after =>, the expression that follows the => will be implicitly returned, so
return arr.reduce((a,b) => a.length < b.length ? b : a, "");
is also equivalent to
return arr.reduce(function(a,b) {
return a.length < b.length ? b : a;
}, '');

reduce works kind of like a loop, involving a value (the current item in the array) and an accumulator (the value returned from the previous call to the function, or the second argument passed to reduce).
Since the purpose of reduce is to reduce the array into a single value, you can think of it like this:
let a = "";
arr.forEach(item => {
if (a.length < b.length) {
a = b;
} else {
a = a;
}
});
return accumulator;
Note that the else statement is irrelevant in the above code, but it represents how reduce works. Try looking it up too - this is a pretty good guide.

If you were to rewrite the code using if it would be:
function longest (a, b) {
if (a.length < b.length) {
return b;
}
else {
return a;
}
}
return arr.reduce(longest, "");
Note that the second argument to reduce (the "") initializes the accumulator variable (the a) to that value (empty string) when reduce starts looping. The syntax is a bit confusing with arrow functions because it could be mistaken for the comma operator when in fact it is simply the comma separating arguments to reduce.

reduce converts multiple (or indeterminate) values into a single value.
It starts with an initial value, which we'll call the "accumulator" and a binary function ("binary" meaning function with two formal parameters), which we'll call the "reducer". reduce applies the reducer to the accumulator and the first element of the list. The return value of the reducer function becomes the new accumulator. That's why we call it "accumulator", because it accumulates or gathers the results of the reducer. reduce repeats this process until there are no more elements in the list, and returns the last return value from the reducer.
Some applications:
Summing a List of Numbers
const add = (x, y) => x + y
[1, 2, 3, 4].reduce(add, 0)
// 10
In this case, x is the accumulator and y is the list element.
If we mentally execute this reducer, we'd have:
x: 0, y: 1
x: 1, y: 2
x: 3, y: 3
x: 6, y: 4
x: 10, STOP
Counting Table Columns from a Table's Data Model
const columnReducer = (tally, {layout}) => {
const isFullLayout = layout === 'full'
const isCompleteSemiLayout = tally.semiLayouts === 2
const columns = isFullLayout || isCompleteSemiLayout ? tally.columns + 1 : tally.columns
const semiLayouts =
isCompleteSemiLayout ? 0
: !isFullLayout ? tally.semiLayouts + 1
: tally.semiLayouts
return { columns, semiLayouts };
}
const { columns } = rows.reduce(columnReducer, { columns: 0, semiLayouts: 0});
Tallying Items
const materialsReducer = (items, { id, material }) => ({
...items,
[material]: {
tally: items[material] || 0 + 1,
ids: [...items[material] || [], id]
}
})
[
{ id: 1, material: 'wood' },
{ id: 2, material: 'gold' },
{ id: 3, material: 'wood' }
].reduce(materialsReducer, {})
// { wood: { tally: 2, ids: [1, 3] }, gold: { tally: 1, ids: [3] } }
reduce is 'declarative', meaning that you as the programmer describe the desired outcome. Contrast that with for loops, which are 'imperative', meaning that you as the programmer instruct the computer what to do in a step-by-step manner.

Related

How to sort keys when using js-yaml dump()

I'm using the js-yaml package. It has a function named dump() that will write out a JavaScript data structure in YAML format. I want to specify the order of the keys when it dumps out an object. Here is what their docs say:
sortKeys (default: false) - if true, sort keys when dumping YAML. If a function, use the function to sort the keys.
But there's absolutely no mention of how to use a function to sort the keys. What parameters does it accept? What return values does it expect? It's exactly what I need, but there's no documentation on how to use a function with the sortKeys option.
FYI, I don't want to sort the keys alphabetically. I don't want them to appear in a random order. I don't want them to appear in the order the keys were added. I want total control over which order they appear.
It appears that when you supply a function as the sortKeys option, that function behaves the same way as the function that you can use with the Perl sort() function. I.e. the function receives 2 arguments, call them a and b. If you want key a to appear before key b, return -1 from the function. If you want key a to appear after key b, return 1 from the function. If keys a and b should be considered equal, return 0 from the function. Using this method, I developed a function named toTAML that accepts a javascript data structure and an array of key names. When displaying an object, it will display the keys in the same order as in the supplied array. Keys that don't appear in the array will be put at the end, ordered alphabetically. I will post that function in CoffeeScript, then in the equivalent JavaScript.
import yaml from 'js-yaml'
compareFunc = (a, b) =>
if (a < b)
return -1
else if (a > b)
return 1
else
return 0
toTAML = (obj, lKeys) ->
h = {}
for key,i in lKeys
h[key] = i+1
sortKeys = (a, b) =>
if h.hasOwnProperty(a)
if h.hasOwnProperty(b)
return compareFunc(h[a], h[b])
else
return -1
else
if h.hasOwnProperty(b)
return 1
else
# --- compare keys alphabetically
return compareFunc(a, b)
return yaml.dump(obj, {
skipInvalid: true
indent: 3
sortKeys
lineWidth: -1
})
console.log toTAML({e:5,d:4,c:3,b:2,a:1}, ['c','b','a'])
Output:
c: 3
b: 2
a: 1
d: 4
e: 5
// Generated by CoffeeScript 2.7.0
var compareFunc, toTAML;
import yaml from 'js-yaml';
compareFunc = (a, b) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
};
toTAML = function(obj, lKeys) {
var h, i, j, key, len, sortKeys;
h = {};
for (i = j = 0, len = lKeys.length; j < len; i = ++j) {
key = lKeys[i];
h[key] = i + 1;
}
sortKeys = (a, b) => {
if (h.hasOwnProperty(a)) {
if (h.hasOwnProperty(b)) {
return compareFunc(h[a], h[b]);
} else {
return -1;
}
} else {
if (h.hasOwnProperty(b)) {
return 1;
} else {
// --- compare keys alphabetically
return compareFunc(a, b);
}
}
};
return yaml.dump(obj, {
skipInvalid: true,
indent: 3,
sortKeys,
lineWidth: -1
});
};
console.log(toTAML({
e: 5,
d: 4,
c: 3,
b: 2,
a: 1
}, ['c', 'b', 'a']));

This question is about an exercise in the book Eloquent JavaScript

The last part to this exercise is to write a recursive function that takes two parameters, a joined list and an index respectively. The function will find the value in the object within the list at it's respective index. The code i have written works the way i want (i can see it working when i console.log for every occasion the function is called. But on the last occasion it refers undefined as my value. I cannot understand why. Oh and it works for index of 0. code as followed.
and first, list looks like this:
list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
// console.log(value);
// console.log(targetNum);
// console.log(list);
nth(list, targetNum);
}
};
console.log(nth(arrayToList([1,2,3]),2));
below is the code for arrayToList it was the first part of the exercise and if you have any comments that's cool, cause the hints ended up suggesting to build the list from the end.
const arrayToList = (arr) => {
let list = {
value: arr[0],
rest: nestObject()
};
function nestObject() {
let rest = {};
arr.shift();
const length = arr.length;
if (length == 1) {
rest.value = arr[0];
rest.rest = null;
} else {
rest.value = arr[0];
rest.rest = nestObject();
}
return rest;
}
return list;
};
Both solutions are convoluted and unnecessary verbose. Actually, both functions could be one-liners. Here are a few hints:
For the toList thing consider the following:
if the input array is empty, return null (base case)
otherwise, split the input array into the "head" (=the first element) and "tail" (=the rest). For example, [1,2,3,4] => 1 and [2,3,4]
return an object with value equal to "head" and rest equal to toList applied to the "tail" (recursion)
On a more advanced note, the split can be done right in the function signature with destructuring:
const toList = ([head=null, ...tail]) => ...
Similarly for nth(list, N)
if N is zero, return list.value (base case)
otherwise, return an application of nth with arguments list.rest and N-1 (recursion)
Again, the signature can benefit from destructuring:
const nth = ({value, rest}, n) =>
Full code, if you're interested:
const toList = ([value = null, ...rest]) =>
value === null
? null
: {value, rest: toList(rest)}
const nth = ({value, rest}, n) =>
n === 0
? value
: nth(rest, n - 1)
//
let lst = toList(['a', 'b', 'c', 'd', 'e', 'f'])
// or simply toList('abcdef')
console.log(lst)
console.log(nth(lst, 0))
console.log(nth(lst, 4))
You simply need to add a return when recursively calling nth. Otherwise the logic is carried out but no value is returned (unless targetNum is 0)
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
return nth(list, targetNum); // return needed here too
}
};
Or more succinctly:
const nth = (list, n) => n === 0 ? list.value : nth(list.rest, n - 1)
Here's another non-recursive arrayToList that builds the list from the end:
const arrayToList = arr => arr.slice().reverse().reduce((rest, value) => ({value, rest}), null);
(The slice here is just to make a copy of the array so that the original is not reversed in place.)
Georg’s recursive solutions are beautiful!
I’d like to add the hinted “build the list from the end” solution from the book:
const arrayToList => (arr) => {
var list
while (arr.length) {
list = {value: arr.pop(), rest: list}
}
return list
}

JavaScript returning ObjectObject with no fields returning as expected { a: 2, b: 2, c: 1 }

I am constructing a reduce function that accepts an array, a callback, and an initial value and returns a single value. The function satisfies 2/3 of the following conditions: should not mutate the input array, should sum up an array, and should create a "totals" object from an array.
function reduce(array, callback, num) {
let sum = 0;
let totals;
for ( let i = 0; i < array.length; i++) {
sum += num + array[i];
}
return sum
array.forEach(function(ele, index){
totals = callback(totals, ele);
});
return totals;
}
I've satisfied all these conditions except the final one. I get the following error after running my code where my reduce function fails to "create a "totals" object from an array":
expected 'expected
'0[object Object]a[object Object]b[object Object]c[object Object]a[object Object]b'
to deeply equal
{ a: 2, b: 2, c: 1 }.
Any input here would be appreciated.
First, the return keyword ends the function. So, writing any code past that return sum would have no effect at all. And in fact, you don't need that return, nor the loop above it:
function reduce(array, callback, totals) {
// By default, if totals is empty, reduce uses the first element as a base
if (typeof totals === "undefined" && array.length > 0) {
totals = array[0];
array = array.slice(1);
}
array.forEach(function(ele, index){
totals = callback(totals, ele);
});
return totals;
}
console.log( reduce([1, 2, 3], (sum, v) => sum + v) ); // 6
console.log( reduce([1, 2, 3], (sum, v) => sum + v, 3) ); // 9
Sorry but your code makes no sense. Here's why:
function reduce(array, callback, num) {
let sum = 0;
let totals;
// Problem one - you are hardcoding behaviour instead of
// using a callback which is normally supposed to inject the reducing logics
for ( let i = 0; i < array.length; i++) {
sum += num + array[i];
}
return sum // after that line all the code is a deadcode
// as the return command terminates your reduce function
// and returns sum
array.forEach(function(ele, index){
totals = callback(totals, ele);
// as I said this is the dead code. Anyways pay attention that
// totals variable is instantiated but undefined. What are you planning
// to achieve by providing it to the callback function?
});
return totals;
}
And here's my implementation of the standard JS API Array.prototype.reduce function:
function myReduce (cb, initialValue) {
if (!Array.isArray(this) || (!this.length && !initialValue))
throw TypeError;
// making a copy of the original array to prevent
// it from being mutated by a callback
const array = [...this];
// if no initial value is provided, we take the first element
// of array instead
let acc = initialValue || array[0];
// if no initialValue is provided we start iteration from 1
// else the iterations starts from 0
for (let i = Number(!initialValue); i < array.length; i++)
acc = cb (acc, array[i], i, array);
return acc;
}
// here's what you supposedly are trying to achieve:
const myArray = [1,2,3,4,5];
// hereinafter I will use Function.prototype.call
// to bind the array to myReduce function
console.log(myReduce.call(myArray, (acc, cur) => acc + cur)); // => 15
// And more advanced example of a simple array-to-object mapper:
const myArray2 = [
{id: 0, email: 'user0#co.cc'},
{id: 1, email: 'user1#co.cc'},
{id: 2, email: 'user2#co.cc'},
];
console.log(myReduce.call(
myArray2, // this
(acc, {email, id}) => ({...acc, [id]: email}), // cb
{} // initial state
)); // => Object {0: "user0#co.cc", 1: "user1#co.cc", 2: "user2#co.cc"}
Hopefully that will be helpful. Good luck!

Checking whether the number of unique numbers within array exceeds n

Just as title reads, I need to check whether the number of unique entries within array exceeds n.
Array.prototype.some() seems to fit perfectly here, as it will stop cycling through the array right at the moment, positive answer is found, so, please, do not suggest the methods that filter out non-unique records and measure the length of resulting dataset as performance matters here.
So far, I use the following code, to check if there's more than n=2 unique numbers:
const res = [1,1,2,1,1,3,1,1,4,1].some((e,_,s,n=2) => s.indexOf(e) != s.lastIndexOf(e) ? false : n-- ? false : true);
console.log(res);
.as-console-wrapper { min-height: 100%}
And it returns false while there's, obviously 3 unique numbers (2,3,4).
Your help to figure out what's my (stupid) mistake here is much appreciated.
p.s. I'm looking for a pure JS solution
You can use a Map() with array values as map keys and count as values. Then iterate over map values to find the count of unique numbers. If count exceeds the limit return true, if not return false.
Time complexity is O(n). It can't get better than O(n) because every number in the array must be visited to find the count of unique numbers.
var data = [1, 1, 2, 1, 1, 3, 1, 1, 4, 1];
function exceedsUniqueLimit(limit) {
var map = new Map();
for (let value of data) {
const count = map.get(value);
if (count) {
map.set(value, count + 1);
} else {
map.set(value, 1);
}
}
var uniqueNumbers = 0;
for (let count of map.values()) {
if (count === 1) {
uniqueNumbers++;
}
if (uniqueNumbers > limit) {
return true;
}
}
return false;
}
console.log(exceedsUniqueLimit(2));
To know if a value is unique or duplicate, the whole array needs to be scanned at least once (Well, on a very large array there could be a test to see how many elements there is left to scan, but the overhead for this kind of test will make it slower)
This version uses two Set
function uniqueLimit(data,limit) {
let
dup = new Set(),
unique = new Set(),
value = null;
for (let i = 0, len = data.length; i < len; ++i) {
value = data[i];
if ( dup.has(value) ) continue;
if ( unique.has(value) ) {
dup.add(value);
unique.delete(value);
continue;
}
unique.add(value);
}
return unique.size > limit;
}
I also tried this version, using arrays:
function uniqueLimit(data, limit) {
let unique=[], dup = [];
for (let idx = 0, len = data.length; idx < len; ++idx) {
const value = data[idx];
if ( dup.indexOf(value) >= 0 ) continue;
const pos = unique.indexOf(value); // get position of value
if ( pos >= 0 ) {
unique.splice(pos,1); // remove value
dup.push(value);
continue;
}
unique.push(value);
}
return unique.length > limit;
};
I tested several of the solutions in this thread, and you can find the result here. If there are only a few unique values, the method by using arrays is the fastest, but if there are many unique values it quickly becomes the slowest, and on large arrays slowest by several magnitudes.
More profiling
I did some more tests with node v12.10.0. The results are normalized after the fastest method for each test.
Worst case scenario: 1000000 entries, all unique:
Set 1.00 // See this answer
Map 1.26 // See answer by Nikhil
Reduce 1.44 // See answer by Bali Balo
Array Infinity // See this answer
Best case scenario: 1000000 entries, all the same:
Array 1.00
Set 1.16
Map 2.60
Reduce 3.43
Question test case: [1, 1, 2, 1, 1, 3, 1, 1, 4, 1]
Array 1.00
Map 1.29
Set 1.47
Reduce 4.25
Another test case: [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,
1,1,1,1,1,1,1,3,4,1,1,1,1,1,1,1,2,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,5 ]
Array 1.00
Set 1.13
Map 2.24
Reduce 2.39
Conclusion
The method that uses Set works for both small and large arrays, and performs well regardless of if there are many unique values or not. The version that are using arrays can be faster if there are few unique values, but quickly becomes very slow if there are many unique values.
Using sets, We count hypothetical unique set size and duplicateSet size and delete unique set element for each duplicate found. If unique set size goes below n, we stop iterating.
function uniqueGtN(res, n) {
let uniqSet = new Set(res);
let max = uniqSet.size;
if (max <= n) return false;
let dupSet = new Set();
return !res.some(e => {
if (dupSet.has(e)) {
if (uniqSet.has(e)) {
uniqSet.delete(e);
console.log(...uniqSet);
return (--max <= n);
}
} else {
dupSet.add(e);
}
});
}
console.log(uniqueGtN([1, 1, 2, 1, 1, 3, 3, 1], 2));
From your original solution, I have changed few things, it seems to be working fine:
(function() {
const array = [1,1,2,1,1,3,1,1,4,1];
function hasExceedingUniqueNumber(array, number) {
return array.some((e,_,s,n=number) => {
let firstIndex = s.indexOf(e);
let lastIndex = s.lastIndexOf(e);
// NOT unique
if (firstIndex != lastIndex) {
return false;
}
// unique
return e > n;
});
}
console.log('1', hasExceedingUniqueNumber(array, 1));
console.log('2', hasExceedingUniqueNumber(array, 2));
console.log('3', hasExceedingUniqueNumber(array, 3));
console.log('4', hasExceedingUniqueNumber(array, 4));
})();
So the shorter version looks like this:
(function() {
const array = [1,1,2,1,1,3,1,1,4,1];
function hasExceedingUniqueNumber(array, number) {
return array.some((e,_,s,n=number) => s.indexOf(e) != s.lastIndexOf(e) ? false : e > n);
}
console.log('1', hasExceedingUniqueNumber(array, 1));
console.log('2', hasExceedingUniqueNumber(array, 2));
console.log('3', hasExceedingUniqueNumber(array, 3));
console.log('4', hasExceedingUniqueNumber(array, 4));
})();
The code listed in your question does not work because m is not shared across the calls to the some callback function. It is a parameter, and its value is 2 at each iteration.
To fix this, either put m outside, or use the thisArg of the some function (but that means you can't use an arrow function)
let m = 2;
const res = [1,1,1,2,1,1,3,1,1,1,4,1,1]
.sort((a,b) => a-b)
.some((n,i,s) => i > 0 && n == s[i-1] ? !(m--) : false);
// ----- or -----
const res = [1,1,1,2,1,1,3,1,1,1,4,1,1]
.sort((a,b) => a-b)
.some(function(n,i,s) { return i > 0 && n == s[i-1] ? !(this.m--) : false; }, { m: 2 });
Note: this code seems to count if the number of duplicates exceeds a certain value, not the number of unique values.
As another side note, I know you mentioned you did not want to use a duplicate removal algorithm, but performant ones (for example hash-based) would result in something close to O(n).
Here is a solution to count all the values appearing exactly once in the initial array. It is a bit obfuscated and hard to read, but you seem to be wanting something concise. It is the most performant I can think of, using 2 objects to store values seen at least once and the ones seen multiple times:
let res = [1,1,2,3,4].reduce((l, e) => (l[+!l[1][e]][e] = true, l), [{},{}]).map(o => Object.keys(o).length).reduce((more,once) => once-more) > 2;
Here is the less minified version for people who don't like the short version:
let array = [1,1,2,3,4];
let counts = array.reduce((counts, element) => {
if (!counts.atLeastOne[element]) {
counts.atLeastOne[element] = true;
} else {
counts.moreThanOne[element] = true;
}
return counts;
}, { atLeastOne: {}, moreThanOne: {} });
let exactlyOnceCount = Object.keys(counts.atLeastOne).length - Object.keys(counts.moreThanOne).length;
let isOverLimit = exactlyOnceCount > 2;
Whenever I have a type of problem like this, I always like to peek at how the underscore JS folks have done it.
[Ed again: removed _.countBy as it isn't relevant to the answer]
Use the _.uniq function to return a list of unique values in the array:
var u = _.uniq([1,1,2,2,2,3,4,5,5]); // [1,2,3,4,5]
if (u.length > n) { ...};
[ed:] Here's how we might use that implementation to write our own, opposite function that returns only non-unique collection items
function nonUnique(array) {
var result = [];
var seen = [];
for (var i = 0, length = array.length; i < length; i++) {
var value = array[i];
if (seen.indexOf(value) === -1) { // warning! naive assumption
seen.push(value);
} else {
result.push(value);
}
}
console.log("non-unique result", result);
return result;
};
function hasMoreThanNUnique(array, threshold) {
var uArr = nonUnique(array);
var accum = 0;
for (var i = 0; i < array.length; i++) {
var val = array[i];
if (uArr.indexOf(val) === -1) {
accum++;
}
if (accum > threshold) return true;
}
return false;
}
var testArrA = [1, 1, 2, 2, 2, 3, 4, 5]; // unique values: [3, 4, 5]
var testArrB = [1, 1, 1, 1, 4]; // [4]
var testResultsA = hasMoreThanNUnique(testArrA, 3)
console.log("testArrA and results", testResultsA);
var testResultsB = hasMoreThanNUnique(testArrB, 3);
console.log("testArrB and results", testResultsB);
So far, I came up with the following:
const countNum = [1,1,1,2,1,1,3,1,1,1,4,1,1].reduce((r,n) => (r[n]=(r[n]||0)+1, r), {});
const res = Object.entries(countNum).some(([n,q]) => q == 1 ? !(m--) : false, m=2);
console.log(res);
.as-console-wrapper{min-height:100%}
But I don't really like array->object->array conversion about that. Is there a faster and (at the same time compact) solution?

Reordering arrays

Say, I have an array that looks like this:
var playlist = [
{artist:"Herbie Hancock", title:"Thrust"},
{artist:"Lalo Schifrin", title:"Shifting Gears"},
{artist:"Faze-O", title:"Riding High"}
];
How can I move an element to another position?
I want to move for example, {artist:"Lalo Schifrin", title:"Shifting Gears"} to the end.
I tried using splice, like this:
var tmp = playlist.splice(2,1);
playlist.splice(2,0,tmp);
But it doesn't work.
The syntax of Array.splice is:
yourArray.splice(index, howmany, element1, /*.....,*/ elementX);
Where:
index is the position in the array you want to start removing elements from
howmany is how many elements you want to remove from index
element1, ..., elementX are elements you want inserted from position index.
This means that splice() can be used to remove elements, add elements, or replace elements in an array, depending on the arguments you pass.
Note that it returns an array of the removed elements.
Something nice and generic would be:
Array.prototype.move = function (from, to) {
this.splice(to, 0, this.splice(from, 1)[0]);
};
Then just use:
var ar = [1,2,3,4,5];
ar.move(0,3);
alert(ar) // 2,3,4,1,5
Diagram:
If you know the indexes you could easily swap the elements, with a simple function like this:
function swapElement(array, indexA, indexB) {
var tmp = array[indexA];
array[indexA] = array[indexB];
array[indexB] = tmp;
}
swapElement(playlist, 1, 2);
// [{"artist":"Herbie Hancock","title":"Thrust"},
// {"artist":"Faze-O","title":"Riding High"},
// {"artist":"Lalo Schifrin","title":"Shifting Gears"}]
Array indexes are just properties of the array object, so you can swap its values.
With ES6 you can do something like this:
const swapPositions = (array, a ,b) => {
[array[a], array[b]] = [array[b], array[a]]
}
let array = [1,2,3,4,5];
swapPositions(array,0,1);
/// => [2, 1, 3, 4, 5]
Here is an immutable version for those who are interested:
function immutableMove(arr, from, to) {
return arr.reduce((prev, current, idx, self) => {
if (from === to) {
prev.push(current);
}
if (idx === from) {
return prev;
}
if (from < to) {
prev.push(current);
}
if (idx === to) {
prev.push(self[from]);
}
if (from > to) {
prev.push(current);
}
return prev;
}, []);
}
You could always use the sort method, if you don't know where the record is at present:
playlist.sort(function (a, b) {
return a.artist == "Lalo Schifrin"
? 1 // Move it down the list
: 0; // Keep it the same
});
Change 2 to 1 as the first parameter in the splice call when removing the element:
var tmp = playlist.splice(1, 1);
playlist.splice(2, 0, tmp[0]);
Immutable version, no side effects (doesn’t mutate original array):
const testArr = [1, 2, 3, 4, 5];
function move(from, to, arr) {
const newArr = [...arr];
const item = newArr.splice(from, 1)[0];
newArr.splice(to, 0, item);
return newArr;
}
console.log(move(3, 1, testArr));
// [1, 4, 2, 3, 5]
codepen: https://codepen.io/mliq/pen/KKNyJZr
EDIT: Please check out Andy's answer as his answer came first and this is solely an extension of his
I know this is an old question, but I think it's worth it to include Array.prototype.sort().
Here's an example from MDN along with the link
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
return a - b;
});
console.log(numbers);
// [1, 2, 3, 4, 5]
Luckily it doesn't only work with numbers:
arr.sort([compareFunction])
compareFunction
Specifies a function that defines the sort order. If omitted, the array is sorted according to each character's Unicode code point value, according to the string conversion of each element.
I noticed that you're ordering them by first name:
let playlist = [
{artist:"Herbie Hancock", title:"Thrust"},
{artist:"Lalo Schifrin", title:"Shifting Gears"},
{artist:"Faze-O", title:"Riding High"}
];
// sort by name
playlist.sort((a, b) => {
if(a.artist < b.artist) { return -1; }
if(a.artist > b.artist) { return 1; }
// else names must be equal
return 0;
});
note that if you wanted to order them by last name you would have to either have a key for both first_name & last_name or do some regex magic, which I can't do XD
Hope that helps :)
Try this:
playlist = playlist.concat(playlist.splice(1, 1));
If you only ever want to move one item from an arbitrary position to the end of the array, this should work:
function toEnd(list, position) {
list.push(list.splice(position, 1));
return list;
}
If you want to move multiple items from some arbitrary position to the end, you can do:
function toEnd(list, from, count) {
list.push.apply(list, list.splice(from, count));
return list;
}
If you want to move multiple items from some arbitrary position to some arbitrary position, try:
function move(list, from, count, to) {
var args = [from > to ? to : to - count, 0];
args.push.apply(args, list.splice(from, count));
list.splice.apply(list, args);
return list;
}
Time complexity of all answers is O(n^2) because had used twice spice. But O(n/2) is possible.
Most Perfomance Solution:
Array with n elements,
x is to, y is from
should be n >x && n > y
time complexity should be |y - x|. So its is number of elements that is between from and to.
bestcase: O(1); //ex: from:4 to:5
average : O(n/2)
worthcase : O(n) //ex: from:0 to:n
function reOrder(from,to,arr) {
if(from == to || from < 0 || to < 0 ) { return arr};
var moveNumber = arr[from];
if(from < to) {
for(var i =from; i< to; i++){
arr[i] = arr[i+1]
}
}
else{
for(var i = from; i > to; i--){
arr[i] = arr[i-1];
}
}
arr[to] = moveNumber;
return arr;
}
var arr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13];
console.log(reOrder(3,7,arr));
As a simple mutable solution you can call splice twice in a row:
playlist.splice(playlist.length - 1, 1, ...playlist.splice(INDEX_TO_MOVE, 1))
On the other hand, a simple inmutable solution could use slice since this method returns a copy of a section from the original array without changing it:
const copy = [...playlist.slice(0, INDEX_TO_MOVE - 1), ...playlist.slice(INDEX_TO_MOVE), ...playlist.slice(INDEX_TO_MOVE - 1, INDEX_TO_MOVE)]
I came here looking for a rearranging complete array, I want something like I did below, but found most of the answers for moving only one element from position A to position B.
Hope my answer will help someone here
function reArrangeArray(firstIndex=0,arr){
var a = [];
var b = []
for(let i = 0; i<= (arr.length-1); i++){
if(i<firstIndex){
a.push(arr[i])
}else{
b.push(arr[i])
}
}
return b.concat(a)
}
const arrayToRearrange = [{name: 'A'},{name: 'B'},{name: 'C'},{name: 'D'},{name: 'E'}];
reArrangeArray(2,arrayToRearrange)
// Output
// [
// { name: 'C' },
// { name: 'D' },
// { name: 'E' },
// { name: 'A' },
// { name: 'B' }
// ]
Reorder its work This Way
var tmpOrder = playlist[oldIndex];
playlist.splice(oldIndex, 1);
playlist.splice(newIndex, 0, tmpOrder);
I hope this will work

Categories

Resources