0

I have 2 lists. They will always be the same length with respect to each other and might look like this toy example. The actual content is not predictable.

val original = [1,   2,  0,  1,  1,  2]
val elements = ["a","b","c","d","e","f"]

I want to create the following list:

val mappedList = [["c"],["a","d","e"],["b","f"]]
                    0         1           2

So the pattern is to group elements in the elements list, based on the value of the same-position element in original list. Any idea how can I achieve this in SML? I am not looking for a hard coded solution for this exact data, but a general one.

potato
  • 4,479
  • 7
  • 42
  • 99
  • What does not work as expected? – ceving Nov 23 '16 at 15:05
  • The process of creating a smart approach. – potato Nov 23 '16 at 15:07
  • This looks much like [Partition a list into equivalence classes](http://stackoverflow.com/questions/40577169/partition-a-list-into-equivalence-classes) answered ten days ago. Call `ListPair.zip` on your two lists and group pairs by an equivalence function that only regards the number. You may also get inspired by Haskell's [`group`](https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-List.html#v:group) / [`groupBy`](https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-List.html#v:groupBy), although the source code might be a little hard to read. – sshine Nov 23 '16 at 15:52

2 Answers2

2

One way is to first write a function which takes an ordered pair such as (2,"c") and a list of ordered pairs such as

[(3,["a"]),(2,["b"]),(1,["a","e"])]

and returns a modified list with the element tacked onto the appropriate list (or creates a new (key,list) pair if none exists) so that the result would look like:

[(3,["a"]),(2,["c","b"]),(1,["a","e"])]

The following function does the trick:

fun store ((k,v), []) = [(k,[v])]
|   store ((k,v), (m,vs)::items) = if k = m 
        then (m,v::vs)::items 
        else (m,vs)::store ((k,v) ,items);

Given a list of keys and a corresponding list of values, you could fold this last function over the corresponding zip of the keys and values:

fun group ks vs = foldl store [] (ListPair.zip(ks,vs));

For example, if

val original = [1,   2,  0,  1,  1,  2];
val elements = ["a","b","c","d","e","f"];

- group original elements;
val it = [(1,["e","d","a"]),(2,["f","b"]),(0,["c"])] : (int * string list) list

Note that you could sort this list according to the keys if so desired.

Finally -- if you just want the groups (reversed to match their original order in the list) the following works:

fun groups ks vs = map rev (#2 (ListPair.unzip (group ks vs)));

For example,

- groups original elements;
val it = [["a","d","e"],["b","f"],["c"]] : string list list

On Edit: if you want the final answer to be sorted according to the keys (as opposed to the order in which they appear) you could use @SimonShine 's idea and store the data in sorted order, or you could sort the output of the group function. Somewhat oddly, the SML Standard Basis Library lacks a built-in sort, but the standard implementations have their own sorts (and it is easy enough to write your own). For example, using SML/NJ's sort you could write:

fun sortedGroups ks vs =
    let 
        val g = group ks vs
        val s = ListMergeSort.sort (fn ((i,_),(j,_)) => i>j) g
    in
        map rev (#2 (ListPair.unzip s))
    end;

Leading to the expected:

- sortedGroups original elements;
val it = [["c"],["a","d","e"],["b","f"]] : string list list
Community
  • 1
  • 1
John Coleman
  • 51,337
  • 7
  • 54
  • 119
  • 1
    It seems that keys are assumed to be integers, rather than `''a` and that they should be ordered from low to high, from the example at least. – sshine Nov 24 '16 at 07:38
  • 1
    @SimonShine Good point. Since your answer adequately addresses the sorting issue and OP might need information about the order of appearance, I'll leave my basic answer the same, although you inspired me to say a bit more about how to sort the output of `group`. – John Coleman Nov 24 '16 at 14:41
  • Right, I thought about sorting it in the end, but this applies better to the code you've already given. :) – sshine Nov 24 '16 at 15:27
2

With the general strategy to first form a list of pairs (k, vs) where k is the value they are grouped by and vs is the elements, one could then extract the elements alone. Since John did this, I'll add two other things you can do:

  1. Assume that original : int list, insert the pairs in sorted order:

    fun group ks vs =
        let fun insert ((k, v), []) = [(k, [v])]
              | insert (k1v as (k1, v), items as ((k2vs as (k2, vs))::rest)) =
                  case Int.compare (k1, k2) of
                       LESS => (k1, [v]) :: items
                     | EQUAL => (k2, v::vs) :: rest
                     | GREATER => k2vs :: insert (k1v, rest)
            fun extract (k, vs) = rev vs
        in
          map extract (List.foldl insert [] (ListPair.zip (ks, vs)))
        end
    

    This produces the same result as your example:

    - val mappedList = group original elements;
    > val mappedList = [["c"], ["a", "d", "e"], ["b", "f"]] : string list list
    

    I'm a bit unsure if by "The actual content is not predictable." you also mean "The types of original and elements are not known." So:

    Assume that original : 'a list and that some cmp : 'a * 'a -> order exists:

    fun group cmp ks vs =
        let fun insert ((k, v), []) = [(k, [v])]
              | insert (k1v as (k1, v), items as ((k2vs as (k2, vs))::rest)) =
                  case cmp (k1, k2) of
                       LESS => (k1, [v]) :: items
                     | EQUAL => (k2, v::vs) :: rest
                     | GREATER => k2vs :: insert (k1v, rest)
            fun extract (k, vs) = rev vs
        in
          map extract (List.foldl insert [] (ListPair.zip (ks, vs)))
        end
    
  2. Use a tree for storing pairs:

    datatype 'a bintree = Empty | Node of 'a bintree * 'a * 'a bintree
    
    (* post-order tree folding *)
    fun fold f e Empty = e
      | fold f e0 (Node (left, x, right)) =
        let val e1 = fold f e0 right
            val e2 = f (x, e1)
            val e3 = fold f e2 left
        in e3 end
    
    fun group cmp ks vs =
        let fun insert ((k, v), Empty) = Node (Empty, (k, [v]), Empty)
              | insert (k1v as (k1, v), Node (left, k2vs as (k2, vs), right)) =
                  case cmp (k1, k2) of
                       LESS => Node (insert (k1v, left), k2vs, right)
                     | EQUAL => Node (left, (k2, v::vs), right)
                     | GREATER => Node (left, k2vs, insert (k1v, right))
            fun extract ((k, vs), result) = rev vs :: result
        in
          fold extract [] (List.foldl insert Empty (ListPair.zip (ks, vs)))
        end
    
sshine
  • 15,635
  • 1
  • 41
  • 66