1

I need to do a nested iteration over the same list twice, but the second loop should start where the first one is currently at.

Here's an example using a for loop:

List l, tuple_l;

for (int x = 0; x < l.length; x++) {
    for (int y = x; y < l.length; y++) {
        tuple_l.push((l[x], l[y]))
    }
}

And here's an example using Python's list comprehension:

[(x, y) for i, x in enumerate(l) for y in l[i:]]

I know how to do this iteration in Haskell iterating over the entire array twice...:

[(x, y) | x <- l, y <- l]

...but I'm not sure how I would go to start the second iteration on what comes after x. Ideally, it would be able to do something like this:

[(x, y) | (x:xs) <- l, y <- xs]  -- Doesn't work 

Obs.: I'm aware a similar question was already answered: Haskell version of double loop

However, that approach only works for integer ranges. I guess I could somehow use that approach and access the list items by index, but that's not very haskell/functional-like.

João Haas
  • 1,883
  • 13
  • 13
  • Not the same, but related: [Iterate over all pair combinations without repetition in Haskell](https://stackoverflow.com/questions/28191103/iterate-over-all-pair-combinations-without-repetition-in-haskell). – chi Sep 08 '22 at 16:02
  • @chi I think that's pretty much the same, guess I was just using different terms while searching (nested loops, double loops, etc). I'll mark this post as a duplicate – João Haas Sep 08 '22 at 16:09

3 Answers3

3

You can exploit tails as follows:

[(x, y) | (x:xs) <- tails l, y <- x:xs, ...]

tails generates all the possible suffixes, e.g. tails [1,2,3] == [[1,2,3], [2,3], [3], []]. Extracting (x:xs) <- tails l we therefore get each element x and the list of its next elements xs. The empty tail [] is silently discarded by the list comprehension. Extracting y <- x:xs completes the task.

Thanks to laziness, iterating over tails l has the same efficiency of iterating over l directly. Indeed, no new list is allocated, only pointers to the already existing l are used.

chi
  • 111,837
  • 3
  • 133
  • 218
  • 3
    Minor nitpick: it needs to be `y <- x : xs` to get the same behavior as the examples in the question. – Noughtmare Sep 08 '22 at 15:51
  • @Noughtmare Indeed, I missed that! Fixed. – chi Sep 08 '22 at 15:52
  • Ah, `tails` was exactly what I was looking for! I think I'll use `[(head xs, y) | xs <- tails l, y <- xs]` instead because the syntax looks a bit nicer. – João Haas Sep 08 '22 at 15:55
  • 3
    I would stay away from `head`. Instead you can use an `@`-pattern: `[(x, y) | xs@(x:_) <- tails, y <- xs]`. – Noughtmare Sep 08 '22 at 15:57
  • Huh, is there any issues with `head`? I would imagine it would result in the same operations done in `(x:xs)`. – João Haas Sep 08 '22 at 15:58
  • 1
    @JoãoHaas Note that using `head xs` can crash the program when `xs` is empty. In this case it is safe since `y` is in `xs`, so it is not empty. In general, though, using `head` and `tail` (unlike `tails`) should be avoided in favor of pattern matching, when possible. Note that `(x:xs)<-...` in list comprehensions will never crash, but will silently ignore the empty tail. – chi Sep 08 '22 at 15:59
  • I see. I started learning Haskell recently, so I still don't know all edge cases. That's a good one to remember. – João Haas Sep 08 '22 at 16:02
  • Really the most [total](https://wiki.haskell.org/Partial_functions) solution would be to use [`tails` from Data.List.NonEmpty](https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-List-NonEmpty.html#v:tails): `[(x,y) | x :| xs <- NE.tails l, y <- x : xs, ...]`. – Noughtmare Sep 08 '22 at 16:04
2

The straightforward translation of your python code is as follows:

[(x, y) | (i, x) <- zip [0..] l, y <- drop i l, ...]

However, in Python l[i:] is a fast operation (O(1)), but in Haskell drop i l is slow (O(i)).

As you already seem to try it is better to try to get the tail of the list at the point where the x element is. In Haskell you can do that in several ways. Perhaps the easiest is using the tails function:

[(x, y) | (x,xs) <- zip l (tails l), y <- xs, ...]
Noughtmare
  • 9,410
  • 1
  • 12
  • 38
  • Yeah, while searching for the response I did see `drop` was an option, but I imagined it wouldn't be as efficient. The `zip` response is nice, although I think I'll just use `head xs` instead of `x` and just use `tails l` – João Haas Sep 08 '22 at 15:57
0

You can do it with a layer of explicit recursion like this:

f xs = case xs of
  [] -> []
  h:t -> [(h, x) | x <- xs] ++ f t

If you had done int y = x + 1; instead of int y = x; in your C code, then you'd use t instead of xs in the list comprehension in my Haskell code.