0

I'd like to read in a file with comma-separated values, and count the frequencies of these values (which are in a range of 0 .. 8 inclusive):

1,1,1,1,1,2,0,0,0,0,0,1,2,3,4,7,7,8,0,0,0

This code works:

with open("data.txt") as file:
    l =  [int(s) for s in file.readline().strip().split(",")]
    a1 = [l.count(i) for i in range(9)]
print(a1)

First I read the file, split it by commas, and convert the string input in integers, collecting everything in the list l. However, combining the same two assignments into a single one breaks:

with open("data.txt") as file:
    a2 = [[int(s) for s in file.readline().strip().split(",")].count(i) for i in range(9)]
print(a2)
$ python -i aa.py # both snippets from above in one file
[0, 162, 36, 27, 47, 28, 0, 0, 0]
Traceback (most recent call last):
  File "aa.py", line 7, in <module>
    a2 = [([int(s) for s in file.readline().strip().split(",")].count(i)) for i in range(9)]
  File "aa.py", line 7, in <listcomp>
    a2 = [([int(s) for s in file.readline().strip().split(",")].count(i)) for i in range(9)]
  File "aa.py", line 7, in <listcomp>
    a2 = [([int(s) for s in file.readline().strip().split(",")].count(i)) for i in range(9)]
ValueError: invalid literal for int() with base 10: ''

P.S.: I am aware that I might use collections.Counter and that strip() might not be necessary here, but that doesn't explain why I can't combine the two assignments into one.

Nils Magnus
  • 310
  • 1
  • 7
  • 4
    The inner comprehension gets evaluated 9 times, but exhausts the file object on the first evaluation. – chepner Dec 06 '21 at 21:44

1 Answers1

1

The list comprehension version is equivalent to this:

with open("data.txt") as file:
    a2 = []
    for i in range(9):
        a2.append([int(s) for s in file.readline().strip().split(",")].count(i))
print(a2)

So it performs the remaining list comprehension 9 times. But the file stream position is not reset between them, so the results are likely different in each execution of the list comprehension.

Depending on what is understood by "one statement", you can make it work with an immediately executed lambda function:

with open("data.txt") as file:
    a1 = (lamda l: [l.count(i) for i in range(9)])([int(s) for s in file.readline().strip().split(",")])

This should only be regarded as a theoretical possibility: such coding pattern should be rejected as it deteriorates the readability of the code and brings no benefit in terms of efficiency.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Ah, I see the problem. But what about a solution? – Nils Magnus Dec 06 '21 at 21:56
  • I thought you had a solution? Why change something that works? – trincot Dec 06 '21 at 21:57
  • Solution to what? You only want to create the list once; why try to combine it with code that iterates over that list 9 times? – chepner Dec 06 '21 at 22:04
  • The question was about nesting two comprehensions, not to read the actual frequencies. That was just an example explaining the problem. I mean, one possible answer is "write shorter statements", but I still wonder if and how this might be possible. – Nils Magnus Dec 06 '21 at 22:08
  • Replacing 2 iterations that are not nested, but where one completes before the other starts, should not be replaced with 2 nested iterations, since that is just a *different algorithm*, and will in general not yield the same results. It's like the difference between addition and multiplication.... Why do you want a different algorithm? – trincot Dec 06 '21 at 22:10
  • Did this answer your question? – trincot Dec 07 '21 at 12:06
  • Only partially. In arithmetics, we have rules of precedence (say, multiply over addition). If you want to overrule them, you use brackets. This is an analogy for my example: Can we express the described task in a single statement? Can I somehow make sure that the inner comprehension is evaluated only once? This question remains unanswered. Yes, I now understand why my approach did not work and I knew even before that it can be done in two statements. But can it be done in one, maybe be some clever reordering or whatever else? – Nils Magnus Dec 07 '21 at 13:30
  • What constitutes one statement is very arbitrary, but you can make it an immediately invoked lambda function. See addition to answer. Of course, this is bad practice, as it makes the code more obscure than is needed. -- You certainly don't want to *nest*, as that implies *repeated* execution of whatever is the inner expression. – trincot Dec 07 '21 at 13:34
  • Does this answer your question? – trincot Dec 07 '21 at 13:50