splitting objects inside an array
14.11.2023 4 min readYou 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 😥