/now
projects
ramblings
smol projects

splitting objects inside an array

14.11.2023 4 min read

You bought a few bunches of fruit over the weekend. Create a function that splits a bunch into singular objects inside an array.

splitBunches([
  { name: "grapes", quantity: 2 }
]) ➞ [
  { name: "grapes", quantity: 1 },
  { name: "grapes", quantity: 1 }
]

splitBunches([
  { name: "currants", quantity: 1 },
  { name: "grapes", quantity: 2 },
  { name: "bananas", quantity: 2 }
]) ➞ [
  { name: "currants", quantity: 1},
  { name: "grapes", quantity: 1 },
  { name: "grapes", quantity: 1 },
  { name: "bananas", quantity: 1 },
  { name: "bananas", quantity: 1 }
]

I opted to do this one in Raku first, because I thought it’d be easiest:

use Test;

sub split-bunches(@b) {
    @b.map({
       [%(name => $_<name>, quantity => 1) xx $_<quantity>].flat;
    }).flat;
}

ok split-bunches([
    %{name => "currants", quantity => 1},
    %{name => "grapes", quantity => 2},
    %{name => "bananas", quantity => 2},
]) == [
    %{name => "currants", quantity => 1},
    %{name => "grapes", quantity => 1},
    %{name => "grapes", quantity => 1},
    %{name => "bananas", quantity => 1},
    %{name => "bananas", quantity => 1},
]

I was incorrect. Working with data structures in Raku can sometimes be challenging. At first, Raku treated individual hashes as multiple key-value pairs. Afterwards, flattening the array “unhashed” the hashes, turning them back into key-value pairs. Some notes:

  • We need to add trailing commas to tell Raku that we’re this is a list / array when we have single-item arrays
  • We need to add the % sigil explicitly? We seem to lose type information when using $_

I don’t know. Anyway, there were a few steps involved, and I’m not sure what I did to make it work, but the tests are passing…

Crystal:

require "spec"

def repeat(h : Hash)
  times : Int32 = h["quantity"].as(Int32)
  Array.new(times) { {"name" => h["name"], "quantity" => 1} }
end

def split_bunches(b : Array(Hash))
  b.flat_map { |e| repeat(e) }
end

split_bunches([
  {"name" => "currants", "quantity" => 1},
  {"name" => "grapes", "quantity" => 2},
  {"name" => "bananas", "quantity" => 2},
]).should eq [
  {"name" => "currants", "quantity" => 1},
  {"name" => "grapes", "quantity" => 1},
  {"name" => "grapes", "quantity" => 1},
  {"name" => "bananas", "quantity" => 1},
  {"name" => "bananas", "quantity" => 1},
]

We cast h["quantity"] as an Int32 because the Crystal compiler complains that the value could be (Int32 | String) - probably because it doesn’t have any type information.

Alternatively, we can use named tuples (I forgot about these on the first pass), which I like a lot more:

require "spec"

alias Bunch = NamedTuple(name: String, quantity: Int32)

def repeat(b : Bunch)
  times : Int32 = b[:quantity]
  Array.new(times) { {name: b[:name], quantity: 1} }
end

def split_bunches(b : Array(Bunch))
  b.flat_map { |e| repeat(e) }
end

split_bunches([
  {name: "currants", quantity: 1},
  {name: "grapes", quantity: 2},
  {name: "bananas", quantity: 2},
]).should eq [
  {name: "currants", quantity: 1},
  {name: "grapes", quantity: 1},
  {name: "grapes", quantity: 1},
  {name: "bananas", quantity: 1},
  {name: "bananas", quantity: 1},
]

We can do away with type casting, and the data structures are much easier to write too.

Nim:

type Bunch = tuple[name: string, quantity: int]

proc splitBunches(s: seq[Bunch]): seq[Bunch] =
    return s.map(proc(b: Bunch): seq[Bunch] =
        let numTimes = b.quantity
        repeat((name: b.name, quantity: 1), numTimes)
    ).foldl(a & b)

assert splitBunches(@[
    (name: "currants", quantity: 1),
    (name: "grapes", quantity: 2),
    (name: "bananas", quantity: 2)
]) == @[
    (name: "currants", quantity: 1),
    (name: "grapes", quantity: 1),
    (name: "grapes", quantity: 1),
    (name: "bananas", quantity: 1),
    (name: "bananas", quantity: 1)
]

Nim expects tables to be homogenous - the only way to have a heterogenous table-like structure is to use the json module, which doesn’t make much sense in this context. Given that, we use named tuples here as well (a bit less hassle than using objects).

Javascript:

import equal from "deep-equal";
import assert from "./assert";

type Bunch = {
  name: string;
  quantity: number;
};

function splitBunches(b: Bunch[]) {
  return b
    .map((el) => {
      return new Array(el.quantity).fill({
        ...el,
        quantity: 1,
      });
    })
    .flat();
}

assert(
  equal(
    splitBunches([
      { name: "currants", quantity: 1 },
      { name: "grapes", quantity: 2 },
      { name: "bananas", quantity: 2 },
    ]),
    [
      { name: "currants", quantity: 1 },
      { name: "grapes", quantity: 1 },
      { name: "grapes", quantity: 1 },
      { name: "bananas", quantity: 1 },
      { name: "bananas", quantity: 1 },
    ]
  )
);

Nothing much to say here, except that I wish the spread operator were a feature in every language 😥

Built with Astro and Tailwind 🚀