I am trying to map where there are items NaN
in my results. For this, what I intend to do is create a new variable that, through 0 and 1, defines if there is or not NaN
. That is, if I have a list of results like the following,
lista = [11.325,99.3696,'NaN','0',0,'NaN',0,0,0,88,0,'NaN']
I would like to have a result like:
map = 001001000001
So far it would be the idea with the general concept. Now, I have to send all this to a server so I need to work with bytes
. So really what should be left is,
map = 00100100 y 00000001
For this I use the library struct
, because I need bytes
and not character strings.
This is what I have done so far and it gives the desired results, since it manages to work in packages of 8 values and does the conversion, but it does not convince me as far as programming is concerned. Is there a more optimal way to achieve it?
import struct
lista = [11.325,99.3696,'NaN','0',0,'NaN',0,0,0,88,0,'NaN']
final = []
while True:
try:
res = 0
for i in range(8):
if lista[i] == 'NaN':
res = res + 2**i
print('\t· ',lista[i],' >> ', res)
final.append(res)
for i in range(8):
lista.pop(0)
print('--')
except:
final.append(res)
for cnt,i in enumerate(final):
result = struct.pack('B', i)
final[cnt] = result
break
print('Servidor recibirá:',final)
EDIT From the solution offered by @Abulafia
Micropython does not allow to use [::-1]
, therefore I have thought of this small modification to try to solve the problem, as well as to provide more coherence to the development.
In addition, I have introduced one more value to the list to avoid the confusion of the capicúa value.
lista = [11.325,99.3696,'NaN',0,'0',0,'NaN',0,0,0,88,0,'NaN']
lista.reverse()
binario = "".join("1" if v == "NaN" else "0" for v in lista)
lista_bytes = [int(binario[i:i+8],2) for i in range(0, len(binario),8)]
final = [b.to_bytes(1, "little") for b in lista_bytes]
print('Servidor recibirá:',final)
Thank you very much!
Previous remarks
The input data you provide, converted to bits indicating the positions of the NaNs, produces the binary value:
but since that's 12 bits and not 16, how do you decide where to split it into two groups and how to pad the resulting incomplete bytes with zeros?
On the other hand, I notice that the chosen example is a bit unfortunate, because the first byte is "capicúa" (00100100) and therefore it is not clear if it should be read from right to left or vice versa. Only after examining your code do I realize that it has been built "backwards", ie the first value in the list (11,325) gives rise to the least significant bit of the result. This is counterintuitive, but I guess that's how you need it.
So it seems that your algorithm would be to take the bits 8 by 8 but interpreted "from low to high" to build the first byte and do the same with the rest, padding with zeros "from the right" (which becomes from the left). left bearing in mind that we process them from lowest to highest weight). I insist, quite confused.
On the other hand, I don't see clearly how the receiver of those bytes will be able to decode them correctly without knowing that the total number of significant bits is 12. Without this information, it would not be possible to know what the padding bits are.
Implementation
All of the above said, another implementation that results in exactly what you ask for can be achieved using the
bitarray
. With this library, not only is the code reduced (to a couple of lines) and it becomes more readable, but it will also be faster since itbitarray
is implemented internally in C.Pay attention to the parameter
endian="little"
, which is what makes the bits inside the bitarray be interpreted "from left to right" when they are converted into bytes, as in your implementation.The result that comes out is the same as when your code is executed:
Note however that it is not the result you described in your statement. The first byte is the ascii code of the '$', which is 00100100. The second byte is the code 0x08, which is 00001000 and not 00000001 as you put in the description. Anyway 0x08 is what your code for this example also produced.
No external libraries
Since the OP indicates in a comment that he is working with micropython and prefers not to use external libraries, here is another pure python implementation:
The result is as expected:
[b'$', b'\x08']
.Some tricks used in the code:
"1" if v == "NaN" else "0" for v in lista
is a generator expression that generates a series of "1" and "0" from the list. It is passed as a parameter to"".join()
so that it joins them all in a single string. In the example that resulting string would contain"001001000001"
int(binario[i:i+8][::-1],2)
takes 8 characters from the stringbinario
, between positioni
ei+8
. It flips them around (that's what the[::-1]
is for) and passes that string toint(..., 2)
which it interprets in base 2 to produce an integer.binario
. In this example the resulting list of numbers is[36, 8]
. These are already the (numeric) values of the bytes to send.int.to_bytes()
that requires two parameters. The first would be the number of bytes to generate (1 in this case) and the second the endianity . Being a single byte, it does not matter to put "little" than "big" in this case.Note that the use of
to_bytes()
makes the use of the module unnecessarystruct
.Adapted to micropython
It seems that micropython doesn't implement the whole language, but a subset. In particular, it doesn't support the step parameter on slices , which prevents you from using the trick
[::-1]
to flip the string.It can be done using
reversed()
, but you need to convert the result to a string again with"".join()
. The code is cumbersome, but it works (this time I have checked it with the online interpreter of micropython.org )"Flip each byte" as I have done in the previous code is not the same as "flip the list" before processing it, since in the latter case we would not only change the weight of the bits, but also their order. the bytes.