0

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.

Ivan
  • 1,967
  • 4
  • 34
  • 60

5 Answers5

4

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"
          }
        ]
      }
    ]
  }
]
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • @Ivan thanks for your feedback. if you study functional style i think you will find that programming complexities have a way of disappearing. it makes for a very pleasant experience. if you have any follow up questions, i'm happy to assist :D – Mulan Dec 16 '21 at 04:26
  • I am a great fan of functional programming. I'm curious as to why people overengineer like crazy with OOP. I often come across unreadable code that is super convoluted with abstractions. – Ivan Dec 16 '21 at 09:09
  • I made your answer the correct one because both the code and your explanation are incredible. – Ivan Dec 16 '21 at 10:36
  • common uses of OOP lead to many pain points as the program grows. however the OOP *interface* is attractive and ergonomic. in [this Q&A](https://stackoverflow.com/a/65598067/633183) i show how OOP interfaces are nothing but a thin wrapper around a functional interface. functions that do work are only implemented once but the module can export both and the end user gets the best of both worlds. [this Q&A](https://stackoverflow.com/a/61945666/633183) offers additional explanation, and though it is written in python, the concept applies identically in javascript. enjoy ^_^ – Mulan Dec 16 '21 at 17:39
2

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.

Kristian
  • 2,456
  • 8
  • 23
  • 23
  • Awesome! Thank you for the solution. Interesting that you are not using recursion. I accepted the first answer as correct due to it being, well, first. I wonder which one is faster. – Ivan Dec 15 '21 at 08:20
  • it might be more readable with recursion, but the one without recursion should be (marginally) faster (might also depends on javascript implementation the browser do). This is explainable for lesser stack memory consumption when using recursion – Kristian Dec 15 '21 at 08:23
  • but you should not need to micro-optimize javascript anyway (unless the page is noticeably slow because of js computation, which should not happen unless you're doing 3d or computation) – Kristian Dec 15 '21 at 08:34
2

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/

frangly
  • 162
  • 7
1

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% }
aerial
  • 1,188
  • 3
  • 7
  • 15
-1

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))
A K
  • 61
  • 3
  • The problem with this solution is that it only handles three items. What if the number of items is not known? – Ivan Dec 15 '21 at 08:18