Recurse in Linked List

后端 未结 2 379
栀梦
栀梦 2021-01-28 18:27

I have been practicing the linked list and wanted to implement the recurse on it, although in some cases I was able to implement it efficiently, in other cases I failed miserabl

2条回答
  •  既然无缘
    2021-01-28 19:08

    I was working on this answer as Scott made his post, making most of this information redundant. There is a portion which shows how to couple OOP-style with functional (persistent) data structures which you should find helpful.

    Similar to Scott's answer, we start by writing plain functions, no classes or methods. I'm going to place mine in a module named list.js -

    // list.js
    import { raise } from "./func"
    
    const nil =
      Symbol("nil")
    
    const isNil = t =>
      t === nil
    
    const node = (value, next) =>
      ({ node, value, next })
    
    const singleton = v =>
      node(v, nil)
    
    const fromArray = a =>
      a.reduceRight((r, _) => node(_, r), nil)
    
    const insert = (t, v, i = 0) =>
      isNil(t)
        ? singleton(v)
    : i > 0
        ? node(t.value, insert(t.next, v, i - 1))
    : node(v, t)
    
    const last = t =>
      isNil(t)
        ? raise("cannot get last element of empty list")
    : isNil(t.next)
        ? t.value
    : last(t.next)
    
    const search = (t, q) =>
      isNil(t)
        ? undefined
    : t.value === q
        ? t
    : search(t.next, q)
    
    const size = t =>
      isNil(t)
        ? 0
        : 1 + size(t.next)
    
    const toString = t =>
      isNil(t)
        ? "Nil"
        : `${t.value}->${toString(t.next)}`
    
    const toArray = t =>
      isNil(t)
        ? []
        : [ t.value, ...toArray(t.next) ]
    

    Now we can implement our OOP-style, List interface. This gives you the chaining behaviour you want. Notice how the methods are simple wrappers around the plain functions we wrote earlier -

    // list.js (continued)
    
    class List
    { constructor(t = nil)
      { this.t = t }
    
      isNil()
      { return isNil(this.t) }
    
      size()
      { return size(this.t) }
    
      add(v)
      { return new List(node(v, this.t)) }
    
      insert(v, i)
      { return new List(insert(this.t, v, i)) }
    
      toString()
      { return toString(this.t) }
    }
    

    Finally, make sure to export the parts of your module

    // list.js (continued)
    
    export { nil, isNil, node, singleton, fromArray, insert, last, search, size, toArray, toString }
    
    export default List
    

    The List interface allows you to do things in the familiar OOP ways -

    import List from "../list"
    
    const t = (new List).add(3).add(2).add(1)
    
    console.log(t.toString())
    // 1->2->3->Nil
    
    console.log(t.insert(9, 0).toString())
    // 9->1->2->3->Nil
    
    console.log(t.isNil())
    // false
    
    console.log(t.size())
    // 3
    

    Or you can import your module and work in a more functional way -

    import * as list from "../list"
    
    const t = list.fromArray([1, 2, 3])
    
    
    console.log(list.toString(t))
    // 1->2->3->Nil
    
    console.log(list.isNil(t))
    // true
    
    console.log(list.size(t))
    // 3
    

    I think the important lesson here is that the module functions can be defined once, and then the OOP interface can be added afterwards.


    A series of tests ensures the information in this answer is correct. We start writing the plain function tests -

    // list_test.js
    
    import List, * as list from "../list.js"
    import * as assert from '../assert.js'
    import { test, symbols } from '../test.js'
    
    await test("list.isNil", _ => {
      assert.pass(list.isNil(list.nil))
      assert.fail(list.isNil(list.singleton(1)))
    })
    
    await test("list.singleton", _ => {
      const [a] = symbols()
      const e = list.node(a, list.nil)
      assert.equal(e, list.singleton(a))
    })
    
    await test("list.fromArray", _ => {
      const [a, b, c] = symbols()
      const e = list.node(a, list.node(b, list.node(c, list.nil)))
      const t = [a, b, c]
      assert.equal(e, list.fromArray(t))
    })
    
    await test("list.insert", _ => {
      const [a, b, c, z] = symbols()
      const t = list.fromArray([a, b, c])
      assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, 0))
      assert.equal(list.fromArray([a,z,b,c]), list.insert(t, z, 1))
      assert.equal(list.fromArray([a,b,z,c]), list.insert(t, z, 2))
      assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 3))
      assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 99))
      assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, -99))
    })
    
    await test("list.toString", _ => {
      const e = "1->2->3->Nil"
      const t = list.fromArray([1,2,3])
      assert.equal(e, list.toString(t))
      assert.equal("Nil", list.toString(list.nil))
    })
    
    await test("list.size", _ => {
      const [a, b, c] = symbols()
      assert.equal(0, list.size(list.nil))
      assert.equal(1, list.size(list.singleton(a)))
      assert.equal(2, list.size(list.fromArray([a,b])))
      assert.equal(3, list.size(list.fromArray([a,b,c])))
    })
    
    await test("list.last", _ => {
      const [a, b, c] = symbols()
      const t = list.fromArray([a,b,c])
      assert.equal(c, list.last(t))
      assert.throws(Error, _ => list.last(list.nil))
    })
    
    await test("list.search", _ => {
      const [a, b, c, z] = symbols()
      const t = list.fromArray([a, b, c])
      assert.equal(t, list.search(t, a))
      assert.equal(list.fromArray([b, c]), list.search(t, b))
      assert.equal(list.singleton(c), list.search(t, c))
      assert.equal(undefined, list.search(t, z))
    })
    
    await test("list.toArray", _ => {
      const [a,b,c] = symbols()
      const e = [a,b,c]
      const t = list.fromArray(e)
      assert.equal(e, list.toArray(t))
    })
    

    Next we ensure the List interface behaves accordingly -

    // list_test.js (continued)
    
    await test("List.isNil", _ => {
      assert.pass((new List).isNil())
      assert.fail((new List).add(1).isNil())
    })
    
    await test("List.size", _ => {
      const [a,b,c] = symbols()
      const t1 = new List
      const t2 = t1.add(a)
      const t3 = t2.add(b)
      const t4 = t3.add(c)
      assert.equal(0, t1.size())
      assert.equal(1, t2.size())
      assert.equal(2, t3.size())
      assert.equal(3, t4.size())
    })
    
    await test("List.toString", _ => {
      const t1 = new List
      const t2 = (new List).add(3).add(2).add(1)
      assert.equal("Nil", t1.toString())
      assert.equal("1->2->3->Nil", t2.toString())
    })
    
    await test("List.insert", _ => {
      const t = (new List).add(3).add(2).add(1)
      assert.equal("9->1->2->3->Nil", t.insert(9, 0).toString())
      assert.equal("1->9->2->3->Nil", t.insert(9, 1).toString())
      assert.equal("1->2->9->3->Nil", t.insert(9, 2).toString())
      assert.equal("1->2->3->9->Nil", t.insert(9, 3).toString())
      assert.equal("1->2->3->9->Nil", t.insert(9, 99).toString())
      assert.equal("9->1->2->3->Nil", t.insert(9, -99).toString())
    })
    

    Dependencies used in this post -

    func.raise - allows you to raise an error using an expression instead of a throw statement

    // func.js
    
    const raise = (msg = "", E = Error) => // functional throw
      { throw E(msg) }
    
    // ...
    
    export { ..., raise }
    

提交回复
热议问题