Sir Rubberduck
Sir Rubberduck

Reputation: 2262

Create a nested object with children from array of arrays

I have the following array of arrays

let arr = [
    [ "Female" , "Male" ],
    [ "Dinner" , "Lunch" ],
    [ "No" , "Yes" ],
]

I'd like to achieve this structure

let foo = [
    { 
        value: "Female",
        children: [
            {
                value: "Dinner",
                children: [
                    {
                        value: "No"
                    },
                    {
                        value: "Yes"
                    },
                ]
            },
            {
                value: "Lunch",
                children: [
                    {
                        value: "No"
                    },
                    {
                        value: "Yes"
                    },
                ]
             },
        ]
    },
    { 
        value: "Male",
        children: [
            {
                value: "Dinner",
                children: [
                    {
                        value: "No"
                    },
                    {
                        value: "Yes"
                    },
                ]
            },
            {
                value: "Lunch",
                children: [
                    {
                        value: "No"
                    },
                    {
                        value: "Yes"
                    },
                ]
             },
        ]
    },
]

I simply can't wrap my head around the problem to achieve this, thus, I don't have a starting code to post, so please if you can help, it would be great.

Upvotes: 0

Views: 954

Answers (5)

frangly
frangly

Reputation: 172

Rearrange your Array using the below code, then iterate as your wish and this is dynamic. you can have more rows in arr variable.

let arr = [
 [ "Female" , "Male" ],
 [ "Dinner" , "Lunch" ],
 [ "No" , "Yes" ],
] 

for(let i=arr.length-2; i>-1; i--){
  for(let j=0; j< arr[i].length; j++) {
    item = {}
    item[arr[i][j]] = arr[i+1];
    arr[i][j] = [];
    arr[i][j] = item;
  }
arr.pop();
}
console.log(arr);
/*output*/
[
[{
    'Female': [{
        'Dinner': ['No', 'Yes']
    }, {
        'Lunch': ['No', 'Yes']
    }]
}, {
    'Male': [{
        'Dinner': ['No', 'Yes']
    }, {
        'Lunch': ['No', 'Yes']
    }]
  }]
]

https://jsfiddle.net/Frangly/ywsL0pbt/149/

Upvotes: 2

Mulan
Mulan

Reputation: 135197

recursion

Recursion is a functional heritage and so using it with functional style yields the best results. This means avoiding things like mutation, variable reassignments, and other side effects.

We can write make(t) using inductive inductive reasoning -

  1. If the input t is empty, return the empty result []
  2. (inductive) t has at least one element. For all value in the first element t[0], return a new object {value, children} where children is the result of the recursive sub-problem make(t.slice(1))

const make = t =>
  t.length == 0
    ? []                                                          // 1
    : t[0].map(value => ({ value, children: make(t.slice(1)) }))  // 2

const myinput = [
  [ "Female" , "Male" ],
  [ "Dinner" , "Lunch" ],
  [ "No" , "Yes" ]
]

console.log(make(myinput))

Above we write make as a single pure functional expression using ?:. This is equivalent to an imperative style if..else -

function make(t) {
  if (t.length == 0)
    return []
  else
    return t[0].map(value => ({ value, children: make(t.slice(1)) }))
}

const myinput = [
  [ "Female" , "Male" ],
  [ "Dinner" , "Lunch" ],
  [ "No" , "Yes" ]
]

console.log(make(myinput))

visualize

It helps for us to visualize how these work

make([[ "Female" , "Male" ], [ "Dinner" , "Lunch" ], [ "No" , "Yes" ]])

= [
    {value: "Female", children: make([[ "Dinner" , "Lunch" ], [ "No" , "Yes" ]]) },
    {value: "Male", children: make([[ "Dinner" , "Lunch" ], [ "No" , "Yes" ]]) }
  ]
make([[ "Dinner" , "Lunch" ], [ "No" , "Yes" ]])

= [
    {value: "Dinner", children: make([[ "No" , "Yes" ]]) },
    {value: "Lunch", children: make([[ "No" , "Yes" ]]) }
  }
make([[ "No" , "Yes" ]])

= [
    {value: "No", children: make([]) },
    {value: "Yes", children: make([]) }
  }
make([])
= []

remove empty children

Now that we see how it works, we prevent making empty children: [] properties by adding one more conditional. When t has just one element, simply create a {value} for all value in the element -

function make(t) {
  switch (t.length) {
    case 0:
      return []
    case 1:
      return t[0].map(value => ({ value })) 
    default:
      return t[0].map(value => ({ value, children: make(t.slice(1)) }))
  }
}

const myinput = [
  [ "Female" , "Male" ],
  [ "Dinner" , "Lunch" ],
  [ "No" , "Yes" ]
]

console.log(make(myinput))

Which produces the output you are looking for -

[
  {
    "value": "Female",
    "children": [
      {
        "value": "Dinner",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      },
      {
        "value": "Lunch",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      }
    ]
  },
  {
    "value": "Male",
    "children": [
      {
        "value": "Dinner",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      },
      {
        "value": "Lunch",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      }
    ]
  }
]

Upvotes: 4

A K
A K

Reputation: 59

Checkout this code snippet. It outputs as per your need.

let arr = [
    [ "Female" , "Male" ],
    [ "Dinner" , "Lunch" ],
    [ "No" , "Yes" ],
]

let foo = [];


let arr2 = [];
arr[2].forEach(yn => {  
    arr2.push({ "value": yn});
});

let arr1 = [];
arr[1].forEach(dl => {
    arr1.push({
        "value": dl,
        "children": arr2
    }); 
});

arr[0].forEach(fm => {
    foo.push({
        "value": fm,
        "children": arr1
    }); 
});     

console.log(JSON.stringify(foo, null, 2))

Upvotes: -1

aerial
aerial

Reputation: 1198

You can try this:

let arr = [
  ['Female', 'Male'],
  ['Dinner', 'Lunch'],
  ['No', 'Yes']
]

function makeTree(a, ch = [], currIndex = 0) {
  for (const item of a[currIndex]) {

    if (a[currIndex + 1]) {
      // If there is an array after this one then 
      // include the 'children' array
      const obj = { value: item, children: [] }
      ch.push(obj)

      // Run the function again to fill the `children` 
      // array with the values of the next array
      makeTree(a, obj.children, currIndex + 1)

    } else {
      // If this is the last array then
      // just include the value 
      ch.push({ value: item })
    }
  }

  return ch
}

const result = makeTree(arr)
console.log(JSON.stringify(result, null, 2))
.as-console-wrapper { min-height: 100% }

Upvotes: 1

Kristian
Kristian

Reputation: 2505

You can also do it without recursion with 2 for

let arr = [
    [ "Female" , "Male" ],
    [ "Dinner" , "Lunch" ],
    [ "No" , "Yes" ],
];

var lastChild = -1;

for(var i = arr.length-1; i >= 0; i--) {
  var item = arr[i];
  var lastChildTemp = [];
  for(var j = 0; j < item.length; j++) {
    var newChild = {value: item[j]};
    if(lastChild != -1) {
      newChild.children = lastChild;
    }
    lastChildTemp.push(newChild);
  }
  lastChild = lastChildTemp;
}

console.log(JSON.stringify(lastChildTemp,null,2));

Output:

[
  {
    "value": "Female",
    "children": [
      {
        "value": "Dinner",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      },
      {
        "value": "Lunch",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      }
    ]
  },
  {
    "value": "Male",
    "children": [
      {
        "value": "Dinner",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      },
      {
        "value": "Lunch",
        "children": [
          {
            "value": "No"
          },
          {
            "value": "Yes"
          }
        ]
      }
    ]
  }
]

The key here is to use backward for (starting from high index to low index), then create a lastChild object. Then put it in .children attribute of each next objects.

Upvotes: 2

Related Questions