4

I need to traverse all pairs i,j with 0 <= i < n, 0 <= j < n and i < j for some positive integer n.

Problem is that I can only loop through another variable, say k. I can control the bounds of k. So the problem is to determine two arithmetic methods, f(k) and g(k) such that i=f(k) and j=g(k) traverse all admissible pairs as k traverses its consecutive values.

How can I do this in a simple way?

NPE
  • 438,426
  • 93
  • 887
  • 970
becko
  • 13,246
  • 24
  • 74
  • 144
  • Does the traversal order matter? If it does, please specify the required order. – NPE Sep 30 '14 at 20:02
  • @NPE traversal order doesn't matter, as long as all pairs are traversed exactly once. – becko Sep 30 '14 at 20:02
  • is this what you are looking for `{(f(k), g(k)) | 0 <= f(k) < n, 0 <= g(k) < n, f(k) < g(k) }`? what are the bounds of k? what is f(k) and g(k)? – Logan Murphy Sep 30 '14 at 20:06
  • @LoganMurphy yeah. We want a method `F(k)(=(f(k),g(k)))` that takes `k` and returns a pair `(i,j)`, such that `i < j`, `0<=i – becko Sep 30 '14 at 20:08
  • Interesting question. Something tells me that there's probably an elegant algorithm for this, but I am struggling to come up with one. :-) – NPE Sep 30 '14 at 20:09
  • where is n, f, and g defined for F? – Logan Murphy Sep 30 '14 at 20:14
  • @LoganMurphy `n` is a given positive integer. `f(k)` and `g(k)` are the methods I want, so they will be defined by whoever answers the question! ;) `F(k) = (f(k), g(k))`, is just another notation. – becko Sep 30 '14 at 20:16
  • Would it be fair to assume that you have some (unstated) computational complexity requirements for `f(k)` and `g(k)`? – NPE Sep 30 '14 at 20:20
  • @NPE I haven't thought about it, but I expect it to be efficient. As you mentioned, I have a feeling too that there's an elegant algorithm that's also efficient. – becko Sep 30 '14 at 20:33
  • One can easily brute force this if `O(n)` is acceptable time complexity for `f(k)` and `g(k)`. Otherwise, I have a feeling that there's a clever way to fold the lower triangular matrix such that it becomes easy to traverse (e.g. becomes rectangular). – NPE Sep 30 '14 at 20:35

5 Answers5

3

I think I got it (in Python):

def get_ij(n, k):
  j = k // (n - 1)  # // is integer (truncating) division
  i = k - j * (n - 1)
  if i >= j:
    i = (n - 2) - i
    j = (n - 1) - j
  return i, j

for n in range(2, 6):
  print n, sorted(get_ij(n, k) for k in range(n * (n - 1) / 2))

It basically folds the matrix so that it's (almost) rectangular. By "almost" I mean that there could be some unused entries on the far right of the bottom row.

The following pictures illustrate how the folding works for n=4:

n=4

and n=5:

n=5

Now, iterating over the rectangle is easy, as is mapping from folded coordinates back to coordinates in the original triangular matrix.

Pros: uses simple integer math.

Cons: returns the tuples in a weird order.

NPE
  • 438,426
  • 93
  • 887
  • 970
3

I think I found another way, that gives the pairs in lexicographic order. Note that here i > j instead of i < j.

Basically the algorithm consists of the two expressions:

i = floor((1 + sqrt(1 + 8*k))/2)
j = k - i*(i - 1)/2

that give i,j as functions of k. Here k is a zero-based index.

Pros: Gives the pairs in lexicographic order.

Cons: Relies on floating-point arithmetic.

Rationale:

We want to achieve the mapping in the following table:

k -> (i,j)
0 -> (1,0)
1 -> (2,0)
2 -> (2,1)
3 -> (3,0)
4 -> (3,1)
5 -> (3,2)
....

We start by considering the inverse mapping (i,j) -> k. It isn't hard to realize that:

k = i*(i-1)/2 + j

Since j < i, it follows that the value of k corresponding to all pairs (i,j) with fixed i satisfies:

i*(i-1)/2 <= k < i*(i+1)/2

Therefore, given k, i=f(k) returns the largest integer i such that i*(i-1)/2 <= k. After some algebra:

i = f(k) = floor((1 + sqrt(1 + 8*k))/2)

After we have found the value i, j is trivially given by

j = k - i*(i-1)/2
becko
  • 13,246
  • 24
  • 74
  • 144
  • It would be nice to see proof/derivation/illustration to understand how it works. The second line seems intuitively clear, but the first is a bit of a mystery (at least to my tired sleepy brain). – NPE Sep 30 '14 at 21:41
  • @NPE I'll write something up. I'm just trying to get it clear in my head first. – becko Sep 30 '14 at 21:42
  • I've briefly tested this and it does seem to work (modulo any potential issues with floating-point math that I haven't tested for or thought through). – NPE Sep 30 '14 at 21:47
  • @NPE Added some explanation. – becko Oct 01 '14 at 18:31
  • It's not really necessary though, as long as you can source an integer `sqrt()` function. – NPE Oct 01 '14 at 19:18
  • 1
    http://stackoverflow.com/questions/1100090/looking-for-an-efficient-integer-square-root-algorithm-for-arm-thumb2 – NPE Oct 01 '14 at 19:19
  • @NPE in the sequence of naturals, the odd count of units between squares increases by two each time (e.g., (4-1),(9-4),(16-9)) so that if we floor the sqrt, we get an increasing count (by two) of repeating integers. Somehow, in this sequence, based on multiples of 8, the repeated count increases by one each time. I doubt I could have found a formula for it though... – גלעד ברקן Oct 01 '14 at 19:42
  • @גלעדברקן: But that would require keeping state across calls, wouldn't it? This seems counter to the whole point of the exercise (if we could keep two integers instead of one, we could iterate over `i` and `j` directly). – NPE Oct 01 '14 at 19:47
  • @NPE I'm not sure if I follow you. If we just took `k` as 1,2,3... and created a function `floor(sqrt(k))` we get repeating integers who's count increases by two each time. With becko's function, the repeating integer count increases by one each time. Where's the state? – גלעד ברקן Oct 01 '14 at 19:49
  • @NPE (ps, my answer might offer a visual illustration of becko's as well) – גלעד ברקן Oct 02 '14 at 09:20
  • wouldn't lexicographic order graph order (for say k<=3) be (0,1), (0,2), (0,3), (1,2), (1,3), (2,3) ? EDIT: No matter if it's called that or not, any idea how I would print thing out in the order I wrote? – Kristaps John Balodis Jul 12 '19 at 01:20
0

I'm not sure to understand exactly the question, but to sum up, if 0 <= i < n, 0 <= j < n , then you want to traverse 0 <= k < n*n

for (int k = 0; k < n*n; k++) {
    int i = k / n;
    int j = k % n;
    // ...
}

[edit] I just saw that i < j ; so, this solution is not optimal since there's less that n*n necessary iterations ...

Joel
  • 2,175
  • 13
  • 24
  • Exactly, if it weren't for the `i < j` restriction, this would be the solution. I suspect that the actual solution can't be too far from this – becko Sep 30 '14 at 20:44
0

If we think of our solution in terms of a number triangle, where k is the sequence

1 
2  3 
4  5  6 
7  8  9  10
11 12 13 14 15
...   

Then j would be our (non zero-based) row number, that is, the greatest integer such that

j * (j - 1) / 2 < k

Solving for j:

j = ceiling ((sqrt (1 + 8 * k) - 1) / 2)

And i would be k's (zero-based) position in the row

i = k - j * (j - 1) / 2 - 1

The bounds for k are:

1 <= k <= n * (n - 1) / 2 
Community
  • 1
  • 1
גלעד ברקן
  • 21,095
  • 3
  • 19
  • 57
0

Is it important that you actually have two arithmetic functions f(k) and g(k) doing this? Because you could first create a list such as

L = []
for i in range(n-1):
    for j in range(n):
        if j>i:
            L.append((i,j))

This will give you all the pairs you asked for. Your variable k can now just run along the index of the list. For example, if we take n=5,

for x in L:
    print(x)

gives us

(0,1), (0,2), (0,3), (0,4), (1,2), (1,3), (1,4), (2,3), (2,4), (3,4)

Suppose your have 2<=k<5 for example, then

for k in range(2, 5)
    print L[k]

yields

(0,3), (0,4), (1,2)