-4

Suppose you want to write a program that asks the user for X numbers and store them in A, then for Y numbers and store them in B

BEFORE YOU VOTE FOR CLOSING : yes, this question has been asked here, here, here, here and possibly elsewhere, but I reply to each of the proposed solutions in this question explaining why they're not what I'm looking for, so please keep reading before voting for closing IF you decide it's a duplicate. This is a serious question, see last paragraph for a small selection of languages supporting the feature I'm trying to achieve here.

A = []
B = []

# First possibilty : using while loops
# you need to have a counter
i = 0
while (i < X):
    A.append(input())
    # and increment it yourself
    i+=1
# you need to reset it
i = 0
while (i < Y):
    B.append(input())
    # and increment it again
    i+=1

So basically you need to repeat a certain thing x times, then another Y times, but python has only while loops and for loops. The while loop is not to repeat a certain thing N times, but to repeat things while a certain condition is True, that's why you have to use a counter, initialize it, increment it, and test against it.

Second solution is to use for loops :

# No need to create a counter
for x in xrange(x):
    A.append(input())
    # No need to increment the counter

# no need to reset the counter
for x in xrange(Y):
    B.append(input())

This is a lot better :), but there's still something slightly anoying : why would I still have to supply "x", when I don't need x ? In my code, I don't need to know in what loop iteration I am, i just need to repeat things N times, so why do I have to create a counter at all ? Isn't there a way to completely get rid of counters ? imagine you could write something like :

# No need to supply any variable !
repeat(x):
    A.append(input())

repeat(Y):
    B.append(input())

Wouldn't that be more elegant ? wouldn't that reflect more accurately your intention to possible readers of your code ? unfortunately, python isn't flexible enough to allow for creating new language constructs (more advanced languages allow this). So I still have to use either for or while to loop over things. With that restriction in mind, how do I get the most closer possible to the upper code ?

Here's what I tried

def repeat(limit):
    if hasattr(repeat,"counter"):
        repeat.counter += 1
        return repeat.counter < limit
    repeat.limit   = limit
    repeat.counters = 0
    return limit > 0 and True

while repeat(x):
    A.append(input())

while repeat(Y):
    B.append(input())

This solution works for simple loops, but obviously not for nested loops.

My question is

Do you know any other possible implementation that would allow me to get the same interface as repeat ?

Rebbutal of suggested solutions

suggested solution 1.1 : use itertools.repeat

you'll have to place your code inside a function and call that function inside itertools.repeat, somehow. It's different from the above repeat implementation where you can use it over arbitrary indented blocks of code.

suggestion solution 1.2 : use itertools.repeat in a for loop

import itertools

for _ in itertools.repeat(None, N):
    do_something()

you're still writing a for loop, and for loops need a variable (you're providing _), which completely misses the point, look again :

while repeat(5):
    do_x()
    do_y()
    do_z()

Nothing is provided just for the sake of looping. This is what I would like to achieve.

suggested solution 2 : use itertools.count

1) you're half the way there. Yes, you get rid of manually incrementing the counter but you still need for a counter to be created manually. Example :

c = itertools.count(1,1) # You still need this line
while( c.next() < 7):
    a = 1
    b = 4
    d = 59
    print "haha"
    # No need to increment anything, that's good.
    # c.incr() or whatever that would be

You also need to reset the counter to 0, this I think could be hidden inside a function if you use the with statement on it, so that at every end of the with statement the counter gets reset.

suggested solution 3 : use a with statement

I have never used it, but from what I saw, it's used for sweeping the try/catch code inside another function. How can you use this to repeat things ? I'd be excited to learn how to use my first with statement :)

suggested solution 4 : use an iterator/generator, xrange, list comprehensions...

No, no, no...

If you write : [X() for _ in xrange(4)]

you are : 1) creating a list for values you don't care about. 2) supplying a counter (_)

if you write :

for _ in xrange(4):
   X()

see point 2) above

if you write :

def repeat(n):
    i = 0
    while i < n : 
        yield i < n
        i+1

Then how are you supposed to use repeat ? like this ? :

for x in repeat(5):
    X() 

then see point 2) above. Also, this is basically xrange, so why not use xrange instead.

Any other cool ideas ?

Some people asked me what language do I come from, because they're not familiar with this repeat(N) syntax. My main programming language is python, but I'm sure I've seen this syntax in other languages. A visit of my bookmarks followed by online search shows that the following languages have this cool syntax :

Ruby

it's just called times instead of repeat.

#!/usr/bin/env ruby

3.times do
  puts "This will be printed 3 times"
end

print "Enter a number: "
num = gets.chomp.to_i

num.times do
  puts "Ruby is great!"
end

Netlogo

 pd repeat 36 [ fd 1 rt 10 ]
 ;; the turtle draws a circle

AppleScript

[applescript]
repeat 3 times
--commands to repeat
end repeat
[/applescript]

Verilog

  1 module repeat_example();
  2 reg  [3:0] opcode;
  3 reg  [15:0] data;
  4 reg        temp;
  5 
  6 always @ (opcode or data)
  7 begin
  8   if (opcode == 10) begin
  9     // Perform rotate
 10     repeat (8) begin 
 11        #1  temp = data[15];
 12       data = data << 1;
 13       data[0] = temp;   
 14     end 
 15   end
 16 end
 17 // Simple test code
 18 initial begin
 19    $display (" TEMP  DATA");
 20    $monitor (" %b     %b ",temp, data);
 21     #1  data = 18'hF0;
 22     #1  opcode = 10;
 23     #10  opcode = 0;
 24     #1  $finish;
 25 end
 26 
 27 endmodule

Rebol

repeat count 50 [print rejoin ["This is loop #: " count]]

Forth

LABEL 10 0 DO something LOOP will repeat something 10 times, and you didn't have to supply a counter (Forth automatically stores it in a special variable I).

   : TEST   10 0 DO  CR ." Hello "  LOOP ;

Groovy

you can either write 1.upTo(4){ code } or 4.times{ code } to execute code 4 times. Groovy is a blast when it comes to looping, it has like half a dozen ways to do it (upTo, times, step, java for, groovy for, foreach and while).

// Using int.upto(max).
0.upto(4, createResult)
assert '01234' == result

// Using int.times.
5.times(createResult)
assert '01234' == result

// Using int.step(to, increment).
0.step 5, 1, createResult
assert '01234' == result
Community
  • 1
  • 1
ychaouche
  • 4,922
  • 2
  • 44
  • 52
  • 3
    is there any particular reason you are trying to do this? – Padraic Cunningham Jul 30 '14 at 01:46
  • The reason is that it makes perfect sens to me. I am in the exact mindset of @ManuelAroz whome I linked the question at the beginning of this one. – ychaouche Jul 30 '14 at 01:51
  • Your answer lies in other, more elegant languages, not in Python. – metatoaster Jul 30 '14 at 02:01
  • 4
    If you want speed, do it in C, but note that you've already spent more time thinking about it than running it. If you want elegance, do it in a functional language. If you want to do it quickly and practically, you already have your answer. In Python. – cxrodgers Jul 30 '14 at 02:19
  • 5
    -1. I really don't see what value this question has to anyone but you, since you're basically asking "how can I write this code in a way that satisfies my highly subjective and arbitrary definition of 'elegance'" – Marius Jul 30 '14 at 02:29
  • 2
    Without changing python's looping constructs, there is never going to be a reasonable way to do what you want to do, at least not in a way that doesn't wind up being 1) significantly more code, defeating your primary objective and 2) is significantly less clear, again defeating your purpose. Looping in python was designed around the concept of *iteration*. And since one of the core idioms of python is that there should be one and only one obvious way of doing something, they declined to make looping when you don't care about the return from the iterator different from when you do. – aruisdante Jul 30 '14 at 02:44
  • 1
    Use `_` to indicate you don't use the iteration variable, as convention has decided. Your need to "not create a counter" is silly, of course there is always going to be a counter at *some* level. Python syntax just doesn't lend itself to hiding it. – roippi Jul 30 '14 at 02:45
  • @aruisdante, most valuable and thoughtful comment to me so far, thanks. Also, that duplicate question is already linked (see right column under Linked). – ychaouche Jul 30 '14 at 02:51

2 Answers2

3

WARNING: DO NOT DO THIS IN PRODUCTION!

I highly recommend you go with one of the many list comprehension solutions that have been offered, because those are infinitely less hacky. However, if you're adventurous and really, really, really want to do this, read on...

We can adapt your repeat solution slightly so that nesting works, by storing state that changes depending on where it's being called. How do we do that? By inspecting the stack, of course!

import inspect

state = {}

def repeat(limit):
    s = tuple((frame, line) for frame, _, line, _, _, _ in inspect.stack()[1:])
    counter = state.setdefault(s, 0)
    if counter < limit:
        state[s] += 1
        return True
    else:
        del state[s]
        return False

We set up a state dict to keep track of current counters for each unique place the repeat function is called, by keying off of a tuple of (stack_frame, line_number) tuples. This has the same caveat as your original repeat function in that break literally breaks everything, but for the most part seems to work. Example:

while repeat(2):
    print("foo")
    while repeat(3):
        print("bar")

Output:

foo
bar
bar
bar
foo
bar
bar
bar
univerio
  • 19,548
  • 3
  • 66
  • 68
  • 1
    I was looking into doing this, but my metric for "elegance" has basis in correctness and practicality so this is no go as it doesn't handle the possibility of multiple threads accessing the same module. Perhaps some thread locals can be sprinkled in for completeness but seriously don't use this. +1 regardless. – metatoaster Jul 30 '14 at 02:40
  • I had exactly that in mind, I thought to myself there's no way for repeat to know if it's being called from the same while loop or from another loop, except maybe if you supply another optional argument to it (depth for example) and it would check against it. But this interface is bad, why would anyone care to give this second argument, it should be the function's job to figure it out. – ychaouche Jul 30 '14 at 02:40
  • The other possibilty is for function repeat to know where it has been called from, i.e use inspection tools. But I didn't know how to do this and I wasn't sure about the efficiency / elegance of it. – ychaouche Jul 30 '14 at 02:42
  • 2
    @ychaouche no what you are asking for is not elegance, it's syntactic sugar. Either work with the best practices for this language or use some other one or better yet, write your own (or fork Python and muck with the interpreter to give you a `repeat` keyword that does exactly what you want, but probably no one will use your fork). – metatoaster Jul 30 '14 at 02:46
  • Are there really four foos in the output for a `repeat(2)`? – msw Jul 30 '14 at 02:54
  • @msw Oops. Good catch! – univerio Jul 30 '14 at 03:06
2

Surely you are over-thinking things. If I understand your (rather lengthy) question correctly you want to avoid repeating code. In this case you first port of call should be to write a function. This seems relatively simple: what you need is a function that takes an argument x (I have used n simply because it reminds me of integers) and returns a list containing that many input elements. Something like (untested):

def read_inputs(n):
    ret_val = []
    for i in range(n):
        ret_val.append(input())
    return retval

The remainder of your code is simply:

A = read_inputs(X)
B = read_inputs(Y)
holdenweb
  • 33,305
  • 7
  • 57
  • 77
  • +1 simple, idiomatic, readable. Although in these iteratory days, you could reduce the body to `for _ in range(n): yield input()` and let the caller decide how the output should be stored. – msw Jul 30 '14 at 02:48
  • @msw Wouldn't that require the caller to also use a loop ? or would `A = list(read_input(X))` work ? – ychaouche Jul 30 '14 at 03:22
  • `read_input()` returns a list already, what's the value of applying the `list()` function to it? The loop is inside the function, and therfore repeated with each call. – holdenweb Jul 30 '14 at 04:19
  • @holdenweb, in the case of @msw 's code, it seems to me that the presence of yield indicates that the function would return a generator, hence my `list` question. – ychaouche Jul 30 '14 at 09:31
  • Agreed. When I wrote that I forgot about all the details of the OP's idiosyncratic desires; but yes, `list(read_inputs(n))` would work. – msw Jul 30 '14 at 12:36