Scheme中的范围集合

时间:2014-03-10 04:29:41

标签: scheme

我需要在给出时编写一个Scheme函数(union s1 s2) 两套,让我们说

s1 = ((1 3) (5 13) (25 100))
s2 = ((2 4) (17 26) (97 100))

将给出

(union s1 s2) ----> ((1 4) (5 13) (17 100))

如果

s1 = ((1 3) (5 13) (25 110) (199 300))
s2 = ((2 4) (17 26) (97 100) (110 200) (288 500))

然后

(union s1 s2) ----> ((1 4) (5 13) (17 500))

有人可以建议我怎么做吗?我不知道如何开始。

4 个答案:

答案 0 :(得分:1)

听起来像个有趣的问题!这听起来像是一个家庭作业问题,所以我会试着指出你的答案,而不是自己写给你。如果我误解了这种情况,我会提前道歉。

首先,我假设各个范围集是有序的,并且是非重叠的。

这个问题非常适合递归模具,具有扭曲性。具体来说,它是“迭代两个复杂数据”的例子,如何设计程序的第17节:

http://www.htdp.org/2003-09-26/Book/curriculum-Z-H-22.html#node_chap_17

更具体地说,你在案例3中,你实际上需要考虑所有可能的组合。

事实上,它甚至比这更糟糕,因为在两组都非空的情况下,你关心的是哪个区间开始较低。

要开始使用,您需要

  • 定义输入数据的类,
  • 开发好的示例集。确保包含两个集合都为空的示例,其中第一个为空,其中第二个为空,其中两个都为空。

按照HtDP模板,你应该没问题。不过,这个 是一个棘手的问题。

答案 1 :(得分:1)

我建议

而不是使用2套
  1. 将2组合并为一个组合列表(在car上订购)
  2. 进行第二次传递以实际合并元素,维护累加器列表(结果,颠倒顺序)。
  3. 这样,您总是比较输入(合并)列表的第一个和第二个元素,并且您知道它们是有序的,这极大地简化了您的代码:

    • empty merged list =>返回结果
    • 剩下一个元素=>将此元素推送到结果上,然后返回结果,反转
    • 列表中至少有两个元素
      • 如果2个元素不重叠,则将 first 元素推送到结果上,然后循环其余的合并列表,
      • 否则用 merged 元素替换这两个元素,然后循环

    执行示例(您的第二个):

    (union '((1 3) (5 13) (25 110) (199 300)) 
           '((2 4) (17 26) (97 100) (110 200) (288 500)))
    
    lst: ((1 3) (2 4) (5 13) (17 26) (25 110) (97 100) (110 200) (199 300) 
          (288 500))
    res: ()
    
    lst: ((1 4) (5 13) (17 26) (25 110) (97 100) (110 200) (199 300) (288 500))
    res: ()
    
    lst: ((5 13) (17 26) (25 110) (97 100) (110 200) (199 300) (288 500))
    res: ((1 4))
    
    lst: ((17 26) (25 110) (97 100) (110 200) (199 300) (288 500))
    res: ((5 13) (1 4))
    
    lst: ((17 110) (97 100) (110 200) (199 300) (288 500))
    res: ((5 13) (1 4))
    
    lst: ((17 110) (110 200) (199 300) (288 500))
    res: ((5 13) (1 4))
    
    lst: ((17 200) (199 300) (288 500))
    res: ((5 13) (1 4))
    
    lst: ((17 300) (288 500))
    res: ((5 13) (1 4))
    
    lst: ((17 500))
    res: ((5 13) (1 4))
    
    lst: ()
    res: ((17 500) (5 13) (1 4))
    
    final result: ((1 4) (5 13) (17 500))
    

    我编写了代码,如果你遵循这种方法,它只有11行并且非常简单。

    修改

    既然你要求代码,这是我写的初始版本:

    (define (union set1 set2)
      (let loop ([lst (sort (append set1 set2) < #:key car)] [res null])
        (if (null? lst)
            (reverse res)
            (let ([e1 (car lst)] [cd (cdr lst)])
              (if (null? cd)
                  (loop null (cons e1 res))
                  (let ([e2 (car cd)] [e1y (cadr e1)])
                    (if (> (car e2) e1y) 
                        (loop cd (cons e1 res))
                        (loop (cons (list (car e1) (max e1y (cadr e2))) 
                                    (cdr cd)) 
                              res))))))))
    

    或者,如果您希望/需要避免appendsort,您可以拥有自己的merge程序,如下所示:

    (define (merge lst1 lst2 cmp key)
      (let loop ((lst1 lst1) (lst2 lst2) (res null))
        (cond 
          ((and (null? lst1) (null? lst2))
           (reverse res))
          ((and (not (null? lst1)) 
                (or (null? lst2) 
                    (cmp (key lst1) (key lst2))))
           (loop (cdr lst1) lst2 (cons (car lst1) res)))
          (else                            
           (loop lst1 (cdr lst2) (cons (car lst2) res))))))
    

    并用

    替换union过程的第二行
      (let loop ([lst (merge set1 set2 < caar)] [res null])
    

    希望这有帮助。

答案 2 :(得分:1)

这是一个非常简单的问题。每个范围规范都包含间隔列表,按其car值排序(此外,任何元素的cadr都小于以下元素的car)。

您只需从空范围规格开始,即一个空范围的列表:( () )

然后,在每一步中,从一个具有最小car值的列表中取一个头元素(两个中,因为列表是有序的,记得吗?),并将其添加到范围规范中。当两个列表都用完(空)时,你就完成了。

更具体地,以相反的顺序保持所得范围规范将是有益的,分别保持最近的范围。如果传入范围(来自两个头中的一个)与它合并,则更新最后一个范围。如果没有,则将最后一个范围推入反转列表,并将输入头保持为新的最后一个范围。

完成同样微不足道。如果您将两个列表保存在一个列表(list s1 s2)中并编写一个特殊函数get-input来获取此列表并将其结果作为列表生成next-range,那么处理输入也会更容易。并且下一个two-lists(或()表示两个列表已用完)。

您知道,它是迭代的一般模式的一个实例(通常使用 named-let 或其他尾部递归函数在Scheme中编码,但是有还有一个显式的 do 构造),逐个处理输入列表的元素,将它们与累加器组合在一起。保持累加器的前一部分反转也是一种模式,即。一个拉链


一个例子(你的第二个):

s1 = ((1 3) (5 13) (25 110) (199 300))
s2 = ((2 4) (17 26) (97 100) (110 200) (288 500)) 

list-of-ranges   last-range          two-input-lists
  ()               ()          ( ((1 3) ...)       ((2 4) ...)     )
  ()              (1 3)        ( ((5 13) ...)      ((2 4) ...)     )
  ()              (1 4)        ( ((5 13) ...)      ((17 26) ...)   )
 ((1 4))          (5 13)       ( ((25 110) ...)    ((17 26) ...)   )
((5 13)(1 4))     (17 26)      ( ((25 110) ...)    ((97 100) ...)  )
((5 13)(1 4))     (17 110)     ( ((199 300))       ((97 100) ...)  )
((5 13)(1 4))     (17 110)     ( ((199 300))       ((110 200) ...) )
((5 13)(1 4))     (17 200)     ( ((199 300))       ((288 500))     )
((5 13)(1 4))     (17 300)     ( ()                ((288 500))     )
((5 13)(1 4))     (17 500)     ( ()                ()              )
STOP

答案 3 :(得分:0)

假设没有srfi的r5rs方案,我建议用一个内部递归累加器函数创建一个函数,该函数最初给出s1,s2,intvl-acc ='(),set-acc ='(),最后两个持有临时区间联合和累积联盟列表。

应遵循以下条件: (我会写伪代码,分为三种情况。)

[基本情况]

(and (null? s1) (null? s2))
  (if (null? intvl-acc)
      reverse(set-acc)
      (union-acc '() '() '() (cons intvl-acc set-acc)))

[累积案例]

(not (null? intvl-acc))
  (compare (cadr intvl-acc) with caar of each set)
  (if s1 is smaller, (union-acc (cdr s1) s2 updated-intvl-acc set-acc))
  (if not found, (union-acc s1 s2 '() (cons intvl-acc set-acc)))

[否则:]

else
  ((compare the caar of s1 s2)
   (if s1 is smaller, (union-acc (cdr s1) s2 (car s1) set-acc)

希望它有所帮助。