2

I am learning how to apply recursion to arrays.

For example, I usually read arrays itiratively, this way:

void read_array(int *a, int n){
        int i;
        for(i = 0; i < n; ++i)
                scanf("%d", &a[i]);
        return;
}

I would like to read an array recursively. I wrote the following function:

void read_array(int *a, int n){
        int i = n - 1;
        if (n < 0)
                return;
        else{
                if(scanf("%d", &a[n - 1 - i]) == 1){
                        read_array(a, n - 1);
                        return;
                }
        }
}

It compiles, but when running it trows a segmentation fault error. It confuses me since the function contemplates a base case 0 that should stop it.

Worice
  • 3,847
  • 3
  • 28
  • 49
  • First of all, what is the initial value of `n`? Is it the max index or the number of elements in the array? You *have* space allocated for the array? And use a debugger to step through the code line by line while monitoring the variables and their values and the results of all operations. – Some programmer dude Jun 07 '17 at 12:45
  • Why are you using `scanf`to read an array ? `printf` would do the trick without being the most complex fonction usable in C programming. Or did I misunderstood what you mean by "reading" ? – Badda Jun 07 '17 at 12:47
  • The iterative function works for any `n` provided as input, and stores the inputs in the related array pointed by `*a`, which is of adequate size. In the main it is declared as big as `n`: es. `int a1[DIM]`; `read_array(a1, DIM)`; – Worice Jun 07 '17 at 12:49
  • @Badda my intention is to read an input from standard input and store it in an array. – Worice Jun 07 '17 at 12:51
  • "I would like to read an array recursively". Why? There is absolutely no benefit from this, it will only slow down your program, eat memory and open up a great potential for bugs. – Lundin Jun 07 '17 at 12:59
  • @Lundin, didactic purposes. I do not really appreciate recursion, but our professors totally do. The reasons to me remain obscure. – Worice Jun 07 '17 at 13:01
  • @Worice The reason is simple: you eliminate side-effects. Take the `scanf()` out of the code of my answer and it is free of side effects, still iterates over the array. Recursion is an elegant principle for writing *pure* functions, C just isn't the language that's best suited for this programming style. –  Jun 07 '17 at 13:04
  • Add `printf("calling read_array with n=%d, i=%d reading into index %d\n", n, i, n-1-i);` to see what's happening. – el.pescado - нет войне Jun 07 '17 at 13:04
  • 7
    @Worice The reason for teaching recursion to students, is that when the students later become teachers, they can teach recursion to students. – Lundin Jun 07 '17 at 13:05
  • 1
    Jokes aside, recursion is immensely overrated and does not have much of a place in real-world programming. It is mostly a failed attempt to translate math and algorithm theory into something practical. The only real-world use is extremely narrow, for example when implementing a binary search tree and you want to optimize the memory occupied by the tree, at expensive of execution speed and peak stack usage. 99% of people who think they found a use for recursion are wrong. Mostly it is a big waste of time: execution time, student time, code reader's time. – Lundin Jun 07 '17 at 13:07
  • @Lundin there are often good reasons to avoid *mutable state* and recursion can be a way to achieve this. But there are often better ways as well. –  Jun 07 '17 at 13:11
  • @Worice Show a minimal compiled program that demonstrates the problem. – Vlad from Moscow Jun 07 '17 at 13:12
  • @Lundin for example, you wouldn't use recursion just to replace an iteration with a mutable counter. Instead, you'd have a [*map*](https://en.wikipedia.org/wiki/Map_(higher-order_function))-like function that takes another function to transform each element of a sequence. Still it makes sense for a student to understand recursion. –  Jun 07 '17 at 13:23
  • @Lundin "in real-world programming" => "in Turing Machine program". Please don't insult Lambda Calculus. – Stargateur Jun 07 '17 at 13:24
  • I am learning a lot from your discussion, guys. I would like to thank you all for your participation. – Worice Jun 07 '17 at 13:32
  • @Stargateur Recursion, in real-world computing, revolves around storing the return address to the previously executed "node" on the stack. If the algorithm or data container is able to store the return address through other means, for example by adding a "previous" pointer to every node of a BST, recursive functions (the programming language definition of such a function) aren't necessary. – Lundin Jun 07 '17 at 13:38
  • The problem arises when the algorithm theory people enter the real world, yelling "recursion" - what they mean then is an abstract function, able to repeatedly calling itself until some condition has been sated. This does not necessarily have to be implemented through a recursive function, as defined in programming languages. Failing to understand this difference between the abstract concept of recursion and the programming concept of recursive functions as a language feature, results in crap programs. – Lundin Jun 07 '17 at 13:39
  • 2
    @Lundin Sorry, but this is your opinion and SO is not here to debate about that. Recursion is a concept, whatever you want, it can be use for good purpose or bad. But you can't say that this concept is bad, this is your opinion. – Stargateur Jun 07 '17 at 13:45

5 Answers5

7

Your calculation of the array index is wrong. This line:

            if(scanf("%d", &a[n - 1 - i]) == 1){

assumes the initial value of n, but at the same time, you decrease n with every recursion step. That being said, it shouldn't crash but just repeatedly write the first element of a, because with i = n - 1, n - 1 - i is always zero.

The idiomatic way to write such a recursion would be to recurse on i:

void read_array(int *a, int n, int i)
{
    if (i < n)
    {
        if(scanf("%d", &a[i]) == 1)
        {
            read_array(a, n, i+1);
        }
    }
}

and call it with the initial value for i, e.g. read_array(a, 10, 0) for reading a 10-element array.

In practice, recursion in C is to be avoided.*

* Functional languages can typically optimize recursion, C just uses the call stack with a lot of overhead.


In this example, the theoretical purpose of recursion for writing a pure function is somewhat defeated with a function returning void. If this is just about learning the principle, the functions actually should return something. You could for example create a functional "list builder":

#include <stdio.h>
#include <stdlib.h>

// place the side effect in a separate function
int getValue(void)
{
    // could have `scanf()` here:
    return rand();
}

typedef struct List
{
    int a[10];
    size_t length;
} List;

// non-functional helper to get around limitations of C:
// (if it could initialize result directly with the new values, it would
// be functional)
List listAppend(List list, int val)
{
    List result = list;
    result.a[result.length++] = val;
    return result;
}

// recursive function without side effects:
List buildList(List list, int (*value)())
{
    if (list.length >= 10) return list;
    return buildList(listAppend(list, value()), value);
}

int main(void)
{
    List myList = buildList((List){0}, &getValue);

    for (size_t i = 0; i < myList.length; ++i)
    {
        printf("myList.a[%zu] is %d\n", i, myList.a[i]);
    }
}
  • Thanks Felix for such an elegant solution and for the details you provided me. I do not really appreciate recursion, but our professors totally do. The reasons to me remain obscure. Anyway, `int i` should assume the same value of `0`, right? Furthermore, is it possible that your code shows a `}` more than necessary? – Worice Jun 07 '17 at 13:08
  • copy&paste bummer from eliminating your `else`, I'll reformat this. And yes, knowing about recursion is important, but I'd still say don't do it in C. –  Jun 07 '17 at 13:09
  • @FelixPalmen C can optimize some (if not all) cases of tail recursion, jumping to the beginning of the function instead of growing the stack. – ArturFH Jun 07 '17 at 13:50
  • @ArturR.Czechowski that's nice. I just added a "better" recursion and I guess this would be hard to optimize ;) –  Jun 07 '17 at 14:03
  • @Worice here's some update for you to see something closer to a pure functional recursion. –  Jun 07 '17 at 14:06
  • I am reading it just now. Thanks Felix, it is helping me having a better comprehension of the recursion! In C programming, it is the most hard concept for me. With F#, just to say, I use it regularly. It work really well. – Worice Jun 07 '17 at 14:14
  • That's because C really isn't a functional language. But if a language provides functions and a way to pass a function to another function (like C function pointers), you *can* do functional programming. Note it's also a lot easier in C# using *lambda expressions*. –  Jun 07 '17 at 14:22
3

There is a bug in the function.

As the variable i is initialized the following way

int i = n - 1;

then the second argument in this call

scanf("%d", &a[n - 1 - i])

is evaluated like

scanf("%d", &a[n - 1 - (n - 1)])

that is it is always equal to zero

scanf("%d", &a[0])

As the recursive function is called with the same value of the pointer a then all entered values are assigned to a[0]. All other elements of the array are still uninitialized.

Though this does not serve as a reason for the abnormal execution of the function.

It is possible that there is used a big array and the stack is too small to call the function recursively.

In any case the function can be defined more simply and correctly the following way

size_t read_array( int *a, size_t n )
{
    return n && scanf( "%d", a ) == 1 ? 1 + read_array( a + 1, n - 1 ) : 0;
}   

Take into account as the input can be interrupted by the user. In this case the function returns the number of initialized elements of the array.

Here is a demonstrative program.

#include <stdio.h>

size_t read_array( int *a, size_t n )
{
    return n && scanf( "%d", a ) == 1 ? 1 + read_array( a + 1, n - 1 ) : 0;
}   

#define N   10

int main(void) 
{
    int a[N];

    size_t n = read_array( a, N );

    for ( size_t i = 0; i < n; i++ ) printf( "%d ", a[i] );
    putchar( '\n' );

    return 0;
}

If to enter sequence of numbers

0 1 2 3 4 5 6 7 8 9

then the output will be

0 1 2 3 4 5 6 7 8 9
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Note: that this version is not tail recursive, `1 + read_array( a + 1, n - 1 )` here you need to wait the return of the function. – Stargateur Jun 07 '17 at 13:36
  • @Stargateur Why do you have decided so? It entirely depends on the compiler. – Vlad from Moscow Jun 07 '17 at 13:37
  • No tail recursion has nothing to do with compiler: https://stackoverflow.com/questions/310974/what-is-tail-call-optimization. – Stargateur Jun 07 '17 at 13:42
  • @Stargateur You are wrong. It directly depends on compiler. For example compilers can not support the tail recursion. – Vlad from Moscow Jun 07 '17 at 13:47
  • No your are talking about the optimization, tail recursion is just a fact, this function is tail recursive or not, point. Some compiler could make a non tail recursive function into a tail recursive and after optimize the call. But this is no the point. Your function is not tail recursive, this is not bad, just a fact. This link is better https://stackoverflow.com/questions/33923/what-is-tail-recursion. – Stargateur Jun 07 '17 at 13:49
  • gcc can optimize tail-calls. [See this link](https://stackoverflow.com/questions/15897452/tail-recursion-in-gcc-g). – ad absurdum Jun 07 '17 at 13:54
  • @Stargateur The fact that a function is defined a shaving tail recursion does not help the MS compiler to implement the tail recursion.:) – Vlad from Moscow Jun 07 '17 at 14:02
  • @VladfromMoscow Yes, of course, MS is always here to be annoying. Unless, you are using a language that guaranty tail recursion optimization, nobody should use recursion whatever this is tail or not. But with tail recursive, a compiler has a chance to optimize it. You could do [this](https://ideone.com/MajFvW) and your function will be tail so it has better chance to be optimize. – Stargateur Jun 07 '17 at 14:13
  • @Stargateur It is an interesting modification of the function implementation.:) – Vlad from Moscow Jun 07 '17 at 14:16
1

Example:

int read_array_aux(int *i, int *n) {
  if (i == n) {
    return 0;
  }
  if (scanf("%d", i) != 1) {
    return -1;
  }
  return read_array_aux(i + 1, n);
}

int read_array_aux2(int *a, size_t i, size_t n) {
  if (i == n) {
    return 0;
  }
  if (scanf("%d", a + i) != 1) {
    return -1;
  }
  return read_array_aux2(a, i + 1, n);
}

int read_array(int *a, size_t n) {
  return read_array_aux(a, a + n);
  // return read_array_aux2(a, 0, n);
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
1

First, condition n<0 is wrong. Probably this is the cause of segfault.

Also, why even bother about calculating the index? When processing any kind of list recursively it's worth to grasp the concept of head (first element of list) and tail (everything except head) of the list. So, filling an array recursively would be defined as (in pseudo code):

void read_array() {
   read_head();
   read_tail();
}

What is head? It's the first element of current array. What's the tail? The array starting from next element. So, read_tail is equivalent of read_array, but with the beginning moved forward by one element.

And, finally, to gather everything into one place:

void read_array(int *a, int n) {
   if(n<=0) {
       return;
   } else {
       if(scanf("%d", a) == 1) {
           read_array(a+1,n-1);
       }
   }
}
ArturFH
  • 1,697
  • 15
  • 28
  • Very "Fsharpish" exaplanation! Thanks! – Worice Jun 07 '17 at 13:30
  • Very "Fsharpish" exaplanation! What do you think are the major differences from the suggestions proposed by Felix? – Worice Jun 07 '17 at 13:39
  • Felix solution is generally better, as it is easily modifiable for different languages, but it requires passing three parameters instead of two: array, index and size. My proposal is suited for C, as it uses its pointer arithmetic and in result requires to pass only two parameters: remainder of array and size of this remainder of array. Choose whatever suits you better. – ArturFH Jun 07 '17 at 13:43
1

As other answers have mentioned, your handling of n is leading to problems. You can return 0 from the base case of sz == 0, otherwise return the result of the next recursive call, or -1 if scanf() fails. At each recursive call, increment a and decrement sz. The value returned in the calling function should be checked for input errors: 0 on success, -1 on failure.

Note that this is a tail recursion, which should be optimized by most good compilers.

#include <stdio.h>

int read_array(int *a, size_t sz);

int main(void)
{
    int arr[5];
    puts("Enter array elements:");
    if (read_array(arr, 5) != 0) {
        fprintf(stderr, "Input error\n");
    } else {
        for (size_t i = 0; i < 5; i++) {
            printf("%8d", arr[i]);
        }
        putchar('\n');
    }

    return 0;
}

int read_array(int *a, size_t sz)
{
    if (sz == 0 ) {
        return 0;
    }
    if (scanf("%d", a) == 1){
        return read_array(a + 1, sz - 1);
    } else {
        return -1;
    }
}

Sample interaction:

Enter array elements:
1 2 3 4 5
       1       2       3       4       5

Enter array elements:
1 2 3 x 5
Input error
ad absurdum
  • 19,498
  • 5
  • 37
  • 60