Python read sdf/mdl coordinates: How to read until two spaces are met?

Viewed 501

I want to read some .sdf files (actually are .mdl files but they appeared as .sdf when I downloaded them), in order to make a program to change them into another format. .sdf files simulate molecules, but that's not relevant there.

I want to read the coordinates forgetting about the bonds, so I need to, from a file format like this:

ALA
  CCTOOLS-0424210918

 13 12  0  0  1  0  0  0  0  0999 V2000
    2.2810   26.2130   12.8040 N   0  0  0  0  0
    1.1690   26.9420   13.4110 C   0  0  0  0  0
    1.5390   28.3440   13.8740 C   0  0  0  0  0
    2.7090   28.6470   14.1140 O   0  0  0  0  0
    0.6010   26.1430   14.5740 C   0  0  0  0  0
    0.5230   29.1940   13.9970 O   0  0  0  0  0
    2.0330   25.2730   12.4930 H   0  0  0  0  0
    3.0800   26.1840   13.4360 H   0  0  0  0  0
    0.3990   27.0670   12.6130 H   0  0  0  0  0
   -0.2470   26.6990   15.0370 H   0  0  0  0  0
    0.3080   25.1100   14.2700 H   0  0  0  0  0
    1.3840   25.8760   15.3210 H   0  0  0  0  0
    0.7530   30.0690   14.2860 H   0  0  0  0  0
  1  2  1  0  0  0
  1  7  1  0  0  0
  1  8  1  0  0  0
  2  3  1  0  0  0
  2  5  1  0  0  0
  2  9  1  0  0  0
  3  4  2  0  0  0
  3  6  1  0  0  0
  5 10  1  0  0  0
  5 11  1  0  0  0
  5 12  1  0  0  0
  6 13  1  0  0  0
M  END
$$$$

I want to read from the 3rd line onward until there are only 2 spaces before the numbers.

If you:

file=open('ALA_model.sdf',mode="r")
string_list=file.readlines()
file.close()
datasplit=[]
for i in range(len(string_list)):
   datasplit.append(string_list[i].split(" "))

(Quick Reminder: ALA_model.sdf is the name of the file above, but not the only one I have to work over with. Feel free to use the ALA file from above as I downloaded it from http://ligand-expo.rcsb.org/reports/A/ALA/ALA_model.sdf)

You will find that

 datasplit[3]=['', '13', '12', '', '0', '', '0', '', '1', '', '0', '', '0', '', '0', '', '0', '', '0999', 'V2000\n']

which would mean the start to read. Then we have:

datasplit[4]=['', '', '', '', '2.2810', '', '', '26.2130', '', '', '12.8040', 'N', '', '', '0', '', '0', '', '0', '', '0', '', '0\n']

Where I only need the first three numbers and the letter/atom, but I can handle that myself.

Last but not least,

datasplit[17]=['', '', '1', '', '2', '', '1', '', '0', '', '0', '', '0\n']

Is the first line I do not want to read.

So how can I stop reading when the first two str objects inside the list are space strings? I believe it would be from string_list[4:16] in this only case, but I have other .sdf files to read with diferent lenghts each and I want to built a function out of this, making iterable you know, without making a script for each.

To sum up, without caring about the format, how can we go through a list where the three/four first string values are spaces until the third string value comes to be a number?

2 Answers

In your case I would read the file line by line and check every line for the four spaces.

string_list = []
with open('ALA_model.sdf') as file:
    for line in file:
        if line.startswith('    '):
            string_list.append(line)
        else:
            # finish reading here
            continue

This example does not include skipping the first lines and treating line 4, that starts with one space, but I think you can handle this.

Another option might be to read the whole file using file.read(), and repeatedly match all lines that start with 3 or more spaces followed by an optional - and a digit using a pattern.

^ {3,}-?\d.*(?:\r?\n {3,}-?\d.*)*
  • ^ Start of string
  • {3,}-?\d.* Match 3 or more spaces, optional -, a digit and the rest of the line
  • (?:\r?\n {3,}-?\d.*)* Optionally repeat a newline and the same pattern again

Regex demo

Then you can use splitlines() and split() each line, appending the first 4 items to datasplit.

For example

import re
from pprint import pprint

file = open('ALA_model.sdf', mode="r")
content = file.read()
file.close()
match = re.search(r"^ {3,}-?\d.*(?:\r?\n {3,}-?\d.*)*", content, re.M)
datasplit = []

if match:
    for line in match.group().splitlines():
        datasplit.append([part for part in line.split()][:4])

pprint(datasplit)

Output

[['2.2810', '26.2130', '12.8040', 'N'],
 ['1.1690', '26.9420', '13.4110', 'C'],
 ['1.5390', '28.3440', '13.8740', 'C'],
 ['2.7090', '28.6470', '14.1140', 'O'],
 ['0.6010', '26.1430', '14.5740', 'C'],
 ['0.5230', '29.1940', '13.9970', 'O'],
 ['2.0330', '25.2730', '12.4930', 'H'],
 ['3.0800', '26.1840', '13.4360', 'H'],
 ['0.3990', '27.0670', '12.6130', 'H'],
 ['-0.2470', '26.6990', '15.0370', 'H'],
 ['0.3080', '25.1100', '14.2700', 'H'],
 ['1.3840', '25.8760', '15.3210', 'H'],
 ['0.7530', '30.0690', '14.2860', 'H']]

Python demo

Related