findFirstIndex.nix: init
This commit is contained in:
parent
ec3006ab6f
commit
d3213f076e
|
@ -15,5 +15,6 @@
|
||||||
subtractLists = import ./src/subtractLists.nix lib;
|
subtractLists = import ./src/subtractLists.nix lib;
|
||||||
unique = import ./src/unique.nix lib;
|
unique = import ./src/unique.nix lib;
|
||||||
overrideExisting = import ./src/overrideExisting.nix lib;
|
overrideExisting = import ./src/overrideExisting.nix lib;
|
||||||
|
findFirstIndex = import ./src/findFirstIndex.nix lib;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
lib:
|
||||||
|
let
|
||||||
|
findFirstIndexOld =
|
||||||
|
pred:
|
||||||
|
default:
|
||||||
|
list:
|
||||||
|
let
|
||||||
|
# A naive recursive implementation would be much simpler, but
|
||||||
|
# would also overflow the evaluator stack. We use `foldl'` as a workaround
|
||||||
|
# because it reuses the same stack space, evaluating the function for one
|
||||||
|
# element after another. We can't return early, so this means that we
|
||||||
|
# sacrifice early cutoff, but that appears to be an acceptable cost. A
|
||||||
|
# clever scheme with "exponential search" is possible, but appears over-
|
||||||
|
# engineered for now. See https://github.com/NixOS/nixpkgs/pull/235267
|
||||||
|
|
||||||
|
# Invariant:
|
||||||
|
# - if index < 0 then el == elemAt list (- index - 1) and all elements before el didn't satisfy pred
|
||||||
|
# - if index >= 0 then pred (elemAt list index) and all elements before (elemAt list index) didn't satisfy pred
|
||||||
|
#
|
||||||
|
# We start with index -1 and the 0'th element of the list, which satisfies the invariant
|
||||||
|
resultIndex = lib.foldl' (index: el:
|
||||||
|
if index < 0 then
|
||||||
|
# No match yet before the current index, we need to check the element
|
||||||
|
if pred el then
|
||||||
|
# We have a match! Turn it into the actual index to prevent future iterations from modifying it
|
||||||
|
- index - 1
|
||||||
|
else
|
||||||
|
# Still no match, update the index to the next element (we're counting down, so minus one)
|
||||||
|
index - 1
|
||||||
|
else
|
||||||
|
# There's already a match, propagate the index without evaluating anything
|
||||||
|
index
|
||||||
|
) (-1) list;
|
||||||
|
in
|
||||||
|
if resultIndex < 0 then
|
||||||
|
default
|
||||||
|
else
|
||||||
|
resultIndex;
|
||||||
|
|
||||||
|
findFirstIndexNew =
|
||||||
|
pred:
|
||||||
|
default:
|
||||||
|
list:
|
||||||
|
if list == [ ] then default else
|
||||||
|
let
|
||||||
|
stopAtListLength = lib.min (builtins.length list - 1);
|
||||||
|
closure = builtins.genericClosure (rec {
|
||||||
|
startSet = [{
|
||||||
|
key = -1;
|
||||||
|
found = false;
|
||||||
|
}];
|
||||||
|
operator = item: if item.found then startSet else [(rec {
|
||||||
|
key = stopAtListLength (item.key + 1);
|
||||||
|
found = pred (builtins.elemAt list key);
|
||||||
|
})];
|
||||||
|
});
|
||||||
|
lastItem = builtins.elemAt closure (builtins.length closure - 1);
|
||||||
|
in if lastItem.found then lastItem.key else default;
|
||||||
|
|
||||||
|
bigdata = (lib.imap0 (i: _: toString i) (lib.replicate 999999 null));
|
||||||
|
|
||||||
|
tests = f: {
|
||||||
|
none = f (x: x == ":)") "not found" bigdata;
|
||||||
|
last = f (x: x == ":)") "not found" (bigdata ++ [":)"]);
|
||||||
|
middle = f (x: x == ":)") "not found" (bigdata ++ [":)"] ++ bigdata);
|
||||||
|
empty = f (x: x == ":)") "not found" [ ];
|
||||||
|
};
|
||||||
|
in rec {
|
||||||
|
old = tests findFirstIndexOld;
|
||||||
|
new = tests findFirstIndexNew;
|
||||||
|
sanityCheck = lib.all (name: assert lib.assertMsg (old.${name} == new.${name}) "output of '${name}' test is not equal"; true) (builtins.attrNames (tests null));
|
||||||
|
}
|
Loading…
Reference in New Issue