Misunderstand singly Linked List - data-structures

I have misunderstood the implementation of a singly linked list, if the length in a linked list is 0, let the head and tail equal the new node there is no problem with me.
But when I try to add a new node I mutate it with this.tail.next = newNode and this.tail equal newNode.
The problem how this.head take new node?

how this.head take new node?
When the second node is added, this.head references the first node, and that is what it still should reference after the second node has been added. The addition happens by mutating the next member of the first node, so that it references the second node.
It may help to visualse this. After the first node has been inserted (let's say with value 1), we have this state with the instance of the SinglyLinkedList class and the first instance of the Node class:
this
↓
┌────────────┐
│ tail: ─────────┐
│ head: ───────┐ │
│ length: 1 │ │ │
└────────────┘ │ │
▼ ▼
┌────────────┐
│ value: 1 │
│ next: null │
└────────────┘
Now let's look at what happens when the second node (with value 2) is added. First newNode is assigned a new instance of Node that has received a value:
this
↓
┌────────────┐
│ tail: ─────────┐
│ head: ───────┐ │
│ length: 1 │ │ │
└────────────┘ │ │
▼ ▼
┌────────────┐ ┌────────────┐
│ value: 1 │ │ value: 2 │
│ next: null │ │ next: null │
└────────────┘ └────────────┘
↑
newNode
Then the link is established with this.tail.next = newNode:
this
↓
┌────────────┐
│ tail: ─────────┐
│ head: ───────┐ │
│ length: 1 │ │ │
└────────────┘ │ │
▼ ▼
┌────────────┐ ┌────────────┐
│ value: 1 │ │ value: 2 │
│ next: ────────🞂│ next: null │
└────────────┘ └────────────┘
↑
newNode
Note how this builds a chain of references, such that now not only this.tail.next === newNode, but also this.head.next === newNode!
Just to finish up the scenario of insertion, we execute this.tail = newNode and this.length++ which results in this state:
this
↓
┌────────────┐
│ tail: ────────────────────────┐
│ head: ───────┐ │
│ length: 2 │ │ │
└────────────┘ │ │
▼ ▼
┌────────────┐ ┌────────────┐
│ value: 1 │ │ value: 2 │
│ next: ────────🞂│ next: null │
└────────────┘ └────────────┘
↑
newNode
I hope this clarifies it.

Related

How to dynamically layout atoms in atomic orbitals given a certain set of desired layouts?

I am laying this out as a square:
Typically you see it it in a circle, which is easy you just distribute the electrons evenly.
But in my square case, the outer interior of each square I want to put small squares to layout the electrons according to the Electron shells. I want to do this somewhat dynamically without having to write out each of the 118 atom cases manually.
The desired layouts are like this if there are 8 electrons in a shell:
x x x
x x
x x x
If there are only 7, it should be like this:
x x x
x x
x x
Then these cases:
6:
x - x
x x
x - x
5:
- x -
x x
x - x
4:
- x -
x x
- x -
3:
- x -
- -
x - x
2:
- - -
x x
- - -
1:
- x -
- -
- - -
Then the 18 case is:
x x x x x
x x
x x
x x
x x
x x x x x
x x - x x
x x
x x
x x
x x
x x x x x
x x - x x
x x
x x
x x
x x
x x - x x
x - x - x
x x
x x
x x
x x
x x - x x
x - x - x
x x
x x
x x
x x
x - x - x
I don't have a super-hard-fast desire to layout each configuration in a specific way (even though I started by showing specific configurations). I am mainly looking to figure out some sort of pattern or sort of equation to lay them out in a semi-nice/decent way. How can it be done?
The electron shells are like this:
const SHELLS = `Hydrogen,1
Helium,2
Lithium,2:1
Beryllium,2:2
Boron,2:3
Carbon,2:4
Nitrogen,2:5
Oxygen,2:6
Fluorine,2:7
Neon,2:8
Sodium,2:8:1
Magnesium,2:8:2
Aluminium,2:8:3
Silicon,2:8:4
Phosphorus,2:8:5
Sulfur,2:8:6
Chlorine,2:8:7
Argon,2:8:8
Potassium,2:8:8:1
Calcium,2:8:8:2
Scandium,2:8:9:2
Titanium,2:8:10:2
Vanadium,2:8:11:2
Chromium,2:8:13:1
Manganese,2:8:13:2
Iron,2:8:14:2
Cobalt,2:8:15:2
Nickel,2:8:16:2
Copper,2:8:18:1
Zinc,2:8:18:2
Gallium,2:8:18:3
Germanium,2:8:18:4
Arsenic,2:8:18:5
Selenium,2:8:18:6
Bromine,2:8:18:7
Krypton,2:8:18:8
Rubidium,2:8:18:8:1
Strontium,2:8:18:8:2
Yttrium,2:8:18:9:2
Zirconium,2:8:18:10:2
Niobium,2:8:18:12:1
Molybdenum,2:8:18:13:1
Technetium,2:8:18:13:2
Ruthenium,2:8:18:15:1
Rhodium,2:8:18:16:1
Palladium,2:8:18:18
Silver,2:8:18:18:1
Cadmium,2:8:18:18:2
Indium,2:8:18:18:3
Tin,2:8:18:18:4
Antimony,2:8:18:18:5
Tellurium,2:8:18:18:6
Iodine,2:8:18:18:7
Xenon,2:8:18:18:8
Caesium,2:8:18:18:8:1
Barium,2:8:18:18:8:2
Lanthanum,2:8:18:18:9:2
Cerium,2:8:18:19:9:2
Praseodymium,2:8:18:21:8:2
Neodymium,2:8:18:22:8:2
Promethium,2:8:18:23:8:2
Samarium,2:8:18:24:8:2
Europium,2:8:18:25:8:2
Gadolinium,2:8:18:25:9:2
Terbium,2:8:18:27:8:2
Dysprosium,2:8:18:28:8:2
Holmium,2:8:18:29:8:2
Erbium,2:8:18:30:8:2
Thulium,2:8:18:31:8:2
Ytterbium,2:8:18:32:8:2
Lutetium,2:8:18:32:9:2
Hafnium,2:8:18:32:10:2
Tantalum,2:8:18:32:11:2
Tungsten,2:8:18:32:12:2
Rhenium,2:8:18:32:13:2
Osmium,2:8:18:32:14:2
Iridium,2:8:18:32:15:2
Platinum,2:8:18:32:17:1
Gold,2:8:18:32:18:1
Mercury,2:8:18:32:18:2
Thallium,2:8:18:32:18:3
Lead,2:8:18:32:18:4
Bismuth,2:8:18:32:18:5
Polonium,2:8:18:32:18:6
Astatine,2:8:18:32:18:7
Radon,2:8:18:32:18:8
Francium,2:8:18:32:18:8:1
Radium,2:8:18:32:18:8:2
Actinium,2:8:18:32:18:9:2
Thorium,2:8:18:32:18:10:2
Protactinium,2:8:18:32:20:2
Uranium,2:8:18:32:21:9:2
Neptunium,2:8:18:32:22:9:2
Plutonium,2:8:18:32:24:8:2
Americium,2:8:18:32:25:8:2
Curium,2:8:18:32:25:9:2
Berkelium,2:8:18:32:27:8:2
Californium,2:8:18:32:28:8:2
Einsteinium,2:8:18:32:29:8:2
Fermium,2:8:18:32:30:8:2
Mendelevium,2:8:18:32:31:8:2
Nobelium,2:8:18:32:32:8:2
Lawrencium,2:8:18:32:32:8:3
Rutherfordium,2:8:18:32:32:10:2
Dubnium,2:8:18:32:32:11:2
Seaborgium,2:8:18:32:32:12:2
Bohrium,2:8:18:32:32:13:2
Hassium,2:8:18:32:32:14:2
Meitnerium,2:8:18:32:32:15:2
Darmstadtium,2:8:18:32:32:16:2
Roentgenium,2:8:18:32:32:17:2
Copernicium,2:8:18:32:32:18:2
Nihonium,2:8:18:32:32:18:3
Flerovium,2:8:18:32:32:18:4
Moscovium,2:8:18:32:32:18:5
Livermorium,2:8:18:32:32:18:6
Tennessine,2:8:18:32:32:18:7
Oganesson,2:8:18:32:32:18:8`
.trim()
.split('\n')
.map(x => {
const [a, b] = x.split(',')
const c = b.split(':').map(x => parseInt(x, 10))
return { name: a, shells: c }
})
Is it possible do you think to come up with a simple algorithm for this, or must it be hardcoded?
Some constraints:
The shells have 2, 8, 18, 32, 32 electrons.
The electrons should go into preexisting slots, so there are only 8 slots for the 8, 18 for the 18, etc.. That is, you can't evenly distribute them around the edge.
Other than that, the general layout should feel somewhat "balanced" (even though that is a fuzzy concept). So if there is just 17, it should take out one from the middle vertically. If there are only 3, it should make them into a triangle sort of thing. I don't see a way out of defining this manually, but I am sure there is a way to do it with some clever perspective.
There might be multiple equally "balanced" ways of creating a layout, so it doesn't matter to me exactly which one is chosen.
It can be simulated just laying out x and - in a monospaced font, so don't need to full Next.js/React/SVG system that I am dealing with currently. Any help would be greatly appreciated, I am stumped.
You could first solve the problem without any actual rendering considerations, but see the orbits as 1-dimensional arrays of bits -- let's say a string of "x" and "-". The inner orbit could have three possibilities:
"--"
"x-"
"xx"
The next orbit would have these:
"--------",
"x-------",
"x---x---",
"x--x-x--",
"x-x-x-x-",
"x-xx-xx-",
"-xxx-xxx",
"xxxx-xxx",
"xxxxxxxx"
To distribute the "x" evenly you would step with fractions of the string length over the desired number of "x". To avoid irregular shapes, you could mirror positions as soon as you find them, and stop generating more when together with the mirrored positions you have them all.
Once you have generated all these bit patterns, we can focus on the format of the squares layout. For that you could define a multiline string that is a template for how you generally want to render it, using specific placeholders for where you want the electron slots to be. So "a" in that string would depict a slot in the inner orbit, "b" in the second one, ...etc. For instance:
┌───────────────────────────────────────────────────┐
│ g │
│ ┌───────────────────────────────────────────┐ │
│ │ f f f f f │ │
│ │ ┌───────────────────────────────────┐ │ │
│ g │ f │ e e e e e e e e e e e │ f │ g │
│ │ │ ┌───────────────────────────┐ │ │ │
│ │ │ │ d d d d d d d d d │ │ │ │
│ │ │ e │ d ┌───────────────────┐ d │ e │ │ │
│ │ │ │ │ c c c c c │ │ │ │ │
│ │ │ │ d │ ┌───────────┐ │ d │ │ │ │
│ │ f │ e │ │ c │ b │ c │ │ e │ f │ │
│ │ │ │ d │ │ b ┌───┐ b │ │ d │ │ │ │
│ │ │ │ │ c │ │ a │ │ c │ │ │ │ │
│ g │ │ e │ d │ │ b │ │ b │ │ d │ e │ │ g │
│ │ │ │ │ c │ │ a │ │ c │ │ │ │ │
│ │ │ │ d │ │ b └───┘ b │ │ d │ │ │ │
│ │ f │ e │ │ c │ b │ c │ │ e │ f │ │
│ │ │ │ d │ └───────────┘ │ d │ │ │ │
│ │ │ │ │ c c c c c │ │ │ │ │
│ │ │ e │ d └───────────────────┘ d │ e │ │ │
│ │ │ │ d d d d d d d d d │ │ │ │
│ │ │ └───────────────────────────┘ │ │ │
│ g │ f │ e e e e e e e e e e e │ f │ g │
│ │ └───────────────────────────────────┘ │ │
│ │ f f f f f │ │
│ └───────────────────────────────────────────┘ │
│ g │
└───────────────────────────────────────────────────┘
I just had some fun with those box drawing characters, but you can of course use an entirely different string. The only requirement is that there are two "a" characters in it, 8 "b", 18 "c", ...etc. All the other characters can be anything.
A little function can find the positions of the slots in the orbits and then use the bit patterns to place the desired character ("x" or "-") at the appropriate slot.
Here is an interactive implementation of that idea:
const templateInput = `
┌───────────────────────────────────────────────────┐
│ g │
│ ┌───────────────────────────────────────────┐ │
│ │ f f f f f │ │
│ │ ┌───────────────────────────────────┐ │ │
│ g │ f │ e e e e e e e e e e e │ f │ g │
│ │ │ ┌───────────────────────────┐ │ │ │
│ │ │ │ d d d d d d d d d │ │ │ │
│ │ │ e │ d ┌───────────────────┐ d │ e │ │ │
│ │ │ │ │ c c c c c │ │ │ │ │
│ │ │ │ d │ ┌───────────┐ │ d │ │ │ │
│ │ f │ e │ │ c │ b │ c │ │ e │ f │ │
│ │ │ │ d │ │ b ┌───┐ b │ │ d │ │ │ │
│ │ │ │ │ c │ │ a │ │ c │ │ │ │ │
│ g │ │ e │ d │ │ b │ │ b │ │ d │ e │ │ g │
│ │ │ │ │ c │ │ a │ │ c │ │ │ │ │
│ │ │ │ d │ │ b └───┘ b │ │ d │ │ │ │
│ │ f │ e │ │ c │ b │ c │ │ e │ f │ │
│ │ │ │ d │ └───────────┘ │ d │ │ │ │
│ │ │ │ │ c c c c c │ │ │ │ │
│ │ │ e │ d └───────────────────┘ d │ e │ │ │
│ │ │ │ d d d d d d d d d │ │ │ │
│ │ │ └───────────────────────────┘ │ │ │
│ g │ f │ e e e e e e e e e e e │ f │ g │
│ │ └───────────────────────────────────┘ │ │
│ │ f f f f f │ │
│ └───────────────────────────────────────────┘ │
│ g │
└───────────────────────────────────────────────────┘
`.trim();
function parseTemplate(template) {
function shellPattern(length, count) {
const arr = [];
const even = 1 - count % 2;
const symbols = count > length >> 1 && count < length ? "x-" : "-x";
count = symbols[0] == "x" ? length - count : count;
for (let j = 0, step = 0; true; j++) {
const surpass = +((j + 0.5) * count >= step);
const symbol = symbols[surpass];
if (surpass) step += length;
arr[j] = symbol;
if (j) { // Mirror left-right
if (length - j <= j) break;
arr[length - j] = symbol;
}
if (even) { // Mirror top-bottom
if ((length >> 1) - j <= j) break;
arr[(length >> 1) + j] = arr[(length >> 1) - j] = symbol;
}
}
const pat = arr.join("");
// Turn 180° if top cell is not occupied:
return pat[0] == "-" ? pat.slice(length >> 1) + pat.slice(0, length >> 1) : pat;
}
return {
shells: Array.from("abcdefg", ch => {
const forward = [];
const backward = [];
let i = 0;
template.split(/^/gm).forEach((line, y, {length}) => {
const hits = Array.from(line.matchAll(ch), ({index}) => i + index);
if (y * 2 < length) {
backward.push(...hits.slice(0, hits.length >> 1).reverse());
forward.push(...hits.slice(hits.length >> 1));
} else {
backward.push(...hits.slice(0, hits.length >> 1));
forward.push(...hits.slice(hits.length >> 1).reverse());
}
i += line.length;
});
const indices = forward.concat(backward.reverse());
return {
indices,
patterns: ['-'.repeat(indices.length),
...indices.map((_, count) => shellPattern(indices.length, count+1))]
}
}),
template: template.replace(/[a-g]/g, ".")
};
}
function toGrid(model, element) {
const shells = element?.match(/\d+/g)?.map(Number) ?? [];
const arr = [...model.template];
shells.forEach((count, shellNum) => {
const {patterns, indices} = model.shells[shellNum];
Array.from(patterns[count], (ch, i) => arr[indices[i]] = ch);
});
return arr.join("");
}
const model = parseTemplate(templateInput);
// I/O handling
const input = document.querySelector("select");
input.onchange = () =>
document.querySelector("pre").textContent = toGrid(model, input.value);
input.onchange();
pre { font-size: 8px; display: inline-block; width = 50hv; float: left }
<pre></pre>
<select multiple size="15">
<option>Hydrogen,1
<option>Helium,2
<option>Lithium,2:1
<option>Beryllium,2:2
<option>Boron,2:3
<option>Carbon,2:4
<option>Nitrogen,2:5
<option>Oxygen,2:6
<option>Fluorine,2:7
<option>Neon,2:8
<option>Sodium,2:8:1
<option>Magnesium,2:8:2
<option>Aluminium,2:8:3
<option>Silicon,2:8:4
<option>Phosphorus,2:8:5
<option>Sulfur,2:8:6
<option>Chlorine,2:8:7
<option>Argon,2:8:8
<option>Potassium,2:8:8:1
<option>Calcium,2:8:8:2
<option>Scandium,2:8:9:2
<option>Titanium,2:8:10:2
<option>Vanadium,2:8:11:2
<option>Chromium,2:8:13:1
<option>Manganese,2:8:13:2
<option>Iron,2:8:14:2
<option>Cobalt,2:8:15:2
<option>Nickel,2:8:16:2
<option>Copper,2:8:18:1
<option>Zinc,2:8:18:2
<option>Gallium,2:8:18:3
<option>Germanium,2:8:18:4
<option>Arsenic,2:8:18:5
<option>Selenium,2:8:18:6
<option>Bromine,2:8:18:7
<option>Krypton,2:8:18:8
<option>Rubidium,2:8:18:8:1
<option>Strontium,2:8:18:8:2
<option>Yttrium,2:8:18:9:2
<option>Zirconium,2:8:18:10:2
<option>Niobium,2:8:18:12:1
<option>Molybdenum,2:8:18:13:1
<option>Technetium,2:8:18:13:2
<option>Ruthenium,2:8:18:15:1
<option>Rhodium,2:8:18:16:1
<option>Palladium,2:8:18:18
<option>Silver,2:8:18:18:1
<option>Cadmium,2:8:18:18:2
<option>Indium,2:8:18:18:3
<option>Tin,2:8:18:18:4
<option>Antimony,2:8:18:18:5
<option>Tellurium,2:8:18:18:6
<option>Iodine,2:8:18:18:7
<option>Xenon,2:8:18:18:8
<option>Caesium,2:8:18:18:8:1
<option>Barium,2:8:18:18:8:2
<option>Lanthanum,2:8:18:18:9:2
<option>Cerium,2:8:18:19:9:2
<option>Praseodymium,2:8:18:21:8:2
<option>Neodymium,2:8:18:22:8:2
<option>Promethium,2:8:18:23:8:2
<option>Samarium,2:8:18:24:8:2
<option>Europium,2:8:18:25:8:2
<option>Gadolinium,2:8:18:25:9:2
<option>Terbium,2:8:18:27:8:2
<option>Dysprosium,2:8:18:28:8:2
<option>Holmium,2:8:18:29:8:2
<option>Erbium,2:8:18:30:8:2
<option>Thulium,2:8:18:31:8:2
<option>Ytterbium,2:8:18:32:8:2
<option>Lutetium,2:8:18:32:9:2
<option>Hafnium,2:8:18:32:10:2
<option>Tantalum,2:8:18:32:11:2
<option>Tungsten,2:8:18:32:12:2
<option>Rhenium,2:8:18:32:13:2
<option>Osmium,2:8:18:32:14:2
<option>Iridium,2:8:18:32:15:2
<option>Platinum,2:8:18:32:17:1
<option>Gold,2:8:18:32:18:1
<option>Mercury,2:8:18:32:18:2
<option>Thallium,2:8:18:32:18:3
<option>Lead,2:8:18:32:18:4
<option>Bismuth,2:8:18:32:18:5
<option>Polonium,2:8:18:32:18:6
<option>Astatine,2:8:18:32:18:7
<option>Radon,2:8:18:32:18:8
<option>Francium,2:8:18:32:18:8:1
<option>Radium,2:8:18:32:18:8:2
<option>Actinium,2:8:18:32:18:9:2
<option>Thorium,2:8:18:32:18:10:2
<option>Protactinium,2:8:18:32:20:2
<option>Uranium,2:8:18:32:21:9:2
<option>Neptunium,2:8:18:32:22:9:2
<option>Plutonium,2:8:18:32:24:8:2
<option>Americium,2:8:18:32:25:8:2
<option>Curium,2:8:18:32:25:9:2
<option>Berkelium,2:8:18:32:27:8:2
<option>Californium,2:8:18:32:28:8:2
<option>Einsteinium,2:8:18:32:29:8:2
<option>Fermium,2:8:18:32:30:8:2
<option>Mendelevium,2:8:18:32:31:8:2
<option>Nobelium,2:8:18:32:32:8:2
<option>Lawrencium,2:8:18:32:32:8:3
<option>Rutherfordium,2:8:18:32:32:10:2
<option>Dubnium,2:8:18:32:32:11:2
<option>Seaborgium,2:8:18:32:32:12:2
<option>Bohrium,2:8:18:32:32:13:2
<option>Hassium,2:8:18:32:32:14:2
<option>Meitnerium,2:8:18:32:32:15:2
<option>Darmstadtium,2:8:18:32:32:16:2
<option>Roentgenium,2:8:18:32:32:17:2
<option>Copernicium,2:8:18:32:32:18:2
<option>Nihonium,2:8:18:32:32:18:3
<option>Flerovium,2:8:18:32:32:18:4
<option>Moscovium,2:8:18:32:32:18:5
<option>Livermorium,2:8:18:32:32:18:6
<option>Tennessine,2:8:18:32:32:18:7
<option>Oganesson,2:8:18:32:32:18:8
</select>
Working out the ideal positions
Let's take for example the orbit with 8 slots and 6 electrons to populate on it. Let's define a unit of measure such that the orbit has a circumference of 8 units (so we take a slot-to-slot distance on the circumference as unit of measure). If we for a moment forget about the slots, then a perfect distribution would be to have a distance (on the circumference) of 8 / 6 between every consecutive pair of electrons, because the sum of these 6 distances would be 8, i.e. making a full circle.
To avoid that the limited floating point precision gives us less accurate results, we could redefine the unit of measure by multiplying the numerator and denominator by 6 (the electron count), so the circumference is actually 8*6 units long, and each step is 8 (the distance between two consecutive electrons).
This is why in the loop of shellPattern you see the step variable increase with length (which is the number of slots in the orbit), giving us the distance (on the circumference) from the "home" position (at 0) to each electron. To translate this unit of measure back to the original unit of measure, we would divide by the electron count (the variable count). But instead of dividing, we multiply the index of the slot by count so we can avoid the floating point issues of a division. This product gives us the distance of the slot from the home position expressed in the new unit of measure. Every time this product passes over the current step, we should "place" the electron in the corresponding slot. This is where the rounding gets done, because we ignore the overrun of the product (the part that is more than step).
The + 0.5 is to make sure that the grid is horizontally mirrored in such a way that the home position is at the exact top. We want to "collapse" the perfect calculated position into a slot index. So we don't want the left picture, but the right picture (the shaded areas represent ranges that would collapse to the slots that are positioned at the center of them):
The difference is that additional 0.5*count which represents a half slot section. Note that 0.5 poses no problem for floating point: it has a perfect representation for 0.5
Next, we really want the slots to be filled in a way that the rounding is done symmetrically, so that the left and right side look the same. This is what the first if block does: whenever we have determined whether a slot is to be filled or left empty, we do exactly the same thing at the mirrored slot. If it turns out the slot number of the mirror is less than the current slot number, then we know we have "crossed" over and can stop the loop.
In case the number of electrons is even we also want the top and bottom half of the distribution to be mirrored. That is what the second if block does, using the same principle. In this case we only need to do one quarter of the total circumference as the rest is derived by mirroring.
I don't know Next.js/React/SVG, so here is python.
At least the matrix indices should be correct regardless of the language.
In the code I built a square matrix representing the ascii drawing. In the matrix, 0 represents an empty ascii space; 1 represents an empty electron spot; 2 represents an actual electron. Substitute 0-> , 1->-, 2->x to get your ascii drawing.
First I wrote a function make_layer that builds a layer as a 1d array. For instance, you can build the third layer of manganese by calling make_layer(3, 18, 13) because it's layer number 3, which holds a total of 18 spots, but only 13 electrons. The logic for this function is that we can distribute k items 0..k-1 evenly among n spots 0..n-1 by placing item j at spot floor(j * n / k). We do this twice: first we place k = n_electrons electrons among n = layer_size spots; then we place these k = layer_size spots inside a blank string of length n = ascii_layer_size.
Then I wrote a function add_layer_to_matrix that wraps a layer around inside a square matrix. This requires juggling with indices. I added a diagram below to explain visually.
And finally, function draw_square declares a square matrix of the appropriate size, and iterates on the layers to build them and wrap them around.
data_as_string = '''Hydrogen,1
Helium,2
Lithium,2:1
Beryllium,2:2
Berkelium,2:8:18:32:27:8:2
Oganesson,2:8:18:32:32:18:8'''
data_as_dict = {row[0]: list(map(int,row[1].split(':'))) for line in data_as_string.split('\n') if len(line) >= 1 and (row:=line.strip().split(','))}
# {'Hydrogen': [1], 'Helium': [2], 'Lithium': [2, 1], 'Beryllium': [2, 2], 'Berkelium': [2, 8, 18, 32, 27, 8, 2], 'Oganesson': [2, 8, 18, 32, 32, 18, 8]}
import numpy as np
def make_layer(radius, layer_size, n_electrons):
square_side_length = 2 * radius + 1
electrons_in_layer = np.ones(layer_size, dtype=int)
electrons_in_layer[(np.arange(n_electrons) * layer_size) // n_electrons] = 2
ascii_layer_size = (square_side_length - 1) * 4
layer = np.zeros(ascii_layer_size, dtype=int)
layer[(np.arange(layer_size) * ascii_layer_size) // layer_size] = electrons_in_layer
return layer
def add_layer_to_matrix(matrix, layer, R, r):
i = R - r
l = 2 * r + 1
L = 2 * R + 1
matrix[i, R:L-1-i] = layer[:r]
matrix[i:L-1-i, L-1-i] = layer[r:3*r]
matrix[L-1-i, i+1:L-1-i+1] = layer[5*r-1:3*r-1:-1]
matrix[i+1:L-1-i+1, i] = layer[7*r-1:5*r-1:-1]
matrix[i, i:R] = layer[7*r:]
def draw_square(layer_list):
max_radius = len(layer_list)
square_side_length = 2 * max_radius + 1
square_matrix = np.zeros((square_side_length, square_side_length), dtype=int)
for (layer_size, (radius, n_electrons)) in zip((2,8,18,32,32,18,8), enumerate(layer_list, start=1)):
layer = make_layer(radius, layer_size, n_electrons)
add_layer_to_matrix(square_matrix, layer, max_radius, radius)
return square_matrix
for element_name in ('Hydrogen', 'Beryllium', 'Berkelium'):
mat = draw_square(data_as_dict[element_name])
ascii = '\n'.join(''.join(' -x'[i] for i in row) for row in mat)
print(element_name, data_as_dict[element_name])
print(ascii)
print()
Output:
Hydrogen [1]
x
-
Beryllium [2, 2]
- x -
x
- -
x
- x -
Berkelium [2, 8, 18, 32, 27, 8, 2]
- x -
- - x - x
xxx- xxxx x
x xxxxxxxxxx-
xxxx xxx x-
xxxx x xxxxx
--x x xx
- xxxx xxxx -
xx x xx-
xxxxx x xxxx
xx xxx xxxx
-xxxxxxxxxx x
x -xxx xxx-
x - x - -
- x -
The arithmetic to wrap a layer around in the matrix follows this diagram:

What is a decrease key operation for doubly linked list?

I came across this data structure description on Gate overflow:
𝑁 items are stored in a sorted doubly linked list. For a delete operation, a pointer is provided to the record to be deleted. For a decrease-key operation, a pointer is provided to the record on which the operation is to be performed.
I don't understand what this decrease-key operation does. What does it do, and what is its time complexity?
It is important to realise that the linked list is sorted and is supposed to stay sorted after every manipulation.
The key of a node (a "record") is the node's value by which the list is sorted. A node might have other data (payload), but that is irrelevant for any of the actions that are listed.
Here is an example of such a sorted doubly linked list, and a given pointer to a node:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ key: 1 │ │ key: 2 │ │ key: 2 │ │ key: 2 │ │ key: 3 │
│ next───┼───►│ next───┼───►│ next───┼───►│ next───┼───►│ next │
│ prev │◄───┼───prev │◄───┼───prev │◄───┼───prev │◄───┼───prev │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
▲
│
given pointer
If we perform a decrease key operation on the referenced node, then not only should its key decrease -- the node should also be moved to its new, sorted position:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ key: 1 │ │ key: 1 │ │ key: 2 │ │ key: 2 │ │ key: 3 │
│ next───┼───►│ next───┼───►│ next───┼───►│ next───┼───►│ next │
│ prev │◄───┼───prev │◄───┼───prev │◄───┼───prev │◄───┼───prev │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
▲
│
moved node
The work to remove the node and insert it again has in itself a constant time complexity, but the work to find the node's new location has not.
In the worst case we would get a (long) list where each node has the same key value, and we get a reference to the last node. The decrease key operation then has to traverse the list from that last node to the first node. Since we only get one reference to a node, all we can do is follow the prev links until we arrive at the spot where the node should end up. This is O(n) in the worst case.
Here I assume that a decrease-key operation will decrease the key with 1. It can also be extended to mean that a parameter is given specifying how much should be subtracted from the key. The reasoning however remains the same: the algorithm will have to search backwards in the list, starting from the given node reference, in order to find the right insertion spot for this node, based on its updated key value.

Clickhouse: runningAccumulate() does not work as I expect

Say, we have a table testint.
SELECT *
FROM testint
┌─f1─┬─f2─┐
│ 2 │ 3 │
│ 2 │ 3 │
│ 4 │ 5 │
│ 4 │ 5 │
│ 6 │ 7 │
│ 6 │ 7 │
└────┴────┘
We try to query runningAccumulate() with sumState().
SELECT runningAccumulate(col)
FROM
(
SELECT sumState(f1) AS col
FROM testint
GROUP BY f1
)
┌─runningAccumulate(col)─┐
│ 8 │
│ 12 │
│ 24 │
└────────────────────────┘
Why is the first row in the response 8, and not 4? If we are grouping by f1, the first row seems to be 4 (we do sum the first 2 and the second 2 in the column f1).
For accumulate-functions the order of elements is important, so just add ORDER BY to fix it:
SELECT runningAccumulate(col)
FROM
(
SELECT sumState(f1) AS col
FROM testint
GROUP BY f1
ORDER BY f1 ASC /* <-- */
)
You got the result [8, 12, 24] for input data [8, 4, 12] when should be used the ordered input - [4, 8, 12].

Aggregate query over multiple columns (one is an array) in clickhouse

I'm trying to get aggregates values for each att1, and att2 column, and also for each value of the arrays in att3 column.
As far I tried:
create table test(value Float32, att1 String, att2 String, att3 Array(String))
ENGINE=MergeTree() ORDER BY ();
INSERT INTO test VALUES (2.0, 'a', 'Z', ['sports', 'office', 'anothertag'])
INSERT INTO test VALUES (4.0, 'b', 'X', ['sports', 'office', 'tag'])
INSERT INTO test VALUES (6.0, 'b', 'X', ['sports', 'internet', 'planes'])
SELECT * from test;
┌─value─┬─att1─┬─att2─┬─att3───────────────────────────┐
│ 6 │ b │ X │ ['sports','internet','planes'] │
└───────┴──────┴──────┴────────────────────────────────┘
┌─value─┬─att1─┬─att2─┬─att3─────────────────────────────┐
│ 2 │ a │ Z │ ['sports','office','anothertag'] │
└───────┴──────┴──────┴──────────────────────────────────┘
┌─value─┬─att1─┬─att2─┬─att3──────────────────────┐
│ 4 │ b │ X │ ['sports','office','tag'] │
└───────┴──────┴──────┴───────────────────────────┘
I want to get the aggregate -sum(value)- for each different attribute.
I have it working for att1 and att2 columns with:
SELECT
att1,
att2,
sum(value)
FROM test
GROUP BY
att1,
att2
WITH CUBE
Result:
┌─att1─┬─att2─┬─sum(value)─┐
│ b │ X │ 10 │
│ a │ Z │ 2 │
└──────┴──────┴────────────┘
┌─att1─┬─att2─┬─sum(value)─┐
│ a │ │ 2 │
│ b │ │ 10 │
└──────┴──────┴────────────┘
┌─att1─┬─att2─┬─sum(value)─┐
│ │ Z │ 2 │
│ │ X │ 10 │
└──────┴──────┴────────────┘
┌─att1─┬─att2─┬─sum(value)─┐
│ │ │ 12 │
└──────┴──────┴────────────┘
Which gives me more than needed, but results two and three give correct results.
But I also need the value for each value on att3, I have it working in another query, but when trying to make a single query:
SELECT
att1,
att2,
arrayJoin(att3) AS tags,
sum(value)
FROM test
GROUP BY
att1,
att2,
tags
WITH CUBE
Which gives (among other things):
┌─att1─┬─att2─┬─tags─┬─sum(value)─┐
│ a │ │ │ 6 │
│ b │ │ │ 30 │
└──────┴──────┴──────┴────────────┘
┌─att1─┬─att2─┬─tags───────┬─sum(value)─┐
│ │ │ tag │ 4 │
│ │ │ anothertag │ 2 │
│ │ │ planes │ 6 │
│ │ │ sports │ 12 │
│ │ │ internet │ 6 │
│ │ │ office │ 6 │
└──────┴──────┴────────────┴────────────┘
Since arrayJoin 'unfolds' array into rows, now values of sum(value) in att1 are not accurate.
I've also tried the LEFT ARRAY JOIN syntax with same results.
Updated:
The ideal result would be something like:
┌─'att1'─┬─'att2'─┬─'tags'─┬─'sum(value)'─┐
│ a │ │ │ 2 │
│ b │ │ │ 10 │
│ │ X │ │ 10 │
│ │ Z │ │ 2 │
│ │ │ sports │ 12 │
│ │ │ office │ 6 │
│ │ │ anot.. │ 2 │
│ │ │ tag │ 4 │
│ │ │internet│ 6 │
│ │ │planes │ 6 │
└────────┴────────┴────────┴──────────────┘
Could be in different rows (results), but ideally in one single query.
SELECT
sumMap(([att1], [value])) AS r1,
sumMap(([att2], [value])) AS r2,
sumMap((att3, replicate(value, att3))) AS r3
FROM test
┌─r1─────────────────┬─r2─────────────────┬─r3──────────────────────────────────────────────────────────────────────────┐
│ (['a','b'],[2,10]) │ (['X','Z'],[10,2]) │ (['anothertag','internet','office','planes','sports','tag'],[2,6,6,6,12,4]) │
└────────────────────┴────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
SELECT
(arrayJoin(arrayZip((arrayJoin([sumMap(([att1], [value])), sumMap(([att2], [value])), sumMap((att3, replicate(value, att3)))]) AS r).1, r.2)) AS x).1 AS y,
x.2 AS z
FROM test
┌─y──────────┬──z─┐
│ a │ 2 │
│ b │ 10 │
│ X │ 10 │
│ Z │ 2 │
│ anothertag │ 2 │
│ internet │ 6 │
│ office │ 6 │
│ planes │ 6 │
│ sports │ 12 │
│ tag │ 4 │
└────────────┴────┘
I think the more straightforward way is to combine two queries:
SELECT
att1,
att2,
'' AS tags,
sum(value)
FROM test
GROUP BY
att1,
att2
WITH CUBE
UNION ALL
SELECT
'' AS att1,
'' AS att2,
arrayJoin(att3) AS tags,
sum(value)
FROM test
GROUP BY tags
/*
┌─att1─┬─att2─┬─tags───────┬─sum(value)─┐
│ │ │ internet │ 6 │
│ │ │ sports │ 12 │
│ │ │ office │ 6 │
│ │ │ tag │ 4 │
│ │ │ planes │ 6 │
│ │ │ anothertag │ 2 │
└──────┴──────┴────────────┴────────────┘
┌─att1─┬─att2─┬─tags─┬─sum(value)─┐
│ b │ X │ │ 10 │
│ a │ Z │ │ 2 │
└──────┴──────┴──────┴────────────┘
┌─att1─┬─att2─┬─tags─┬─sum(value)─┐
│ a │ │ │ 2 │
│ b │ │ │ 10 │
└──────┴──────┴──────┴────────────┘
┌─att1─┬─att2─┬─tags─┬─sum(value)─┐
│ │ Z │ │ 2 │
│ │ X │ │ 10 │
└──────┴──────┴──────┴────────────┘
┌─att1─┬─att2─┬─tags─┬─sum(value)─┐
│ │ │ │ 12 │
└──────┴──────┴──────┴────────────┘
*/

ClickHouse: How to find databases under a certain cluster?

I have a clickhouse with two clusters deployed on same machines like
clickhouse :) select * from system.clusters;
SELECT *
FROM system.clusters
┌─cluster────────────────────┬─shard_num─┬─shard_weight─┬─replica_num─┬─host_name─┬─host_address─┬─port─┬─is_local─┬─user────┬─default_database─┐
│ cluster-******** │ 1 │ 1 │ 1 │ ******* │ *.*.*.* │ 9000 │ 1 │ default │ │
│ cluster-******** │ 1 │ 1 │ 2 │ ******* │ *.*.*.* │ 9000 │ 1 │ default │ │
│ cluster-******** │ 2 │ 1 │ 1 │ ******* │ *.*.*.* │ 9000 │ 0 │ default │ │
│ cluster-******** │ 2 │ 1 │ 2 │ ******* │ *.*.*.* │ 9000 │ 0 │ default │ │
│ cluster-********-********* │ 1 │ 1 │ 1 │ ******* │ *.*.*.* │ 9000 │ 1 │ default │ │
│ cluster-********-********* │ 2 │ 1 │ 1 │ ******* │ *.*.*.* │ 9000 │ 0 │ default │ │
│ cluster-********-********* │ 3 │ 1 │ 1 │ ******* │ *.*.*.* │ 9000 │ 0 │ default │ │
│ cluster-********-********* │ 4 │ 1 │ 1 │ ******* │ *.*.*.* │ 9000 │ 0 │ default │ │
└────────────────────────────┴───────────┴──────────────┴─────────────┴────────────────────────────┴──────────────┴──────┴──────────┴─────────┴──────────────────┘
8 rows in set. Elapsed: 0.004 sec.
clickhouse :) select * from system.databases;
SELECT *
FROM system.databases
┌─name─────────────────┬─engine───┬─data_path───────────────────────────────────┬─metadata_path───────────────────────────────────┐
│ default │ Ordinary │ /data/clickhouse/data/default/ │ /data/clickhouse/metadata/default/ │
│ aaaaaaaaaaaaaaaaaaaa │ Ordinary │ /data/clickhouse/data/aaaaaaaaaaaaaaaaaaaa/ │ /data/clickhouse/metadata/aaaaaaaaaaaaaaaaaaaa/ │
│ bbbbb │ Ordinary │ /data/clickhouse/data/bbbbb/ │ /data/clickhouse/metadata/bbbbb/ │
│ system │ Ordinary │ /data/clickhouse/data/system/ │ /data/clickhouse/metadata/system/ │
└──────────────────────┴──────────┴─────────────────────────────────────────────┴─────────────────────────────────────────────────┘
4 rows in set. Elapsed: 0.001 sec.
Then I wanna to analyse all databases belonged to certain clusters, but table databases as well as clusters doesn't contain any information about cluster-database relations, so how to find which databases belonged to a certain cluster, Please help if you have any idea.
Databases and tables don't belong to clusters. There is no any relation.
A table can be queried using any cluster or without a cluster.

Resources