6

I know how to do both things separately, but I'm sure there must be a way to combine them.

I have an array of categories, which I am extracting from an array of objects:

 this.videoCategories = this.videos.map(v => v.category);

But of course there are duplicates in this array. So now I do

this.uniqueVideoCategories = this.videoCategories.filter((item, index) => {
  return this.videoCategories.indexOf(item) === index;
});

Which works fine, I get an array of the categories without dupes. But I'm trying to learn and dry up the code a bit by stringing them together, and this does not work - yields empty array

  constructor(private videoService: VideoService) {
    this.videos = videoService.getVideos();
    this.videoCategories = this.videos
      .map(v => v.category)
      .filter((item, index) => {
        return this.videoCategories.indexOf(item) === index;
      });
    console.log(this.videoCategories);
  }
Steve
  • 14,401
  • 35
  • 125
  • 230

6 Answers6

5

Inside the filter() you are checking the index inside the array of objects. You can use the third argument of filter() method which will be the newly created array after map()

 constructor(private videoService: VideoService) {
    this.videos = videoService.getVideos();
    this.videoCategories = this.videos
      .map(v => v.category)
      .filter((item, index, arr) => {
        return arr.indexOf(item) === index;
      });
    console.log(this.videoCategories);
  }

Instead of using filter() and indexOf() you can use Set to remove duplicates. This will be the time-complexity O(N)

constructor(private videoService: VideoService) {
    this.videos = videoService.getVideos();
    this.videoCategories = [...new Set(this.videos.map(v => v.category))]
    console.log(this.videoCategories);
  }
Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
3

Sometimes the solution is choosing the right data structure. ES6 has introduced Set, which only contains unique objects.

Then you just do:

this.videoCategories = new Set(this.videos.map(v => v.category))

The uniqueness will be handled by browser implementation, instead of cluttering your codebase.

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • 1
    Filter is faster https://blog.usejournal.com/performance-of-javascript-array-ops-2690aed47a50 – Wilhelmina Lohan Aug 01 '19 at 15:16
  • 1
    @WilliamLohan that example is spreading Set back to array...not a valid comparison. You may be correct but that's not a valid test for this use case – charlietfl Aug 01 '19 at 15:18
  • @WilliamLohan The article compares filter with expanding the set with `...`, I didn't suggest expanding it. At any rate, if performance mattered, I would suggest normal `for`. The intermediate `.map` is one extra allocation and loop through the array, much more important than `Set` vs `.filter` nuances. – Tomáš Zato Aug 01 '19 at 15:20
  • Oh right I see, I just googled which is faster because filter-indexOf is my "goto" and saw Set solutions here and was curious – Wilhelmina Lohan Aug 01 '19 at 15:30
2

var videos = [
  { category: 'category1', title: 'Category 1'},
  { category: 'category1', title: 'Category 1'},
  { category: 'category1', title: 'Category 1'},
  { category: 'category2', title: 'Category 2'},
  { category: 'category2', title: 'Category 2'}
];
var categoryVideos =
  videos
    .map(v => v.category)
    .filter((item, index, arr) => arr.indexOf(item) === index);
    
console.log(categoryVideos);

Array.prototype.filter

Syntax

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

Parameters

callback

Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:

  • element: The current element being processed in the array.
  • index: (Optional) The index of the current element being processed in the array.
  • array: (Optional) The array filter was called upon.
  • thisArg: (Optional) Value to use as this when executing callback.

Return value

A new array with the elements that pass the test. If no elements pass the test, an empty array will be returned.

silentw
  • 4,835
  • 4
  • 25
  • 45
0

Array is empty because when you does filtering array return this.videoCategories.indexOf(item) === index;, field this.videoCategories was empty.

Try it:

this.videoCategories = this.videos
    .map(v => v.category)
    .filter((item, index, array) => {
        return array.indexOf(item) === index;
    });
an_parubets
  • 589
  • 7
  • 15
0
this.videoCategories = this.videos
.map(v => v.category)
.filter((item, index, array) => 
    array.indexOf(item) === index;
);

If you don't use brackets and is just a line, you don't need the return.

Tatoh
  • 11
  • 1
0

I would do it this way

constructor(private videoService: VideoService) {
  this.videos = videoService.getVideos();
  const categories = this.videos.map(v => v.category);
  this.videoCategories = [...new Set(categories)];
  console.log(this.videoCategories);
}
  • Welcome to SO, and thanks for your answer! Please note it is generally considered good practice to explain _why_ your code works. We are not a code-writing service after all, but a place to learn. Also refer to [how do I write a good answer](https://stackoverflow.com/help/how-to-answer) for more information on this. – Tijmen Apr 05 '23 at 09:51