如何填充现有列表/数组

how to populate existing list / array

我是 reason / ocaml / 函数式编程的新手。

我知道 List.append[] @ [] 但这些函数会创建新列表,但如何填充现有列表/数组?

  1. 填充列表的最佳方式是什么?
  2. 填充数组的最佳方法是什么?意味着如果坐标类型是 let coords: array point = [];
  3. 或者对于这种情况,这是错误的流程(算法)?

原因代码:

type point = {x: int, y: int};

let coords: list point = [];

let append raw =>
  Array.iter
    (
      fun data => {
        let p = {x: data.x, y: data.y};
        /* how to append p to coords */
        ()
      }
    )
    raw;

JS 模拟:

const coords = [];
const append = raw => raw.forEach({x, y} => {
  coords.push({
    x: process(x),
    y: process(y)
  });
});

欢迎来到 Reason!

在Reason/OCaml中,列表是不可变的。在引擎盖下,它们是简单的单链表。每次 "modify" 都会创建新的。这是一个例子:

let a = [1, 2, 3];
let b = [0, ...a];

这类似于 JavaScript 的数组 "spread",除了这里您使用现有的 a,在前面链接一个新节点 0,并称之为 b。 a 仍然指向 [1, 2, 3](因此 "immutable")。 b 现在是 [0, 1, 2, 3]。这是有效的,因为 [1, 2, 3] 部分是共享的。

这样做的好处是您不必担心传递您的列表而意外地让一个晦涩的函数修改它。 List 的不变性允许您纯粹通过查看您现在正在盯着的值来推理您的代码(因为它永远不会改变!)。

list的缺点是在末尾加东西效率低:

let c = a @ [4] 

该操作基本上是获取一个包含一项 [4] 的列表,然后将 [1, 2, 3] 的每一项依次附加到它。在性能方面如此线性。但从列表实现的简单性来看,历史上认为它值得权衡。

所以 3. 如果您尝试设置列表项,这是错误的流程。

  1. 在您的情况下填充列表的最佳方法是从旧列表非可变地映射它:let newList = List.map (fun blabla => ...) raw
  2. 数组相同。映射它。如果您遇到困难,可以使用 Array.of_listArray.to_list

关于数组的更多信息:OCaml 数组是可变的,它的大小是不变的。把它想象成一块内存。您将通过 Array.make newSize 分配一个新数组,然后通过 Array.set 填充它。如果您大量调整数组的大小,这将没有意义,因此请选择正确的数据结构。

对于JS编译,BuckleScript将一个ocaml数组编译成一个JS数组。因此,它是可变的 可调整大小的。你会在 Js.Array

下找到你熟悉的 JS 数组操作

作为一般启发式算法,如果您想更改长度:尝试 filter。如果您想更改长度和包含的项目,请尝试 fold_left。否则,map.

最近,我们已经开始实现一些不可变的、可调整大小的、可选的可变数组。敬请期待!

这里有几种不同的方法。如果你想坚持使用评论中提到的 Js.Array,你可以这样做:

使用Js.Array and Array.iter

    type point = {
      x: int,
      y: int,
    };

    let points: array(point) = [|{x: 2, y: 4}|];

    let rawArray = [|[|1, 2|], [|3, 4|], [|9, 4|]|];

    let append = raw => {
      Array.iter(
        a => {
          let data: point = {x: a[0], y: a[1]};
          Js.Array.push(data, points)->ignore;
        },
        raw,
      );
    };

    append(rawArray);

    Js.log2("new points", points);
...or with tuples with `Js.Array`
    let rawTuples = [|(3, 5), (4, 9), (9, 4)|];

    append(rawTuples);

    Js.log2("new points", points);
[ { x: 2, y: 4 }, { x: 3, y: 5 }, { x: 4, y: 9 }, { x: 9, y: 4 } ]

如果你想按照@chenglou的建议用Array.fold_leftArray.append来做,你可以试试

        rawArray|>Array.fold_left((a, b) =>
        Array.append(a,  [|{x: b[0], y: b[1]}|]), [||]);

使用一些辅助函数可能会更干净,例如:

    let concatMap = f =>
      Array.fold_left((a, b) => Array.append(a, f(b)), [||]);

    let newPoint = coord => [|{x: coord[0], y: coord[1]}|];

然后调用:

    let rawTuples = [|(3, 5), (4, 9), (9, 4)|];

    let arrayTuples = rawTuples |> concatMap(a => [|{x: fst(a), y: snd(a)}|]);

使用助手也有助于我理解函数的每个部分在做什么。

也适用于 tuples,因为它们只是 ReasonML/Ocaml/Rescript

中的数组
    let rawTuples = [|(3, 5), (4, 9), (9, 4)|];

    let arrayTuples = rawTuples |> concatMap(a => [|{x: fst(a), y: snd(a)}|]);

使用Mutable Records

您可以选择使用 mutable records 创建数组。

这就是更新可变记录数组的原因。差别不大。

这里我们使用一个函数来改变数组中的数据。

    let printCoords = coords => Array.iter(Js.log, coords);

    type mutablePoint('a, 'b) = {
      mutable x: 'a,
      mutable y: 'b,
    };

    let data1: mutablePoint(int, int) = {x: 2, y: 4};

    let data2: mutablePoint(int, int) = {x: 3, y: 4};

    let data3: mutablePoint(int, int) = {x: 4, y: 4};

    let isEven = n => {
      n mod 2 == 0;
    };

    let multiplyByY = data => data.x = data.x * data.y;

    let makeItOdd = data => data.x = data.x + 1;

    let updateData = data => data.x->isEven
       ? data->makeItOdd :  multiplyByY(data);

    let points: array(mutablePoint('x, 'y)) = [|data1, data2, data3|];

    let append = (fn, data) => {
      Array.iter(x => {fn(x)}, data);

      data;
    };

    points |> append(updateData);

    Js.log("points after");

    printCoords(points);

    // points after { x: 3, y: 4 } { x: 12, y: 4 }{ x: 5, y: 4 }

你的问题是关于从一些原始数据更新的,所以这里有一个例子,我们在索引 i 处获取原始数据并使用它来更改可变数组上的 x 值:

    let points2: array(mutablePoint('x, 'y)) = [|data1, data2, data3|];

    let printCoords = coords => Array.iter(Js.log, coords);

    printCoords(points2);

    let rawData = [|[|1, 2|], [|3, 4|], [|9, 4|]|];

    let update_x_on_point_i = (i, x) => points2[i].x = x;

    let append = raw =>
      Array.iteri(
        (i, d) => {
          let x: int = d[0];

          update_x_on_point_i(i, x);

        },
        raw,
      );

    append(rawData);

    Js.log2("points2 after: ", points2);

    printCoords(points2);

    // points2 after:  [ { x: 1, y: 4 }, { x: 3, y: 4 }, { x: 9, y: 4 } ]

这两个数组恰好大小相同,所以没有例外,但一旦数组长度不同,就很容易出现一个,所以我们应该在现实生活中处理。

JS 模拟 Belt.Array

只是因为我今天碰巧出于与导致我post 相同的原因做了这个,这里是 JS Analogue 版本。

    type point = {
      x: int,
      y: int,
    };
    let coords = [|{x: 9, y: 7}, {x: 2, y: 4}, {x: 3, y: 8}|];

    Js.log("coords before");

    Js.log("-------");

    let append = raw =>
      raw->Belt.Array.mapWithIndex(
             _,
             (i, r) => {
               let new_point_i = {x: r[0], y: r[1]};
               coords[i] = new_point_i;
             },
           );

    let rawData = [|[|1, 2|], [|3, 4|], [|9, 4|]|];

    append(rawData);

    Js.log("coords after");

    Js.log(coords);
    coords before
    [ { x: 9, y: 7 }, { x: 2, y: 4 }, { x: 3, y: 8 } ]
     -------
    coords after
    [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 9, y: 4 } ]

更新

以下代码是上述评论中要点中@yawar 代码的更新语法。他的解释值得一读。

type data_item = {
  symbol: string,
  next: bool,
};

/* Convenience function for making data items. */
let make_data_item = (symbol, next) => {symbol, next};

let process = (data: list(data_item)) => {
  /*
   We track the current symbol as well as the result list in the folding function.
   */
  let fold_func = ((current, result), {symbol, next}) =>
    if (next) {
      (symbol, [current, ...result]);
    } else {
      (current ++ symbol, result);
    };

  let (current, result) = List.fold_left(fold_func, ("", []), data);
  /*
   We need to reverse the result list because `[el, ...els]` above actually
   builds it up in the _opposite_ order to the original input list.
   */
  (current, List.rev(result));
};

let result =
  process([
    make_data_item("a", false),
    make_data_item("b", false),
    make_data_item("c", true),
    make_data_item("d", false),
  ]);
/* result = ("cd", ["ab"]) */