home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.cs.arizona.edu
/
ftp.cs.arizona.edu.tar
/
ftp.cs.arizona.edu
/
icon
/
historic
/
v941.tgz
/
icon.v941src.tar
/
icon.v941src
/
ipl
/
procs
/
lists.icn
< prev
next >
Wrap
Text File
|
2002-01-24
|
29KB
|
1,340 lines
############################################################################
#
# File: lists.icn
#
# Subject: Procedures to manipulate lists
#
# Author: Ralph E. Griswold
#
# Date: October 22, 2001
#
############################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Contributor: Richard L. Goerwitz
#
############################################################################
#
# file2lst(s) create list from lines in file
#
# imag2lst(s) convert limage() output to list
#
# l_Bscan(e1) begin list scanning
#
# l_Escan(l_OuterEnvir, e2)
# end list scanning
#
# l_any(l1,l2,i,j)
# any() for list scanning
#
# l_bal(l1,l2,l3,l,i,j
# bal() for list scanning
#
# l_find(l1,l2,i,j)
# find() for list scanning
#
# l_many(l1,l2,i,j)
# many() for list scanning
#
# l_match(l1,l2,i,j)
# match() for list scanning
#
# l_move(i) move() for list scanning
#
# l_pos(i) pos() for list scanning
#
# l_tab(i) tab() for list scanning
#
# l_upto(l1,l2,i,j)
# upto() for list scanning
#
# lclose(L) close open palindrome
#
# lcomb(L,i) list combinations
#
# lcompact(L) compact list, mapping out missing values
#
# ldecollate(I, L)
# list decollation
#
# ldelete(L, spec)
# list deletion
#
# ldupl(L, i) list term duplication
#
# lequiv(L1, L2) list equivalence
#
# levate(L, m, n) list elevation
#
# lextend(L, i) list extension
#
# lfliph(L) list horizontal flip (reversal)
#
# lflipv(L) list vertical flip
#
# limage(L) unadorned list image
#
# lindex(L, x)
# generate indices of L whose values are x
#
# lcollate(L1, L2, ...)
# list collation; like linterl() except stops on
# short list
#
# lconstant(L) succeeds and returns element if all are the same
#
# linterl(L1, L2) list interleaving
#
# llayer(L1, L2, ...)
# layer and interleave L1, L2, ...
#
# llpad(L, i, x) list padding at left
#
# lltrim(L, S) list left trimming
#
# lmap(L1,L2,L3) list mapping
#
# lpalin(L, x) list palindrome
#
# lpermute(L) list permutations
#
# lreflect(L, i) returns L concatenated with its reversal to produce
# palindrome; the values of i determine "end
# conditions" for the reversal:
#
# 0 omit first and last elements; default
# 1 omit first element
# 2 omit last element
# 3 don't omit element
#
# lremvals(L, x1, x2, ...)
# remove values from list
#
# lrepl(L, i) list replication
#
# lresidue(L, m, i)
# list residue
#
# lreverse(L) list reverse
#
# lrotate(L, i) list rotation
#
# lrpad(L, i, x) list right padding
#
# lrundown(L1, L2, L3)
# list run down
#
# lrunup(L1, L2, L3)
# list run up
#
# lrtrim(L, S) list right trimming
#
# lshift(L, i) shift list terms
#
# lswap(L) list element swap
#
# lunique(L) keep only unique list elements
#
# lmaxlen(L, p) returns the size of the largest value in L.
# If p is given, it is applied to each string as
# as a "length" procedure. The default for p is
# proc("*", 1).
#
# lminlen(L, p) returns the size of the smallest value in L.
# If p is given, it is applied to each string as
# as a "length" procedure. The default for p is
# proc("*", 1).
#
# sortkeys(L) returns list of keys from L, where L is the
# result of sorting a table with option 3 or 4.
#
# sortvalues(L) return list of values from L, where L is the
# result of sorting a table with option 3 or 4.
#
# str2lst(s, i) creates list with i-character lines from s. The
# default for i is 1.
#
############################################################################
#
# About List Mapping
#
# The procedure lmap(L1,L2,L3) maps elements of L1 according to L2
# and L3. This procedure is the analog for lists of the built-in
# string-mapping function map(s1,s2,s3). Elements in L1 that are
# the same as elements in L2 are mapped into the corresponding ele-
# ments of L3. For example, given the lists
#
# L1 := [1,2,3,4]
# L2 := [4,3,2,1]
# L3 := ["a","b","c","d"]
#
# then
#
# lmap(L1,L2,L3)
#
# produces a new list
#
# ["d","c","b","a"]
#
# Lists that are mapped can have any kinds of elements. The
# operation
#
# x === y
#
# is used to determine if elements x and y are equivalent.
#
# All cases in lmap are handled as they are in map, except that
# no defaults are provided for omitted arguments. As with map, lmap
# can be used for transposition as well as substitution.
#
# Warning:
#
# If lmap is called with the same lists L2 and L3 as in
# the immediately preceding call, the same mapping is performed,
# even if the values in L2 and L3 have been changed. This improves
# performance, but it may cause unexpected effects.
#
# This ``caching'' of the mapping table based on L2 and L3
# can be easily removed to avoid this potential problem.
#
############################################################################
#
# About List Scanning by Richard L. Goerwitz
#
# PURPOSE: String scanning is terrific, but often I am forced to
# tokenize and work with lists. So as to make operations on these
# lists as close to corresponding string operations as possible, I've
# implemented a series of list analogues to any(), bal(), find(),
# many(), match(), move(), pos(), tab(), and upto(). Their names are
# just like corresponding string functions, except with a prepended
# "l_" (e.g. l_any()). Functionally, the list routines parallel the
# string ones closely, except that in place of strings, l_find and
# l_match accept lists as their first argument. L_any(), l_many(),
# and l_upto() all take either sets of lists or lists of lists (e.g.
# l_tab(l_upto([["a"],["b"],["j","u","n","k"]])). Note that l_bal(),
# unlike the builtin bal(), has no defaults for the first four
# arguments. This just seemed appropriate, given that no precise
# list analogue to &cset, etc. occurs.
#
# The default subject for list scans (analogous to &subject) is
# l_SUBJ. The equivalent of &pos is l_POS. Naturally, these
# variables are both global. They are used pretty much like &subject
# and &pos, except that they are null until a list scanning
# expression has been encountered containing a call to l_Bscan() (on
# which, see below).
#
# Note that environments cannot be maintained quite as elegantly as
# they can be for the builtin string-scanning functions. One must
# use instead a set of nested procedure calls, as explained in the
# _Icon Analyst_ 1:6 (June, 1991), p. 1-2. In particular, one cannot
# suspend, return, or otherwise break out of the nested procedure
# calls. They can only be exited via failure. The names of these
# procedures, at least in this implementation, are l_Escan and
# l_Bscan. Here is one example of how they might be invoked:
#
# suspend l_Escan(l_Bscan(some_list_or_other), {
# l_tab(10 to *l_SUBJ) & {
# if l_any(l1) | l_match(l2) then
# old_l_POS + (l_POS-1)
# }
# })
#
# Note that you cannot do this:
#
# l_Escan(l_Bscan(some_list_or_other), {
# l_tab(10 to *l_SUBJ) & {
# if l_any(l1) | l_match(l2) then
# suspend old_l_POS + (l_POS-1)
# }
# })
#
# Remember, it's no fair to use suspend within the list scanning
# expression. l_Escan must do all the suspending. It is perfectly OK,
# though, to nest well-behaved list scanning expressions. And they can
# be reliably used to generate a series of results as well.
#
############################################################################
#
# Here's another simple example of how one might invoke the l_scan
# routines:
#
# procedure main()
#
# l := ["h","e","l","l","o"," ","t","t","t","h","e","r","e"]
#
# l_Escan(l_Bscan(l), {
# hello_list := l_tab(l_match(["h","e","l","l","o"]))
# every writes(!hello_list)
# write()
#
# # Note the nested list-scanning expressions.
# l_Escan(l_Bscan(l_tab(0)), {
# l_tab(l_many([[" "],["t"]]) - 1)
# every writes(!l_tab(0))
# write()
# })
# })
#
# end
#
# The above program simply writes "hello" and "there" on successive
# lines to the standard output.
#
############################################################################
#
# PITFALLS: In general, note that we are comparing lists here instead
# of strings, so l_find("h", l), for instance, will yield an error
# message (use l_find(["h"], l) instead). The point at which I
# expect this nuance will be most confusing will be in cases where
# one is looking for lists within lists. Suppose we have a list,
#
# l1 := ["junk",[["hello"]," ",["there"]],"!","m","o","r","e","junk"]
#
# and suppose, moreover, that we wish to find the position in l1 at
# which the list
#
# [["hello"]," ",["there"]]
#
# occurs. If, say, we assign [["hello"]," ",["there"]] to the
# variable l2, then our l_find() expression will need to look like
#
# l_find([l2],l1)
#
############################################################################
#
# Extending scanning to lists is really very difficult. What I think
# (at least tonight) is that scanning should never have been
# restricted to strings. It should have been designed to operate on
# all homogenous one-dimensional arrays (vectors, for you LISPers).
# You should be able, in other words, to scan vectors of ints, longs,
# characters - any data type that seems useful. The only question in
# my mind is how to represent vectors as literals. Extending strings
# to lists goes beyond the bounds of scanning per-se. This library is
# therefore something of a stab in the dark.
#
############################################################################
#
# Links: equiv, indices, numbers
#
############################################################################
link equiv
link indices
link numbers
procedure file2lst(s) #: create list from lines in file
local input, result
input := open(s) | fail
result := []
every put(result, !input)
close(input)
return result
end
procedure imag2lst(seqimage) #: convert limage() output to list
local seq, term
seq := []
seqimage[2:-1] ? {
while term := tab(upto(',') | 0) do {
term := numeric(term) # special interest
put(seq, term)
move(1) | break
}
}
return seq
end
global l_POS
global l_SUBJ
record l_ScanEnvir(subject,pos)
procedure l_Bscan(e1) #: begin list scanning
#
# Prototype list scan initializer. Based on code published in
# the _Icon Analyst_ 1:6 (June, 1991), p. 1-2.
#
local l_OuterEnvir
initial {
l_SUBJ := []
l_POS := 1
}
#
# Save outer scanning environment.
#
l_OuterEnvir := l_ScanEnvir(l_SUBJ, l_POS)
#
# Set current scanning environment to subject e1 (arg 1). Pos
# defaults to 1. Suspend the saved environment. Later on, the
# l_Escan procedure will need this in case the scanning expres-
# sion as a whole sends a result back to the outer environment,
# and the outer environment changes l_SUBJ and l_POS.
#
l_SUBJ := e1
l_POS := 1
suspend l_OuterEnvir
#
# Restore the saved environment (plus any changes that might have
# been made to it as noted in the previous run of comments).
#
l_SUBJ := l_OuterEnvir.subject
l_POS := l_OuterEnvir.pos
#
# Signal failure of the scanning expression (we're done producing
# results if we get to here).
#
fail
end
procedure l_Escan(l_OuterEnvir, e2) #: end list scanning
local l_InnerEnvir
#
# Set the inner scanning environment to the values assigned to it
# by l_Bscan. Remember that l_SUBJ and l_POS are global. They
# don't need to be passed as parameters from l_Bscan. What
# l_Bscan() needs to pass on is the l_OuterEnvir record,
# containing the values of l_SUBJ and l_POS before l_Bscan() was
# called. l_Escan receives this "outer environment" as its first
# argument, l_OuterEnvir.
#
l_InnerEnvir := l_ScanEnvir(l_SUBJ, l_POS)
#
# Whatever expression produced e2 has passed us a result. Now we
# restore l_SUBJ and l_POS, and send that result back to the outer
# environment.
#
l_SUBJ := l_OuterEnvir.subject
l_POS := l_OuterEnvir.pos
suspend e2
#
# Okay, we've resumed to (attempt to) produce another result. Re-
# store the inner scanning environment (the one we're using in the
# current scanning expression). Remember? It was saved in l_Inner-
# Envir just above.
#
l_SUBJ := l_InnerEnvir.subject
l_POS := l_InnerEnvir.pos
#
# Fail so that the second argument (the one that produced e2) gets
# resumed. If it fails to produce another result, then the first
# argument is resumed, which is l_Bscan(). If l_Bscan is resumed, it
# will restore the outer environment and fail, causing the entire
# scanning expression to fail.
#
fail
end
procedure l_any(l1,l2,i,j) #: any() for list scanning
#
# Like any(c,s2,i,j) except that the string & cset arguments are
# replaced by list arguments. l1 must be a list of one-element
# lists, while l2 can be any list (l_SUBJ by default).
#
local x, sub_l
/l1 & stop("l_any: Null first argument!")
if type(l1) == "set" then l1 := sort(l1)
/l2 := l_SUBJ
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := \l_POS | 1
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
(i+1) > j & i :=: j
every sub_l := !l1 do {
if not (type(sub_l) == "list", *sub_l = 1) then
stop("l_any: Elements of l1 must be lists of length 1.")
# Let l_match check to see if i+1 is out of range.
if x := l_match(sub_l,l2,i,i+1) then
return x
}
end
procedure l_bal(l1,l2,l3,l,i,j) #: bal() for list scanning
local default_val, l2_count, l3_count, x, position
/l1 & stop("l_bal: Null first argument!")
if type(l1) == "set" then l1 := sort(l1) # convert to a list
if type(l2) == "set" then l1 := sort(l2)
if type(l3) == "set" then l1 := sort(l3)
if /l2 := l_SUBJ
then default_val := \l_POS | 1
else default_val := 1
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := default_val
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
l2_count := l3_count := 0
every x := i to j-1 do {
if l_any(l2, l, x, x+1) then {
l2_count +:= 1
}
if l_any(l3, l, x, x+1) then {
l3_count +:= 1
}
if l2_count = l3_count then {
if l_any(l1,l,x,x+1)
then suspend x
}
}
end
procedure l_comp(l1,l2) # list comparison
#
# List comparison routine basically taken from Griswold & Griswold
# (1st ed.), p. 174.
#
local i
/l1 | /l2 & stop("l_comp: Null argument!")
l1 === l2 & (return l2)
if type(l1) == type(l2) == "list" then {
*l1 ~= *l2 & fail
every i := 1 to *l1
do l_comp(l1[i],l2[i]) | fail
return l2
}
end
procedure l_find(l1,l2,i,j) #: find() for list scanning
#
# Like the builtin find(s1,s2,i,j), but for lists.
#
local x, old_l_POS, default_val
/l1 & stop("l_find: Null first argument!")
if /l2 := l_SUBJ
then default_val := \l_POS | 1
else default_val := 1
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := default_val
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
#
# See l_upto() below for a discussion of why things have to be done
# in this manner.
#
old_l_POS := l_POS
suspend l_Escan(l_Bscan(l2[i:j]), {
l_tab(1 to *l_SUBJ) & {
if l_match(l1) then
old_l_POS + (l_POS-1)
}
})
end
procedure l_many(l1,l2,i,j) #: many() for list scanning
local x, old_l_POS, default_val
/l1 & stop("l_many: Null first argument!")
if type(l1) == "set" then l1 := sort(l1)
if /l2 := l_SUBJ
then default_val := \l_POS | 1
else default_val := 1
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := default_val
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
#
# L_many(), like many(), is not a generator. We can therefore
# save one final result in x, and then later return (rather than
# suspend) that result.
#
old_l_POS := l_POS
l_Escan(l_Bscan(l2[i:j]), {
while l_tab(l_any(l1))
x := old_l_POS + (l_POS-1)
})
#
# Fails if there was no positional change (i.e. l_any() did not
# succeed even once).
#
return old_l_POS ~= x
end
procedure l_match(l1,l2,i,j) #: match() for list scanning
#
# Analogous to match(s1,s2,i,j), except that s1 and s2 are lists,
# and l_match returns the next position in l2 after that portion
# (if any) which is structurally identical to l1. If a match is not
# found, l_match fails.
#
local default_val
if /l1
then stop("l_match: Null first argument!")
if type(l1) ~== "list"
then stop("l_match: Call me with a list as the first arg.")
if /l2 := l_SUBJ
then default_val := \l_POS | 1
else default_val := 1
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := default_val
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
i + *l1 > j & i :=: j
i + *l1 > j & fail
if l_comp(l1,l2[i+:*l1]) then
return i + *l1
end
procedure l_move(i) #: move() for list scanning
/i & stop("l_move: Null argument.")
if /l_POS | /l_SUBJ then
stop("l_move: Call l_Bscan() first.")
#
# Sets l_POS to l_POS+i; suspends that portion of l_SUBJ extending
# from the old l_POS to the new one. Resets l_POS if resumed,
# just the way matching procedures are supposed to. Fails if l_POS
# plus i is larger than l_SUBJ+1 or if l_POS+i is less than 1.
#
suspend l_SUBJ[.l_POS:l_POS <- (0 < (*l_SUBJ+1 >= l_POS+i))]
end
procedure l_pos(i) #: pos() for list scanning
local x
if /l_POS | /l_SUBJ
then stop("l_move: Call l_Bscan() first.")
if i <= 0
then x := 0 < (*l_SUBJ+1 >= (*l_SUBJ+1)+i) | fail
else x := 0 < (*l_SUBJ+1 >= i) | fail
if x = l_POS
then return x
else fail
end
procedure l_tab(i) #: tab() for list scanning
/i & stop("l_tab: Null argument.")
if /l_POS | /l_SUBJ then
stop("l_tab: Call l_Bscan() first.")
if i <= 0
then suspend l_SUBJ[.l_POS:l_POS <- 0 < (*l_SUBJ+1 >= (*l_SUBJ+1)+i)]
else suspend l_SUBJ[.l_POS:l_POS <- 0 < (*l_SUBJ+1 >= i)]
end
procedure l_upto(l1,l2,i,j) #: upto() for list scanning
#
# See l_any() above. This procedure just moves through l2, calling
# l_any() for each member of l2[i:j].
#
local old_l_POS, default_val
/l1 & stop("l_upto: Null first argument!")
if type(l1) == "set" then l1 := sort(l1)
if /l2 := l_SUBJ
then default_val := \l_POS | 1
else default_val := 1
if \i then {
if i < 1 then
i := *l2 + (i+1)
}
else i := default_val
if \j then {
if j < 1 then
j := *l2 + (j+1)
}
else j := *l_SUBJ+1
#
# Save the old pos, then try arb()ing through the list to see if we
# can do an l_any(l1) at any position.
#
old_l_POS := l_POS
suspend l_Escan(l_Bscan(l2[i:j]), {
l_tab(1 to *l_SUBJ) & {
if l_any(l1) then
old_l_POS + (l_POS-1)
}
})
#
# Note that it WILL NOT WORK if you say:
#
# l_Escan(l_Bscan(l2[i:j]), {
# l_tab(1 to *l_SUBJ) & {
# if l_any(l1) then
# suspend old_l_POS + (l_POS-1)
# }
# })
#
# If we are to suspend a result, l_Escan must suspend that result.
# Otherwise scanning environments are not saved and/or restored
# properly.
#
end
procedure lblock(L1, L2)
local L3, i, j
if *L1 < *L2 then L1 := lextend(L1, *L2) | fail
else if *L2 < *L1 then L2 := lextend(L2, *L1) | fail
L3 := []
every i := 1 to *L1 do
every j := 1 to L2[i] do
put(L3, L2[i])
return L3
end
procedure llayer(args[]) #: interleave lists with layering
local offsets, offset, seq, arg, lists, k
lists := []
every put(lists, lcompact(!args))
offsets := []
offset := 0
every arg := !lists do {
put(offsets, offset)
offset +:= max ! arg
}
seq := []
repeat {
every k := 1 to *lists do {
arg := lists[k]
put(seq, get(arg) + offsets[k]) | break break
}
}
return seq
end
procedure lcompact(seq) #: compact sequence
local unique, target
unique := set(seq)
target := []
every put(target, 1 to *unique)
return lmap(seq, sort(unique), target)
end
procedure lclose(L) #: close open palindrome
if equiv(L, lreverse(L)) then return L
else {
L := copy(L)
put(L, L[1])
return L
}
end
procedure lcomb(L,i) #: list combinations
local j
if i < 1 then fail
suspend if i = 1 then [!L]
else [L[j := 1 to *L - i + 1]] ||| lcomb(L[j + 1:0],i - 1)
end
procedure ldecollate(indices, L) #: list decollation
local result, i, x
result := list(max ! indices) # list of lists to return
every !result := [] # initially empty
every x := !L do {
i := get(indices) | fail
put(indices, i)
put(result[i], x)
}
return result
end
procedure ldelete(L, spec) #: delete specified list elements
local i, tmp
tmp := indices(spec, *L) | fail # bad specification
while i := pull(tmp) do
L := L[1+:i - 1] ||| L[i + 1:0]
return L
end
procedure ldupl(L1, L2) #: list term duplication
local L3, i, j
if integer(L2) then L2 := [L2]
L3 := []
every i := !L2 do
every j := !L1 do
every 1 to i do
put(L3, j)
return L3
end
procedure lequiv(x,y) #: compare lists for equivalence
local i
if x === y then return y
if type(x) == type(y) == "list" then {
if *x ~= *y then fail
every i := 1 to *x do
if not lequiv(x[i],y[i]) then fail
return y
}
end
procedure levate(seq, m, n) #: elevate values
local shafts, reseq, i, j, k
shafts := list(m)
every !shafts := []
every i := 1 to m do
every put(shafts[i], i to n by m)
reseq := []
while j := get(seq) do {
i := j % m + 1
k := get(shafts[i])
put(reseq, k)
put(shafts[i], k)
}
return reseq
end
procedure lextend(L, i) #: list extension
local result
if *L = 0 then fail
result := copy(L)
until *result >= i do
result |||:= L
result := result[1+:i]
return result
end
procedure lfliph(L) #: list horizontal flip (reversal)
lfliph := lreverse
return lfliph(L)
end
procedure lflipv(L) #: list vertical flip
local L1, m, i
m := max ! L
L1 := []
every i := !L do
put(L1, residue(-i + 1, m, 1))
return L1
end
procedure limage(L) #: list image
local result
if type(L) ~== "list" then stop("*** invalid type to limage()")
result := ""
every result ||:= image(!L) || ","
return ("[" || result[1:-1] || "]") | "[]"
end
procedure lcollate(args[]) #: generalized list collation
local seq, arg, lists, k
lists := []
every put(lists, copy(!args))
seq := []
repeat {
every k := 1 to *lists do {
arg := lists[k]
put(seq, get(arg)) | break break
}
}
return seq
end
procedure lconstant(L) #: test list for all terms equal
if *set(L) = 1 then return L[1]
else fail
end
procedure lindex(lst, x) #: generate indices for items matching x
local i
every i := 1 to *lst do
if lst[i] === x then suspend i
end
procedure linterl(L1, L2) #: list interleaving
local L3, i
if *L1 < *L2 then L1 := lextend(L1, *L2) | fail
else if *L2 < *L1 then L2 := lextend(L2, *L1) | fail
L3 := []
every i := 1 to *L1 do
put(L3, L1[i], L2[i])
return L3
end
procedure llpad(L, i, x) #: list padding at left
L := copy(L)
while *L < i do push(L, x)
return L
end
procedure lrunup(L1, L2, L3) #: list run up
local L4
/L3 := [1] # could be /L3 := 1 ...
L4 := []
every put(L4, !L1 to !L2 by !L3)
return L4
end
procedure lrundown(L1, L2, L3) #: list run up
local L4
/L3 := [1] # could be /L3 := 1 ...
L4 := []
every put(L4, !L1 to !L2 by -!L3)
return L4
end
procedure lltrim(L, S) #: list left trimming
L := copy(L)
while member(S, L[1]) do
get(L)
return L
end
procedure lmap(L1,L2,L3) #: list mapping
static lmem2, lmem3, lmaptbl, tdefault
local i, a
initial tdefault := []
if type(a := L1 | L2 | L3) ~== "list" then runerr(108,a)
if *L2 ~= *L3 then runerr(208,L2)
L1 := copy(L1)
if not(lmem2 === L2 & lmem3 === L3) then { # if an argument is new, rebuild
lmem2 := L2 # save for future reference
lmem3 := L3
lmaptbl := table(tdefault) # new mapping table
every i := 1 to *L2 do # build the map
lmaptbl[L2[i]] := L3[i]
}
every i := 1 to *L1 do # map the values
L1[i] := (tdefault ~=== lmaptbl[L1[i]])
return L1
end
procedure lresidue(L, m, i) #: list residue
local result
/i := 0
result := []
every put(result, residue(!L, m, i))
return result
end
procedure lpalin(L, x) #: list palindrome
L |||:= lreverse(L)
if /x then pull(L)
return L
end
procedure lpermute(L) #: list permutations
local i
if *L = 0 then return []
suspend [L[i := 1 to *L]] ||| lpermute(L[1:i] ||| L[i+1:0])
end
procedure lreflect(L, i) #: list reflection
local L1
/i := 0
if i > 3 then stop("*** invalid argument to lreflect()")
if i < 3 then L1 := copy(L)
return L ||| lreverse(
case i of {
0: {get(L1); pull(L1); L1}
1: {get(L1); L1}
2: {pull(L1); L1}
3: L
}
)
end
procedure lremvals(L, x[]) #: remove values from list
local result, y
result := []
every y := !L do
if y === !x then next
else put(result, y)
return result
end
procedure lrepl(L, i) #: list replication
local j, k
i := (0 < integer(i)) | stop("*** invalid replication factor in lrepl()")
L := copy(L)
j := *L
every 1 to i - 1 do
every k := 1 to j do
put(L, L[k])
return L
end
procedure lreverse(L) #: list reverse
local i
L := copy(L)
every i := 1 to *L / 2 do
L[i] :=: L[-i]
return L
end
procedure lrotate(L, i) #: list rotation
/i := 1
L := copy(L)
if i > 0 then
every 1 to i do
put(L, get(L))
else
every 1 to -i do
push(L, pull(L))
return L
end
procedure lrpad(L, i, x) #: list right padding
L := copy(L)
while *L < i do put(L, x)
return L
end
procedure lrtrim(L, S) #: list right trimming
L := copy(L)
while member(S, L[-1]) do
pull(L)
return L
end
procedure lshift(L, i) #: shift list terms
L := copy(L)
every !L +:= i
return L
end
procedure lswap(L) #: list element swap
local i
L := copy(L)
every i := 1 to *L by 2 do
L[i] :=: L[i + 1]
return L
end
procedure lunique(L) #: keep only unique list elements
local result, culls, x
result := []
culls := set(L)
every x := !L do
if member(culls, x) then {
delete(culls, x)
put(result, x)
}
return result
end
procedure lmaxlen(L, p) #: size of largest list entry
local i
/p := proc("*", 1)
i := p(L[1]) | fail
every i <:= p(!L)
return i
end
procedure lminlen(L, p) #: size of smallest list entry
local i
/p := proc("*", 1)
i := p(L[1]) | fail
every i >:= p(!L)
return i
end
procedure sortkeys(L) #: extract keys from sorted list
local result
result := []
every put(result, L[1 to *L by 2])
return result
end
procedure sortvalues(L) #: extract values from sorted list
local result
result := []
every put(result, L[2 to *L by 2])
return result
end
procedure str2lst(s, i) #: list from string
local L
/i := 1
L := []
s ? {
while put(L, move(i))
if not pos(0) then put(L, tab(0))
}
return L
end