简体   繁体   中英

How can I filter an array based on the first letter of a contained property?

I have the following JavaScript array of objects:

[{name: '4 Arn', isLetter: false},
{name: 'Abax', isLetter: false},
{name: 'Aramex', isLetter: false},
{name: 'Booking', isLetter: false},
{name: 'Dangerous', isLetter: false},
{name: 'Manali', isLetter: false}]

In the above array, I want to check the first letter of each item's name property. If it matches, I want to append a new object just before the object, as shown in the following examples:

[{letter: "#", isLetter: true},       // new object 
{name: '4 Arn', isLetter: false},
{letter: "A", isLetter: true},        // new Object
{name: 'Abax', isLetter: false},
{name: 'Aramex', isLetter: false},
{letter: "B", isLetter: true},        // new object
{name: 'Booking', isLetter: false},
{letter: "D", isLetter: true},        // new object
{name: 'Dangerous', isLetter: false},
{letter: "M", isLetter: true},        // new object
{name: 'Manali', isLetter: false}]

I tried the reduce() function, but I don't understand why it's giving me the wrong result:

var newArr = [];
list.reduce(function(prev, cur, index, originalArrray) {
    var previousCharcode = prev.name.toUpperCase().charCodeAt(0);
    currentCharCode = cur.name.toUpperCase().charCodeAt(0);

    newArr.push(prev);
    if(previousCharcode != currentCharCode) {
        newArr.splice(index, 0, {isLetter: true, letter: String.fromCharCode(currentCharCode)});
    }
    return cur;
});

There are at least two reasons why your code does not give the expected result:

  • The index you work with, points to the index in the original array. As your new array will have more elements, it makes no sense to use that index for a splice on the new array. It will be pointing to the wrong place eventually;

  • You only push the prev element, so the last element will never be pushed to the result array

I would suggest to use reduce with a accumulated value (first argument of the callback) that will build up the final array. To remember the last letter object that was introduced, I will pair this accumulated value with that letter. So I'll work with an array that contains two elements:

  • The final array being accumulated
  • The letter of the most recently added letter object

Then the final result will be taken from the first element of that array, ignoring the second value.

I suggest not to work with character codes, but just with the characters. It saves you from converting the code back to a character.

Here is the code:

 var list = [ {name: '4 Arn', isLetter: false}, {name: 'Abax', isLetter: false}, {name: 'Aramex', isLetter: false}, {name: 'Booking', isLetter: false}, {name: 'Dangerous', isLetter: false}, {name: 'Manali', isLetter: false} ]; var newArr = list.reduce(function(collect, cur, index, originalArrray) { var currentChar = cur.name.toUpperCase().substr(0,1); if (currentChar < 'A') currentChar = '#'; if (collect[1] != currentChar) { collect[0].push({isLetter: true, letter: currentChar}); collect[1] = currentChar; } collect[0].push(cur); return collect; }, [[], null])[0]; // output console.log(newArr); 

Check this solution. Iterate the array and append it to a new array.

 var names = [{ name: '4 Arn', isLetter: false }, { name: 'Abax', isLetter: false }, { name: 'Aramex', isLetter: false }, { name: 'Booking', isLetter: false }, { name: 'Dangerous', isLetter: false }, { name: 'Manali', isLetter: false }]; var newNames = []; for (var i in names) { var char = names[i].name.substring(0, 1); var isNumber = !isNaN(char); var entry = { letter: (isNumber ? '#' : char.toUpperCase()), isLetter: isNumber }; newNames.push(entry); newNames.push(names[i]); } console.log(newNames); 

Maybe with this approach you can resolve the problem

 var list = [{name: '4 Arn', isLetter: false}, {name: 'Abax', isLetter: false}, {name: 'Aramex', isLetter: false}, {name: 'Booking', isLetter: false}, {name: 'Dangerous', isLetter: false}, {name: 'Manali', isLetter: false}]; var listResult = []; list.map(function(item, index) { if(index > 0) { var currentCharcode = item.name.toUpperCase().charCodeAt(0); var previousCharcode = list[index-1].name.toUpperCase().charCodeAt(0); if(previousCharcode != currentCharcode) { listResult.push({isLetter: true, letter: String.fromCharCode(currentCharcode)}); } } else { listResult.push({isLetter: true, letter: String.fromCharCode(currentCharcode)}); } listResult.push(item); }); console.log(JSON.stringify(listResult)); 

I guess you can also do in a functional way like this;

 var arr = [{name: '4 Arn', isLetter: false}, {name: 'Abax', isLetter: false}, {name: 'Aramex', isLetter: false}, {name: 'Booking', isLetter: false}, {name: 'Dangerous', isLetter: false}, {name: 'Manali', isLetter: false}], table = arr.reduce((p,c) => {var firstLetter = c.name[0]; isNaN(+firstLetter) ? p[firstLetter] ? p[firstLetter].push(c) : p[firstLetter] = [c] : p["#"] ? p["#"].push(c) : p["#"] = [c]; return p; },{}), result = Object.keys(table).reduce((p,k) => p.concat({letter: k, isLetter: true},table[k]),[]); console.log(result); 

Hints : +"A" returns NaN but +"4" returns 4 as number. So isNaN() is a very useful function to check the type.

Here is the version with conventional functions instead of arrows;

 var arr = [{name: '4 Arn', isLetter: false}, {name: 'Abax', isLetter: false}, {name: 'Aramex', isLetter: false}, {name: 'Booking', isLetter: false}, {name: 'Dangerous', isLetter: false}, {name: 'Manali', isLetter: false}], table = arr.reduce(function(p,c){ var firstLetter = c.name[0]; isNaN(+firstLetter) ? p[firstLetter] ? p[firstLetter].push(c) : p[firstLetter] = [c] : p["#"] ? p["#"].push(c) : p["#"] = [c]; return p; },{}), result = Object.keys(table).reduce(function(p,k){ return p.concat({letter: k, isLetter: true},table[k]); },[]); console.log(result); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM