############################################################################
#
#	File:     lists.icn
#
#	Subject:  Procedures to manipulate lists
#
#	Author:   Ralph E. Griswold
#
#	Date:     March 5, 2003
#
############################################################################
#
#   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
#
#	lst2str(L)	string from concatenated values in L
#
#	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] ? {
      if pos(0) then return seq
      tab(many(' '))
      while term := tab(bal(',', '[', ']') | 0) do {
         term := numeric(term)			# special interest
         put(seq, term)
         move(1) | break
         tab(many(' '))
         }
      }

   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 lst2str(L)			#: convert list to string
   local str

   str := ""

   every str ||:= !L

   return str

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
