1

我是 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)
  });
});
4

2 回答 2

8

欢迎来到理性!

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

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

这类似于 JavaScript 的数组“spread”,除了这里你使用现有的,在前面a链接一个新节点,并将其命名为 b。仍然指向(因此是“不可变的”)。现在是。这是有效的,因为部件是共享的。0a[1, 2, 3]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 数组操作Js.Array

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

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

于 2017-06-07T22:38:21.223 回答
0

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

使用Js.ArrayArray.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_left使用Array.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)}|]);

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

也可以使用,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.

这就是更新可变记录数组的内容。它没有太大的不同。

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

    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

只是因为我今天碰巧这样做了,原因与我写这篇文章的原因相同,这里是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"]) */
于 2021-06-09T20:18:19.267 回答