Getting lost trying to create a nested dictionary

I’ve been trying to do this for 3 days now. I have watched YouTubes, looked up nested dictionaries on about a dozen different sites (e.g. https://discuss.python.org/t/how-to-create-a-dictionary-from-scanning-text/9676/2 but I still can’t do what must be a simple thing.

My text file structure is like this (lines reduced in length to one word for simplicity). Some verses contain colons which is why I don’t split on colons.

1#1|Blessed…
1#2|But…
1#3|And…
1#4|The…
2#1|Why …
2#2|The…
2#3|Let…
2#4|He…
3#1|Lord…

The leftmost number on each line is the chapter number (chapter 1, 2, 3 etc all the way up to 150)
The number after the # is the verse number (a chapter may have a few verses or it may have over 100 verses).
The text after the | is the actual verse which might be say 10 words long or might be twice that length).

What I am trying to achieve is a nested dictionary that looks like:
Psalms = {1: {‘verseNum’: 1, ‘verse’: ‘Blessed…’},
1: {‘verseNum’: 2, ‘verse’: ‘But…’},
1: {‘verseNum’: 3, ‘verse’: ‘And…’},
1: {‘verseNum’: 4, ‘verse’: ‘The…’},
2: {‘verseNum’: 1, ‘verse’: ‘Why…’},
2: {‘verseNum’: 2, ‘verse’: ‘The…’},
2: {‘verseNum’: 3, ‘verse’: ‘Let…’},
2: {‘verseNum’: 4, ‘verse’: ‘He…’},
3: {‘verseNum’: 1, ‘verse’: ‘Lord…’},

150: {‘verseNum’: 6, ‘verse’: ‘Let…’} }

Here is my code which is able to correctly print the first chapter.

print(’\n\nSTART’)

PsalmsDict = {}
PsalmsDict[1]={}
verseDict ={}
counter = 1

filename = ‘test.txt’
with open(filename) as f:
lines = f.readlines()

while counter < 160:
for line in lines:
split_line = line.split("#")
chapNum = split_line[0].strip()
chapNum = int(chapNum)
print(‘chapNum’,chapNum) # debugging ouput statement
temp = split_line[1].strip()
#print(temp)
second = temp.split(’|’)
verseNum=second[0]
verse =second[1]
#print(str(chapNum)+’::’+verseNum+’ = '+verse)
# now put it into a nested dictionary

    if chapNum == counter:
        print('True') # debugging ouput statement
        PsalmsDict[counter]['verseNum'] = verseNum
        PsalmsDict[counter]['verse']= verse
        print(PsalmsDict) # debugging ouput statement
    else:
        PsalmsDict[chapNum] = PsalmsDict.get(chapNum, 0) + 1
        counter +=1
#print('increment counter')

print(PsalmsDict) # debugging ouput statement

Here is my output.

START
chapNum 1
True
{1: {‘verseNum’: ‘1’, ‘verse’: ‘Blessed is the man that walketh not in the counsel of the ungodly, nor standeth in the way of sinners, nor sitteth in the seat of the scornful.’}}
chapNum 1
True
{1: {‘verseNum’: ‘2’, ‘verse’: ‘But his delight is in the law of the LORD; and in his law doth he meditate day and night.’}}
chapNum 1
True
{1: {‘verseNum’: ‘3’, ‘verse’: ‘And he shall be like a tree planted by the rivers of water, that bringeth forth his fruit in his season; his leaf also shall not wither; and whatsoever he doeth shall prosper.’}}
chapNum 1
True
{1: {‘verseNum’: ‘4’, ‘verse’: ‘The ungodly are not so: but are like the chaff which the wind driveth away.’}}
chapNum 1
True
{1: {‘verseNum’: ‘5’, ‘verse’: ‘Therefore the ungodly shall not stand in the judgment, nor sinners in the congregation of the righteous.’}}
chapNum 1
True
{1: {‘verseNum’: ‘6’, ‘verse’: ‘For the LORD knoweth the way of the righteous: but the way of the ungodly shall perish.’}}
chapNum 2
chapNum 2
True
Traceback (most recent call last):
File “main.py”, line 66, in
PsalmsDict[counter][‘verseNum’] = verseNum
TypeError: ‘int’ object does not support item assignment

Please would someone help me to figure this out?

That’s not valid. A dictionary is a collection of unique keys, each of which may have an associated value. What you have shown is a dictionary with non-unique keys (the key 1 is shown multiple times).

As your keys are all numeric, you could go with either a nested list or a nested dict, but the data would look a bit different.

# nested dict
Psalms = {1: {1: "Blessed...", 2: "But...", 3: "And..."}, 2: {1: "Why...", 2: "The...", 3: "Let..."}}
print(Psalms[1][3]) # And...

# nested list
Psalms = [[], ['', 'Blessed...', 'But...', 'And...'], ['', 'Why...', 'The...', 'Let...']]
print(Psalms[1][3]) # And...

Maybe a description of how you intend to use the data structure would be helpful.

Hey thanks for the quick reply.
When I take your dictionary and reformat it down the page it looks perfect.

Psalms = {1: {1: “Blessed…”,
2: “But…”,
3: “And…”},
2: {1: “Why…”,
2: “The…”,
3: “Let…”}}

Yes, that is exactly what I am trying to produce.
Maybe now that I can see it more clearly I might understand it better.

I want to pick a Psalm chapter number at random and display that Psalm.

It removed all my spaces to format it. :thinking:

Are the removed spaces a problem? The only spaces that matter are the ones inside the quoted strings. Everything else is just for your display. You can construct a printout that formats things differently.

I’m sorry but I don’t understand what you are saying about the spaces.

I have managed to read in the first line from the file and put it into the nested dictionary.

Here is my code now:

chapNum = 0
verseNum = 0
Psalms = {}
Psalms[chapNum]={}
Psalms[chapNum][verseNum]=‘start’

filename = ‘Psalms.txt’
with open(filename) as f:
line = f.readline()
split_line = line.split(“#”)
chapNum = split_line[0].strip()
chapNum = int(chapNum)
temp = split_line[1].split(‘|’)
verseNum = temp[0].strip()
verse = temp[1].strip()
#print(chapNum,verse,verseNum) # print it in any order
# data type chapNum:int, verseNum:str, verse:str
Psalms[verseNum] = verse

print(Psalms)

I know that I have to somehow read the next line from the file and do the same as what I have just done… I’m just not sure how to do that.
Would you be willing to give me a hand and point me in the right direction?

Hi Roger,

You say:

What I am trying to achieve is a nested dictionary that looks like:

Psalms = {1: {‘verseNum’: 1, ‘verse’: ‘Blessed…’},

      1: {'verseNum': 2, 'verse': 'But...'},
      1: {'verseNum': 3, 'verse': 'And...'},
      1: {'verseNum': 4, 'verse': 'The...'},
      2: {'verseNum': 1, 'verse': 'Why...'},
      2: {'verseNum': 2, 'verse': 'The...'},
      2: {'verseNum': 3, 'verse': 'Let...'},
      2: {'verseNum': 4, 'verse': 'He...'},
      3: {'verseNum': 1, 'verse': 'Lord...'},
      ...
      150: {'verseNum': 6, 'verse': 'Let...'} }

That can’t work, because you have duplicate keys in the outer

dictionary. Let’s scrub the inner dictionaries and replace them with

letters as placeholders , you are trying to do this:

psalms = {1: 'a',

          1: 'b',

          1: 'c',

          2: 'd',

          2: 'e',

          2: 'f'}

If you try that, you will see that it doesn’t work. Each key (1, 2, etc)

must be unique, or the second one will replace the existing one.

So we have to change the design. Instead, let us design it to be like

this:

psalms = {  # chapter_number: verse_dictionary }

and the verse dictionary will look like this:

{ # verse_number: verse }

Putting that together, we have:

psalms = {

    1: {1: 'Blessed...',

        2: 'But...',

        3: 'And...',

        4: 'The...'},

    2: {1: 'Why...',

        2: 'The...',

        3: 'Let...',

        4: 'He...'},

    3: {1: 'Lord...'}

    }

Then, to access Psalms 12:5, say, you would say:

psalms[12][5]

That’s a better design. Does that help?

This makes such good sense:

Tell me please, why is there a # in front of chapter_number?
Does this tell the dictionary that it is an integer?

Would you please tell me why

  1. the curly braces are different colours?
  2. there is a syntax error?

(I’m using replit.com)

It is happier now when I have
Screenshot 2021-08-26 160806

I have tried

chapter_number = 0
verse_number = 0
verse = ‘’
verse_dictionary = {verse_number: verse }
Psalms = {chapter_number : verse_dictionary }

counter =1

filename = ‘Psalms.bak’
with open(filename) as f:
for line in f:
key,value = line.strip().split(“|”)
verse_dictionary[verse_number] = verse
print(verse_dictionary)
Psalms[chapter_number] = verse_dictionary
#print(Psalms[150][6])

I have tried

    split_line = line.split("|")
    chapter_number = split_line[0].strip()
    chapter_number = int(chapter_number)
    verse_number = split_line[1].strip()
    verse = split_line[2].strip()
    # print(chapter_number)
    # print(verse_number)
    # print(verse)

But I cannot get it to put the verses into a nested dictionary.

Here’s one way to do it.

from collections import defaultdict

psalm_text = """1#1|Blessed…
1#2|But…
1#3|And…
1#4|The…
2#1|Why …
2#2|The…
2#3|Let…
2#4|He…
3#1|Lord…"""

psalm_info = defaultdict(dict)
for line in psalm_text.splitlines():
    location, verse_text = line.split("|")
    chapter, verse = location.split("#") # stored as str, not as ints.  OK?
    psalm_info[chapter][verse] = verse_text

chapter = "2"
verse = "3"
print(f"Chapter {chapter} verse {verse} contains {psalm_info[chapter][verse]}")```

BowlOfRed Thank you for your help. That works when I copy and paste it.

However, I want to read in the text file and I was getting an error…

Traceback (most recent call last):
  File "main.py", line 8, in <module>
    for line in f.splitlines():
AttributeError: '_io.TextIOWrapper' object has no attribute 'splitlines'

After a bit it dawned on me that all I had to do was read the entire file into the variable “psalm_text”.

Here is my code now which works!!!

I struggled with this for 4 days. You are a legend! Thank you so much for your help!
Why I never thought to read the whole file in and then work on it I don’t know.

Thank you again for your time and for all you help!

I also want to thank Steven D’Aprano for explaining the structure of nested dictionaries to me so clearly.

That’s perfectly fine. Although the more common way would be to just iterate over the file object and you will get each line in turn. (I didn’t do that in my example because you can’t do the same thing with strings).

with open('Psalms.txt') as f:
    for line in f:
        location, verse_text = line.split("|")
        ...

Remove the #, thats just a comment for showcasing an example.