31

I finally gave up and wrote a for loop to initialize a simple array of objects where each object has an incremented counter (id) as an attribute of the object. In other words, I just want:

var sampleData = [{id: 1},{id: 2},...];

I was hoping for a compact syntax I could just put on my return statement.

let sampleData = [];
for (var p = 0; p < 25; p++){
    sampleData.push({id: p});
}

return {
    data: sampleData,
    isLoading: true
};
Adelin
  • 6,791
  • 5
  • 33
  • 58
Pete
  • 1,761
  • 3
  • 11
  • 22
  • closely related, if not duplicate of: [How to generate range of numbers from 0 to n in ES2015?](https://stackoverflow.com/q/36947847/1048572), [Is there a mechanism to loop x times in ES6 without mutable variables?](https://stackoverflow.com/q/30452263/1048572) and [functional way to iterate over range in ES6](https://stackoverflow.com/q/30650961/1048572) – Bergi Jul 26 '18 at 12:31

6 Answers6

51

Array.from() is a nice way to do this. You can pass a {length: somlength} object or some other array-like object and a function that defines each item. The first argument (calling it _ just to indicate it's not used) to that function would be the item from an array we passed in (but we only passed in a length so it doesn't mean much), the second i is the index, which is used for your id:

let sampleData = Array.from({length: 10}, (_, id) => ({id}))

console.log(sampleData)
Mark
  • 74,559
  • 4
  • 81
  • 117
  • Nice solution @Mark , I had thought that "underline" was a lodash thing. Any pointers to where I can read more about the "underline" and what it means? – Pete Jul 26 '18 at 01:48
  • 5
    @Pete, what the thing you're asking about it -- looks like a character didn't come through. I added a bit about `_` it's just a function argument -- could have called it anything. I use `_` sometimes for arguments that will be unused or undefined. – Mark Jul 26 '18 at 01:51
  • 1
    Yup, meant the underscore. very elegant. I need of course expand it out so I can understand it. I'll check your answer as soon as SO lets me. – Pete Jul 26 '18 at 01:53
  • 1
    @Pete: `_` was added because Mark needed second argument only, so generally using `_` to skip arguments that you're not needed. Unless you're using `lodash` of course – Isaac Jul 26 '18 at 01:53
  • 2
    To add to this, quite a few languages include `_` as an actual language feature. It will literally ignore that value. Two examples I can think of off the top of my head would be Rust and Haskell. In Javascript, `_` is purely convention and is actually being assigned a value. `const a = _ => "hi" + _;` is valid code, for instance. It's literally just an identifier. It's to communicate intent rather than to change anything about how it works. – Nathaniel Pisarski Jul 26 '18 at 13:18
16

What I usually do is this:

const data = Array(10).fill().map((v, i) => ({id: i + 1}));
console.log({data});

fill ensures it can be used with map

Teocci
  • 4,348
  • 1
  • 34
  • 36
jian
  • 906
  • 7
  • 10
  • This seems equivalent to [JohnP's solution](https://stackoverflow.com/a/51529905/6225838), but this `fill()` is 10%+ faster than the spread operator ([`fill()` VS `...` JSBench.me test](https://jsbench.me/fdjk2qdov6/1)), but both of them are 30%+ slower when compared to [Mark's `Array.from(...)` solution](https://jsbench.me/fdjk2qdov6/2). – CPHPython Jul 26 '18 at 15:56
9

You can use spread operator with Array and then map each undefined element to the object that you want.

var arr = [...Array(10)].map((_,i)=>({id:i}));
console.log(arr)
JohanP
  • 4,244
  • 1
  • 18
  • 30
  • Why do you need to use the spread operator? Is it because otherwise you'll be mapping over an empty sparse array, which has 0 items? – wizzwizz4 Jul 26 '18 at 08:27
  • 4
    Yes, `Array(10)` just sets the `length`, it has no items in it. – JohanP Jul 26 '18 at 09:48
  • 1
    Assuming `[...Array(10)]` is equivalent to `Array(10).fill()`, isn't the latter more readable? Admittedly, I don't have a very good intuition for what people find clear in Javascript.... – JollyJoker Jul 26 '18 at 13:51
7

You're looking for an anamorphism, or reverse fold –

// unfold : ((r, state) -> List r, unit -> List r, state) -> List r
const unfold = (f, init) =>
  f ( (x, next) => [ x, ...unfold (f, next) ]
    , () => [] 
    , init
    )
    
// sampleData : List { id: Int }
const sampleData =
  unfold
    ( (next, done, i) =>
        i > 25
          ? done ()
          : next ({ id: i }, i + 1)
    , 0
    )
    
console .log (sampleData)
// [ { id: 0 }, { id : 1 }, ... { id: 25 } ]

You can get an intuition for how unfold works by seeing it used in other common programs –

// unfold : ((r, state) -> List r, unit -> List r, state) -> List r
const unfold = (f, init) =>
  f ( (x, next) => [ x, ...unfold (f, next) ]
    , () => []
    , init
    )
    
// fibseq : Int -> List Int
const fibseq = init =>
  unfold
    ( (next, done, [ n, a, b ]) =>
         n === 0
           ? done ()
           : next (a, [ n - 1, b, a + b ])
    , [ init, 0, 1 ]
    )
    
console .log (fibseq (10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]

The implementation of unfold is just one possibility. Get tinkering and implement it in a way of your choosing –

// type Maybe a = Nothing | Just a    

// Just : a -> Maybe a
const Just = x =>
  ({ match: ({ Just: f }) => f (x) })

// Nothing : unit -> Maybe a
const Nothing = () =>
  ({ match: ({ Nothing: f }) => f () })

// unfold : (state -> Maybe (a, state), state) -> List a  
const unfold = (f, init) =>
  f (init) .match
    ( { Nothing: () => []
      , Just: ([ x, next ]) => [ x, ...unfold (f, next) ]
      }
    )

// fibseq : Int -> List Int
const fibseq = init =>
  unfold
    ( ([ n, a, b ]) =>
        n === 0
          ? Nothing ()
          : Just ([ a, [ n - 1, b, a + b ] ]) // <-- yikes, read more below
    , [ init, 0, 1 ]
    )
    
console .log (fibseq (10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]

I cheated a little above using a [] as a tuple. This kept the program shorter but it's better to explicitly model things and consider their types. You tagged this question with functional-programming so it's worth going the extra inch to remove this kind of implicit handling from our programs. By showing this as a separate step, we isolate a technique that can be applied not just to unfold, but for any program we design –

// type Maybe a = Nothing | Just a
// type Tuple a b = { first: a, second: b }

// Just : a -> Maybe a
const Just = x =>
  ({ match: ({ Just: f }) => f (x) })

// Nothing : unit -> Maybe a
const Nothing = () =>
  ({ match: ({ Nothing: f }) => f () })

// Tuple : (a, b) -> Tuple a b
const Tuple = (first, second) =>
  ({ first, second })

// unfold : (state -> Maybe Tuple (a, state), state) -> List a  
const unfold = (f, init) =>
  f (init) .match
    ( { Nothing: () => []
      , Just: (t) => [ t.first, ...unfold (f, t.second) ] // <-- Tuple
      }
    )

// fibseq : Int -> List Int
const fibseq = init =>
  unfold
    ( ([ n, a, b ]) =>
        n === 0
          ? Nothing ()
          : Just (Tuple (a, [ n - 1, b, a + b ])) // <-- Tuple
    , [ init, 0, 1 ]
    )
    
console .log (fibseq (10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
Thank you
  • 107,507
  • 28
  • 191
  • 224
  • Very nice implementation of an anamorpishm. Unfortunately, `ana` is sometimes just not enough: `tails("ana")` yields `["ana", "na", "a", ""]`. This cannot be implemented with `ana`, as `Maybe` doesn't provide a value for the base case. If you combine `Mabye` with `Either` you get an even more general morphism called `apo`. I picture `apo` as the big yet kind brother of `ana`. Happy (un)foldology :D – scriptum May 26 '19 at 15:19
  • Thank you kindly, bob. Indeed `tails` is tricky to implement with `ana` but it's [still possible](https://repl.it/repls/HeartySillyAccess) using a compound state. Thanks for sharing `apo`. I like exposure to category theory when I can get it; I don't make enough time to explore the dense topic too thoroughly on my own. Other readers that are feeling lost can start by reading about [apomorphism](https://en.wikipedia.org/wiki/Apomorphism). – Thank you May 26 '19 at 15:48
  • Btw, you should totally show me how to write `tails` using `apo` :D – Thank you May 26 '19 at 16:25
  • _This cannot be implemented_ - usually I don't make such absolute claims. If I learn something new, I get all excited though. Anyway, here is my first rough implementation of `tails` using [`apo`](https://repl.it/@ftor/JampackedThunderousMicroprogramming). You probably don't like it, because it's pretty imperative (trampolines, local mutations) etc., so that I can eventually use it in production. – scriptum May 26 '19 at 17:04
  • Recursion schemes without the `Fix` stuff is totally worth learning. `zygo` encodes two folds where the latter depends on the former. `mutu` abstracts mutual recursion also combining two folds. `histo` gives your algebra access to all intermediate results. `futu`, well, I am not quite there yet... – scriptum May 26 '19 at 17:08
  • Thanks for sharing `apo`! We can implement `cata` for your `union` and add a `Loop` type. [The result](https://repl.it/repls/RottenRealisticPredictions) is a purely functional `apo` that is stack-safe. The array concats could be easily swapped with a mutating `push` function to optimise it a bit. - That aside, I'm confused with the `Some("", Left([s]))` bit in the example. Why `Left([s])` instead of `Left(s)`? – Thank you May 26 '19 at 19:51
  • I am confused with this very part too. In haskell string ist just [char]. I should drop the array.... – scriptum May 26 '19 at 20:23
4

The .from() example is great but if you really want to get creative check this out.

const newArray = length => [...`${Math.pow(10, length) - 1}`]
newArray(2)
newArray(10)

Massively limited though

newArray(1000)
["I", "n", "f", "i", "n", "i", "t", "y"]
stwilz
  • 1,598
  • 1
  • 15
  • 19
3

You can use a simple recursive process to do that.

const iter = (arr, counter) => {
  if (counter === 25) return arr;
  return iter([...arr, {id:counter}], counter + 1)
}
iter([], 0)
Aliaksandr Sushkevich
  • 7,264
  • 6
  • 29
  • 36