【问题标题】:Cons box implementation缺点框实现
【发布时间】:2014-06-15 10:43:14
【问题描述】:

我似乎无法找到在 Ruby 中使用 Scheme 的 cons 框的方法(似乎几乎所有数组)。这是一个相当粗略的大纲:

class cons
    def initialize (car, cdr)
    @car = car
    @cdr = cdr
    end

    #return the car of the pair
    def car
        return @car
    end

    #return the cdr of the pair
    def cdr
        return @cdr
    end
end

我可以传递两个值并调用carcdr,但这不是任何类型的列表(只有两个值)。如何制作一个列表,我可以在其中插入一些内容,如 Scheme cons:

myCons = (cons(1, cons(2, cons(3, cons(4, 5)))))

我能找到的最接近的方法是创建自己的数组,例如 myArray = Array[1, 2, 3, 4, 5],然后使用 puts myArray.join(' ')。这只给了我"1 2 3 4 5"而不是(1 2 3 4 5),这还没有考虑到我仍然不能用缺点构建数组,我只是自己做的。

【问题讨论】:

  • 在尝试实现任何东西之前,您至少应该学习 Ruby 语法的基础知识。 Class cons 在 Ruby 中无效。
  • 你的问题与Ruby无关。看来您根本不了解缺点列表的工作原理。也许你应该问一个问题?
  • 一个 cons 单元格只是一个包含两个字段的结构。这已经足够实现单链表的结构了,在 Lisp 中,约定是一个列表要么是符号 nil(空列表,也写为 ()),要么是一个 cons 单元格,其中 car单元格的第一个元素是列表的第一个元素,cdr 是列表的 rest(即 nil 或其他列表)。就这样。在 Lisps 中,以一种特殊的方式打印这个很方便,但这只是为了展示。
  • 请参阅Dot notation in schemeRecursive range in Lisp adds a period? 了解更多关于这些约定以及conscarcdr 必须做什么。前面的评论说 cons 单元格是具有两个字段的结构,但这超出了保证范围。例如,您可以只使用词法闭包,如 this answerWhat is the Definition of a Lisp Cons Cell?
  • @JoshuaTaylor 没错。请参阅我对 Cons 类的实现,它确实实现了点分符号。

标签: ruby scheme


【解决方案1】:

您可以声明一个方法 (to_a) 来构建您的数组:

class Cons

  def initialize(car, cdr)
    @car = car
    @cdr = cdr
  end

  attr_reader :car, :cdr

  def to_a
    list = cdr.respond_to?(:to_a) ? cdr.to_a : cdr
    [car, *list]
  end
end

myCons = Cons.new(1, Cons.new(2, Cons.new(3, Cons.new(4,5))))
myCons.to_a
# => [1, 2, 3, 4, 5]

该方法检查cdr本身是否支持to_a,如果可能,调用它,然后将car添加到列表的开头,并返回创建的列表。

如果您想使用类似于cons(1, cons(2,...) 的语法,可以使用[]

class Cons
  def self.[](car, cdr)
    Cons.new(car, cdr)
  end
end

现在你可以写了:

myCons = Cons[1, Cons[2, Cons[3, Cons[4,5]]]]

【讨论】:

    【解决方案2】:

    这是Cons 的一个实现,它内置了不错的打印语法(包括对不正确列表的支持),并且是可枚举的:

    class Cons
      include Enumerable
      attr_accessor :car, :cdr
    
      class << self
        alias [] new
      end
    
      def initialize(car, cdr)
        self.car = car
        self.cdr = cdr
      end
    
      def each_pair
        return to_enum(:each_pair) unless block_given?
        cell = self
        while cell.is_a? Cons
          yield cell.car, cell.cdr
          cell = cell.cdr
        end
      end
    
      def each
        return to_enum unless block_given?
        each_pair { |car,| yield car }
      end
    
      def print
        sb = '('
        each_pair do |car, cdr|
          sb << yield(car)
          case cdr
          when Cons
            sb << ' '
          when nil
          else
            sb << ' . ' << yield(cdr)
          end
        end
        sb << ')'
      end
    
      def to_s
        print &:to_s
      end
    
      def inspect
        print &:inspect
      end
    end
    

    哦,这里有一个创建列表的简单方法(类似于 Common Lisp 和 Scheme 中的 list 函数):

    def list(*items)
      items.reverse_each.reduce(nil) { |result, item| Cons[item, result] }
    end
    

    例子:

    irb(main):001:0> a = Cons[1, Cons[2, Cons[3, nil]]]
    => (1 2 3)
    irb(main):002:0> b = Cons[1, Cons[2, Cons[3, 4]]]
    => (1 2 3 . 4)
    irb(main):003:0> a.to_a
    => [1, 2, 3]
    irb(main):004:0> a.map(&Math.method(:sqrt))
    => [1.0, 1.4142135623730951, 1.7320508075688772]
    irb(main):005:0> list(1, 2, 3, 4, 5)
    => (1 2 3 4 5)
    

    更新:一位用户写信问我如何(除其他外)附加基于缺点的列表。作为一名 Schemer,我喜欢将 cons 单元格视为不可变的,因此附加的标准方法是将左侧列表中的每个元素从右到左 cons 到右侧列表中。以下是我将如何实现它。

    首先,让我们定义一个reduce_right 方法。 (从技术上讲,这是一个正确的 fold,而不是正确的 reduce,但 Ruby 更喜欢术语“reduce”而不是“fold”,所以我将在这里使用.) 我们将重新打开 NilClassCons 以完成这项工作:

    class NilClass
      def reduce_right(init)
        init
      end
    end
    
    class Cons
      def reduce_right(init, &block)
        block.call(cdr.reduce_right(init, &block), car)
      end
    end
    

    那么append就像使用reduce_right一样简单:

    def append(lhs, rhs)
      lhs.reduce_right(rhs) { |result, item| Cons[item, result] }
    end
    

    这允许您附加两个列表,但通常允许附加任意数量的列表更方便(这是 Scheme 的 append 允许的):

    def append(*lists)
      lists.reverse_each.reduce do |result, list|
        list.reduce_right(result) { |cur, item| Cons[item, cur] }
      end
    end
    

    请注意,在这两种情况下,最右边的“列表”不需要是正确的列表,您可以通过在其中放置不是 cons 单元格的内容来创建不正确的列表:

    irb(main):001:0> append(list(1, 2, 3), list(4, 5))
    => (1 2 3 4 5)
    irb(main):002:0> append(list(1, 2, 3), list(4, 5), 6)
    => (1 2 3 4 5 . 6)
    irb(main):003:0> append(list(1, 2, 3), list(4, 5), list(6))
    => (1 2 3 4 5 6)
    

    (非最右边的列表必须是正确的列表。)

    【讨论】:

    • 这非常有效。您能否详细说明 print &:to_s 的作用?例如,仅将 print 中的代码放入 to_s 是行不通的。 &:to_s 完成了什么? (正如我所说,我是新手,想真正了解正在发生的事情)。
    • 当然,&amp;:to_s{ |x| x.to_s } 的简写,同样&amp;:inspect{ |x| x.inspect } 的简写(有关详细信息,请参阅 Symbol#to_proc)。这些块由print 方法内的yields 调用。我这样做的原因是我想同时实现 to_sinspect 而没有大量重复代码。
    • 如果我们传入&amp;:to_s,那么yield(car)car.to_s一样,yield(cdr)cdr.to_s一样。同样,如果我们传入&amp;:inspect,那么我们会得到car.inspectcdr.inspect
    【解决方案3】:

    或者来自 SICP 的一种更奇特的方法 :)

    def cons(a, b)
        lambda { |seek|
            seek == 0 ? a : b
        }
    end
    
    def car(pair)
        pair.call(0)
    end
    
    def cdr(pair)
        pair.call(1)
    end
    

    【讨论】:

      猜你喜欢
      • 2016-01-15
      • 2014-10-30
      • 1970-01-01
      • 2014-08-21
      • 2012-05-15
      • 2014-09-07
      • 2013-10-12
      • 2010-12-01
      • 1970-01-01
      相关资源
      最近更新 更多