Working vs Elegant/Efficient

I’m working on the Wikipedia Viewer, and have been beating my head against the wall trying to get the API response converted into an array of objects I can loop over to display on the page.

Their API gives me something like this:

[[title1, title2, title3], [summary1, summary2, summary3], [link1, link2, link3]]

I want to make this an object with keys so it’s easier to display the results. I’ve got a working solution, but it’s not very elegant:

  let articlesArr = response.data;
  let searchTerm = articlesArr.shift();

  let titles = articlesArr[0];
  let summaries = articlesArr[1];
  let links = articlesArr[2];
  let articles = [];

  for (var i = 0; i < titles.length; i++) {
    articles.push({"title": titles[i], "summary": summaries[i], "link": links[i]});
  }

It looks like map is what I want, but I can’t figure out how to use map to convert an array of arrays into an object with specific keys.

Am I on the right track tinkering with map? Is this worth worrying about, or should I be satisfied that my app finds and displays results, and move on to making it look pretty?

You should always feel comfortable massaging data into whichever form makes it easier for you to work with. In this case, however, you can just get the data in the form you want by some deciphering Wikimedia’s arcane API. Here’s a link to the settings I use, which gets me an object much like what you’re making

I played around with map but couldn’t get to where you need with that. Here’s what I did instead… it still seems more complicated than it needs to be, but it’s early. Some refactoring on it later maybe. It would be far better if you have a means of getting a different format out of Wikipedia directly.

Here’s a map-reduce soution:

let articles=articlesArr[0].map(
// map callback on titles array returns new article object per title
  (x, i) => ["title", "summary", "link"].reduce(
// reduce callback on property names inserts property into object
    (obj, prop, j) => {
// property value is from row corresponding to property index and
// column corresponding to title index
      obj[prop]=articlesArr[j][i]
      return obj
    },
    {}
  )
)
2 Likes

That’s far more compact and efficient than what I provided. Great example of two useful array functions together. Thank you @ppc.

If you like that you’re gonna love this - for details see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

First override default array iterator to traverse two-dimensional array column by column

// define columnwise iterator
articlesArr[Symbol.iterator]=()=>{
// track current column
  let col=0
  return {
    next: ()=>{
      let curcol=col++
// use titles array length for max columns
      return curcol < articlesArr[0].length?
        {
// new array size is same as source array
          value: Array.from(
            {length:articlesArr.length},
// fill new array with one value from each row of source array
            (x, i) => articlesArr[i][curcol]
          ),
          done: false
        }:
        {done:true}
    }
  }
}

Now generate objects from subarrays with Array.from that uses custom iterator

// convert each array entry to an object
articles=Array.from(
// uses columnar iterator defined above
  articlesArr,
  x => ["title", "summary", "link"].reduce(
    (obj, prop, i) => {
// assign property corresponding value from array of column values
      obj[prop]=x[i]
      return obj
    },
    {}
  )
)

I actually prefer the original loop to these methods - it is simple and straightforward - these methods have merit and can be useful for other problems - it is easier to understand them in the context of a simple problem

Thanks @PortableStick! Lesson learned on digging through the documentation a bit more to see if I can get the data in the format I need.

And thanks @nhavar and @ppc – this is what I was looking for! While being able to get the data from their API in a more useable manner is much simpler, knowing how to solve these sorts of problems is useful as well and certainly a good learning experience, as it may not always be so simple. :slight_smile: Great stuff!

@SkyC I like that solution, it’s very simple and readable. Here’s a modification taking some of what @ppc did and yours and merging them together.

const results = [
  ["title1", "title2", "title3"],
  ["summary1", "summary2", "summary3"],
  ["link1", "link2", "link3"]
];

const articles = results[0].map((key, index) => {
  return {
    article: {
      title:   results[0][index],
      summary: results[1][index],
      link:    results[2][index]
    }
  };
});

If you don’t want to use a return statement with the arrow function, you can do:

const results = [
  ["title1", "title2", "title3"],
  ["summary1", "summary2", "summary3"],
  ["link1", "link2", "link3"]
];

const articles = results[0].map((_,idx) => 
  ({title: results[0][idx], summary: results[1][idx], link: results[2][idx]}));
1 Like

That’s what I wanted. @SkyC, you’re right – that is much easier and locks in on the gap in my logic, but I was looking for a more readable solution. I wanted to be able to send the title, summaries, and links as props easily like title={article.title}, which looks nicer and is more legible than title={article[0][i]}, especially if those values will be used later in different contexts. Though, I guess the prop name itself does make it clear what the value is. :stuck_out_tongue: