/// <summary> /// Initializes an instance with the specified information. /// </summary> /// <param name="conclusions">All conclusions.</param> /// <param name="views">All views.</param> /// <param name="als1">The ALS 1.</param> /// <param name="als2">The ALS 2.</param> /// <param name="bridgeAls">The bridge ALS.</param> /// <param name="xDigitsMask">The X digits mask.</param> /// <param name="yDigitsMask">The Y digits mask.</param> /// <param name="zDigitsMask">The Z digits mask.</param> public AlsXyWingTechniqueInfo( IReadOnlyList <Conclusion> conclusions, IReadOnlyList <View> views, Als als1, Als als2, Als bridgeAls, short xDigitsMask, short yDigitsMask, short zDigitsMask) : base(conclusions, views) => (Als1, Als2, BridgeAls, XDigitsMask, YDigitsMask, ZDigitsMask) = (als1, als2, bridgeAls, xDigitsMask, yDigitsMask, zDigitsMask);
public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid) { var rccs = new List <(Als _left, Als _right, short _mask)>(); var alses = Als.GetAllAlses(grid).ToArray(); // Gather all RCCs. for (int i = 0, length = alses.Length; i < length - 1; i++) { var als1 = alses[i]; var(_, _, mask1, map1, _, _) = als1; for (int j = i + 1; j < length; j++) { var als2 = alses[j]; var(_, _, mask2, map2, _, _) = als2; var map = map1 | map2; if (map.AllSetsAreInOneRegion(out _) || map1.Overlaps(map2)) { continue; } short mask = (short)(mask1 & mask2); if (mask == 0) { continue; } short rccMask = 0; foreach (int digit in mask.GetAllSets()) { if ((map & CandMaps[digit]).AllSetsAreInOneRegion(out _)) { rccMask |= (short)(1 << digit); } } if (rccMask == 0) { continue; } rccs.Add((als1, als2, rccMask)); } } // Now check them. for (int i = 0, count = rccs.Count; i < count - 1; i++) { var(als11, als12, mask1) = rccs[i]; for (int j = i + 1; j < count; j++) { var(als21, als22, mask2) = rccs[j]; if (mask1 == mask2 && mask1.IsPowerOfTwo() && mask2.IsPowerOfTwo()) { // Cannot form a XY-Wing. continue; } if (!(als11 == als21 ^ als12 == als22 || als11 == als22 ^ als12 == als21)) { continue; } // Get the logical order of three ALSes. var(a, b, c) = true switch { _ when als11 == als21 => (als12, als22, als11), _ when als11 == als22 => (als12, als21, als11), _ when als12 == als21 => (als11, als22, als12), _ => (als11, als21, als12) }; var(_, aRegion, aMask, aMap, _, _) = a; var(_, bRegion, bMask, bMap, _, _) = b; var(_, cRegion, _, cMap, _, _) = c; var map = aMap | bMap; if (map == aMap || map == bMap) { continue; } if (!_allowOverlapping && (aMap.Overlaps(bMap) || aMap.Overlaps(cMap) || bMap.Overlaps(cMap))) { continue; } foreach (int digit1 in mask1.GetAllSets()) { foreach (int digit2 in mask2.GetAllSets()) { if (digit1 == digit2) { continue; } short finalX = (short)(1 << digit1); short finalY = (short)(1 << digit2); short digitsMask = (short)(aMask & bMask & ~(finalX | finalY)); if (digitsMask == 0) { continue; } // Gather eliminations. short finalZ = 0; var conclusions = new List <Conclusion>(); foreach (int digit in digitsMask.GetAllSets()) { var elimMap = ( ((aMap | bMap) & CandMaps[digit]).PeerIntersection & CandMaps[digit]) - (aMap | bMap | cMap); if (elimMap.IsEmpty) { continue; } finalZ |= (short)(1 << digit); foreach (int cell in elimMap) { conclusions.Add(new Conclusion(Elimination, cell, digit)); } } if (conclusions.Count == 0) { continue; } // Record highlight candidates and cells. var cellOffsets = new List <(int, int)>(); var candidateOffsets = new List <(int, int)>(); foreach (int cell in aMap) { short mask = grid.GetCandidatesReversal(cell); short alsDigitsMask = (short)(mask & ~(finalX | finalZ)); short xDigitsMask = (short)(mask & (finalX)); short zDigitsMask = (short)(mask & finalZ); foreach (int digit in alsDigitsMask.GetAllSets()) { candidateOffsets.Add((-1, cell * 9 + digit)); } foreach (int digit in xDigitsMask.GetAllSets()) { candidateOffsets.Add((1, cell * 9 + digit)); } foreach (int digit in zDigitsMask.GetAllSets()) { candidateOffsets.Add((2, cell * 9 + digit)); } } foreach (int cell in bMap) { short mask = grid.GetCandidatesReversal(cell); short alsDigitsMask = (short)(mask & ~(finalY | finalZ)); short yDigitsMask = (short)(mask & finalY); short zDigitsMask = (short)(mask & finalZ); foreach (int digit in alsDigitsMask.GetAllSets()) { candidateOffsets.Add((-1, cell * 9 + digit)); } foreach (int digit in yDigitsMask.GetAllSets()) { candidateOffsets.Add((1, cell * 9 + digit)); } foreach (int digit in zDigitsMask.GetAllSets()) { candidateOffsets.Add((2, cell * 9 + digit)); } } foreach (int cell in cMap) { short mask = grid.GetCandidatesReversal(cell); short alsDigitsMask = (short)(mask & ~(finalX | finalY)); short xyDigitsMask = (short)(mask & (finalX | finalY)); foreach (int digit in alsDigitsMask.GetAllSets()) { candidateOffsets.Add((-3, cell * 9 + digit)); } foreach (int digit in xyDigitsMask.GetAllSets()) { candidateOffsets.Add((1, cell * 9 + digit)); } } accumulator.Add( new AlsXyWingTechniqueInfo( conclusions, views: new[]
/// <inheritdoc/> public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid) { var house = (Span <int>) stackalloc int[2]; var alses = Als.GetAllAlses(grid).ToArray(); for (int i = 0, length = alses.Length; i < length - 1; i++) { var als1 = alses[i]; var(isBivalue1, region1, mask1, map1, possibleElimMap1, _) = als1; for (int j = i + 1; j < length; j++) { var als2 = alses[j]; var(isBivalue2, region2, mask2, map2, possibleElimMap2, _) = als2; short xzMask = (short)(mask1 & mask2); var map = map1 | map2; var overlapMap = map1 & map2; if (!_allowOverlapping && overlapMap.IsNotEmpty) { continue; } // Remove all digits to satisfy that the RCC digit cannot appear // in the intersection of two ALSes. if (overlapMap.IsNotEmpty) { foreach (int cell in overlapMap) { xzMask &= (short)~grid.GetCandidatesReversal(cell); } } // If the number of digits that both two ALSes contain is only one (or zero), // the ALS-XZ will not be formed. if (xzMask.CountSet() < 2 || map.AllSetsAreInOneRegion(out int region)) { continue; } short rccMask = 0, z = 0; int nh = 0; foreach (int digit in xzMask.GetAllSets()) { if ((map & CandMaps[digit]).AllSetsAreInOneRegion(out region)) { // 'digit' is the RCC digit. rccMask |= (short)(1 << digit); house[nh++] = region; } else { // 'digit' is the eliminating digit. z |= (short)(1 << digit); } } if (rccMask == 0 || rccMask.IsPowerOfTwo() && z == 0) { continue; } // Check basic eliminations. bool? isDoublyLinked = false; short finalZ = 0; var conclusions = new List <Conclusion>(); foreach (int elimDigit in z.GetAllSets()) { var elimMap = (CandMaps[elimDigit] & map).PeerIntersection & CandMaps[elimDigit]; if (elimMap.IsEmpty) { continue; } foreach (int cell in elimMap) { conclusions.Add(new Conclusion(Elimination, cell, elimDigit)); } finalZ |= (short)(1 << elimDigit); } if (_allowAlsCycles && rccMask.CountSet() == 2) { // Doubly linked ALS-XZ. isDoublyLinked = true; foreach (int elimDigit in (z & ~rccMask).GetAllSets()) { var zMap = CandMaps[elimDigit] & map1; if (zMap.IsEmpty) { continue; } var elimMap = zMap.PeerIntersection & CandMaps[elimDigit] & map2; if (elimMap.IsEmpty) { continue; } finalZ |= (short)(1 << elimDigit); } // RCC digit 2 eliminations. int k = 0; foreach (int digit in rccMask.GetAllSets()) { var elimMap = (RegionMaps[house[k]] & CandMaps[digit]) - map; if (elimMap.IsNotEmpty) { foreach (int cell in elimMap) { if (grid.Exists(cell, digit) is true) { conclusions.Add(new Conclusion(Elimination, cell, digit)); } } } k++; } // Possible eliminations. var tempMap = map; tempMap = CandMaps[mask1.FindFirstSet()]; for (k = 1; k < mask1.CountSet(); k++) { tempMap |= CandMaps[mask1.SetAt(k)]; } tempMap &= possibleElimMap1; if (tempMap.IsNotEmpty) { foreach (int cell in tempMap) { foreach (int digit in (grid.GetCandidatesReversal(cell) & (mask1 & ~rccMask)).GetAllSets()) { conclusions.Add(new Conclusion(Elimination, cell, digit)); } } } tempMap = CandMaps[mask2.FindFirstSet()]; for (k = 1; k < mask2.CountSet(); k++) { tempMap |= CandMaps[mask2.SetAt(k)]; } tempMap &= possibleElimMap2; if (tempMap.IsNotEmpty) { foreach (int cell in tempMap) { foreach (int digit in (grid.GetCandidatesReversal(cell) & (mask2 & ~rccMask)).GetAllSets()) { conclusions.Add(new Conclusion(Elimination, cell, digit)); } } } } if (conclusions.Count == 0) { continue; } // Now record highlight elements. bool isEsp = als1.IsBivalueCell || als2.IsBivalueCell; var candidateOffsets = new List <(int, int)>(); var cellOffsets = new List <(int, int)>(); if (isEsp) { foreach (int cell in map) { foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets()) { candidateOffsets.Add((finalZ >> digit & 1, cell * 9 + digit)); } } } else { foreach (int cell in map1) { short mask = grid.GetCandidatesReversal(cell); short alsDigitsMask = (short)(mask & ~(finalZ | rccMask)); short targetDigitsMask = (short)(mask & finalZ); short rccDigitsMask = (short)(mask & rccMask); foreach (int digit in alsDigitsMask.GetAllSets()) { candidateOffsets.Add((-1, cell * 9 + digit)); } foreach (int digit in targetDigitsMask.GetAllSets()) { candidateOffsets.Add((2, cell * 9 + digit)); } foreach (int digit in rccDigitsMask.GetAllSets()) { candidateOffsets.Add((1, cell * 9 + digit)); } } foreach (int cell in map2) { short mask = grid.GetCandidatesReversal(cell); short alsDigitsMask = (short)(mask & ~(finalZ | rccMask)); short targetDigitsMask = (short)(mask & finalZ); short rccDigitsMask = (short)(mask & rccMask); foreach (int digit in alsDigitsMask.GetAllSets()) { candidateOffsets.Add((-2, cell * 9 + digit)); } foreach (int digit in targetDigitsMask.GetAllSets()) { candidateOffsets.Add((2, cell * 9 + digit)); } foreach (int digit in rccDigitsMask.GetAllSets()) { candidateOffsets.Add((1, cell * 9 + digit)); } } } accumulator.Add( new AlsXzTechniqueInfo( conclusions, views: new[]
/// <summary> /// Initializes an instance with the specified information. /// </summary> /// <param name="conclusions">All conclusions.</param> /// <param name="views">All views.</param> /// <param name="als1">The ALS 1 used.</param> /// <param name="als2">The ALS 2 used.</param> /// <param name="xDigitsMask">The X digits mask.</param> /// <param name="zDigitsMask">The Z digits mask.</param> /// <param name="isDoublyLinked">Indicates whether the instance is a doubly linked ALS-XZ.</param> public AlsXzTechniqueInfo( IReadOnlyList <Conclusion> conclusions, IReadOnlyList <View> views, Als als1, Als als2, short xDigitsMask, short zDigitsMask, bool?isDoublyLinked) : base(conclusions, views) => (Als1, Als2, XDigitsMask, ZDigitsMask, IsDoublyLinked) = (als1, als2, xDigitsMask, zDigitsMask, isDoublyLinked);
/// <inheritdoc/> public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid) { var alses = Als.GetAllAlses(grid).ToArray(); // Gather all conjugate pairs. var conjugatePairs = new ICollection <ConjugatePair>?[9]; for (int digit = 0; digit < 9; digit++) { for (int region = 0; region < 27; region++) { var temp = RegionMaps[region] & CandMaps[digit]; if (temp.Count != 2) { continue; } (conjugatePairs[digit] ??= new List <ConjugatePair>()).Add(new ConjugatePair(temp, digit)); } } // Iterate on each ALS. for (int i = 0, length = alses.Length; i < length - 1; i++) { var als1 = alses[i]; var(_, region1, mask1, map1, _, _) = als1; for (int j = i + 1; j < length; j++) { var als2 = alses[j]; var(_, region2, mask2, map2, _, _) = als2; if (map1.Overlaps(map2) || (map1 | map2).AllSetsAreInOneRegion(out _)) { continue; } short mask = (short)(mask1 & mask2); if (mask.CountSet() < 2) { continue; } foreach (int x in mask.GetAllSets()) { if ((conjugatePairs[x]?.Count ?? 0) == 0) { continue; } var p1 = (map1 & CandMaps[x]).PeerIntersection & CandMaps[x]; var p2 = (map2 & CandMaps[x]).PeerIntersection & CandMaps[x]; if (p1.IsEmpty || p2.IsEmpty) { // At least one of two ALSes cannot see the node of the conjugate pair. continue; } // Iterate on each conjugate pair. short wDigitsMask = 0; var conclusions = new List <Conclusion>(); foreach (var conjugatePair in conjugatePairs[x] ?? Array.Empty <ConjugatePair>()) { var cpMap = conjugatePair.Map; if (cpMap.Overlaps(map1) || cpMap.Overlaps(map2)) { // Conjugate pair cannot overlap with the ALS structure. continue; } if ((cpMap & p1).Count != 1 || (cpMap & p2).Count != 1 || ((p1 | p2) & cpMap).Count != 2) { continue; } foreach (int w in (mask & ~(1 << x)).GetAllSets()) { var tempMap = ((map1 | map2) & CandMaps[w]).PeerIntersection & CandMaps[w]; if (tempMap.IsEmpty) { continue; } wDigitsMask |= (short)(1 << w); foreach (int cell in tempMap) { conclusions.Add(new Conclusion(Elimination, cell, w)); } } if (conclusions.Count == 0) { continue; } // Record highlight cell and candidate offsets. var cellOffsets = new List <(int, int)>(); cellOffsets.AddRange(from cell in map1 select(-1, cell)); cellOffsets.AddRange(from cell in map2 select(-2, cell)); var candidateOffsets = new List <(int, int)> { (0, cpMap.SetAt(0) * 9 + x), (0, cpMap.SetAt(1) * 9 + x) }; foreach (int cell in map1) { foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets()) { candidateOffsets.Add(( true switch { _ when digit == x => 1, _ when(wDigitsMask >> digit & 1) != 0 => 2, _ => - 1 },
/// <summary> /// Initializes an instance with the specified information. /// </summary> /// <param name="conclusions">All conclusions.</param> /// <param name="views">All views.</param> /// <param name="als1">The ALS 1.</param> /// <param name="als2">The ALS 2.</param> /// <param name="conjugatePair">The conjugate pair.</param> /// <param name="wDigitsMask">The W digits mask.</param> /// <param name="x">The X digit.</param> public AlsWWingTechniqueInfo( IReadOnlyList <Conclusion> conclusions, IReadOnlyList <View> views, Als als1, Als als2, ConjugatePair conjugatePair, short wDigitsMask, int x) : base(conclusions, views) => (Als1, Als2, ConjugatePair, WDigitsMask, XDigit) = (als1, als2, conjugatePair, wDigitsMask, x);