import numpy as np

def step(z):
    return np.where(z >= 0, 1, 0)

# ---- AND-gate dataset, in the exact order used in the lecture ----
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 0, 0, 1])

w = np.zeros(2)
b = 0.0
eta = 0.1
max_epochs = 20

for epoch in range(1, max_epochs + 1):
    updates = 0
    for xi, yi in zip(X, y):
        z = np.dot(w, xi) + b
        y_hat = step(z)
        error = yi - y_hat
        if error != 0:
            w += eta * error * xi
            b += eta * error
            updates += 1
    print(f"Epoch {epoch}: w={w}, b={b:.3f}, updates={updates}")
    if updates == 0:
        print("Converged -- no updates this epoch.")
        break

print("\nFinal weights:", w, " Final bias:", round(b, 3))
print("\nPredictions on AND:")
for xi, yi in zip(X, y):
    pred = step(np.dot(w, xi) + b)
    print(f"  x={xi} -> predicted={pred}, target={yi}")
