Solve binary expression tree for given variable - algorithm
For the expression:
res = ((v + 10) * (i - 2)) / 3
My program builds the following binary tree:
(=)
├─ (res)
└─ (/)
├─ (3)
└─ (*)
├─ (+)
│ ├─ (v)
│ └─ (10)
└─ (-)
├─ (i)
└─ (2)
To obtain a solution for variable v, I manually solve it like this:
((v + 10) * (i - 2)) / 3 = res
(v + 10) * (i - 2) = res * 3
v + 10 = (res * 3) / (i - 2)
v = ((res * 3) / (i - 2)) - 10
How can I programmatically do the same operation?
For example, given the binary tree and its specific node (in this case v), rebuild this tree so that it evaluates to that node:
(=)
├─ (v) <-- my node
└─ (-)
├─ (10)
└─ (/)
├─ ...
└─ ...
First some comments on the input/output:
The first operand of an operator should consistently be a left child in the tree representation, while the second operand should be a right child.
So, since 3 is a divisor (and thus the second operand of a division), it should occur in the input tree as a second child. So the input tree for res = ((v + 10) * (i - 2)) / 3 should be:
(=)
├─ (res)
└─ (/)
├─ (*)
│ ├─ (+)
│ │ ├─ (v)
│ │ └─ (10)
│ └─ (-)
│ ├─ (i)
│ └─ (2)
└─ (3)
Similarly, the output should have 10 as the second child of the - node:
(=)
├─ (v)
└─ (-)
├─ (/)
│ ├─ (*)
│ │ ├─ (3)
│ │ └─ (res)
│ └─ (-)
│ ├─ (i)
│ └─ (2)
└─ (10)
Some (other) assumptions:
The root should have = as symbol, and have two children.
The target symbol (here "v") should only occur once.
The non-root operators should be +, -, * or /, and should be nodes with two children (binary).
Non-operator nodes should be leaves.
Here is an implementation in JavaScript, which defines a Node class, including the method that will mutate its subtrees so to bring the target node at left side of the equation and all the rest at the right side:
class Node {
constructor(symbol, left=null, right=null) {
this.symbol = symbol;
this.left = left;
this.right = right;
}
toString() {
return !this.left && !this.right ? "(" + this.symbol + ")"
: "(" + this.symbol + ")\n"
+ " ├─ " + this.left.toString().replace(/\n/g, "\n │ ") + "\n"
+ " └─ " + this.right.toString().replace(/\n/g, "\n ");
}
markPathTo(target, found=false) {
if (this.left) this.left.markPathTo(target, found);
if (this.right) this.right.markPathTo(target, found);
if (this.mark) {
if (found) throw "symbol (" + target + ") occurs more than once";
found = true;
}
this.mark = this.symbol == target || this.left && this.left.mark || this.right && this.right.mark;
}
resolve(target) {
if (this.symbol != "=") throw "Can only resolve an equality, not (" + symbol + ")";
this.markPathTo(target);
if (!this.mark) throw "Could not find symbol (" + target + ")";
// Make sure the target is in the left subtree
if (!this.left.mark) {
// swap sides of equation
[this.left, this.right] = [this.right, this.left];
}
let operators = {
"+": "-",
"-": "+",
"*": "/",
"/": "*"
};
let op = this.left.symbol;
while (op !== target) {
if (!(op in operators)) throw "Unsupported operator (" + op + ")";
let toMove = this.left.left.mark ? this.left.right : this.left.left;
if (op === "+" || op === "*" || this.left.right.mark) {
this.right = new Node(operators[op], this.right, toMove);
} else {
this.right = new Node(op, toMove, this.right);
}
this.left = this.left.left.mark ? this.left.left : this.left.right;
op = this.left.symbol;
}
}
}
// Demo tree from question (with corrected position of 3)
let tree = new Node("=",
new Node("res"),
new Node("/",
new Node("*",
new Node("+",
new Node("v"),
new Node(10)
),
new Node("-",
new Node("i"),
new Node(2)
)
),
new Node(3)
)
);
// Display the input tree
console.log(tree.toString());
// Apply the algorithm
tree.resolve("v");
// Display the resulting tree
console.log(tree.toString());
Of course, this could be extended to deal with more scenarios, like exponentiation, multiple occurrences of the target symbol, where polynomials are resolved, ...etc. This is however beyond the scope of the question.
Related
Join two datasets with key duplicates one by one
I need to join two datasets from e.g. left and right source to match values by some keys. Datasets can contain duplicates: ┌─key─┬─value──┬─source──┐ │ 1 │ val1 │ left │ │ 1 │ val1 │ left │ << duplicate from left source │ 1 │ val1 │ left │ << another duplicate from left source │ 1 │ val1 │ right │ │ 1 │ val1 │ right │ << duplicate from right source │ 2 │ val2 │ left │ │ 2 │ val3 │ right │ └─────┴────────┴─-----───┘ I cant use full join, it gives cartesian products of all duplicates. I am trying to use group by instead: select `key`, anyIf(value, source = 'left') as left_value, anyIf(value, source = 'right') as right_value from test_raw group by key; It works good, but is there any way to match left and right duplicates? Expected result: ┌─key─┬─left_value─┬─right_value─┐ │ 1 │ val1 │ val1 │ │ 1 │ val1 │ val1 │ │ 1 │ val1 │ │ │ 2 │ val2 │ val3 │ └─────┴────────────┴─────────────┘ Scripts to reproduce: create table test_raw (`key` Int64,`value` String,`source` String) ENGINE = Memory; insert into test_raw (`key`,`value`,`source`) values (1, 'val1', 'left'), (1, 'val1', 'left'), (1, 'val1', 'left'), (1, 'val1', 'right'), (1, 'val1', 'right'), (2, 'val2', 'left'), (2, 'val3', 'right'); select `key`, anyIf(value, source = 'left') as left_value, anyIf(value, source = 'right') as right_value from test_raw group by key;
SELECT key, left_value, right_value FROM ( SELECT key, arraySort(groupArrayIf(value, source = 'left')) AS l, arraySort(groupArrayIf(value, source = 'right')) AS r, arrayMap(i -> (l[i + 1], r[i + 1]), range(greatest(length(l), length(r)))) AS t FROM test_raw GROUP BY key ) ARRAY JOIN t.1 AS left_value, t.2 AS right_value ORDER BY key ASC ┌─key─┬─left_value─┬─right_value─┐ │ 1 │ val1 │ val1 │ │ 1 │ val1 │ val1 │ │ 1 │ val1 │ │ │ 1 │ val1 │ │ │ 2 │ val2 │ val3 │ └─────┴────────────┴─────────────┘
Clickhouse - How can I get distinct values from all values inside an array type column
On a clickhouse database, I've an array type as column and I want to make an distinct for all elements inside them Instead of getting this Select distinct errors.message_grouping_fingerprint FROM views WHERE (session_date >= toDate('2022-07-21')) and (session_date < toDate('2022-07-22')) and notEmpty(errors.message) = 1 and project_id = 162 SETTINGS distributed_group_by_no_merge=0 [-8964675922652096680,-8964675922652096680] [-8964675922652096680] [-8964675922652096680,-8964675922652096680,-8964675922652096680,-8964675922652096680,-8964675922652096680,-8964675922652096680,-8964675922652096680,-827009490898812590,-8964675922652096680,-8964675922652096680,-8964675922652096680,-8964675922652096680] [-8964675922652096680,-8964675922652096680,-8964675922652096680] [-827009490898812590] [-1660275624223727714,-1660275624223727714] [1852265010681444046] [-2552644061611887546] [-7142229185866234523] [-7142229185866234523,-7142229185866234523] To get this -8964675922652096680 -827009490898812590 -1660275624223727714 1852265010681444046 -2552644061611887546 -7142229185866234523 and finally, to make a count of all them as 6
groupUniqArrayArray select arrayMap( i-> rand()%10, range(rand()%3+1)) arr from numbers(10); ┌─arr─────┐ │ [0] │ │ [1] │ │ [7,7,7] │ │ [8,8] │ │ [9,9,9] │ │ [6,6,6] │ │ [2,2] │ │ [8,8,8] │ │ [2] │ │ [8,8,8] │ └─────────┘ SELECT groupUniqArrayArray(arr) AS uarr, length(uarr) FROM ( SELECT arrayMap(i -> (rand() % 10), range((rand() % 3) + 1)) AS arr FROM numbers(10) ) ┌─uarr──────────────┬─length(groupUniqArrayArray(arr))─┐ │ [0,5,9,4,2,8,7,3] │ 8 │ └───────────────────┴──────────────────────────────────┘ ARRAY JOIN SELECT A FROM ( SELECT arrayMap(i -> (rand() % 10), range((rand() % 3) + 1)) AS arr FROM numbers(10) ) ARRAY JOIN arr AS A GROUP BY A ┌─A─┐ │ 0 │ │ 1 │ │ 4 │ │ 5 │ │ 6 │ │ 9 │ └───┘
How to rewrite this deprecated expression using do and "by", with "groupby" (Julia)
The goal is to generate fake data. We generate a set of parameters, ## Simulated data df_3 = DataFrame(y = [0,1], size = [250,250], x1 =[2.,0.], x2 =[-1.,-2.]) Now, I want to generate the fake data per se, df_knn =by(df_3, :y) do df DataFrame(x_1 = rand(Normal(df[1,:x1],1), df[1,:size]), x_2 = rand(Normal(df[1,:x2],1), df[1,:size])) end How I can replace by with groupby, here? SOURCE: This excerpt is from the book, Data Science with Julia (2019).
I think this is what you mean here: julia> combine(groupby(df_3, :y)) do df DataFrame(x_1 = rand(Normal(df[1,:x1],1), df[1,:size]), x_2 = rand(Normal(df[1,:x2],1), df[1,:size])) end 500×3 DataFrame Row │ y x_1 x_2 │ Int64 Float64 Float64 ─────┼───────────────────────────── 1 │ 0 1.88483 0.890807 2 │ 0 2.50124 -0.280708 3 │ 0 1.1857 0.823002 ⋮ │ ⋮ ⋮ ⋮ 498 │ 1 -0.611168 -0.856527 499 │ 1 0.491412 -3.09562 500 │ 1 0.242016 -1.42652 494 rows omitted
What does head.next actually mean in linked lists?
I have created a linked list with a Node class having Node next and int data. What does head.next mean? I'm confused. Consider this list: 1 > 2 > 3 > 4, and 1 is the head. Now if I execute head=head.next, head is now pointing to 2. But when I execute head.next=null, 1 is pointing to null. Can someone explain why this happens?
It may help to visualise things. Consider this list: 1 > 2 > 3 > 4, and 1 is the head. head ↓ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ value: 1 │ │ value: 2 │ │ value: 3 │ │ value: 4 │ │ next: ———————→ │ next: ———————→ │ next: ———————→ │ next: null│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ if I execute head=head.next, head is now pointing to 2 head ↓ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ value: 1 │ │ value: 2 │ │ value: 3 │ │ value: 4 │ │ next: ———————→ │ next: ———————→ │ next: ———————→ │ next: null│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ but when I execute head.next=null, 1 is pointing to null. head ↓ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ value: 1 │ │ value: 2 │ │ value: 3 │ │ value: 4 │ │ next: null│ │ next: ———————→ │ next: ———————→ │ next: null│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ In conclusion: when you assign a value to head, you change what head refers to. This does not affect any next reference: the links between the nodes remain unchanged. You just might have lost access to the first node when head was the only reference to it. And in that case you actually make the list one node shorter*, as it now starts at 2. *(When a node becomes unreachable, it no longer has any practical use. The garbage collector may free the memory it occupied.) when you don't assign a value to head, but do head.next =, then you assign a value to a property of the node that head references, and that affects the list. This is called mutation. On the other hand, head will still refer to node 1, since you didn't assign a value to the head variable.
Assuming that you are referring to the linked list example below #include <bits/stdc++.h> using namespace std; class Node { public: int data; Node* next; }; int main() { Node* head = NULL; Node* second = NULL; Node* third = NULL; head = new Node(); second = new Node(); third = new Node(); head->data = 1; head->next = second; second->data = 2; second->next = third; third->data = 3; third->next = NULL; return 0; } here Node is a class in which a pointer of that node class next is declared and a integer type data is used to store the data values that the next pointer will point to class Node { public: int data; Node* next; }; head here is an object of class Node which is created and by using its pointer declared inside of it previosly, we point to a next Node object second using head.next = second. So now the pointer of our head object is pointing to the address of our second object we created, this way the head object is linked with our second object. data variable is used to store the value given to every object or in this case every node head = new Node(); second = new Node(); third = new Node(); head->data = 1; head->next = second; second->data = 2; second->next = third; third->data = 3; third->next = NULL; comming to your second point, i don't get it why would you point head.next to head(itself) and head.next = NULL gives nothing as you have to point the pointer to a second object to create a linked list.
How to avoid memory allocations in custom Julia iterators?
Consider the following Julia "compound" iterator: it merges two iterators, a and b, each of which are assumed to be sorted according to order, to a single ordered sequence: struct MergeSorted{T,A,B,O} a::A b::B order::O MergeSorted(a::A, b::B, order::O=Base.Order.Forward) where {A,B,O} = new{promote_type(eltype(A),eltype(B)),A,B,O}(a, b, order) end Base.eltype(::Type{MergeSorted{T,A,B,O}}) where {T,A,B,O} = T #inline function Base.iterate(self::MergeSorted{T}, state=(iterate(self.a), iterate(self.b))) where T a_result, b_result = state if b_result === nothing a_result === nothing && return nothing a_curr, a_state = a_result return T(a_curr), (iterate(self.a, a_state), b_result) end b_curr, b_state = b_result if a_result !== nothing a_curr, a_state = a_result Base.Order.lt(self.order, a_curr, b_curr) && return T(a_curr), (iterate(self.a, a_state), b_result) end return T(b_curr), (a_result, iterate(self.b, b_state)) end This code works, but is type-instable since the Julia iteration facilities are inherently so. For most cases, the compiler can work this out automatically, however, here it does not work: the following test code illustrates that temporaries are created: >>> x = MergeSorted([1,4,5,9,32,44], [0,7,9,24,134]); >>> sum(x); >>> #time sum(x); 0.000013 seconds (61 allocations: 2.312 KiB) Note the allocation count. Is there any way to efficiently debug such situations other than playing around with the code and hoping that the compiler will be able to optimize out the type ambiguities? Does anyone know there any solution in this specific case that does not create temporaries?
How to diagnose the problem? Answer: use #code_warntype Run: julia> #code_warntype iterate(x, iterate(x)[2]) Variables #self#::Core.Const(iterate) self::MergeSorted{Int64, Vector{Int64}, Vector{Int64}, Base.Order.ForwardOrdering} state::Tuple{Tuple{Int64, Int64}, Tuple{Int64, Int64}} #_4::Int64 #_5::Int64 #_6::Union{} #_7::Int64 b_state::Int64 b_curr::Int64 a_state::Int64 a_curr::Int64 b_result::Tuple{Int64, Int64} a_result::Tuple{Int64, Int64} Body::Tuple{Int64, Any} 1 ─ nothing │ Core.NewvarNode(:(#_4)) │ Core.NewvarNode(:(#_5)) │ Core.NewvarNode(:(#_6)) │ Core.NewvarNode(:(b_state)) │ Core.NewvarNode(:(b_curr)) │ Core.NewvarNode(:(a_state)) │ Core.NewvarNode(:(a_curr)) │ %9 = Base.indexed_iterate(state, 1)::Core.PartialStruct(Tuple{Tuple{Int64, Int64}, Int64}, Any[Tuple{Int64, Int64}, Core.Const(2)]) │ (a_result = Core.getfield(%9, 1)) │ (#_7 = Core.getfield(%9, 2)) │ %12 = Base.indexed_iterate(state, 2, #_7::Core.Const(2))::Core.PartialStruct(Tuple{Tuple{Int64, Int64}, Int64}, Any[Tuple{Int64, Int64}, Core.Const(3)]) │ (b_result = Core.getfield(%12, 1)) │ %14 = (b_result === Main.nothing)::Core.Const(false) └── goto #3 if not %14 2 ─ Core.Const(:(a_result === Main.nothing)) │ Core.Const(:(%16)) │ Core.Const(:(return Main.nothing)) │ Core.Const(:(Base.indexed_iterate(a_result, 1))) │ Core.Const(:(a_curr = Core.getfield(%19, 1))) │ Core.Const(:(#_6 = Core.getfield(%19, 2))) │ Core.Const(:(Base.indexed_iterate(a_result, 2, #_6))) │ Core.Const(:(a_state = Core.getfield(%22, 1))) │ Core.Const(:(($(Expr(:static_parameter, 1)))(a_curr))) │ Core.Const(:(Base.getproperty(self, :a))) │ Core.Const(:(Main.iterate(%25, a_state))) │ Core.Const(:(Core.tuple(%26, b_result))) │ Core.Const(:(Core.tuple(%24, %27))) └── Core.Const(:(return %28)) 3 ┄ %30 = Base.indexed_iterate(b_result, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)]) │ (b_curr = Core.getfield(%30, 1)) │ (#_5 = Core.getfield(%30, 2)) │ %33 = Base.indexed_iterate(b_result, 2, #_5::Core.Const(2))::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(3)]) │ (b_state = Core.getfield(%33, 1)) │ %35 = (a_result !== Main.nothing)::Core.Const(true) └── goto #6 if not %35 4 ─ %37 = Base.indexed_iterate(a_result, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)]) │ (a_curr = Core.getfield(%37, 1)) │ (#_4 = Core.getfield(%37, 2)) │ %40 = Base.indexed_iterate(a_result, 2, #_4::Core.Const(2))::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(3)]) │ (a_state = Core.getfield(%40, 1)) │ %42 = Base.Order::Core.Const(Base.Order) │ %43 = Base.getproperty(%42, :lt)::Core.Const(Base.Order.lt) │ %44 = Base.getproperty(self, :order)::Core.Const(Base.Order.ForwardOrdering()) │ %45 = a_curr::Int64 │ %46 = (%43)(%44, %45, b_curr)::Bool └── goto #6 if not %46 5 ─ %48 = ($(Expr(:static_parameter, 1)))(a_curr)::Int64 │ %49 = Base.getproperty(self, :a)::Vector{Int64} │ %50 = Main.iterate(%49, a_state)::Union{Nothing, Tuple{Int64, Int64}} │ %51 = Core.tuple(%50, b_result)::Tuple{Union{Nothing, Tuple{Int64, Int64}}, Tuple{Int64, Int64}} │ %52 = Core.tuple(%48, %51)::Tuple{Int64, Tuple{Union{Nothing, Tuple{Int64, Int64}}, Tuple{Int64, Int64}}} └── return %52 6 ┄ %54 = ($(Expr(:static_parameter, 1)))(b_curr)::Int64 │ %55 = a_result::Tuple{Int64, Int64} │ %56 = Base.getproperty(self, :b)::Vector{Int64} │ %57 = Main.iterate(%56, b_state)::Union{Nothing, Tuple{Int64, Int64}} │ %58 = Core.tuple(%55, %57)::Tuple{Tuple{Int64, Int64}, Union{Nothing, Tuple{Int64, Int64}}} │ %59 = Core.tuple(%54, %58)::Tuple{Int64, Tuple{Tuple{Int64, Int64}, Union{Nothing, Tuple{Int64, Int64}}}} └── return %59 and you see that there are too many types of return value, so Julia gives up specializing them (and just assumes the second element of return type is Any). How to fix the problem? Answer: reduce the number of return type options of iterate. Here is a quick write up (I do not claim it is most terse and have not tested it extensively so there might be some bug, but it was simple enough to write quickly using your code to show how one could approach your problem; note that I use special branches when one of the collections is empty as then it should be faster to just iterate one collection): struct MergeSorted{T,A,B,O,F1,F2} a::A b::B order::O fa::F1 fb::F2 function MergeSorted(a::A, b::B, order::O=Base.Order.Forward) where {A,B,O} fa, fb = iterate(a), iterate(b) F1 = typeof(fa) F2 = typeof(fb) new{promote_type(eltype(A),eltype(B)),A,B,O,F1,F2}(a, b, order, fa, fb) end end Base.eltype(::Type{MergeSorted{T,A,B,O}}) where {T,A,B,O} = T struct State{Ta, Tb} a::Union{Nothing, Ta} b::Union{Nothing, Tb} end function Base.iterate(self::MergeSorted{T,A,B,O,Nothing,Nothing}) where {T,A,B,O} return nothing end function Base.iterate(self::MergeSorted{T,A,B,O,F1,Nothing}) where {T,A,B,O,F1} return self.fa end function Base.iterate(self::MergeSorted{T,A,B,O,F1,Nothing}, state) where {T,A,B,O,F1} return iterate(self.a, state) end function Base.iterate(self::MergeSorted{T,A,B,O,Nothing,F2}) where {T,A,B,O,F2} return self.fb end function Base.iterate(self::MergeSorted{T,A,B,O,Nothing,F2}, state) where {T,A,B,O,F2} return iterate(self.b, state) end #inline function Base.iterate(self::MergeSorted{T,A,B,O,F1,F2}) where {T,A,B,O,F1,F2} a_result, b_result = self.fa, self.fb return iterate(self, State{F1,F2}(a_result, b_result)) end #inline function Base.iterate(self::MergeSorted{T,A,B,O,F1,F2}, state::State{F1,F2}) where {T,A,B,O,F1,F2} a_result, b_result = state.a, state.b if b_result === nothing a_result === nothing && return nothing a_curr, a_state = a_result return T(a_curr), State{F1,F2}(iterate(self.a, a_state), b_result) end b_curr, b_state = b_result if a_result !== nothing a_curr, a_state = a_result Base.Order.lt(self.order, a_curr, b_curr) && return T(a_curr), State{F1,F2}(iterate(self.a, a_state), b_result) end return T(b_curr), State{F1,F2}(a_result, iterate(self.b, b_state)) end And now you have: julia> x = MergeSorted([1,4,5,9,32,44], [0,7,9,24,134]); julia> sum(x) 269 julia> #allocated sum(x) 0 julia> #code_warntype iterate(x, iterate(x)[2]) Variables #self#::Core.Const(iterate) self::MergeSorted{Int64, Vector{Int64}, Vector{Int64}, Base.Order.ForwardOrdering, Tuple{Int64, Int64}, Tuple{Int64, Int64}} state::State{Tuple{Int64, Int64}, Tuple{Int64, Int64}} #_4::Int64 #_5::Int64 #_6::Int64 b_state::Int64 b_curr::Int64 a_state::Int64 a_curr::Int64 b_result::Union{Nothing, Tuple{Int64, Int64}} a_result::Union{Nothing, Tuple{Int64, Int64}} Body::Union{Nothing, Tuple{Int64, State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}}} 1 ─ nothing │ Core.NewvarNode(:(#_4)) │ Core.NewvarNode(:(#_5)) │ Core.NewvarNode(:(#_6)) │ Core.NewvarNode(:(b_state)) │ Core.NewvarNode(:(b_curr)) │ Core.NewvarNode(:(a_state)) │ Core.NewvarNode(:(a_curr)) │ %9 = Base.getproperty(state, :a)::Union{Nothing, Tuple{Int64, Int64}} │ %10 = Base.getproperty(state, :b)::Union{Nothing, Tuple{Int64, Int64}} │ (a_result = %9) │ (b_result = %10) │ %13 = (b_result === Main.nothing)::Bool └── goto #5 if not %13 2 ─ %15 = (a_result === Main.nothing)::Bool └── goto #4 if not %15 3 ─ return Main.nothing 4 ─ %18 = Base.indexed_iterate(a_result::Tuple{Int64, Int64}, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)]) │ (a_curr = Core.getfield(%18, 1)) │ (#_6 = Core.getfield(%18, 2)) │ %21 = Base.indexed_iterate(a_result::Tuple{Int64, Int64}, 2, #_6::Core.Const(2))::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(3)]) │ (a_state = Core.getfield(%21, 1)) │ %23 = ($(Expr(:static_parameter, 1)))(a_curr)::Int64 │ %24 = Core.apply_type(Main.State, $(Expr(:static_parameter, 5)), $(Expr(:static_parameter, 6)))::Core.Const(State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}) │ %25 = Base.getproperty(self, :a)::Vector{Int64} │ %26 = Main.iterate(%25, a_state)::Union{Nothing, Tuple{Int64, Int64}} │ %27 = (%24)(%26, b_result::Core.Const(nothing))::State{Tuple{Int64, Int64}, Tuple{Int64, Int64}} │ %28 = Core.tuple(%23, %27)::Tuple{Int64, State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}} └── return %28 5 ─ %30 = Base.indexed_iterate(b_result::Tuple{Int64, Int64}, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)]) │ (b_curr = Core.getfield(%30, 1)) │ (#_5 = Core.getfield(%30, 2)) │ %33 = Base.indexed_iterate(b_result::Tuple{Int64, Int64}, 2, #_5::Core.Const(2))::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(3)]) │ (b_state = Core.getfield(%33, 1)) │ %35 = (a_result !== Main.nothing)::Bool └── goto #8 if not %35 6 ─ %37 = Base.indexed_iterate(a_result::Tuple{Int64, Int64}, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)]) │ (a_curr = Core.getfield(%37, 1)) │ (#_4 = Core.getfield(%37, 2)) │ %40 = Base.indexed_iterate(a_result::Tuple{Int64, Int64}, 2, #_4::Core.Const(2))::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(3)]) │ (a_state = Core.getfield(%40, 1)) │ %42 = Base.Order::Core.Const(Base.Order) │ %43 = Base.getproperty(%42, :lt)::Core.Const(Base.Order.lt) │ %44 = Base.getproperty(self, :order)::Core.Const(Base.Order.ForwardOrdering()) │ %45 = a_curr::Int64 │ %46 = (%43)(%44, %45, b_curr)::Bool └── goto #8 if not %46 7 ─ %48 = ($(Expr(:static_parameter, 1)))(a_curr)::Int64 │ %49 = Core.apply_type(Main.State, $(Expr(:static_parameter, 5)), $(Expr(:static_parameter, 6)))::Core.Const(State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}) │ %50 = Base.getproperty(self, :a)::Vector{Int64} │ %51 = Main.iterate(%50, a_state)::Union{Nothing, Tuple{Int64, Int64}} │ %52 = (%49)(%51, b_result::Tuple{Int64, Int64})::State{Tuple{Int64, Int64}, Tuple{Int64, Int64}} │ %53 = Core.tuple(%48, %52)::Tuple{Int64, State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}} └── return %53 8 ┄ %55 = ($(Expr(:static_parameter, 1)))(b_curr)::Int64 │ %56 = Core.apply_type(Main.State, $(Expr(:static_parameter, 5)), $(Expr(:static_parameter, 6)))::Core.Const(State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}) │ %57 = a_result::Union{Nothing, Tuple{Int64, Int64}} │ %58 = Base.getproperty(self, :b)::Vector{Int64} │ %59 = Main.iterate(%58, b_state)::Union{Nothing, Tuple{Int64, Int64}} │ %60 = (%56)(%57, %59)::State{Tuple{Int64, Int64}, Tuple{Int64, Int64}} │ %61 = Core.tuple(%55, %60)::Tuple{Int64, State{Tuple{Int64, Int64}, Tuple{Int64, Int64}}} └── return %61 EDIT: now I have realized that my implementation is not fully correct, as it assumes that the return value of iterate if it is not nothing is type stable (which it does not have to be). But if it is not type stable then compiler must allocate. So a fully correct solution would first check if iterate is type stable. If it is - use my solution, and if it is not - use e.g. your solution.